TaintDroid剖析之Native方法級汙點跟蹤分析
TaintDroid剖析之Native方法級汙點跟蹤分析
簡行、走位@阿里聚安全
1、Native方法的汙點傳播
在前兩篇文章中我們詳細分析了TaintDroid對DVM棧幀的修改,以及它是如何在修改之後的棧幀中實現DVM變數級汙點跟蹤的。現在我們繼續分析其第二個粒度的汙點跟蹤——Native方法級跟蹤。
回顧前文,我們知道Native方法執行在Native棧幀中,且Native棧幀由dvmPushJNIFrame函式分配棧空間,再由dvmCallMethodV/A或者dvmInvokeMethod對棧幀進行初始化,所以我們也按照之前的方式進行Native方法級跟蹤機制分析。
TaintDroid深入剖析系列目錄:
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並沒有完成)。
作者:簡行、走位@阿里聚安全,更多技術文章,請點選阿里聚安全部落格
阿里聚安全由阿里巴巴移動安全部出品,面向企業和開發者提供企業安全解決方案,全面覆蓋移動安全、資料風控、內容安全、實人認證等維度,並在業界率先提出“以業務為中心的安全”,賦能生態,與行業共享阿里巴巴集團多年沉澱的專業安全能力。
相關文章
- TaintDroid深入剖析之啟動篇AI
- Golang 大殺器之跟蹤剖析 traceGolang
- KCF目標跟蹤方法分析與總結
- [原始碼分析] OpenTracing之跟蹤Redis原始碼Redis
- 會話級SQL跟蹤會話SQL
- SQL 的跟蹤方法traceSQL
- Istio最佳實踐系列:如何實現方法級呼叫跟蹤?
- tkprof: 分析ORACLE跟蹤檔案Oracle
- 動態跟蹤分析VB程式
- Android 程式碼跟蹤到native怎麼辦Android
- Linux 跟蹤器之選Linux
- 【剖析 | SOFARPC 框架】系列之鏈路追蹤剖析RPC框架
- 演算法分析__遞迴跟蹤演算法遞迴
- 使用 Tkprof 分析 ORACLE 跟蹤檔案Oracle
- 跟蹤session 與 trace檔案分析Session
- SQL 跟蹤方法相關介紹SQL
- 轉:使用 Tkprof 分析 ORACLE 跟蹤檔案Oracle
- 一個非侵入式跟蹤分析程式
- [zt] oracle跟蹤檔案與跟蹤事件Oracle事件
- oracle跟蹤檔案與跟蹤事件(zt)Oracle事件
- oracle跟蹤檔案和跟蹤事件(zt)Oracle事件
- 使用汙點分析檢查log4j問題
- 專案經理之專案跟蹤
- 揭祕ORACLE備份之----RMAN之四(塊跟蹤)Oracle
- IDEA之如何Debug原始碼跟蹤Idea原始碼
- sqlnet跟蹤SQL
- ORACLE 跟蹤工具Oracle
- 螞蟻金服分散式鏈路跟蹤元件 SOFATracer 總覽 | 剖析分散式元件
- 螞蟻金服分散式鏈路跟蹤元件 SOFATracer 資料上報機制和原始碼分析 | 剖析分散式元件原始碼
- [AI開發]目標跟蹤之速度計算AI
- 無人駕駛之車輛檢測與跟蹤
- 基於行跟蹤的ROWDEPENDENCIES實現資訊變化跟蹤
- 【Longkin】ASP.NET應用程式跟蹤---(一)跟蹤頁面ASP.NET
- 使用10046事件跟蹤分析執行計劃事件
- MYSQL sql執行過程的一些跟蹤分析(二.mysql優化器追蹤分析)MySql優化
- SQL效能的度量 - 語句級別的SQL跟蹤autotraceSQL
- 反跟蹤技術
- 【TRACE】Oracle跟蹤事件Oracle事件