Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> TaintDroid剖析之Native方法級污點跟蹤分析,taintdroidnative

TaintDroid剖析之Native方法級污點跟蹤分析,taintdroidnative

編輯:關於android開發

TaintDroid剖析之Native方法級污點跟蹤分析,taintdroidnative


1、Native方法的污點傳播

在前兩篇文章中我們詳細分析了TaintDroid對DVM棧幀的修改,以及它是如何在修改之後的棧幀中實現DVM變量級污點跟蹤的。現在我們繼續分析其第二個粒度的污點跟蹤——Native方法級跟蹤。

回顧前文,我們知道Native方法執行在Native棧幀中,且Native棧幀由dvmPushJNIFrame函數分配棧空間,再由dvmCallMethodV/A或者dvmInvokeMethod對棧幀進行初始化,所以我們也按照之前的方式進行Native方法級跟蹤機制分析。

 

TaintDroid深入剖析系列目錄:

TaintDroid深入剖析之啟動篇

TaintDroid剖析之DVM變量級污點跟蹤(下篇)

 

1.1 dvmPushJNIFrame分析

該函數與dvmPushInterpFrame基本相同,也是對寄存器所占用的空間進行了倍增。但是,在此棧幀中,變量與污點的位置卻與DVM棧幀大相徑庭!Native棧幀的結構如下圖所示:

 

 

顯然在Native棧幀中,並沒有為變量交叉存儲污點信息,而是跟傳統的系統棧幀一樣(必須一樣,因為這是ARM通用的標准調用約定),將參數依次存儲在棧幀開始部分。只是,在最後一個參數之後分配了4字節的空間用於存儲return taint(用於存儲方法的返回污點信息),然後又緊鄰這個return taint依次為每個參數分配了4字節大小的空間用於存儲各個參數的污點信息。所以雖然兩種棧幀都是對method->registers進行了倍增,但是其倍增後的棧幀的布局卻是完全不同的,並且在這裡我們就能理解為什麼在進行倍增的時候會額外多分配4字節的空間了——原來是用於存儲return taint!

只要理解了Native方法的棧幀結構,就不難以分析Native方法中Internal VM method的污點跟蹤機制了,所以我們主要講解JNI方法的污點跟蹤——因為它額外使用了較為復雜的method profile policy。

 

1.2 JNI方法的污點跟蹤分析

所有的JNI方法都通過dvmCallJniMethod方法調用,TaintDroid在其代碼中添加如下語句: 

#ifdef WITH_TAINT_TRACKING

    // Copy args to another array, to ensure correct taint propagation in case args change

    int nArgs = method->insSize * 2 + 1;

    u4* oldArgs = (u4*)malloc(sizeof(u4)*nArgs);

    memcpy(oldArgs, args, sizeof(u4)*nArgs);

#endif /*WITH_TAINT_TRACKING*/

 

首先將所有的參數存儲下來,注意紅色部分代碼,對於native 棧幀來說,insSize就是參數的個數,由於TaintDroid對棧進行了擴展,所以這裡也要對應的進行擴展。

然後就是在JNI方法執行結束之後調用如下代碼:

#ifdef WITH_TAINT_TRACKING

    dvmTaintPropJniMethod(oldArgs, pResult, method);

    free(oldArgs);

#endif /*WITH_TAINT_TRACKING*/

 

這個dvmTaintPropJniMethod方法定義在dalvik/vm/tprop/TaintProp.cpp中,此函數結合dvmCallMethod對JNI幀的賦值,解釋了為何可以以及如何實現NATIVE層污點傳播。

 

1.3 dvmTaintPropJniMethod分析

此方法用於JNI方法的污點傳播,這裡共包含兩種污點傳播類型(同時使用):

1、基於參數的簡單保守的污點傳播;

2、基於函數剖析策略(function profile policies)的污點傳播;

 

1.3.1基於參數的簡單保守的污點傳播分析

下面詳細分析dvmTaintPropJniMethod函數的代碼。

第一部分,參數准備:

    const DexProto* proto = &method->prototype;

    DexParameterIterator pIterator;

    int nParams = dexProtoGetParameterCount(proto);

    int pStart = (dvmIsStaticMethod(method)?0:1); /* index where params start */

 

    /* Consider 3 arguments. [x] indicates return taint index

     * 0 1 2 [3] 4 5 6

     */

    int nArgs = method->insSize;

    u4* rtaint = (u4*) &args[nArgs]; /* The return taint value */

    int tStart = nArgs+1; /* index of args[] where taint values start */

    int tEnd   = nArgs*2; /* index of args[] where taint values end */

    u4            tag = TAINT_CLEAR;

    int i;

 

