TaintDroid剖析之Native方法級汙點跟蹤分析

聚安全平臺發表於2016-07-26

TaintDroid剖析之Native方法級汙點跟蹤分析


簡行、走位@阿里聚安全



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並沒有完成)。


作者:簡行、走位@阿里聚安全,更多技術文章,請點選阿里聚安全部落格



阿里聚安全由阿里巴巴移動安全部出品,面向企業和開發者提供企業安全解決方案,全面覆蓋移動安全、資料風控、內容安全、實人認證等維度,並在業界率先提出“以業務為中心的安全”,賦能生態,與行業共享阿里巴巴集團多年沉澱的專業安全能力。


相關文章