通过研究Dalvik指令格式和类型,将可能产生信息流指令分为16大类,如表1所示。其中vx,vy,vz是寄存器,fy,fz是字段ID,T()表示污点值。
序号 | 指令类型 | 指令含义 | 污点传播逻辑 | 污点传播描述 |
1 | Move vx,vy | 移动vy的内容到vx | T(vy)→T(vx) | 将T(vy)复制到T(vx) |
2 | Return vx | 返回在vx的值 | T(vx) →T(∑) | 将T(vx)返回 |
3 | Const vx, lit32 | 将lit32值存入vx | T(vx)→0 | 清空污点 |
4 | Throw vx | 将出错信息返回vx | T(vx)→T(E) | 设置出错污点 |
5 | Iput vx,vy,fz | 根据fz将vx值存入实例vy的int型字段 | T(vx)|T(vy)→T(fz) | 将T(vx) 与T(vy)存入实例int字段 |
6 | Iget vx,vy,fz | 根据fz读取实例vy的int型字段到vx | T(fz)|T(vy)→T(vx) | 将T(vy)与实例int字段相与存入T(vx) |
7 | Sput vx,fy | 根据fy将vx存入int型字段 | T(vx)→T(fy) | 将T(vx)复制到T(fy) |
8 | Sget vx,fy | 根据fy读取int型字段到vx | T(fy)→T(vx) | 将T(fy)复制到T(vx) |
9 | Aput vx,vy,vz | 将vx值存入位于vy的数组引用且索引位置为vz | T(vx)→T(vy) | 将T(vx)复制数组污点 |
10 | Aget vz,vy,vz | 从位于vy的数组引用获取索引位置为vz的int型值存入vx | T(vy)→T(vx) | 将数组污点复制到T(vx) |
11 | And vx,vy,vz | 将vy|vz并存入vx | T(vy)|T(vz)→T(vx) | 将T(vy)与T(vz)相与并存入T(vx) |
12 | Add vx,vy,vz | 计算vy+vz并存入vx | T(vy)|T(vz)→T(vx) | 将T(vy)与T(vz)相与并存入T(vx) |
13 | If-lt vx,vy,0080 | 如果vx<vy,跳转到目标0080 | T(vx)|T(vy)→T(vy) ∪T(vx) | 将T(vx)与T(vy)相与并更新各自污点 |
14 | Int-to-long vx,vy | 将vy中int值转换long并保存到vx,vx+1 | T(vy)→T(vx) | 将T(vy)复制到T(vx) |
15 | Neg vx,vy | 计算vx=-vy并保存在vx | T(vy)→T(vx) | 将T(vy)复制到T(vx) |
16 | Cmpl vx,vy,vz | 比较vy和vz的值并在vz存入int型返回值 | T(vy)|T(vz)→T(vz)∪T(vy) ∪T(vz) | 将T(vy)与T(vz)相与更新各自污点并存入T(vz) |
相对于taintdroid,其跟踪指令并没有13,15,16,所以聪明的人可能会通过这几类指令使得污点能够脱离跟踪,从而不能报告隐私泄露。
taintdroid是如何跟踪的呢,在此解释两种不同实现方式:
C 实现主要思想用C重写对栈的操作,定义如下宏:
# define GET_REGISTER_TAINT(_idx) (fp[((_idx)<<1)+1])
# define SET_REGISTER_TAINT(_idx, _val) (fp[((_idx)<<1)+1] = (u4)(_val))
# define GET_REGISTER_TAINT_WIDE(_idx) (fp[((_idx)<<1)+1])
# define SET_REGISTER_TAINT_WIDE(_idx, _val) (fp[((_idx)<<1)+1] = \
fp[((_idx)<<1)+3] = (u4)(_val))
/* Alternate interfaces to help dereference register width */
# define GET_REGISTER_TAINT_INT(_idx) GET_REGISTER_TAINT(_idx)
# define SET_REGISTER_TAINT_INT(_idx, _val) SET_REGISTER_TAINT(_idx, _val)
# define GET_REGISTER_TAINT_FLOAT(_idx) GET_REGISTER_TAINT(_idx)
# define SET_REGISTER_TAINT_FLOAT(_idx, _val) SET_REGISTER_TAINT(_idx, _val)
# define GET_REGISTER_TAINT_DOUBLE(_idx) GET_REGISTER_TAINT_WIDE(_idx)
# define SET_REGISTER_TAINT_DOUBLE(_idx, _val) SET_REGISTER_TAINT_WIDE(_idx, _val)
# define GET_REGISTER_TAINT_AS_OBJECT(_idx) GET_REGISTER_TAINT(_idx)
# define SET_REGISTER_TAINT_AS_OBJECT(_idx, _val) SET_REGISTER_TAINT(_idx, _val)
/* Object Taint interface */
# define GET_ARRAY_TAINT(_arr) ((_arr)->taint.tag)
# define SET_ARRAY_TAINT(_arr, _val) ((_arr)->taint.tag = (u4)(_val))
/* Return value taint (assumes rtaint variable is in scope */
# define GET_RETURN_TAINT() (rtaint.tag)
# define SET_RETURN_TAINT(_val) (rtaint.tag = (u4)(_val))
例如对于加法指令:
#define HANDLE_OP_X_INT_2ADDR(_opcode, _opname, _op, _chkdiv) \
HANDLE_OPCODE(_opcode /*vA, vB*/)
其实现的C 代码如下:
vdst = INST_A(inst); \
vsrc1 = INST_B(inst); \
ILOGV("|%s-int-2addr v%d,v%d", (_opname), vdst, vsrc1); \
if (_chkdiv != 0) { \
s4 firstVal, secondVal, result; \
firstVal = GET_REGISTER(vdst); \
secondVal = GET_REGISTER(vsrc1); \
if (secondVal == 0) { \
EXPORT_PC(); \
dvmThrowArithmeticException("divide by zero"); \
GOTO_exceptionThrown(); \
} \
if ((u4)firstVal == 0x80000000 && secondVal == -1) { \
if (_chkdiv == 1) \
result = firstVal; /* division */ \
else \
result = 0; /* remainder */ \
} else { \
result = firstVal _op secondVal; \
} \
SET_REGISTER(vdst, result); \
} else { \
SET_REGISTER(vdst, \
(s4) GET_REGISTER(vdst) _op (s4) GET_REGISTER(vsrc1)); \
}
为完成相应的污点操作,则可依据定义的宏进行栈中污点位置的操作如下:
SET_REGISTER_TAINT(vdst, \
(GET_REGISTER_TAINT(vdst)|GET_REGISTER_TAINT(vsrc1)) );
一目了然。
对于其用汇编语言实现,则较为复杂,难于理解一些,但如若对arm汇编比较了解,学习过编译原理的则能较好理解,毕竟汇编实现要快很多。
首先要了解的是汇编对于寄存器的分配:
在硬件上arm如下分配寄存器:
r0-r3 hold first 4 args to a method; they are not preserved across method calls
r4-r8 are available for general use
r9 is given special treatment in some situations, but not for us
r10 (sl) seems to be generally available
r11 (fp) is used by gcc (unless -fomit-frame-pointer is set)
r12 (ip) is scratch -- not preserved across method calls
r13 (sp) should be managed carefully in case a signal arrives
r14 (lr) must be preserved
r15 (pc) can be tinkered with directly
但dalvik则不这样进行分配,其如下分配和定义:
reg nick purpose
r4 rPC interpreted program counter, used for fetching instructions
r5 rFP interpreted frame pointer, used for accessing locals and args
r6 rSELF self (Thread) pointer
r7 rINST first 16-bit code unit of current instruction
r8 rIBASE interpreted instruction base pointer, used for computed goto
由此可以这样定义对栈污点的操作:
#define SET_TAINT_FP(_reg) add _reg, rFP, #4
#define SET_TAINT_CLEAR(_reg) mov _reg, #0
#define GET_VREG(_reg, _vreg) ldr _reg, [rFP, _vreg, lsl #3]
#define SET_VREG(_reg, _vreg) str _reg, [rFP, _vreg, lsl #3]
#define GET_VREG_TAINT(_reg, _vreg, _rFP) ldr _reg, [_rFP, _vreg, lsl #3]
#define SET_VREG_TAINT(_reg, _vreg, _rFP) str _reg, [_rFP, _vreg, lsl #3]
#else
#define GET_VREG(_reg, _vreg) ldr _reg, [rFP, _vreg, lsl #2]
#define SET_VREG(_reg, _vreg) str _reg, [rFP, _vreg, lsl #2]
其操作语义也比较好理解,不再多说。
由此对于加法指令:
/* binop vAA, vBB, vCC */
FETCH(r0, 1) @ r0<- CCBB
mov r9, rINST, lsr #8 @ r9<- AA
mov r3, r0, lsr #8 @ r3<- CC
and r2, r0, #255 @ r2<- BB
GET_VREG(r1, r3) @ r1<- vCC
GET_VREG(r0, r2) @ r0<- vBB
.if 0
cmp r1, #0 @ is second operand zero?
beq common_errDivideByZero
.endif
FETCH_ADVANCE_INST(2) @ advance rPC, load rINST
@ optional op; may set condition codes
add r0, r0, r1 @ r0<- op, r0-r3 changed
GET_INST_OPCODE(ip) @ extract opcode from rINST
SET_VREG(r0, r9) @ vAA<- r0
GOTO_OPCODE(ip) @ jump to next instruction
/* 11-14 instructions */
即可在Fetch之前加入污点传播指令
.LOP_ADD_INT_taint_prop
其定义为,
.LOP_ADD_INT_taint_prop:
SET_TAINT_FP(r10)
GET_VREG_TAINT(r3, r3, r10)
GET_VREG_TAINT(r2, r2, r10)
orr r2, r3, r2
SET_VREG_TAINT(r2, r9, r10)
bx lr
这些结合前面定义都比较好理解:设置污点fp,取两源数据的污点,污点相与实现传播,然后将污点保存。
这样对于其它指令也可以很好理解,至此应该就了解了全部的程序变量级别的污点传播,和方法调用的污点传播过程
方法调用污点传播再附一图如下: