TaintDroid剖析之DVM变量级污点跟踪(下篇)
简行、走位@阿里聚安全
1 回顾
在上一章节中我们详细分析了TaintDroid对DVM方法参数和方法变量的变量级污点跟踪机制,现在我们将继续分析TaintDroid对类的静态域、实例域以及数组的污点跟踪。
2 了解DVM中类的数据结构
由于DVM师从JVM,所以DVM中所有类的祖先也是Object类,该类定义在dalvik/vm/oo/Object.h中。其实不仅仅是Object类,DVM所有的基本类都定义在Object.h文件中。
众所周知,Object类共分三种类型:
1)Class Objects,它是java.lang.Class的实例,此类object的公共基类是ClassObject;
2)Array Objects,由new Array指令创建,此类object的公共基类是ArrayObject;
3)Data Objects,除了上面两种Object之外的所有object,公共基类是DataObject。
这里有一个特例需要注意,那就是String Objects!String Objects当前等同于Data Objects,但鉴于该类在DVM中大量使用,因此DVM单独定义了一个类StringObject,它直接继承至Object。
了解了类的数据结构,再去分析TaintDroid对类的静态域、实例域和数组的污点跟踪就不会觉得无从下手了。
3 对各种数据结构的修改
要想实现对类的实例域和静态域的污点跟踪,最简单粗暴的方式就是对类中相关的数据结构进行修改。TaintDroid就是这么做的。
1)首先,修改了ClassObject::Object:
struct ClassObject : Object { /* leave space for instance data; we could access fields directly if we freeze the definition of java/lang/Class */ #ifdef WITH_TAINT_TRACKING // x2 space for interleaved taint tags u4 instanceData[CLASS_FIELD_SLOTS*2]; #else u4 instanceData[CLASS_FIELD_SLOTS]; #endif /*WITH_TAINT_TRACKING*/ |
TaintDroid将其中的u4 instanceData[CLASS_FILED_SLOTS]改为u4 instanceData[CLASS_FILED_SLOTS * 2]。这里CLASS_FILED_SLOTS默认为4。倍增的空间用于交叉存储各个实例域的污点。联想到类的实例域有两种类型:1)诸如int之类的基本类型;2)类对象的引用。所以我们可以知道,TaintDroid为每个引用也分配了一个tag,用于表示该引用的污点信息。充分理解这一点,对我们后续分析复杂污点传播逻辑很有帮助。
2)其次,修改了静态域StaticField:Field:
struct StaticField : Field { JValue value; /* initially set from DEX for primitives */ #ifdef WITH_TAINT_TRACKING Taint taint; #endif }; |
在JValue之后添加了Taint tiant成员。Taint成员定义在vm/interp/Taint.h文件中定义如下:
typedef struct Taint{ u4 tag}Taint;
通过这样的修改,再对涉及到操作这些数据结构的方法进行修复就能实现类的实例域和静态域的污点跟踪了。这里以computeFieldOffsets函数为例,此函数定义在dalvik/vm/oo/Class.cpp中,由于代码较多,仅截取部分修复相关部分:
…… if (clazz->super != NULL) fieldOffset = clazz->super->objectSize; else fieldOffset = OFFSETOF_MEMBER(DataObject, instanceData); …… /*Start by moving all reference fields to the front */ for (i = 0; i < clazz->ifieldCount; i++) { InstField* pField = &clazz->ifields[i]; char c = pField->signature[0];
if (c != '[' && c != 'L') { while (j > i) { InstField* refField = &clazz->ifields[j--]; char rc = refField->signature[0]; if (rc == '[' || rc == 'L'] { swapField(pField, refField); c = rc; clazz->ifieldRefCount++; break; } } /* We may or may not have swapped a field.*/ } else { /* This is a reference field.*/ clazz->ifieldRefCount++; } /*If we've hit the end of the reference fields, break.*/ if (c != '[' && c != 'L') break;
pField->byteOffset = fieldOffset; #ifdef WITH_TAINT_TRACKING fieldOffset += sizeof(u4) + sizeof(u4); /* interleaved tag */ #else fieldOffset += sizeof(u4); #endif LOGVV(" --- offset1 '%s'=%d", pField->name,pField->byteOffset); } ……
/* Alignment is good, shuffle any double-wide fields forward, and finish assigning field offsets to all fields.*/ for ( ; i < clazz->ifieldCount; i++) { InstField* pField = &clazz->ifields[i]; char c = pField->signature[0];
if (c != 'D' && c != 'J') { while (j > i) { InstField* doubleField = &clazz->ifields[j--]; char rc = doubleField->signature[0]; if (rc == 'D' || rc == 'J') { swapField(pField, doubleField); c = rc; break; } } } else { } pField->byteOffset = fieldOffset; #ifdef WITH_TAINT_TRACKING fieldOffset += sizeof(u4) + sizeof(u4); /* room for tag */ if (c == 'J' || c == 'D') fieldOffset += sizeof(u4) + sizeof(u4); /* keep 64-bit aligned */ #else fieldOffset += sizeof(u4); if (c == 'J' || c == 'D') fieldOffset += sizeof(u4); #endif /* ndef WITH_TAINT_TRACKING */ } |
显然,在计算类中各个实例域的偏移值的时候,由于TaintDroid对实例域的空间进行了倍增(交叉存储污点),所以这里应该加上2*sizeof(u4)。另外需要注意的是对于Double和Long类型的数据,要加上4*sizeof(u4)!
至此类的实例域和静态域的污点跟踪分析完毕,下一步轮到数组了。
3)对数组对象ArrayObject:Object的修改:
struct ArrayObject : Object { /* number of elements; immutable after init */ u4 length; #ifdef WITH_TAINT_TRACKING Taint taint; #endif u8 contents[1]; }; |
在length成员之后添加Taint tiant成员。之所以这样做,是因为出于性能的考虑:如果数组中每个成员都存储一个tag的话,对性能的影响就太大了,所以TaintDroid对每个ArrayObject对象只分配一个tag。
同样的,修改了ArrayObject的结构体,就必须同步修改涉及到对ArrayObject进行操作的函数。这里以oo/Array.cpp中的allocArray函数为例:
static ArrayObject* allocArray(ClassObject* arrayClass, size_t length, size_t elemWidth, int allocFlags) { …… ArrayObject* newArray = (ArrayObject*)dvmMalloc(totalSize, allocFlags); if (newArray != NULL) { DVM_OBJECT_INIT(newArray, arrayClass); newArray->length = length; #ifdef WITH_TAINT_TRACKING newArray->taint.tag = TAINT_CLEAR; #endif dvmTrackAllocation(arrayClass, totalSize); } } |
在分配一个新的数组的时候,TaintDroid将它的taint成员赋值为TAINT_CLEAR(即清空污点信息)。
4)特殊类StringObject的结构分析。它的结构体如下:
struct StringObject : Object { /* variable #of u4 slots; u8 uses 2 slots */ u4 instanceData[1]; /** Returns this string's length in characters. */ int length() const; /** * Returns this string's length in bytes when encoded as modified UTF-8. * Does not include a terminating NUL byte. */ int utfLength() const; /** Returns this string's char[] as an ArrayObject. */ ArrayObject* array() const; /** Returns this string's char[] as a u2*. */ const u2* chars() const; }; |
由于StringObject提供了一个方法array(),此方法返回一个ArrayObject型指针,所以在获取和设置StringObject的污点信息的时候,需要通过StringObject.array()->taint.tag进行操作。
4 进一步分析DVM污点传播逻辑
在前一章节中,我们分析了两参数相加的DVM opcode(OP_ADD_INT_2ADDR),这是因为我们当时对类的静态域、实例域以及数组的污点存储并不熟悉,所以也就仅仅能捏一捏这类软柿子而已,现在我们挑战一下更高难度的数组操作相关的opcode——OP_AGET_OBJECT(即aget-obj)。该opcode的汇编实现在dalvik/vm/mterp/armv*te_taint/OP_AGET_OBJECT.S文件中:
%verify "executed" %include "armv5te_taint/OP_AGET.S" |
转到OP_AGET.S:
%default { "load":"ldr", "shift":"2" } //表示移位基准为2位,即乘以4 %verify "executed" /* * Array get, 32 bits or less. vAA <- vBB[vCC]. * * Note: using the usual FETCH/and/shift stuff, this fits in exactly 17 * instructions. We use a pair of FETCH_Bs instead. * * for: aget, aget-object, aget-boolean, aget-byte, aget-char, aget-short */ /* op vAA, vBB, vCC */ FETCH_B(r2, 1, 0) @ r2<- BB mov r9, rINST, lsr #8 @ r9<- AA FETCH_B(r3, 1, 1) @ r3<- CC GET_VREG(r0, r2) @ r0<- vBB (array object) GET_VREG(r1, r3) @ r1<- vCC (requested index) cmp r0, #0 @ null array object? beq common_errNullObject @ yes, bail // begin WITH_TAINT_TRACKING bl .L${opcode}_taint_prop_1 // end WITH_TAINT_TRACKING ldr r3, [r0, #offArrayObject_length] @ r3<- arrayObj->length add r0, r0, r1, lsl #$shift @ r0<- arrayObj + index*width cmp r1, r3 @ compare unsigned index, length // begin WITH_TAINT_TRACKING // bcs common_errArrayIndex @ index >= length, bail // in subroutine // FETCH_ADVANCE_INST(2) @ advance rPC, load rINST // in subroutine bl .L${opcode}_taint_prop_2 // end WITH_TAINT_TRACKING $load r2, [r0, #offArrayObject_contents] @ r2<- vBB[vCC] GET_INST_OPCODE(ip) @ extract opcode from rINST SET_VREG(r2, r9) @ vAA<- r2 GOTO_OPCODE(ip) @ jump to next instruction
%break
.L${opcode}_taint_prop_1: ldr r2, [r0, #offArrayObject_taint] @获取数组对象vBB的taint,赋给r2 SET_TAINT_FP(r10) GET_VREG_TAINT(r3, r3, r10) @获取索引数据vCC的taint,赋给r3 orr r2, r3, r2 @ r2<- r2 | r1 bx lr
.L${opcode}_taint_prop_2: bcs common_errArrayIndex @ index >= length, bail FETCH_ADVANCE_INST(2) @ advance rPC, load rINST SET_TAINT_FP(r3) SET_VREG_TAINT(r2, r9, r3) @将r2(即此时的污点信息)赋值给vAA的taint tag bx lr |
显然重点在*_taint_prop_1和*_taint_prop_2两个代码段。简要概括它们的功能:
1)taint_prop_1首先取得数组对象vBB的taint。注意这里offArrayObject_taint定义在dalvik/vm/common/asm-constants.h中:
#ifdef WITH_TAINT_TRACKING MTERP_OFFSET(offArrayObject_taint, ArrayObject, taint, 12) //结合ArrayObject数据结构,不难理解此代码 #endif
MTERP_OFFSET宏的定义如下: # define MTERP_OFFSET(_name, _type, _field, _offset) \ if (OFFSETOF_MEMBER(_type, _field) != _offset) { \ ALOGE("Bad asm offset %s (%d), should be %d", \ #_name, _offset, OFFSETOF_MEMBER(_type, _field)); \ failed = true; \ } |
获取了vBB的taint tag之后,再获取索引vCC的taint tag,然后将两者相或,最终结果赋给r2寄存器;
2)taint_prop_2再将此时的r2寄存器中的tag信息赋值给vAA的taint tag。这样就完成了aget-object的污点传播了。
至此整个DVM的变量级污点跟踪机制我们都已经分析完毕,下一步就是分析Native层的方法级污点跟踪,这里给各位读者预留一个问题:为什么在DVM中可以实现变量街污点跟踪,但是native层却只能实现方法级污点跟踪呢?
作者:简行、走位@阿里聚安全,更多技术文章,请点击阿里聚安全博客
阿里聚安全由阿里巴巴移动安全部出品,面向企业和开发者提供企业安全解决方案,全面覆盖移动安全、数据风控、内容安全、实人认证等维度,并在业界率先提出“以业务为中心的安全”,赋能生态,与行业共享阿里巴巴集团多年沉淀的专业安全能力。