TaintDroid剖析之DVM变量级污点跟踪(下篇)

简介: 在上一章节中我们详细分析了TaintDroid对DVM方法参数和方法变量的变量级污点跟踪机制,现在我们将继续分析TaintDroid对类的静态域、实例域以及数组的污点跟踪。


TaintDroid剖析之DVM变量级污点跟踪(下篇)

简行、走位@阿里聚安全



回顾

上一章节中我们详细分析了TaintDroidDVM方法参数和方法变量的变量级污点跟踪机制,现在我们将继续分析TaintDroid对类的静态域、实例域以及数组的污点跟踪。


了解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 ObjectsString Objects当前等同于Data Objects,但鉴于该类在DVM中大量使用,因此DVM单独定义了一个类StringObject,它直接继承至Object

了解了类的数据结构,再去分析TaintDroid对类的静态域、实例域和数组的污点跟踪就不会觉得无从下手了。


对各种数据结构的修改

要想实现对类的实例域和静态域的污点跟踪,最简单粗暴的方式就是对类中相关的数据结构进行修改。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)。另外需要注意的是对于DoubleLong类型的数据,要加上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进行操作。


进一步分析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]   @获取数组对象vBBtaint,赋给r2

    SET_TAINT_FP(r10)

    GET_VREG_TAINT(r3, r3, r10)                  @获取索引数据vCCtaint,赋给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(即此时的污点信息)赋值给vAAtaint tag

    bx      lr

显然重点在*_taint_prop_1*_taint_prop_2两个代码段。简要概括它们的功能:

1)taint_prop_1首先取得数组对象vBBtaint注意这里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;                                                      \

    }

获取了vBBtaint tag之后,再获取索引vCCtaint tag,然后将两者相或,最终结果赋给r2寄存器;

2)taint_prop_2再将此时的r2寄存器中的tag信息赋值给vAAtaint tag这样就完成了aget-object的污点传播了。

至此整个DVM的变量级污点跟踪机制我们都已经分析完毕,下一步就是分析Native层的方法级污点跟踪,这里给各位读者预留一个问题:为什么在DVM中可以实现变量街污点跟踪,但是native层却只能实现方法级污点跟踪呢?




作者:简行、走位@阿里聚安全,更多技术文章,请点击阿里聚安全博客



阿里聚安全由阿里巴巴移动安全部出品,面向企业和开发者提供企业安全解决方案,全面覆盖移动安全、数据风控、内容安全、实人认证等维度,并在业界率先提出“以业务为中心的安全”,赋能生态,与行业共享阿里巴巴集团多年沉淀的专业安全能力。

相关文章
|
7月前
|
消息中间件 监控 算法
深入理解Linux进程管理与优化:原理、调度和资源控制详解
深入理解Linux进程管理与优化:原理、调度和资源控制详解
113 0
|
22天前
|
存储 Kubernetes 应用服务中间件
【CKA模拟题】综合演练演示Pod如何引用PVC的资源
【CKA模拟题】综合演练演示Pod如何引用PVC的资源
108 2
|
1月前
|
存储 编译器 C语言
【C/C++ POD 类型】深度解析C++中的POD类型:从理论基础到项目实践
【C/C++ POD 类型】深度解析C++中的POD类型:从理论基础到项目实践
67 0
|
1月前
|
存储 调度
进程的奥德赛:并发世界中的核心概念与动态管理
进程的奥德赛:并发世界中的核心概念与动态管理
38 2
|
4月前
|
Kubernetes Cloud Native 调度
k8s学习-污点和容忍(概念、模版、创建、删除)
k8s学习-污点和容忍(概念、模版、创建、删除)
44 0
|
4月前
|
监控 DataWorks 调度
调度任务的责任人如果已经不在该项目空间了,调度任务可否正常运行?
调度任务的责任人如果已经不在该项目空间了,调度任务可否正常运行?
35 0
|
5月前
|
机器学习/深度学习 存储 人工智能
【网安AIGC专题11.8】论文15 ChatGPT在软件工程中的全面作用:程序语法(AST生成、表达式匹配) 静态行为、动态分析(数据依赖和污点分析、指针分析) 提示设计(角色提示、指令提示)
【网安AIGC专题11.8】论文15 ChatGPT在软件工程中的全面作用:程序语法(AST生成、表达式匹配) 静态行为、动态分析(数据依赖和污点分析、指针分析) 提示设计(角色提示、指令提示)
73 0
|
8月前
|
机器人 API 区块链
Pionex派网量化网格交易机器人开发策略部署[源码执行规则示例]
Pionex派网量化网格交易机器人开发策略部署[源码执行规则示例]
|
9月前
|
Kubernetes API 容器
【k8s 系列】k8s 学习二十四,如何访问 pod 元数据
**我们在 pod 中运行容器的时候,是否也会有想要获取当前 pod 的环境信息呢?**咱们写的 yaml 清单写的很简单,实际上部署之后, k8s 会给我们补充在 yaml 清单中没有写的字段,那么我们的 pod 环境信息和容器的元数据如何传递到容器中呢?是不是也是通过获取这些 k8s 默认给我填写的字段呢?
126 0
|
算法 调度
【操作系统篇】第五篇——调度(概念,层次,调度时机,切换与过程,方式,评价指标)
【操作系统篇】第五篇——调度(概念,层次,调度时机,切换与过程,方式,评价指标)
【操作系统篇】第五篇——调度(概念,层次,调度时机,切换与过程,方式,评价指标)

热门文章

最新文章