這部分代碼很簡單,結合上文的Native棧幀,不難理解其中各個變量的含義。

現在回到dvmTaintPropJniMethod的第二部分:

    for (i = tStart; i <= tEnd; i++) {

              tag |= args[i];

    }

    /* If not static, pull any taint from the "this" object */

    if (!dvmIsStaticMethod(method)) {

              tag |= getObjectTaint((Object*)args[0], method->clazz->descriptor);

    }

    /* Union taint from Objects we care about */

    dexParameterIteratorInit(&pIterator, proto);

    for (i=pStart; ; i++) {

              const char* desc = dexParameterIteratorNextDescriptor(&pIterator);

              if (desc == NULL) {

                  break;

              }          

              if (desc[0] == '[' || desc[0] == 'L'] { 

                  tag |= getObjectTaint((Object*) args[i], desc); //當前只支持array和string對象的污點獲取!

              }

              if (desc[0] == 'J' || desc[0] == 'D') {

                  /* wide argument, increment index one more */

                  i++;

              }

    }

    /* Look at the taint policy profiles (may have return taint) */

    tag |= propMethodProfile(args, method);

    /* Update return taint according to the return type */

    if (tag) {

              const char* desc = dexProtoGetReturnType(proto);

              setReturnTaint(tag, rtaint, pResult, desc);

    }

 

這部分代碼的功能簡要概括為:

1. 將所有參數的污點都集合到返回tag中;
2. 對於非靜態方法,將this指針的tag集合到返回tag中;
3. 將參數中所有arrayObject和object對象的tag集合到返回tag中;getObjectTaint的實現並不復雜,結合第3章的講解,讀者可以很容易地理解其實現邏輯。
4. 通過函數propMethodProfile對參數的污點進行profile,如果該函數有返回值的話就將這個值(其實就是一個tag值)集合到返回tag中, 後文會詳細分析其實現機制;
5. 最後通過函數setReturnTaint方法將返回tag放置到返回值中。

 

這裡有幾個概念需要說明:

1. 雖然每個參數在棧幀中都有一個專門的空間存儲其污點,但是這並不意味著參數的污點數據就一定存儲在這個空間了,因為對於ArrayObject/StringObject之類的參數其污點是存儲在自己的存儲空間的(如第3章所述,TaintDroid對它們的數據結構進行了修改——添加了一個Taint成員)。
2. setReturnTaint對不同類型參數的污點處理需要注意。比如,對ArrayObject與StringObject,以及對Object的引用(等同於普通的變量)。這裡就涉及到TaintDroid對基於參數的簡單保守的污點傳播的一些定義與限制了,TaintDroid將其稱之為啟發式污點傳播補丁。結合前文對DVM的變量級污點跟蹤分析,TaintDroid僅僅對原始類型數據和ArrayObject以及類的靜態域、實例域進行了污點傳播,它並不關心其他object類型的污點,另外它還有一個TODO:考慮String的派生類的污點。

 

1.3.2 基於函數剖析策略的污點傳播分析

上文主要分析了JNI污點傳播中基於參數的簡單保守污點傳播方式,下面繼續分析其基於函數剖析策略(function profile policies)的污點傳播,其實現接口就是前文提到的propMethodProfile函數。

在分析Taint method Profile之前,需要先了解其所需的各種結構體,這些結構體定義在dalvik/vm/tprop/TaintPolicyPriv.h中:

typedef enum {

    kTaintProfileUnknown = 0,

    kTaintProfileClass,

    kTaintProfileParam,

    kTaintProfileReturn

} TaintProfileEntryType;

 

typedef struct {

    const char* from;  //格式大概為:[class/param/argX/return].[xxx].[xxx]

    const char* to;

} TaintProfileEntry;

 

#define TAINT_PROFILE_TABLE_SIZE 8 /* per class */

#define TAINT_POLICY_TABLE_SIZE 32 /* number of classes */

 

typedef struct {

    const char* methodName;

    const TaintProfileEntry* entries;

} TaintProfile;

 

typedef struct {

    const char* classDescriptor;

    const TaintProfile* profiles;

    HashTable* methodTable; /* created on startup */

} TaintPolicy;

 

三者的相互關系如下圖所示:

 

TaintProfileEntry的[from, to]數據對,用於記錄變量之間(包括方法參數、類變量以及返回值)的數據流。顯然,這三種結構體構成了一個完整的數據流鏈表。了解了這些結構體之後就可以繼續分析TaintDroid是如何部署、實施這種策略的了。為了便於理解,我們將method profile policy的整個實現分為三個階段:1)初始化階段;2)策略執行之搜索階段;3)策略執行之更新階段。

 

1)初始化階段

首先我們進入jni.cpp中dvmJniStartup()函數,發現其添加了如下代碼:

