TaintDroid剖析之Native方法级污点跟踪分析

简介: 在前两篇文章中我们详细分析了TaintDroid对DVM栈帧的修改,以及它是如何在修改之后的栈帧中实现DVM变量级污点跟踪的。现在我们继续分析其第二个粒度的污点跟踪——Native方法级跟踪。

TaintDroid剖析之Native方法级污点跟踪分析


简行、走位@阿里聚安全



1、Native方法的污点传播

在前两篇文章中我们详细分析了TaintDroid对DVM栈帧的修改,以及它是如何在修改之后的栈帧中实现DVM变量级污点跟踪的。现在我们继续分析其第二个粒度的污点跟踪——Native方法级跟踪。

回顾前文,我们知道Native方法执行在Native栈帧中,且Native栈帧由dvmPushJNIFrame函数分配栈空间,再由dvmCallMethodV/A或者dvmInvokeMethod对栈帧进行初始化,所以我们也按照之前的方式进行Native方法级跟踪机制分析。

TaintDroid深入剖析系列目录:

TaintDroid深入剖析之启动篇

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

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并没有完成)。


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



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

相关文章
|
消息中间件 缓存 安全
抱歉,Xposed真的可以为所欲为——终 · 庖丁解码(下)
Xposed的使用不难,API也就那些,难点是: 逆向弄清楚Hook APP的方法调用流程,怎么调,参数都是干嘛的等。 经过反复练习,逆向Hook一个普通的APP(非企业级加固)写出可用的Xposed插件早已驾轻就熟(主要是磨时间),但有一个顾虑一直萦绕心间:不知道Xposed底层的具体实现原理。Tips:Xposed通常只能 Hook java层 及 应用资源的替换,有两个实现版本:4.4前的Dalvik虚拟机实现 和 5.0后ART虚拟机实现,本文针对后者进行分析,同时搭配 Android 5.1.1_r6 源码食用。
2375 0
|
人工智能 机器人 数据安全/隐私保护
【必看!】阿里云推出QWen-7B和QWen-7b-Chat,开放免费商用!
阿里云最近发布了两款大型开源模型QWen-7B和QWen-7b-Chat,这两款模型的参数规模达到了70亿,用户可以在Hugging Face和ModelScope上免费使用。尽管大型模型的热度近期有所下降,但阿里云选择开源模型来赢得用户的支持,并保持自身在竞争中的优势。这一举措也引起了人们的关注,因为不开源可能会导致信息泄露的风险。通过开源模型,阿里云既能满足用户需求,又能保持技术竞争力。
2288 0
 【必看!】阿里云推出QWen-7B和QWen-7b-Chat,开放免费商用!
|
3月前
|
Web App开发 安全 数据建模
OV证书在浏览器地址栏长什么样?DV和OV的用户看到的内容有什么不同
DV与OV SSL证书虽均显示绿色小锁,但点开后差异显著:DV仅验证域名所有权,证书详情无企业信息;OV则经人工核验企业真实身份,证书中明确展示公司名称、所在地等,大幅提升用户信任感,适合企业官网、电商等场景。(239字)
|
人工智能 语音技术 API
破解大模型语音交互延迟痛点!MaixinVoiceAI 3.0 流式TTS适配阿里百炼落地实践
阿里百炼大模型语音交互落地中,传统TTS“先推理、再合成、后播放”的模式导致延迟过高,影响服务效率与体验。MaixinVoiceAI 3.0流式TTS打破这一逻辑,采用“边推理、边合成、边播放”的并行流水线机制,深度适配阿里百炼大模型。其首包响应≤300ms,可将总延迟压缩至用户无感知范围,原生兼容阿里百炼API,零开发适配企业级部署,同时兼顾高保真音质与拟人化韵律,解决传统TTS痛点,为智能呼叫中心等场景提供高效落地支撑。
破解大模型语音交互延迟痛点!MaixinVoiceAI 3.0 流式TTS适配阿里百炼落地实践
|
机器学习/深度学习 人工智能 算法
恶疟原虫目标检测数据集(2700张图片已划分、已标注)【数据集分享】
本数据集为 恶性疟原虫目标检测 提供了一个完整且高质量的基础,涵盖了显微镜下典型的细胞图像,并配备了标准化的 YOLO 格式标注,便于快速上手深度学习模型训练。研究人员和开发者无需从零开始标注和清洗数据,即可直接将其应用于 YOLOv5、YOLOv8、11、Faster R-CNN、Detectron2 等主流目标检测框架,极大缩短了实验周期。
|
9月前
|
敏捷开发 人工智能 程序员
备考像“开荒”却没地图?用这条AI指令给大纲做个CT扫描,3分钟生成通关攻略
面对厚重的考试教材无从下手?本文提供一套基于逆向教学设计的AI指令,能将复杂的考试大纲瞬间转化为带权重、有路径的“作战地图”。拒绝盲目努力,用AI开启上帝视角,精准攻克重难点。
726 5
|
10月前
|
数据采集 人工智能 搜索推荐
AI 问答占 52%!长沙别墅装修 GEO 突围:30 天引用率暴涨 40%
周有贵,巴黎学院人工智能博士,GGI商学院GEO首席技术专家,专注AI时代数字营销革新。2025年12月1日,长沙著名别墅设计师张主华专程拜访交流,共探GEO技术在装修设计行业中的AI引流逻辑与实操应用。面对生成式AI问答入口占比突破52%的新趋势,传统SEO正被GEO取代——从链接点击到答案呈现,企业需通过构建灯塔内容、E-E-A-T信任链与结构化数据,让品牌信息被AI优先引用。本次对话揭示:未来流量之争,本质是“被AI推荐”的能力之争。
|
9月前
|
安全 网络安全 API
伪装成税务文件的“银狐”:中国背景APT组织首次用印度所得税诱饵发动精准网络间谍战
“银狐”APT组织伪装税务邮件,利用双扩展名诱骗点击,植入木马窃取敏感数据。其攻击高度定制化,采用LOLBins、进程镂空等技术绕过传统防护,凸显企业需加强EDR、零信任与员工安全意识的紧迫性。
696 0
|
10月前
|
DataWorks 监控 数据可视化
数据可视化大屏项目
数据可视化大屏是数据价值具象化的核心工具,广泛应用于企业实时监控、运营分析与大型活动指挥。本文系统解析从多源数据整合、DataWorks加工、DataV可视化设计,到WebSocket实时推送、权限控制与性能优化的全链路实践,并结合双11实战案例,揭示高并发、高实时场景下的技术落地逻辑,助力企业构建高效、安全、流畅的决策中枢。(238字)
817 0
|
消息中间件 Java Kafka
开发者如何使用云消息队列 Kafka 版
【10月更文挑战第15天】开发者如何使用云消息队列 Kafka 版
814 115

热门文章

最新文章