从Android运行时出发,打造我们的脱壳神器
https://juejin.im/post/5aa117e9f265da23945f0f0e
DexHunter的原理分析和使用说明(一)
https://blog.csdn.net/QQ1084283172/article/details/53710357
DexHunter的原理分析和使用说明(二)
https://blog.csdn.net/QQ1084283172/article/details/53715325
DexHunter在Dalvik虚拟机模式下的脱壳原理分析
https://blog.csdn.net/qq1084283172/article/details/78494671
DexHunter在ART虚拟机模式下的脱壳原理分析
https://blog.csdn.net/QQ1084283172/article/details/78494620
换一个帅一点姿势实现DexHunter
https://bbs.pediy.com/thread-225427.htm
[原创]Xdex(百度版)脱壳工具基本原理
https://bbs.pediy.com/thread-206441.htm
Dalvik模式下,dexHunter的实现
//------------------------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;
}
// 读取类的数据头信息DexClassDataHeader
ReadClassDataHeader(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;
}
// 从内存中读取类的所有的静态成员变量的DexField
for (uint32_t i = 0; i < header.staticFieldsSize; i++)
{
ReadClassDataField(pData, &result->staticFields[i]);
}
// 从内存中读取类的所有的实例成员变量的DexField
for (uint32_t i = 0; i < header.instanceFieldsSize; i++)
{
ReadClassDataField(pData, &result->instanceFields[i]);
}
// 从内存中读取类的所有的直接成员方法的DexMethod
for (uint32_t i = 0; i < header.directMethodsSize; i++)
{
ReadClassDataMethod(pData, &result->directMethods[i]);
}
// 从内存中读取类的所有的虚成员方法的DexMethod
for (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);
// 保存整个类所有的静态成员变量的DexField
if (pData->staticFields)
{
for (uint32_t i = 0; i < pData->header.staticFieldsSize; i++)
{
writeLeb128(&store,pData->staticFields[i].fieldIdx);
writeLeb128(&store,pData->staticFields[i].accessFlags);
}
}
// 保存整个类所有的实例成员变量的DexField
if (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);
}
}
// 保存整个类所有的直接成员方法的DexMethod
if (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);
}
}
// 保存整个类所有的静虚成员方法的DexMethod
if (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 data
size_t length; //length of data
void* baseAddr; //page-aligned base address
size_t baseLength; //length of mapping
};
***/
/***
// /dalvik/vm/DvmDex.h
// DvmDex代表一个dex文件
//Some additional VM data structures that are associated with the DEX file.
struct DvmDex
{
DexFile* pDexFile; //pointer to the DexFile we're associated with ,odex文件的信息
const DexHeader* pHeader; //clone of pDexFile->pHeader (it's used frequently enough),dex文件头相关信息
struct StringObject** pResStrings; //interned strings; parallel to "stringIds" ,字符串
struct ClassObject** pResClasses; //resolved classes; parallel to "typeIds" ,通过DexFile里的ClassDefItem构造出来的一个结构体(类信息)
struct Method** pResMethods; //resolved methods; parallel to "methodIds" ,通过Method_Item构造出来的结构体(方法信息)
struct Field** pResFields; //resolved instance fields; parallel to "fieldIds",(this holds both InstField and StaticField)
struct AtomicCache* pInterfaceCache; //interface method lookup cache
//shared memory region with file contents
bool isMappedReadOnly;
MemMapping memMap; // memMap->addr为odex文件的内存位置,memMap->length为odex文件的长度
jobject dex_object;
//lock ensuring mutual exclusion during updates
pthread_mutex_t modLock;
};
****/
/*******
// /dalvik/libdex/DexFile.h
//DexFile结构体存储了odex文件的一些信息。
//Structure representing a DEX file.
struct DexFile
{
//directly-mapped "opt" header
const DexOptHeader* pOptHeader;
//pointers to directly-mapped structs and arrays in base DEX
const DexHeader* pHeader;
const DexStringId* pStringIds;
const DexTypeId* pTypeIds;
const DexFieldId* pFieldIds;
const DexMethodId* pMethodIds;
const DexProtoId* pProtoIds;
const DexClassDef* pClassDefs;
const DexLink* pLinkData;
//These are mapped out of the "auxillary" section, and may not be included in the file.
const DexClassLookup* pClassLookup;
const void* pRegisterMapPool; // RegisterMapClassPool
const u1* baseAddr; // points to start of DEX file data
int overhead; // track memory overhead for auxillary structures
//void* auxData; // additional app-specific data structures associated with the DEX
};
//Direct-mapped "class_def_item".
struct DexClassDef {
u4 classIdx; // index into typeIds for this class
u4 accessFlags;
u4 superclassIdx; // index into typeIds for superclass
u4 interfacesOff; // file offset to DexTypeList
u4 sourceFileIdx; // index into stringIds for source file name
u4 annotationsOff; // file offset to annotations_directory_item
u4 classDataOff; // file offset to class_data_item
u4 staticValuesOff; // file offset to DexEncodedArray
};
//Direct-mapped "code_item".
//The "catches" table is used when throwing an exception,
//"debugInfo" is used when displaying an exception stack trace or
//debugging. An offset of zero indicates that there are no entries.
struct DexCode {
u2 registersSize;
u2 insSize;
u2 outsSize;
u2 triesSize;
u4 debugInfoOff; // file offset to debug info stream
u4 insnsSize; // size of the insns array, in u2 units
u2 insns[1];
// followed by optional u2 padding
// followed by try_item[triesSize]
// followed by uleb128 handlersSize
// followed by catch_handler_item[handlersSize]
};
*****/
/****
// dalvik/vm/oo/Object.h
//Class objects have many additional fields. This is used for both classes and interfaces, including synthesized classes (arrays and primitive types).
//
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; //static, private, and <init> methods
Method* directMethods;
int virtualMethodCount; // virtual methods defined in this class; invoked through vtable
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];
};
struct Method {
ClassObject* clazz; //the class we are a part of
u4 accessFlags; //access flags; low 16 bits are defined by spec (could be u2?)
//For concrete virtual methods, this is the offset of the method in "vtable".
//For abstract methods in an interface class, this is the offset of the method in "iftable[n]->methodIndexArray".
u2 methodIndex;
// Method bounds; not needed for an abstract method.
// For a native method, we compute the size of the argument list, and set "insSize" and "registerSize" equal to it.
u2 registersSize; // ins + locals
u2 outsSize;
u2 insSize;
// method name, e.g. "<init>" or "eatLunch"
const char* name;
//Method prototype descriptor string (return and argument types).
// TODO: This currently must specify the DexFile as well as the proto_ids
// index, because generated Proxy classes don't have a DexFile. We can
// remove the DexFile* and reduce the size of this struct if we generate
// a DEX for proxies.
DexProto prototype;
// short-form method descriptor string
const char* shorty;
// The remaining items are not used for abstract or native methods.
// (JNI is currently hijacking "insns" as a function pointer, set
// after the first call. For internal-native this stays null.)
//
// the actual code
const u2* insns; // instructions, in memory-mapped .dex
// JNI: cached argument and return-type hints
int jniArgInfo;
// JNI: native method ptr; could be actual function or a JNI bridge. We
// don't currently discriminate between DalvikBridgeFunc and
// DalvikNativeFunc; the former takes an argument superset (i.e. two
// extra args) which will be ignored. If necessary we can use
// insns==NULL to detect JNI bridge vs. internal native.
DalvikBridgeFunc nativeFunc;
// JNI: true if this static non-synchronized native method (that has no
// reference arguments) needs a JNIEnv* and jclass/jobject. Libcore
// uses this.
bool fastJni;
// JNI: true if this method has no reference arguments. This lets the JNI
// bridge avoid scanning the shorty for direct pointers that need to be converted to local references.
// TODO: replace this with a list of indexes of the reference arguments.
bool noRef;
// JNI: true if we should log entry and exit. This is the only way
// developers can log the local references that are passed into their code.
// Used for debugging JNI problems in third-party code.
bool shouldTrace;
// Register map data, if available. This will point into the DEX file
// if the data was computed during pre-verification, or into the
// linear alloc area if not.
const RegisterMap* registerMap;
// set if method was called during method profiling
bool inProfile;
};
***/
//处理class_defs部分的线程函数
/*
创建原生线程,遍历被脱壳Android应用odex文件的 DexClassDef 数据数组,基于Android运行时进行被脱壳Android应用类的描述数据 DexClassDef 、DexClassData 和类方法的实现数据 DexCode 的收集和内存dump,将类的定义描述数据 DexClassDef内存dump保存到文件/data/data/pakegname/classdef中,将类的实际描述结构体数据DexClassData 和类方法的实现数据DexCode内存dump保存到文件/data/data/pakegname/extra中,然后将内存dump的odex文件的4部分数据 进行重组,得到能够反编译odex文件,其中pakegname为被脱壳Android应用的包名。
*/
void* DumpClass(void *parament)
{
// 休眠一段时间
while (timer_flag)
{
sleep(5);
}
// 获取odex文件的内存描述结构DvmDex
DvmDex* 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的dump数据的文件保存路径
strcpy(path, dumppath);
// 拼接字符串(得到存放classdef的文件路径)
//xxxx/classdef文件中保存着内存dump的类DexClassDef
strcat(path, "classdef");
// 打开文件xxxx/classdef
FILE *fp = fopen(path, "wb+");
strcpy(path, dumppath);
// 拼接字符串(得到存放extra数据的文件路径)
//xxxx/extra文件中保存着内存dump的类DexClassData数据与DexCode,保存格式如下:
// |{[DexCode#DexCode....]DexClassData}|{[DexCode#DexCode....]DexClassData}|{[DexCode#DexCode....]DexClassData}...
strcat(path, "extra");
// 打开文件xxxx/extra
FILE *fp1 = fopen(path,"wb+");
//被脱壳Android应用的odex文件中的类数据以Android运行时的DexClassData的实际填充数据为基准进行内存dump,意思就是内存dump的类描述结构体数据以dex文件加载到内存后转换为ClassObject *中的类实际填充数据为基准进行内存dump,
//为什么要这样做呢? 因为,Android加固会对被保护的dex文件进行加固处理,对被保护的dex文件 中的类数据偏移 classDataOff 进行修改并将类数据 DexClassData 进行加密处理,在类被执行时先对被加密的类数据 DexClassData 进行解密处理,
//然后修正类数据的偏移classDataOff 使其指向解密后正确的DexClassData数据(一般情况,被解密的DexClassData数据会存放在odex文件文件头之前的内存区域 或者 在odex文件文件尾之后的内存区域);
//还有一些Android加固采取的加固粒度更细,对被保护dex文件的 codeOff 进行修改并对类方法实现 DexCode 的数据进行加密处理,在类方法执行时,先解密被加密的类方法实现 DexCode的数据,然后修正指向DexCode数据的偏移 codeOff
//(一般情况,解密后的DexCode数据会被存放在在odex文件文件头之前的内 存区域 或者 在odex文件文件尾之后的内存区域)。由于当dex文件中类被执行时,类相关的描述数据都会被解密后正确填充,因此基于Android运行时进行dex文件类数据的dump。
uint32_t mask = 0x3ffff;
char padding = 0;
//Android系统的类库是以android开头的类(用于过滤)
const char* header = "Landroid";
// 获取dex文件的DexClassDef的数量classDefsSize
unsigned int num_class_defs = pDexFile->pHeader->classDefsSize;
// pDexFile->baseAddr为存放dex文件的内存地址,
//mem->addr为存放odex的文件的数据的内存地址,mem->length为存放的odex文件的长度
// 获取dex文件起始到odex文件结束在内存区域的长度(因为偏移量是以dex文件起始地址来计算的,extra文件内容是添加到odex尾部)
uint32_t total_pointer = mem->length - uint32_t(pDexFile->baseAddr-(const u1*)mem->addr);
// 保存dex文件起始到odex文件结束在内存区域的长度
uint32_t rec = total_pointer;
//鉴于Android进程中内存数据4字节对齐的要求(目的是为了提高内存数据访问的效率),因此需要对被脱壳Android进程的odex文件的内存数据进行 4 字节对齐处理,不足4字节进行 0 的数据填充,
//也是为了后面odex文件重组时正确的设置odex文件类数据DexClassData和DexCode的偏移。
// 鉴于dex文件在内存中的4字节对齐问题(4字节对齐)
while (total_pointer&3)
{
total_pointer++;
}
// 增加的内存字节数inc(4字节内存对齐导致的填充字节数)
int inc = total_pointer - rec;
//为了判断被脱壳Android应用dex文件的DexClassData数据是否被Android加固所加固处理,因此需要对DexClassData数据的偏移 classDataOff 进行边界的判断,
//正常的DexClassData数据保存的起始文件偏移是dex文件存放DexClassDef段数据结束的位置,DexClassData数据保存的结束文件偏移是整个odex文件结束的位置。
//遍历dex文件的每个类定义描述结构体DexClassDef,基于Android运行时的类数据DexClassData和DexCode的收集;在进行dex文件类数据收集时,排除过滤掉 "Landroid" 开头的Android系统类和空类的类数据DexClassData和DexCode的收集。
// 在内存中dex文件存放DexClassData的起始文件相对偏移地址(即存放DexClassDef的结束地址)
uint32_t start = pDexFile->pHeader->classDefsOff+sizeof(DexClassDef)*num_class_defs;
// 在内存中dex文件起始内存地址到整个odex文件结束在内存区域的长度
uint32_t end = (uint32_t)((const u1*)mem->addr+mem->length - pDexFile->baseAddr);
// 遍历dex文件的所有的DexClassDef
for (size_t i = 0; i < num_class_defs; i++) //DexClassDef Loop start ----------
{
const u1* data = NULL;
DexClassData* pData = NULL;
//设置初始值
bool need_extra = false;
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;
}
//调用函数dvmDefineClass加载当前类descriptor
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是否在正常的内存范围
// pClassDef->classDataOff < start 解密的DexClassData数据存放在odex文件文件头之前的内存区域情况
// pClassDef->classDataOff > end 解密的DexClassData数据存放在在odex文件文件尾之后的内存区域情况
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)
{
for (uint32_t i = 0; i < pData->header.directMethodsSize; i++) // directMethods looop start++++++++++++++++++++++++++++
{
// 获取类成员的直接方法的指针
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;
// 设置类成员直接方法的字节码指针为0
pData->directMethods[i].codeOff=0;
}
// 继续下次循环
continue;
}
/****
method->insns不是Native方法的情况,则为指向方法具体的Dalvik指令的指针
(指向的是实际加载到内存中的Dalvik指令,而不是在Dex文件中的)
****/
// 获取method->insns不是Native方法的情况下,方法的字节码实际相对偏移codeOff
u4 codeitem_off = u4((const u1*)method->insns-16-pDexFile->baseAddr);
// 修正method->insns不是Native方法的accessFlags
if (ac != pData->directMethods[i].accessFlags)
{
ALOGI("GOT IT method ac");
need_extra=true;
pData->directMethods[i].accessFlags=ac;
}
// 修正method->insns不是Native方法的字节码正常相对偏移codeOff
if (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;
// 获取类方法的信息结构体DexCode
DexCode *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++;
}
}
} // directMethods looop over++++++++++++++++++++++++++++
}
// 获取类成员虚方法的method->insns字节码指令保存到文件xxxx/extra
if (pData->virtualMethods)
{
for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++) //virtualMethods looop start========================
{
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++;
}
}//virtualMethods looop over========================
}
}
/*
对于是Android系统(Landroid开头的)的类和空类,need_extra为 false 即不对 Landroid开头 的Android系统类和空类进行类实现数据DexClassData的收集,并设置这两种情况的类DexClassDef的成员变量 classDataOff 和 annotationsOff 的文件偏移值为 0 ,还有对于这两种情况的类,只保存 类定义的数据DexClassDef 到文件 /data/data/pakegname/classdef 中。这里还需要对标志 need_extra 和 pass 的意思进行说明一下,标志need_extra 的意思是是否保存类的实现数据 DexClassData 到文件/data/data/pakegname/extra 中;标志 pass的意思是 是否设置类定义DexClassDef 的成员变量 classDataOff 和 annotationsOff 的文件偏移值为 0,标志 need_extra 和 pass 的bool值总是相反的,其中pakegname为被脱壳Android应用的包名。
*/
// 当android系统类或者当前类是无数据的空类的情况下,need_extra = false, pass = true
// 针对非android系统有效类的情况下,need_extra = true, pass = false
classdef:
// 获取dex文件的第i个DexClassDef结构体的信息
DexClassDef temp = *pClassDef;
uint8_t *p = (uint8_t *)&temp;
// 判断该类是否需要额外保存的类的数据DexClassData和类方法的DexCode数据信息
// 这种情况下,需要保存DexClassData结构体的数据信息
if (need_extra)
{
// 将类的DexCode和DexClassData数据信息保存到xxxx/extra文件中,保存格式如下:
// |{[DexCode#DexCode....]DexClassData}|{[DexCode#DexCode....]DexClassData}|{[DexCode#DexCode....]DexClassData}...
// xxxx/extra文件中DexCode的文件偏移保存在DexClassData结构体的pData->virtualMethods[i].codeOff和pData->directMethods[i].codeOff中
// xxxx/extra文件中DexClassData的文件偏移保存在xxxx/classdef文件中的DexClassDef->classDataOff中
ALOGI("GOT IT classdata before");
int class_data_len = 0;
// pData = ReadClassData(&data);
// 将DexClassData结构体的数据信息leb128编码后保存到申请的内存空间中
// out为指向DexClassData结构体的数据存放指针,class_data_len为DexClassData结构体数据的长度
uint8_t *out = EncodeClassData(pData, class_data_len);
if (!out)
{
// 保存到申请的内存中失败
continue;
}
// 设置类的DexClassDef的成员classDataOff(指向DexClassData)的相对文件偏移
temp.classDataOff = total_pointer;
// 将类的DexClassData的信息写入xxxx/extra文件中
fwrite(out, 1, class_data_len, fp1);
// 刷新文件流
fflush(fp1);
// 更新文件偏移地址
total_pointer += class_data_len;
// 处理dex文件在内存中4字节对齐的问题
while (total_pointer&3)
{
// 写入填充的数据0
fwrite(&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值为0
if (pass)
{
// 设置类信息结构体的classDataOff和annotationsOff为0
temp.classDataOff = 0;
temp.annotationsOff = 0;
}
ALOGI("GOT IT classdef");
// 将DexClassDef的第i个DexClassDef结构体的信息写入到文件xxxx/classdef中
fwrite(p, sizeof(DexClassDef), 1, fp);
// 刷新文件流
fflush(fp);
} //DexClassDef Loop over ----------
// 关闭文件
fclose(fp1);
fclose(fp);
strcpy(path, dumppath);
// 拼接字符串
strcat(path, "whole.dex");
// 打开文件xxxx/whole.dex
fp = 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/part1
fd = 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/classdef
fd = 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/data
fd = 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)
{
// 向文件中写入填充数据0
fwrite(&padding, 1, 1, fp);
// 刷新文件流
fflush(fp);
inc--;
}
// ******************************************
strcpy(path,dumppath);
// 拼接字符串
strcat(path,"extra");
// 打开文件xxxx/extra
fd=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; //被加载dex文件或者jar文件的路径字符串, <memory>表示由Dalvik_dalvik_system_DexFile_openDexFile_bytearray打开
bool isDex; //是dex文件还是jar包
bool okayToFree;
RawDexFile* pRawDexFile; //如果是dex,则指向dex内存区,由Dalvik_dalvik_system_DexFile_openDexFileNative打开
JarFile* pJarFile; //如果是jar包,则指向JarFile结构,由Dalvik_dalvik_system_DexFile_openDexFileNative打开
u1* pDexMemory; //指向dex内存区,由Dalvik_dalvik_system_DexFile_openDexFile_bytearray打开
} 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);
else
pDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile);
/* once we load something, we can't unmap the storage */
pDexOrJar->okayToFree = false;
//------------------------added begin----------------------//
int uid = getuid();
// 排除掉Android系统进程(因为系统进程的uid为0)
if (uid)
{
if (readable)
{
// 创建互斥信号通量
pthread_mutex_lock(&read_mutex);
if (readable)
{
readable = false;
// 释放互斥信号通量
pthread_mutex_unlock(&read_mutex);
pthread_t read_thread;
// 创建线程,读取脱壳的配置文件/data/dexname以及等待定时器timer
pthread_create(&read_thread, NULL, ReadThread, NULL);
}
else
{
// 释放互斥信号通量
pthread_mutex_unlock(&read_mutex);
}
}
}
// 非Android系统进程且特征字符串不为空的情况下
if(uid && strcmp(dexname, ""))
{
// 判断当前进程的apk是否是需要被脱壳的apk
char * 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/part1
FILE *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/data
fp = fopen(temp, "wb+");
// 获取内存中dex文件存放DexClassDef结构体的结束内存地址
addr = pDexFile->baseAddr+pDexFile->pHeader->classDefsOff+sizeof(DexClassDef)*pDexFile->pHeader->classDefsSize;
// 获取内存中odex文件存放DexClassDef结构体结尾到odex文件结尾的数据长度
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步-----------------------
// 用于保存线程id
pthread_t dumpthread;
// 构建线程的传入参数
param.loader = loader;
param.pDvmDex = pDvmDex;
// 创建线程进行类class的内存dump
dvmCreateInternalThread(&dumpthread, "ClassDumper", DumpClass, (void*)param);
}
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);
}