#ifdef WITH_TAINT_TRACKING

    dvmTaintPropJniStartup();

#endif

 

顧名思義dvmJniStartup用於啟動整個dvm的jni功能,TD將dvmTaintPropStartup添加到此函數中,表示整個Taint method Profile是對所有JNImethods起作用的。dvmTaintPropStartup定義在TaintProp.cpp文件中:

/* Code called from dvmJniStartup()

 * Initializes the gPolicyTable for fast lookup of taint policy

 * profiles for methods.

 */

void dvmTaintPropJniStartup()

{

    TaintPolicy* policy;

    u4 hash;

   

    /* Create the policy table (perfect size) */

    gPolicyTable = dvmHashTableCreate(

                  dvmHashSize(TAINT_POLICY_TABLE_SIZE),    

                  freeTaintPolicy);

 

    for (policy = gDvmJniTaintPolicy; policy->classDescriptor != NULL; policy++) {

              const TaintProfile *profile;

   

              /* Create the method table for this class */

              policy->methodTable = dvmHashTableCreate(

                            TAINT_PROFILE_TABLE_SIZE, freeTaintProfile);

 

              /* Add all of the methods */

              for (profile = &policy->profiles[0]; profile->methodName != NULL; profile++) {

                  hash = dvmComputeUtf8Hash(profile->methodName);

                  dvmHashTableLookup(policy->methodTable, hash,(void *) profile,

                                hashcmpTaintProfile, true); //最後一個參數表示在hash表中找不到目標item時,是否將這個item添加到hash表中。

              }

 

              /* Add this class to gPolicyTable */

              hash = dvmComputeUtf8Hash(policy->classDescriptor);

              dvmHashTableLookup(gPolicyTable, hash, policy,

                            hashcmpTaintPolicy, true);

    }

 

#ifdef TAINT_JNI_LOG

    /* JNI logging for debugging purposes */

    gJniLogSeen = dvmHashTableCreate(dvmHashSize(50), free);

#endif

}

 

1. 通過dvmHashTableCreate創建一個全局hash表gPolicyTable;

2. 遍歷全局變量gDvmJniTaintPolicy,這是一個TaintPolicy結構體數組,定義在tprop/TaintPolicy.cpp中:

TaintPolicy gDvmJniTaintPolicy[] = {

    {"Llibcore/icu/NativeConverter;", libcore_icu_NativeConverter_methods, NULL},

    {"Lfoo/bar/name2;", foo_bar_name2_methods, NULL},

    {NULL, NULL, NULL}

};

 

由於起初NativeConverter與name2類的methodTable指針為空,它又是一個HashTable指針成員,所以需要通過dvmHashTableCreate為其創建hash表;然後將該類的所有方法(也定義在tprop/TaintPolicy.cpp中)加入到這個hash表中;最後將該類加入到全局hash表gPolicyTable中。至於為何只定義了這兩個類,見後面分析。

至此jni method profile的初始化工作就做完了。以後就是根據具體的jni method對TaintPolicy, TaintProfile以及TaintProfileEntry進行更新了。

 

2)策略執行之搜索階段

由於整個策略通過hash表實現,所以在開始分析具體的JNI method執行的時候TaintDroid是如何對TaintProfile等結構進行更新之前,我們需要了解TaindDroid對以上三種結構是如何進行搜索的。

涉及到搜索的方法主要有getPolicyProfile以及getEntryTaint。這裡主要分析getEntryTaint的實現。函數代碼如下:

/*

entry = entry->from

*/

u4 getEntryTaint(const char* entry, const u4* args, const Method* method)

{

    u4 tag = TAINT_CLEAR;

    char *pos;

    /* Determine split point if any */

    pos = index((char *) entry, '.'); //這裡涉及到entry的命名方式

 

    switch (getEntryType(entry)) {

              case kTaintProfileClass:  //如果是類的話就獲取該類由entry指定的filed的tag

                  if (dvmIsStaticMethod(method)) {

                            tag = getFieldEntryTaint(pos+1, method->clazz, NULL);

                  } else {

                            tag = getFieldEntryTaint(pos+1, method->clazz, (Object*)args[0]);

                  }

                  break;

 

              case kTaintProfileParam:

                  tag = getParamEntryTaint(entry, args, method);

                  break;

 

              default:

                  ALOGW("TaintPolicy: Invalid from type: [%s]", entry);

    }

   

    return tag;

}

 

函數邏輯還是很簡單的,概括如下:

1. 通過getEntryType函數獲取entry->from所對應的變量的類型;

2. 根據變量的具體類型調用不同的處理方法獲取該變量的taint,如getFieldEntryTaint、getParamEntryTaint。

