編輯:關於android開發
在前兩篇文章中我們詳細分析了TaintDroid對DVM棧幀的修改,以及它是如何在修改之後的棧幀中實現DVM變量級污點跟蹤的。現在我們繼續分析其第二個粒度的污點跟蹤——Native方法級跟蹤。
回顧前文,我們知道Native方法執行在Native棧幀中,且Native棧幀由dvmPushJNIFrame函數分配棧空間,再由dvmCallMethodV/A或者dvmInvokeMethod對棧幀進行初始化,所以我們也按照之前的方式進行Native方法級跟蹤機制分析。
TaintDroid深入剖析系列目錄:
TaintDroid深入剖析之啟動篇
TaintDroid剖析之DVM變量級污點跟蹤(下篇)
該函數與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。
所有的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層污點傳播。
此方法用於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並沒有完成)。
作者:簡行、走位@阿裡聚安全,更多技術文章,請訪問阿裡聚安全博客
深入理解Android之Java虛擬機Dalvik 一、背景 這個選題很大,但並不是一開始就有這麼高大上的追求。最初之時,只是源於對Xposed的好奇。Xposed幾乎是
Android開發4: Notification編程基礎、Broadcast的使用及其靜態注冊、動態注冊方式,靜態庫與動態庫編程前言 啦啦啦~(博主每次開篇都要賣個萌
自定義輪播圖片框架的使用,自定義播圖片框架我是一個比較懶惰的人。。 不想說太多高深的東西,,只會淺淺的表達。。。。因為妹子做了程序員 是很悲慘的故事。。哈哈 現在 就來說
onTouchEvent,ontouchevent使用新建MyView類 package onTouchEvent; import android.content.C
加載頁面遮擋耗時操作任務頁面--第三方開源--AndroidProgre