本文主要是介绍DexHunter的原理分析和使用说明(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53715325
前面的博文《Android通用脱壳工具DexHunter的原理分析和使用说明(一)》中已经记录了很多关于DexHunter脱壳工具的脱壳原理和思考的思路问题并没有涉及到DexHunter脱壳工具的代码的分析,今天我就代码分析、理解和DexHunter脱壳工具的使用以及需要注意的地方进行博文的记录。
在阅读DexHunter的代码之前,复习下几个须知:
1>. POSIX定时器编程相关的知识
struct sigevent
The <signal.h> header shall define the sigevent structure, which shall include at least the following members:
struct sigevent {int sigev_notify; //Notification type. int sigev_signo; //Signal number. union sigval sigev_value; //Signal value. void (*sigev_notify_function)(union sigval); //Notification function. pthread_attr_t *sigev_notify_attributes; //Notification attributes.
};
sigev_notify
sigev_notify 的取值范围如下,只有3种情况(对应的宏在<signal.h>中定义)。
- SIGEV_NONE
- 事件发生时,什么也不做. SIGEV_SIGNAL 事件发生时,将sigev_signo 指定的信号(A queued signal)发送给指定的进程. SIGEV_THREAD
- 事件发生时,内核会(在此进程内)以sigev_notification_attributes为线程属性创建一个线程,并且让它执行sigev_notify_function,
- 传入sigev_value作为为一个参数.
sigev_signo
在sigev_notify = SIGEV_SIGNAL 时使用,指定信号的种别(number).
sigev_value
在sigev_notify = SIGEV_THREAD 时使用,作为sigev_notify_function 即定时器线程回调函数的参数.
union sigval
{int sival_int;void *sival_ptr;
};
(*sigev_notify_function)(union sigval)
函数指针(指向通知执行函数即定时器线程回调函数),在sigev_notify = SIGEV_THREAD 时使用, 其他情况下置为NULL.
sigev_notify_attributes
指向线程属性的指针,在sigev_notify = SIGEV_THREAD 时使用,指定创建线程的属性, 其他情况下置为NULL.
The <time.h> header shall declare the timespec structure, which shall include at least the following members:
struct timespec
{time_t tv_sec; /* Seconds */long tv_nsec; /* Nanoseconds(纳秒:十亿分之一秒) */
};
struct itimerspec
The <time.h> header shall also declare the itimerspec structure, which shall include at least the following members:
struct itimerspec
{struct timespec it_interval; /* Timer interval(timer循环时间间隔) */struct timespec it_value; /* Initial expiration(timer初次到期时间间隔) */
};
clockid_t
clockid_t is used for clock ID type in the clock and timer functions, 取值范围如下(前4个是POSIX定义的,灰色部分为Linux的扩展),
/* Identifier for system-wide realtime clock, Setting this clock requires appropriate privileges */
#define CLOCK_REALTIME 0
/* Monotonic system-wide clock, Clock that cannot be set and represents monotonic time since some unspecified starting point */
#define CLOCK_MONOTONIC 1
/* High-resolution timer from the CPU. (since Linux 2.6.12) */
#define CLOCK_PROCESS_CPUTIME_ID 2
/* Thread-specific CPU-time clock. (since Linux 2.6.12) */
#define CLOCK_THREAD_CPUTIME_ID 3
/* Monotonic system-wide clock, not adjusted for frequency scaling. */
#define CLOCK_MONOTONIC_RAW 4
/* Identifier for system-wide realtime clock, updated only on ticks. */
#define CLOCK_REALTIME_COARSE 5
/* Monotonic system-wide clock, updated only on ticks. */
#define CLOCK_MONOTONIC_COARSE 6
CLOCK_REALTIME : 这种时钟表示的是绝对时间, 指的是从1970年1月1月0:00到目前经过多少秒, 相当于你的linux系统中显示的时间, 所以这个时间是可以更改的, 当系统的时钟源被改变,或者系统管理员重置了系统时间之后,这种类型的时钟可以得到相应的调整, 对设定为此类型的timer是有影响的.
CLOCK_MONOTONIC : 这种时钟表示的是相对时间, 其值对通过累积时钟节拍(嘀嗒)计算出来的, 不受时钟源等的影响, 从系统启动这一刻起开始计时, 如果你想计算出在一台计算机上不受重启的影响,两个事件发生的间隔时间的话,那么它将是最好的选择。
CLOCK_PROCESS_CPUTIME_ID : 测量调用进程(包括该进程内所有的线程)用户和系统消耗的总CPU时间.
CLOCK_THREAD_CPUTIME_ID : 测量调用线程消耗的CPU时间.
The <sys/time.h> header shall define the timeval structure, which shall include at least the following members:
struct timeval
{time_t tv_sec; // Seconds(秒). suseconds_t tv_usec; // Microseconds(微秒:千分之一毫秒).
};
struct itimerval
The <sys/time.h> header shall define the itimerval structure, which shall include at least the following members:
struct itimerval
{struct timeval it_interval; /* Timer interval(timer循环时间间隔) */struct timeval it_value; /* Initial expiration(timer初次到期时间间隔) */
};
POSIX时钟创建、初始化以及删除一个定时器的操作被分为三个不同的函数:timer_create()(创建定时器)、timer_settime()(初始化定时器)以及timer_delete(销毁它)。
使用timer定时器时注意两点:
1.定时器创建的线程回调函数,如果不是C函数而是C++的类成员函数,则不能用普通成员函数,必须用静态成员函数,因为普通成员函数含有隐含参数--this指针
2.timer定时器的时间间隔,第一次是ts.it_value这么长,后面每次时间间隔是ts.it_interval这么长
ts.it_interval.tv_sec = 0; //定时器第一次之后的每次时间间隔
ts.it_interval.tv_nsec = 200000000; //200ms
ts.it_value.tv_sec = 0; //定时器第一次定时的时间
ts.it_value.tv_nsec = 200000000; //200ms
感谢链接:
http://www.cnblogs.com/LubinLew/p/POSIX-DataStructure.html
http://blog.csdn.net/ustcxiangchun/article/details/6339762
http://www.ccvita.com/508.html
http://www.linuxidc.com/Linux/2011-08/41862.htm
2>. Android的Dex文件相关的重要结构体
//Use this to keep track of mapped segments.
struct MemMapping
{void* addr; //start of datasize_t length; //length of datavoid* baseAddr; //page-aligned base addresssize_t baseLength; //length of mapping
};
// odex文件在内存中的存放地址memMap->addr和长度memMap->length
struct DvmDex
{DexFile* pDexFile; // odex文件的信息const DexHeader* pHeader; // dex文件头相关信息struct StringObject** pResStrings; // dex文件的字符串结构体struct ClassObject** pResClasses; // 通过DexFile里的ClassDefItem构造出来的一个结构体(类信息)struct Method** pResMethods; // 通过Method_Item构造出来的结构体(方法信息)struct Field** pResFields;struct AtomicCache* pInterfaceCache;bool isMappedReadOnly;MemMapping memMap; // memMap->addr为odex文件的内存存放位置,memMap->length为odex文件的长度jobject dex_object;pthread_mutex_t modLock;
};
// DexFile结构体存储了整个odex文件在内存的一些信息。
struct DexFile
{//directly-mapped "opt" header const DexOptHeader* pOptHeader; //指向odex文件的文件头DexOptHeader的信息//pointers to directly-mapped structs and arrays in base DEXconst DexHeader* pHeader; // dex文件的文件头DexHeader的信息const DexStringId* pStringIds;const DexTypeId* pTypeIds;const DexFieldId* pFieldIds;const DexMethodId* pMethodIds;const DexProtoId* pProtoIds;const DexClassDef* pClassDefs; // 指向dex文件的DexClassDef结构体的指针const DexLink* pLinkData;const DexClassLookup* pClassLookup;const void* pRegisterMapPool; // RegisterMapClassPoolconst u1* baseAddr; // dex文件在内存中的存放地址int overhead;
};
// 类成员方法的结构体
struct Method { ClassObject* clazz; // 该方法所在的类u4 accessFlags; // 该方法对应的访问属性(native对应0x00100) u2 methodIndex; // 该方法在函数表或者接口表中的偏移u2 registersSize; // 该方法总共用到的寄存器个数u2 outsSize; // 当该方法要调用其它方法时,用作参数传递而使用的寄存器个数u2 insSize; // 作为调用该方法时,参数传递而使用到的寄存器个数const char* name; // 该方法的名称DexProto prototype; // 对该方法调用参数类型、顺序还有返回类型的描述const char* shorty; // 方法对应协议的短表示法,一个字符代表一种类型const u2* insns; // 该方法对应的实现的字节码的内存地址int jniArgInfo; DalvikBridgeFunc nativeFunc; // 本地方法的内存地址bool fastJni; bool noRef; bool shouldTrace; const RegisterMap* registerMap; bool inProfile;
};
// DexOrJar->fileName当前apk进程的文件路径
typedef struct DexOrJar {char* fileName; // 当前apk文件的文件路径bool isDex; // 是dex文件还是jar包bool okayToFree;RawDexFile* pRawDexFile;JarFile* pJarFile; // 如果是jar包,则指向JarFile结构u1* pDexMemory; // 如果是dex文件,则指向dex文件存放的内存区
} DexOrJar;
Dex文件与Odex文件结构对比图
DexClassLookup数据结构的定义
struct DexFile 结构体成员变量
Dex 文件与 DexFile 数据结构映射关系图
struct ClassObject 结构体的定义
类加载的工作流程以及 ClassObject 结构体的结构图
findClassNoInit 函数执行流程图
DexClassDef 数据结构的定义
loadClassFromDex 函数的执行流程图
Method 数据结构的定义
类成员方法的method->insns字段的说明:
感谢连接:
http://blog.csdn.net/roland_sun/article/details/38640297
http://blog.csdn.net/zhangyun438/article/details/17193411
http://blog.csdn.net/qq1084283172/article/details/53584495
http://www.blogfshare.com/defineclassnative.html
Android软件安全与逆向分析
Android Dalvik虚拟机结构及机制剖析(第2卷):Dalvik虚拟机各模块机制分析(有不少小错误,推荐)
4>. DexHunter脱壳工具代码分析
dvm情况下的代码分析,见下面,整个DexHunter脱壳工具代码大分部都理解清楚了,极个别函数的实现不是很理解,后期有时间在看看,代码的注释很全,但是有些地方表述的不是很明白,将就着看吧。art模式下的修改的class_linker.cpp文件的大部分代码一样,但是有一些细节的地方处理不一样,后期再分析了。dvm情况下的DexHunter脱壳工具代码只要是修改Android源码文件/dalvik/vm/native/dalvik_system_DexFile.cpp里的Dalvik_dalvik_system_DexFile_defineClassNative函数的实现,具体操作是在Android系统代码调用函数dvmDefineClass进行类加载之前,主动地一次性加载并初始化Dex文件所有的类。
//------------------------added begin----------------------//#include <asm/siginfo.h>
#include "libdex/DexClass.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>// 保存加固类型的特征字符串,其实是由pDexOrJar->fileName决定的
static char dexname[100]={0};
// 保存需要脱壳的apk文件的内存dump出来的part1、data等文件的保存路径
static char dumppath[100]={0};static bool readable=true;static pthread_mutex_t read_mutex;static bool flag=true;static pthread_mutex_t mutex;static bool timer_flag=true;static timer_t timerId;/**** dexname-----特征字符串,由pDexOrJar->fileName决定的(the feature string)* dumppath----内存dump文件的保存路径(the data path of the target app )* /data/dexname文件的示例:* /data/data/com.example.seventyfour.tencenttest/files/libmobisecy1.zip* /data/data/com.example.seventyfour.tencenttest/* * dexname(the feature string)的引用,具体参见作者zyq8709给出的slide.pptx* 重要的提醒:* Its line ending should be in the style of Unix/Linux* 意思就是/data/dexname文件中的字符串的结尾换行必须是Unix/Linux格式0A的不能是windows的0D0A***/struct arg
{DvmDex* pDvmDex;Object * loader;
} param;/*******删除定时器********/
void timer_thread(sigval_t)
{timer_flag = false;timer_delete(timerId);ALOGI("GOT IT time up");
}/*****读取配置文件/data/dexname中的数据信息并创建初始化定时器******/
void* ReadThread(void *arg)
{FILE *fp = NULL;while (dexname[0]==0 || dumppath[0]==0) {// 打开脱壳的配置文件/data/dexname// 脱壳的时,需要adb push dexname /data到Andrid系统里// 配置文件/data/dexname是可以自定义的,一些加固如梆梆可能会检测这个路径fp=fopen("/data/dexname", "r");if (fp==NULL) {sleep(1);continue;}// 读取文件中的第1行字符串---加固的特征字符串fgets(dexname, 99, fp);dexname[strlen(dexname)-1]=0;// 读取文件中的第2行字符串---需要脱壳的apk文件的数据路径fgets(dumppath,99,fp);dumppath[strlen(dumppath)-1]=0;fclose(fp);fp=NULL;}struct sigevent sev;// 定时器事件类型为创建线程sev.sigev_notify=SIGEV_THREAD;// 设置事件的线程回调函数的传入参数sev.sigev_value.sival_ptr=&timerId;// 设置事件的线程回调函数--删除定时器sev.sigev_notify_function=timer_thread;// 设置事件的线程回调函数的属性sev.sigev_notify_attributes = NULL;// 创建定时器timer_create(CLOCK_REALTIME, &sev, &timerId);struct itimerspec ts;// 定时器的第一次时间间隔ts.it_value.tv_sec=5;ts.it_value.tv_nsec=0;// 定时器的第一次之后的每次时间间隔ts.it_interval.tv_sec=0;ts.it_interval.tv_nsec=0;// 初始化定时器timer_settime(timerId, 0, &ts, NULL);return NULL;
}/****获取一个类中静态字段,实例字段,直接方法和虚方法的个数*****/
void ReadClassDataHeader(const uint8_t** pData,DexClassDataHeader *pHeader)
{// 静态成员变量的个数pHeader->staticFieldsSize = readUnsignedLeb128(pData);// 实例成员变量的个数pHeader->instanceFieldsSize = readUnsignedLeb128(pData);// 直接成员方法的个数pHeader->directMethodsSize = readUnsignedLeb128(pData);// 虚成员方法的个数pHeader->virtualMethodsSize = readUnsignedLeb128(pData);
}/****获取一个类中成员变量的信息****/
void ReadClassDataField(const uint8_t** pData, DexField* pField)
{// 成员变量的DexFieldId索引pField->fieldIdx = readUnsignedLeb128(pData);// 成员变量的访问权限 pField->accessFlags = readUnsignedLeb128(pData);
}/***获取一个类中成员方法的信息****/
void ReadClassDataMethod(const uint8_t** pData, DexMethod* pMethod)
{// 成员方法的DexMethodId索引pMethod->methodIdx = readUnsignedLeb128(pData);// 成员方法的访问权限pMethod->accessFlags = readUnsignedLeb128(pData);// 成员方法的实现字节码DexCode的数据结构pMethod->codeOff = readUnsignedLeb128(pData);
}/***获取一个类的DexClassData和所有成员变量以及个数、成员方法以及个数的结构体的信息*****/
DexClassData* ReadClassData(const uint8_t** pData)
{// 类信息的数据头DexClassDataHeader header;if (*pData == NULL) {return NULL;}// 读取类的数据头信息DexClassDataHeaderReadClassDataHeader(pData, &header);// 获取保存整个类的所有的成员变量和成员方法的结构信息需要的内存空间大小size_t resultSize = sizeof(DexClassData) + (header.staticFieldsSize * sizeof(DexField)) + (header.instanceFieldsSize * sizeof(DexField)) + (header.directMethodsSize * sizeof(DexMethod)) + (header.virtualMethodsSize * sizeof(DexMethod));// 为保存整个类的全部结构信息申请内存空间DexClassData* result = (DexClassData*) malloc(resultSize);if (result == NULL) {return NULL;}// 申请的内存中,指向类的实际存储成员变量DexField和成员方法DexMethod的内存起始地址uint8_t* ptr = ((uint8_t*) result) + sizeof(DexClassData);// 内存浅拷贝获取类的数据头信息result->header = header;// 判断类是否有静态成员变量if (header.staticFieldsSize != 0) {// 设置保存静态成员变量的DexField的起始地址result->staticFields = (DexField*) ptr;ptr += header.staticFieldsSize * sizeof(DexField);} else {result->staticFields = NULL;}// 判断类是否有实例成员变量if (header.instanceFieldsSize != 0) {// 设置保存实例成员变量的DexField的起始地址result->instanceFields = (DexField*) ptr;ptr += header.instanceFieldsSize * sizeof(DexField);} else {result->instanceFields = NULL;}// 判断类是否有直接成员方法if (header.directMethodsSize != 0) {// 设置保存直接成员方法的DexMethod的起始地址result->directMethods = (DexMethod*) ptr;ptr += header.directMethodsSize * sizeof(DexMethod);}else {result->directMethods = NULL;}// 判断类是否有虚成员方法if (header.virtualMethodsSize != 0) {// 设置保存虚成员方法的DexMethod的起始地址result->virtualMethods = (DexMethod*) ptr;} else {result->virtualMethods = NULL;}// 从内存中读取类的所有的静态成员变量的DexFieldfor (uint32_t i = 0; i < header.staticFieldsSize; i++) {ReadClassDataField(pData, &result->staticFields[i]);}// 从内存中读取类的所有的实例成员变量的DexFieldfor (uint32_t i = 0; i < header.instanceFieldsSize; i++) {ReadClassDataField(pData, &result->instanceFields[i]);}// 从内存中读取类的所有的直接成员方法的DexMethodfor (uint32_t i = 0; i < header.directMethodsSize; i++) {ReadClassDataMethod(pData, &result->directMethods[i]);}// 从内存中读取类的所有的虚成员方法的DexMethodfor (uint32_t i = 0; i < header.virtualMethodsSize; i++) {ReadClassDataMethod(pData, &result->virtualMethods[i]);}return result;
}/*****Leb128类型数据的写入*****/
void writeLeb128(uint8_t ** ptr, uint32_t data)
{while (true) {uint8_t out = data & 0x7f;if (out != data) {*(*ptr)++ = out | 0x80;data >>= 7;} else {*(*ptr)++ = out;break;}}
}/****获取一个类的所有成员变量以及个数、成员方法以及个数的结构体的信息(自定义数据保存格式)*******/
uint8_t* EncodeClassData(DexClassData *pData, int& len)
{len = 0;// 获取保存整个类的所有成员变量和成员方法的个数需要的内存大小len+=unsignedLeb128Size(pData->header.staticFieldsSize);len+=unsignedLeb128Size(pData->header.instanceFieldsSize);len+=unsignedLeb128Size(pData->header.directMethodsSize);len+=unsignedLeb128Size(pData->header.virtualMethodsSize);// 继续获取保存类的静态成员变量的DexField的大小if (pData->staticFields) {for (uint32_t i = 0; i < pData->header.staticFieldsSize; i++) {len+=unsignedLeb128Size(pData->staticFields[i].fieldIdx);len+=unsignedLeb128Size(pData->staticFields[i].accessFlags);}}// 继续获取保存类的实例成员变量的DexField的大小if (pData->instanceFields) {for (uint32_t i = 0; i < pData->header.instanceFieldsSize; i++) {len+=unsignedLeb128Size(pData->instanceFields[i].fieldIdx);len+=unsignedLeb128Size(pData->instanceFields[i].accessFlags);}}// 继续获取保存类的直接成员方法的DexMethod的大小if (pData->directMethods) {for (uint32_t i=0; i<pData->header.directMethodsSize; i++) {len+=unsignedLeb128Size(pData->directMethods[i].methodIdx);len+=unsignedLeb128Size(pData->directMethods[i].accessFlags);len+=unsignedLeb128Size(pData->directMethods[i].codeOff);}}// 继续获取保存类的虚成员方法的DexMethod的大小if (pData->virtualMethods) {for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++) {len+=unsignedLeb128Size(pData->virtualMethods[i].methodIdx);len+=unsignedLeb128Size(pData->virtualMethods[i].accessFlags);len+=unsignedLeb128Size(pData->virtualMethods[i].codeOff);}}// 为存储整个类的所有的类成员和类方法申请内存空间uint8_t * store = (uint8_t *) malloc(len);if (!store) {// 申请内存空间失败return NULL;}uint8_t * result=store;// 保存整个类所有的静态成员变量的数量大小writeLeb128(&store,pData->header.staticFieldsSize);// 保存整个类所有的实例成员变量的数量大小writeLeb128(&store,pData->header.instanceFieldsSize);// 保存整个类的所有的直接成员方法的数量大小writeLeb128(&store,pData->header.directMethodsSize);// 保存整个类的所有的虚成员方法的数量大小writeLeb128(&store,pData->header.virtualMethodsSize);// 保存整个类所有的静态成员变量的DexFieldif (pData->staticFields) {for (uint32_t i = 0; i < pData->header.staticFieldsSize; i++) {writeLeb128(&store,pData->staticFields[i].fieldIdx);writeLeb128(&store,pData->staticFields[i].accessFlags);}}// 保存整个类所有的实例成员变量的DexFieldif (pData->instanceFields) {for (uint32_t i = 0; i < pData->header.instanceFieldsSize; i++) {// 指向成员变量索引表中的一个表项,即一个DexFieldId的数据结构writeLeb128(&store,pData->instanceFields[i].fieldIdx);// 访问权限writeLeb128(&store,pData->instanceFields[i].accessFlags);}}// 保存整个类所有的直接成员方法的DexMethodif (pData->directMethods) {for (uint32_t i=0; i<pData->header.directMethodsSize; i++) {// 指向成员方法索引表中的一个表项,即一个DexMethodId数据结构writeLeb128(&store,pData->directMethods[i].methodIdx);// 访问权限writeLeb128(&store,pData->directMethods[i].accessFlags);// 成员方法的指向一个DexCode的数据结构writeLeb128(&store,pData->directMethods[i].codeOff);}}// 保存整个类所有的静虚成员方法的DexMethodif (pData->virtualMethods) {for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++) {writeLeb128(&store,pData->virtualMethods[i].methodIdx);writeLeb128(&store,pData->virtualMethods[i].accessFlags);writeLeb128(&store,pData->virtualMethods[i].codeOff);}}// 释放内存空间free(pData);return result;
}/***这个函数暂时还是不太理解,与try/catch语句解析有关***/
uint8_t* codeitem_end(const u1** pData)
{uint32_t num_of_list = readUnsignedLeb128(pData);for (; num_of_list > 0; num_of_list--) {int32_t num_of_handlers = readSignedLeb128(pData);int num = num_of_handlers;if (num_of_handlers <= 0) {num =- num_of_handlers;}for (; num > 0; num--) {readUnsignedLeb128(pData);readUnsignedLeb128(pData);}if (num_of_handlers <= 0) {readUnsignedLeb128(pData);}}return (uint8_t*)(*pData);
}/*** Use this to keep track of mapped segments.
struct MemMapping
{void* addr; //start of datasize_t length; //length of datavoid* baseAddr; //page-aligned base addresssize_t baseLength; //length of mapping
};
***//***
struct DvmDex
{DexFile* pDexFile; //odex文件的信息const DexHeader* pHeader; //dex文件头相关信息struct StringObject** pResStrings; //字符串struct ClassObject** pResClasses; //通过DexFile里的ClassDefItem构造出来的一个结构体(类信息)struct Method** pResMethods; //通过Method_Item构造出来的结构体(方法信息)struct Field** pResFields;struct AtomicCache* pInterfaceCache;bool isMappedReadOnly;MemMapping memMap; // memMap->addr为odex文件的内存位置,memMap->length为odex文件的长度jobject dex_object;pthread_mutex_t modLock;
};
****//*******
//DexFile结构体存储了odex文件的一些信息。
struct DexFile
{//directly-mapped "opt" header const DexOptHeader* pOptHeader;//pointers to directly-mapped structs and arrays in base DEXconst DexHeader* pHeader;const DexStringId* pStringIds;const DexTypeId* pTypeIds;const DexFieldId* pFieldIds;const DexMethodId* pMethodIds;const DexProtoId* pProtoIds;const DexClassDef* pClassDefs;const DexLink* pLinkData;const DexClassLookup* pClassLookup;const void* pRegisterMapPool; // RegisterMapClassPoolconst u1* baseAddr;int overhead;
};
*****//****
struct ClassObject : Object {u4 instanceData[CLASS_FIELD_SLOTS];const char* descriptor;char* descriptorAlloc;u4 accessFlags;u4 serialNumber;DvmDex* pDvmDex;ClassStatus status;ClassObject* verifyErrorClass;u4 initThreadId;size_t objectSize;ClassObject* elementClass;int arrayDim;PrimitiveType primitiveType;ClassObject* super;Object* classLoader;InitiatingLoaderList initiatingLoaderList;int interfaceCount;ClassObject** interfaces;int directMethodCount;Method* directMethods;int virtualMethodCount;Method* virtualMethods;int vtableCount;Method** vtable;int iftableCount;InterfaceEntry* iftable;int ifviPoolCount;int* ifviPool;int ifieldCount;int ifieldRefCount;InstField* ifields;u4 refOffsets;const char* sourceFile;int sfieldCount;StaticField sfields[0];
};
***/void* DumpClass(void *parament)
{// 休眠一段时间while (timer_flag) {sleep(5);}// 获取odex文件的内存描述结构DvmDexDvmDex* pDvmDex=((struct arg*)parament)->pDvmDex;Object *loader=((struct arg*)parament)->loader;// 获取指向odex文件的指针DexFile* pDexFile = pDvmDex->pDexFile;// 获取内存存放odex文件的内存信息描述MemMapping * mem = &pDvmDex->memMap;// 获取当前时间u4 time = dvmGetRelativeTimeMsec();ALOGI("GOT IT begin: %d ms", time);// 申请内存空间char *path = new char[100];// 获取需要脱壳的apk的数据目录的路径strcpy(path, dumppath);// 拼接字符串strcat(path, "classdef");// 打开文件xxxx/classdefFILE *fp = fopen(path, "wb+");strcpy(path, dumppath);// 拼接字符串strcat(path, "extra");// 打开文件xxxx/extraFILE *fp1 = fopen(path,"wb+");uint32_t mask = 0x3ffff;char padding = 0;const char* header = "Landroid";// 获取dex文件的DexClassDef的数量classDefsSizeunsigned int num_class_defs = pDexFile->pHeader->classDefsSize;// pDexFile->baseAddr为存放dex文件的内存地址,//mem->addr为存放odex的文件的数据的内存地址,mem->length为存放的odex文件的长度 // 获取dex文件起始到odex文件结束在内存区域的长度uint32_t total_pointer = mem->length - uint32_t(pDexFile->baseAddr-(const u1*)mem->addr);// 保存dex文件起始到odex文件结束在内存区域的长度uint32_t rec = total_pointer;// 鉴于dex文件在内存中的4字节对齐问题while (total_pointer&3) {total_pointer++;}// 增加的内存字节数incint inc = total_pointer - rec;// 在内存中dex文件存放DexClassData的起始文件相对偏移#############uint32_t start = pDexFile->pHeader->classDefsOff+sizeof(DexClassDef)*num_class_defs;// 在内存中dex文件存放DexClassData的结束文件相对偏移#############uint32_t end = (uint32_t)((const u1*)mem->addr+mem->length - pDexFile->baseAddr);// 遍历dex文件的所有的DexClassDeffor (size_t i = 0; i < num_class_defs; i++) //Loop start ----------{bool need_extra = false;const u1* data = NULL;DexClassData* pData = NULL;bool pass = false;// 获取dex文件的第i个DexClassDef的结构体信息const DexClassDef *pClassDef = dexGetClassDef(pDvmDex->pDexFile, i);// 获取类的描述符信息即类类型字符串如:Landroid/xxx/yyy;const char *descriptor = dexGetClassDescriptor(pDvmDex->pDexFile, pClassDef);// 判断该类是否是Landroid开头的系统类,是否是一个有效的类if(!strncmp(header, descriptor, 8) || !pClassDef->classDataOff){// 设置跳过过滤标签pass = true;// ******是系统类或者当前类不是有效的类,直接跳转******goto classdef;}ClassObject * clazz=NULL;// ########加载类描述符指定的类#####################clazz = dvmDefineClass(pDvmDex, descriptor, loader);if (!clazz) {continue;}//#################################################// 打印加载的类描述符信息ALOGI("GOT IT 加载class: %s", descriptor);// 判断加载的指定的类是否已经初始化完成if (!dvmIsClassInitialized(clazz)) {if(dvmInitClass(clazz)){ALOGI("GOT IT init: %s", descriptor);}}// 判断类的classData的偏移classDataOff是否在正常的内存范围 if(pClassDef->classDataOff < start || pClassDef->classDataOff > end){need_extra = true;}// 获取dex文件的一个类的类数据DexClassData的内存地址data = dexGetClassData(pDexFile, pClassDef);// 获取dex文件的一个类的类数据DexClassData以及成员变量DexField和成员方法DexMethod的结构pData = ReadClassData(&data);if (!pData) {// 获取失败,继续下次循环continue;}// 获取类成员直接方法if (pData->directMethods) // looop start++++++++++++++++++++++++++++{for (uint32_t i = 0; i < pData->header.directMethodsSize; i++) {// 获取类成员的直接方法的指针Method *method = &(clazz->directMethods[i]);// 用于判断函数是否是native函数相关uint32_t ac = (method->accessFlags) & mask;// 打印类成员直接方法的名称ALOGI("GOT IT direct method name %s.%s", descriptor, method->name);// method->insns为Dalvik虚拟机自带的Native函数(Internal Native)则值为Null// method->insns为普通的Native函数则为指向JNI实际函数机器码的首地址if (!method->insns || ac&ACC_NATIVE) {// 获取指向类成员直接方法字节码的指针if (pData->directMethods[i].codeOff) {// 设置需要额外数据的标志need_extra = true;// 设置类成员直接方法的访问权限值pData->directMethods[i].accessFlags=ac;// 设置类成员直接方法的字节码指针为0pData->directMethods[i].codeOff=0;}// 继续下次循环continue;}/**** method->insns不是Native方法的情况,则为指向方法具体的Dalvik指令的指针(指向的是实际加载到内存中的Dalvik指令,而不是在Dex文件中的)****/// 获取method->insns不是Native方法的情况下,方法的字节码实际相对偏移codeOffu4 codeitem_off = u4((const u1*)method->insns-16-pDexFile->baseAddr);// 修正method->insns不是Native方法的accessFlagsif (ac != pData->directMethods[i].accessFlags){ALOGI("GOT IT method ac");need_extra=true;pData->directMethods[i].accessFlags=ac;}// 修正method->insns不是Native方法的字节码正常相对偏移codeOffif (codeitem_off!=pData->directMethods[i].codeOff && ((codeitem_off>=start&&codeitem_off<=end) || codeitem_off==0)) {ALOGI("GOT IT method code");need_extra = true;pData->directMethods[i].codeOff=codeitem_off;}// 修正method->insns不是Native方法但是codeOff被app加固修改的情况下的codeOff// 此种情况的方法的dex文件在内存中被分开存放了,codeOff的相对偏移值超出了正常dex文件的内存范围if ((codeitem_off < start || codeitem_off > end) && codeitem_off != 0) {need_extra=true;// 设置类方法的codeOff值为total_pointer#############pData->directMethods[i].codeOff = total_pointer;// 获取类方法的信息结构体DexCodeDexCode *code = (DexCode*)((const u1*)method->insns-16);// item保存类方法的字节码指令集内存指针uint8_t *item=(uint8_t *) code;// 获取类方法的实际字节码的长度int code_item_len = 0;if (code->triesSize) {// 类方法中有Try/Catch语句存在的情况// 获取DexCode中的try/catch语句的hander数据信息const u1 * handler_data = dexGetCatchHandlerData(code);const u1** phandler=(const u1**)&handler_data;// 计算DexCode结构体中insns字节码指令集的结束地址偏移uint8_t * tail = codeitem_end(phandler);// 获取到类方法的DexCode中的字节码指令集的长度code_item_len = (int)(tail-item);}else{// 计算DexCode中的字节码指令集的长度code_item_len = 16+code->insnsSize*2;}ALOGI("GOT IT method code changed");// 将类方法的字节码指令集method->insns写入文件xxxx/extra中fwrite(item, 1, code_item_len, fp1);// 刷新文件流fflush(fp1);// 设置下一个method->insns的内存地址total_pointer+=code_item_len;// method->insns的4字节对齐问题while (total_pointer&3){// 写入0填充,处理4字节的对齐的问题fwrite(&padding, 1, 1, fp1);// 刷新文件流fflush(fp1);total_pointer++;}}}} // looop over++++++++++++++++++++++++++++// 获取类成员虚方法的method->insns字节码指令保存到文件xxxx/extraif (pData->virtualMethods) // looop start========================{for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++) {Method *method = &(clazz->virtualMethods[i]);uint32_t ac = (method->accessFlags) & mask;ALOGI("GOT IT virtual method name %s.%s", descriptor, method->name);if (!method->insns||ac&ACC_NATIVE) {if (pData->virtualMethods[i].codeOff) {need_extra = true;pData->virtualMethods[i].accessFlags = ac;pData->virtualMethods[i].codeOff = 0;}continue;}u4 codeitem_off = u4((const u1 *)method->insns - 16 - pDexFile->baseAddr);if (ac != pData->virtualMethods[i].accessFlags){ALOGI("GOT IT method ac");need_extra = true;pData->virtualMethods[i].accessFlags=ac;}if (codeitem_off!=pData->virtualMethods[i].codeOff&&((codeitem_off>=start&&codeitem_off<=end)||codeitem_off==0)) {ALOGI("GOT IT method code");need_extra=true;pData->virtualMethods[i].codeOff=codeitem_off;}// 类成员方法的codeOff被加固修改了,dex文件在内存中分成了2份if ((codeitem_off < start || codeitem_off > end) && codeitem_off!=0) {need_extra = true;// 将这部分类方法的DexCode数据保存到odex文件末尾的后面pData->virtualMethods[i].codeOff = total_pointer;DexCode *code = (DexCode*)((const u1*)method->insns-16);uint8_t *item=(uint8_t *) code;int code_item_len = 0;if (code->triesSize) {const u1 *handler_data = dexGetCatchHandlerData(code);const u1** phandler=(const u1**)&handler_data;uint8_t * tail=codeitem_end(phandler);code_item_len = (int)(tail-item);}else{code_item_len = 16+code->insnsSize*2;}ALOGI("GOT IT method code changed");// 将这部分类方法的DexCode数据暂时保存到文件ata/data/xxxx.xxxx.xxx/extra中fwrite(item, 1, code_item_len, fp1);fflush(fp1);total_pointer += code_item_len;while (total_pointer&3) {fwrite(&padding, 1, 1, fp1);fflush(fp1);total_pointer++;}}}} // looop over========================// 系统类或者当前类不是有效的类情况
classdef:// 获取类的整体信息结构DexClassDefDexClassDef temp = *pClassDef;uint8_t *p = (uint8_t *)&temp;// 判断是否需要额外的dex文件的类数据信息if (need_extra) {ALOGI("GOT IT classdata before");int class_data_len = 0;// 将类数据信息如成员变量的DexField和成员方法的DexMethod进行编码保存到申请的内存中// pData = ReadClassData(&data);uint8_t *out = EncodeClassData(pData, class_data_len);if (!out) {// 保存到申请的内存中失败continue;}// 设置类成员方法的DexClassData的相对文件偏移temp.classDataOff = total_pointer;// 将类的成员方法的信息写入xxxx/extra文件fwrite(out, 1, class_data_len, fp1);// 刷新文件流fflush(fp1);// 继续更新指向的类成员方法存储位置的指针total_pointer += class_data_len;// 处理dex文件在内存中4字节对齐的问题while (total_pointer&3) {// 写入填充的数据0fwrite(&padding,1,1,fp1);// 刷新文件流fflush(fp1);total_pointer++;}free(out);ALOGI("GOT IT classdata written");}else{// pData = ReadClassData(&data);if (pData) {free(pData);}}// 对系统类或者当前类不是有效的类的情况,修改classDataOff和annotationsOff值为0if (pass) {// 设置类信息结构体的classDataOff和annotationsOff为0temp.classDataOff = 0;temp.annotationsOff = 0;}ALOGI("GOT IT classdef");// 将DexClassDef结构体的大小写入到文件xxxx/classdef中fwrite(p, sizeof(DexClassDef), 1, fp);// 刷新文件流fflush(fp);} //Loop over ----------// 关闭文件fclose(fp1);fclose(fp);strcpy(path, dumppath);// 拼接字符串strcat(path, "whole.dex");// 打开文件xxxx/whole.dexfp = fopen(path,"wb+");// 设置文件指针在文件头的位置rewind(fp);int fd = -1;int r = -1;int len = 0; char *addr = NULL;struct stat st;// ******************************************strcpy(path, dumppath);// 拼接字符串strcat(path, "part1");// 打开文件xxxx/part1fd = open(path, O_RDONLY, 0666);if (fd==-1) {return NULL;}// 获取文件的文件状态信息r = fstat(fd, &st); if(r==-1){ close(fd); return NULL;}// 获取文件的大小len = st.st_size;// 为文件创建内存映射addr = (char*)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);// 将odex文件的开头到dex文件classDefsOff之间的文件数据写入到文件xxxx/whole.dex中fwrite(addr, 1, len, fp);// 刷新文件流fflush(fp);// 取消内存映射munmap(addr, len);// 关闭文件close(fd);// ******************************************strcpy(path, dumppath);// 拼接字符串strcat(path,"classdef");// 打开文件xxxx/classdeffd = open(path, O_RDONLY, 0666);if(fd==-1) {return NULL;}// 获取文件的文件状态信息r = fstat(fd,&st); if(r==-1){ close(fd); return NULL;}// 获取文件的大小len=st.st_size;// 为文件创建内存映射addr = (char*)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);// 将addr中的数据写入到文件xxxx/whole.dex中fwrite(addr, 1, len, fp);// 刷新文件流fflush(fp);// 取消内存映射munmap(addr, len);// 关闭文件close(fd);// *********************************************strcpy(path, dumppath);// 拼接字符串strcat(path, "data");// 打开文件xxxx/datafd = open(path, O_RDONLY, 0666);if (fd==-1) {return NULL;}// 获取文件的文件状态信息r = fstat(fd, &st); if(r==-1){ close(fd); return NULL;}// 获取文件的大小len=st.st_size;// 创建文件的内存映射addr=(char*)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);// 将内存odex文件中存放DexClassDef结构体以后的所有的文件数据写入到文件xxxx/whole.dex中fwrite(addr, 1, len, fp);// 刷新文件流fflush(fp);// 取消内存映射munmap(addr,len);// 关闭文件close(fd);// 4字节对齐导致的0填充while (inc > 0) {// 向文件中写入填充数据0fwrite(&padding, 1, 1, fp);// 刷新文件流fflush(fp);inc--;}// ******************************************strcpy(path,dumppath);// 拼接字符串strcat(path,"extra");// 打开文件xxxx/extrafd=open(path, O_RDONLY, 0666);if (fd==-1) {return NULL;}// 获取文件的文件状态信息r = fstat(fd, &st); if(r==-1){ close(fd); return NULL;}// 获取文件的大小len=st.st_size;// 创建文件内存映射addr=(char*)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);// 将addr中的数据写入到文件xxxx/whole.dex中fwrite(addr,1,len,fp);// 刷新文件流fflush(fp);// 取消文件文件内存映射munmap(addr,len);// 关闭文件close(fd);// 关闭文件fclose(fp);// 释放申请的内存空间delete path;// 获取此时的时间time = dvmGetRelativeTimeMsec();ALOGI("GOT IT end: %d ms", time);return NULL;
}
//------------------------added end----------------------///****
typedef struct DexOrJar {char* fileName;bool isDex; //是dex文件还是jar包bool okayToFree;RawDexFile* pRawDexFile;JarFile* pJarFile; //如果是jar包,则指向JarFile结构u1* pDexMemory; //如果是dex,则指向dex内存区
} DexOrJar;
***/static void Dalvik_dalvik_system_DexFile_defineClassNative(const u4* args,JValue* pResult)
{StringObject* nameObj = (StringObject*) args[0];Object* loader = (Object*) args[1];int cookie = args[2];ClassObject* clazz = NULL;DexOrJar* pDexOrJar = (DexOrJar*) cookie;DvmDex* pDvmDex;char* name;char* descriptor;name = dvmCreateCstrFromString(nameObj);descriptor = dvmDotToDescriptor(name);ALOGV("--- Explicit class load '%s' l=%p c=0x%08x",descriptor, loader, cookie);free(name);if (!validateCookie(cookie))RETURN_VOID();if (pDexOrJar->isDex)pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);elsepDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile);/* once we load something, we can't unmap the storage */pDexOrJar->okayToFree = false;//------------------------added begin----------------------//int uid = getuid();
// 排除掉Android系统进程
if (uid) {if (readable) {// 创建互斥信号通量pthread_mutex_lock(&read_mutex);if (readable) {readable = false;// 释放互斥信号通量pthread_mutex_unlock(&read_mutex);pthread_t read_thread;// 创建线程,读取脱壳的配置文件/data/dexname以及等待定时器timerpthread_create(&read_thread, NULL, ReadThread, NULL);}else{// 释放互斥信号通量pthread_mutex_unlock(&read_mutex);}}}// 非root情况且特征字符串不为空的情况下if(uid && strcmp(dexname, "")){// 判断当前进程的apk是否是需要被脱壳的apkchar * res = strstr(pDexOrJar->fileName, dexname);if (res&&flag) {// 创建互斥信号通量pthread_mutex_lock(&mutex);if (flag) {// 设置脱壳操作的开关为闭flag = false;// 释放互斥信号通量pthread_mutex_unlock(&mutex);// 获取内存中odex文件的结构信息DexFile* pDexFile = pDvmDex->pDexFile;// 获取odex文件在内存中的存放信息MemMapping * mem = &pDvmDex->memMap;char * temp = new char[100];//-----------------第1步-----------------------// 获取当前进程apk的数据目录路径/data/data/xxxx/strcpy(temp, dumppath);// 拼接字符串strcat(temp, "part1");// 打开文件/data/data/xxxx/part1FILE *fp = fopen(temp, "wb+");// 获取odex文件在内存中的存放指针const u1 *addr = (const u1*)mem->addr;// 获取odex文件的开头到dex文件classDefsOff之间的文件数据的长度int length = int(pDexFile->baseAddr+pDexFile->pHeader->classDefsOff-addr);// 将这部分文件数据写入到文件/data/data/xxxx/part1中fwrite(addr,1,length,fp);// 刷新文件流fflush(fp);// 关闭文件fclose(fp);//-----------------第2步-----------------------// 获取当前进程apk的数据目录路径/data/data/xxxx/strcpy(temp, dumppath);// 拼接字符串strcat(temp,"data");// 打开文件/data/data/xxxx/datafp = fopen(temp, "wb+");// 获取内存中dex文件存放DexClassDef结构体的结束内存地址addr = pDexFile->baseAddr+pDexFile->pHeader->classDefsOff+sizeof(DexClassDef)*pDexFile->pHeader->classDefsSize;// 获取内存中odex文件存放DexClassDef结构体以后的所有的文件的数据的长度length = int((const u1*)mem->addr+mem->length-addr);// 将这部分数据写入到文件/data/data/xxxx/data中fwrite(addr, 1, length, fp);// 刷新文件流fflush(fp);// 关闭文件fclose(fp);// 删除申请的内存空间delete temp;//-----------------第3步-----------------------// 用于保存线程idpthread_t dumpthread;// 构建线程的传入参数param.loader = loader;param.pDvmDex = pDvmDex;// 创建线程进行类class的内存dumpdvmCreateInternalThread(&dumpthread, "ClassDumper", DumpClass, (void*)¶m); }else{// 释放互斥信号通量pthread_mutex_unlock(&mutex);}}}
//------------------------added end----------------------//clazz = dvmDefineClass(pDvmDex, descriptor, loader);Thread* self = dvmThreadSelf();if (dvmCheckException(self)) {/** If we threw a "class not found" exception, stifle it, since the* contract in the higher method says we simply return null if* the class is not found.*/Object* excep = dvmGetException(self);if (strcmp(excep->clazz->descriptor,"Ljava/lang/ClassNotFoundException;") == 0 ||strcmp(excep->clazz->descriptor,"Ljava/lang/NoClassDefFoundError;") == 0){dvmClearException(self);}clazz = NULL;}free(descriptor);RETURN_PTR(clazz);
}
DexHunter脱壳工具的在脱壳的时候需要构造配置文件dexname并且dexname文件中的字符换行必须是Unix/Linux格式0A的不能是windows的0D0A,特别是在windows平台上使用DexHunter作者提供的system.img镜像的同学要注意了。DexHunter脱壳工具的配置文件的dexname在使用的时候,需要adb push 到Android设备的/data/路径下[/data/dexname]。
脱壳配置文件/data/dexname的路径和文件名称是可以自定修改的,具体的使用位置见下面的代码截图。dexname文件中包含2行字符串,第一行字符串(the feature string)--特征字符串,是有加固app的pDexOrJar->fileName决定的,第2行字符串是用于内存dump文件如:part1、data、classdef和extra以及最终脱壳文件whole.dex存放位置也即目标加固app的数据目录。特别说下,DexHunter工具的作者在github上说特征字符串(the feature string)在其演讲的ppt--slide.pptx上,但是呢,我觉的不能死板,还是要根据作者的DexHunter中的代码引用为准,按照加固app的实际pDexOrJar->fileName来确定。
脱壳配置文件/data/dexname中的2行字符串内容被引用的位置:
DexHunter作者在slide.pptx上给出的特征字符串(the feature string):
DexHunter脱壳工具内存dump文件产生的文件part1、data、classdef和extra合成脱壳后的whole.dex文件的过程:
重建加固前的app的dex文件
6>. DexHunter脱壳工具的个人DIY
DexHunter脱壳工具的作者提供给读者的DexHunter工具的Android镜像文件是基于Android 4.4.3的源码编译的。根据作者zyq8709提供的有关DexHunter脱壳工具的详细信息,我们可以自己按照的作者的脱壳思路和实现手法修改自己版本的Android源码/dalvik/vm/native/dalvik_system_DexFile.cpp里的Dalvik_dalvik_system_DexFile_defineClassNative函数的实现,来打造自己的DexHunter脱壳工具,有能力的大牛甚至可以根据最近的加固对抗思路,扩展DexHunter的代码,打造终极Android通用脱壳工具。DIY自己的DexHunter工具的时,既可以使用make命令编译一遍所有的Android源码产生system.img镜像文件部署到Android真机或者Android模拟器上测试,也可以使用Android源码的模块编译命令mm或者mmm(使用前提Android源码已经make编译一次成功)只编译Android源码的dalvik模块产生libdvm.so库文件然后通过修改Android系统的内存分区属性替换掉Android真机或者模拟器上原来的system/lib/libdvm.so文件,然后进行DexHunter的脱壳测试。
7>. DexHunter工具的几点说明
1.DexHunter作者zyq8709在github上给的建议:
2.DexHunter脱壳工具的第1行---特征字符串(the feature string)一直是在随着加固的对抗在变化的,具体是由当前加固app的pDexOrJar->fileName决定的,按照实际情况来;一些加固像梆梆什么的,会对DexHunter进行检测,检测的方法是检测Android系统的/data/目录下是否有文件/data/dexname,过掉的方式也比较简单,将DexHunter脱壳需要的配置文件dexname改为其他的名称就行;一些加固像梆梆什么的,会Hook一些系统的调用函数如fread、fwrite、fopen等,过掉方式是直接使用系统调用替代掉如:open、write等;一些加固像梆梆、百度什么的,还会采用类成员方法的实现代码抽取的方式对抗脱壳工具(只有代码在需要执行的时候才会被还原,执行完成以后又会被抽去掉)可能会采用比Dalvik_dalvik_system_DexFile_defineClassNative函数更深层的调用函数的Hook,如Hook掉libdvm.so库中dvmResolveClass 函数什么的,导致DexHunter内存dump获取不到dex文件的原代码,因此需要对Hook的dvmResolveClass 函数进行修改才能使DexHunter工具发挥作用。
Dalvik_dalvik_system_DexFile_defineClassNative函数到dvmResolveClass函数的整个调用过程:
在dvmResolveClass中进行修改的代码片段如下(以directMethod为例):
感谢链接:
https://github.com/zyq8709/DexHunter
http://www.liuhaihua.cn/archives/377032.html
http://blog.csdn.net/zhangyun438/article/details/17192787
http://www.cnblogs.com/vendanner/p/4844235.html
http://blog.csdn.net/qq1084283172/article/details/53710357
http://blog.csdn.net/zhangyun438/article/details/17193411
http://blog.csdn.net/justfwd/article/details/51164281
http://www.cnblogs.com/vendanner/p/4884969.html
http://bbs.pediy.com/showthread.php?t=203776
3.其他的大牛对DexHunter工具的弱点的总结:
感谢链接:
https://my.oschina.net/cve2015/blog/734360
OK,完成了,格式有点乱。DexHunter脱壳工具的DVM模式下的原理代码已经搞得比较清楚了,后面再补补课分析一下ART模式下DexHunter脱壳工具的原理的代码及其细节。
这篇关于DexHunter的原理分析和使用说明(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!