不過要想理解getFieldEntryTaint之類的函數,需要先了解entry->from與entry->to的命名規則:

其命名有三種方式:

1) class.field1[.field2[…]]。class只能用於第一個參數arg0,即this或靜態方法的當前class;

2) param.num[.field1[.field2[…]]]。Num表示參數的序列號,另外如果某變量的tag並不是保存在棧幀中與參數相鄰的tag中,就可能繼續添加字段名;

3) return。 Ununsed。

 

現在再分析getFieldEntryTaint就簡單了:

u4 getFieldEntryTaint(const char* entry, ClassObject* clazz, Object* obj)

{

    u4 tag = TAINT_CLEAR;

    FieldRef fRef;

    fRef = getFieldFromEntry(entry, clazz, obj);  //此時的entry已經去掉了’class.’ ,‘argX.’, ‘return.’ 前綴

    if (fRef.field != NULL) {

              tag = getTaintFromField(fRef.field, fRef.obj);

    }

    return tag;

}

 

對於靜態域來說,obj為Null。首先,發現有一個新的結構體FieldRef :

/* 這是一個封裝結構體,在處理嵌套的實例域entry的時候會用到*/

typedef struct {

    Field *field;

    Object *obj;

} FieldRef;

 

繼續分析getFieldFromEntry,此函數遞歸地查找某個class對象的某個field,最後返回由object和field構成的封裝結構體FieldRef.

如果fRef不為空的話,就通過getTaintFromField函數獲取該field的tag。其代碼如下:

u4 getTaintFromField(Field* field, Object* obj)

{

    u4 tag = TAINT_CLEAR;

 

    if (dvmIsStaticField(field)) {

                            StaticField* sfield = (StaticField*) field;

                            tag = dvmGetStaticFieldTaint(sfield);

    } else {

              InstField* ifield = (InstField*) field;

              if (field->signature[0] == 'J' || field->signature[0] == 'D') {

                  tag = dvmGetFieldTaintWide(obj, ifield->byteOffset);

              } else {

                  tag = dvmGetFieldTaint(obj, ifield->byteOffset);

              }

    }

    return tag;

}

 

這裡用到的dvmGetFieldTaintXXX系列函數都是inline函數,定義在oo/ObjectInlines.h中。

搜索完畢,下面就開始進行更新了。

 

3)策略執行之更新階段

首先,看propMethodProfile方法的實現:

/* Returns a taint if the profile policy indicates propagation

 * to the return

 */

u4 propMethodProfile(const u4* args, const Method* method)

{

    u4 rtaint = TAINT_CLEAR;

    TaintProfile* profile = NULL;

    const TaintProfileEntry* entry = NULL;

 

    profile = getPolicyProfile(method);  //根據method結構體獲取該方法對應的TaintProfile結構體(此函數很耗時)

    if (profile == NULL) {

              return rtaint; //若為空,表示當前profile鏈表中沒有此方法,那麼就直接返回空tag

    }

 

    //LOGD("TaintPolicy: applying policy for %s.%s",

    //                  method->clazz->descriptor, method->name);

 

    /* Cycle through the profile entries */

    for (entry = &profile->entries[0]; entry->from != NULL; entry++) {

              u4 tag = TAINT_CLEAR;

 

              tag = getEntryTaint(entry->from, args, method);

              if (tag) {

                  //LOGD("TaintPolicy: tag = %d %s -> %s",

                  //                  tag, entry->from, entry->to);

                  rtaint |= addEntryTaint(tag, entry->to, args, method);

              }

 

    }

 

    return rtaint;

}

 

其功能簡要概括如下:

1. 根據method結構體獲取該方法對應的TaintProfile結構體;

2. 遍歷該TaintProfile包含的所有TaintProfileEntry結構體,通過getEntryTaint獲取每個entry->from所對應的變量的污點,如果其污點不為空的話,就將這個污點通過addEntryTaint函數添加到entry->to所對應的變量中,並將addEntryTaint的返回值添加給rtaint。這裡涉及到addEntryTaint的處理邏輯:如果entry->to對應的變量的類型為kTaintProfileReturn的話,就說明這是一個返回函數,那麼我們就不需要再存儲其tag,只需要將它返回給上層函數就行,否則就存儲tag到entry->to對於的變量中,且返回給上層函數的tag為空。

通過上面的處理,就實現了taint profile的污點傳播了,但是枚舉所有JNI方法的數據流是一件及其耗時的任務,所以最好能通過源碼分析工具來離線地、自動化地實現數據流更新(這項工作TD並沒有完成)。

 

作者:簡行、走位@阿裡聚安全,更多技術文章,請訪問阿裡聚安全博客

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved