DexHunter在ART虚拟机模式下的脱壳原理分析
Posted Fly20141201
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DexHunter在ART虚拟机模式下的脱壳原理分析相关的知识,希望对你有一定的参考价值。
本文博客地址: http://blog.csdn.net/qq1084283172/article/details/78494620
DexHunter脱壳工具在Dalvik虚拟机模式下的脱壳原理分析,在前面的博客中已经提到了,但是自我感觉分析和理解的还不够透彻,后面又对DexHunter脱壳工具的源码再阅读了几遍,总算是把DexHunter脱壳工具在Dalvik虚拟机模式下和ART虚拟机模式下的脱壳原理理解清楚了。关于DexHunter脱壳工具在Dalvik虚拟机模式下的脱壳原理分析,已经准备了一篇博客想详细和深入的讲一讲,一直搁置了没有完成;本来还计划把DexHunter脱壳工具在ART虚拟机模式下的脱壳原理也详细说一说,看来是不可能了。有关android加固脱壳和Hook的好几个源码工程都看的差不多了,因为各种事情和状态不好,懒得动笔了。
在理解DexHunter脱壳工具在ART虚拟机模式下的脱壳原理之前,建议先阅读一下老罗的博客了解一下Android的ART虚拟机模式下的类加载和dex文件的优化过程,《Android运行时ART加载OAT文件的过程分析》这篇博客需要好好读一读。DexHunter在ART虚拟机模式下的脱壳编译配置主要是针对源码文件 /art/runtime/class_linker.cc 进行修改,以Android 4.4.4 r1的源码文件为示例 http://androidxref.com/4.4.4_r1/xref/art/runtime/class_linker.cc,然后对Android系统源码进行重新编译生成Android系统的镜像文件,刷到手机设备或者Android模拟器上使用,用来进行DexHunter在ART虚拟机模式下的脱壳操作。
//-----------------------added begin-----------------------//
/**
*
// OAT文件里面的oatdata段的开始储存着一个OAT头OatHeader
const OatHeader& OatFile::GetOatHeader() const
return *reinterpret_cast<const OatHeader*>(Begin());
// OAT文件里面的oatdata段开始地址
const byte* OatFile::Begin() const
CHECK(begin_ != NULL);
return begin_;
// OAT文件里面的oatexec段的结束地址
const byte* OatFile::End() const
CHECK(end_ != NULL);
return end_;
// OAT文件里面的oatdata段的开始储存着一个OAT头,这个OAT头通过类OatHeader描述
class PACKED(4) OatHeader
public:
......
private:
uint8_t magic_[4]; // 标识OAT文件的魔法数,等于‘oat\\n’
uint8_t version_[4]; // OAT文件版本号,目前的值等于‘007、0’。
uint32_t adler32_checksum_; // OAT头部检验和
// 本地机指令集,有四种取值,分别为 kArm(1)、kThumb2(2)、kX86(3)和kMips(4)。
InstructionSet instruction_set_;
// OAT文件包含的DEX文件个数。
uint32_t dex_file_count_;
// oatexec段开始位置与oatdata段开始位置的相对偏移值。
uint32_t executable_offset_;
// 在由打包在应用程序里面的classes.dex生成的OAT文件的oatdata段头部中,下述七个成员变量的值均等于0。
uint32_t interpreter_to_interpreter_bridge_offset_;
uint32_t interpreter_to_compiled_code_bridge_offset_;
uint32_t jni_dlsym_lookup_offset_;
uint32_t portable_resolution_trampoline_offset_;
uint32_t portable_to_interpreter_bridge_offset_;
uint32_t quick_resolution_trampoline_offset_;
uint32_t quick_to_interpreter_bridge_offset_;
// 用来创建Image空间的OAT文件的检验和。
uint32_t image_file_location_oat_checksum_;
// 用来创建Image空间的OAT文件的oatdata段在内存的位置。
uint32_t image_file_location_oat_data_begin_;
// 用来创建Image空间的文件的路径的大小。
uint32_t image_file_location_size_;
// 用来创建Image空间的文件的路径的在内存中的地址。
uint8_t image_file_location_data_[0]; // note variable width data at end
......
;
// 参考:http://blog.csdn.net/Luoshengyang/article/details/39307813
**/
#include <asm/siginfo.h>
#define LOGI
static char dexname[100]=0;
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;
// 自定义的线程回调的传入参数
struct arg
const DexFile* dex_file;
mirror::ClassLoader* class_loader;
ClassLinker* cl;
param;
// 类成员信息
struct DexField
uint32_t delta_fieldIdx;
uint32_t accessFlags;
;
// 类方法信息
struct DexMethod
uint32_t delta_methodIdx;
uint32_t accessFlags;
uint32_t codeOff;
;
struct DexClassDataHeader
uint32_t staticFieldsSize;
uint32_t instanceFieldsSize;
uint32_t directMethodsSize;
uint32_t virtualMethodsSize;
;
// 整个类的方法和成员信息
struct DexClassData
DexClassDataHeader header;
DexField* staticFields;
DexField* instanceFields;
DexMethod* directMethods;
DexMethod* virtualMethods;
;
// 等待dump任务执行的等待定时器
void timer_thread(::sigval_t)
timer_flag=false;
timer_delete(timerId);
#ifdef LOGI
LOG(INFO)<<"GOT IT time up";
#endif
// 读取脱壳配置文件
void* ReadThread(void *arg)
FILE *fp = NULL;
while (dexname[0]==0||dumppath[0]==0)
// 打开apk脱壳配置文件/data/dexname
// 一些加固会检测这个配置文件/data/dexname
fp=fopen("/data/dexname", "r");
if (fp==NULL)
sleep(1);
continue;
// 获取被加固的dex文件内存加载的文件路径
fgets(dexname, 99, fp);
dexname[strlen(dexname)-1]=0;
// 保存被加固dex文件的dump路径
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;
// leb128数据的写
void writeUnsignedLeb128(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;
// leb128数据的字节长度
int unsignedLeb128Size(uint32_t data)
int count = 0;
do
data >>= 7;
count++;
while (data != 0);
return count;
// 获取DexClassDataHeader结构体的各个成员的数据信息
void dexReadClassDataHeader(const uint8_t** pData,
DexClassDataHeader *pHeader)
pHeader->staticFieldsSize = DecodeUnsignedLeb128(pData);
pHeader->instanceFieldsSize = DecodeUnsignedLeb128(pData);
pHeader->directMethodsSize = DecodeUnsignedLeb128(pData);
pHeader->virtualMethodsSize = DecodeUnsignedLeb128(pData);
// 获取DexClassData结构体DexFiled的数据信息
void dexReadClassDataField(const uint8_t** pData, DexField* pField)
pField->delta_fieldIdx = DecodeUnsignedLeb128(pData);
pField->accessFlags = DecodeUnsignedLeb128(pData);
// 获取DexClassData结构体DexMethod的数据信息
void dexReadClassDataMethod(const uint8_t** pData, DexMethod* pMethod)
pMethod->delta_methodIdx = DecodeUnsignedLeb128(pData);
pMethod->accessFlags = DecodeUnsignedLeb128(pData);
pMethod->codeOff = DecodeUnsignedLeb128(pData);
// 从内存dex文件中读取指定DexFile::ClassDef对应的DexClassData即类成员和类方法描述数据到申请内存空间中
DexClassData* dexReadClassData(const uint8_t** pData)
DexClassDataHeader header;
if (*pData == NULL)
return NULL;
// 获取DexClassDataHeader结构体的各个成员的数据信息()
dexReadClassDataHeader(pData,&header);
// 计算DexClassData结构体及其所有DexFiled和所有DexMethod占用的内存空间
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;
// 更新指针的位置,用于存放类静态成员变量、类实例成员变量、类直接方法以及类虚拟方法
uint8_t* ptr = ((uint8_t*) result) + sizeof(DexClassData);
result->header = header;
if (header.staticFieldsSize != 0)
// 设置存放静态类成员变量的内存起始地址
result->staticFields = (DexField*) ptr;
// 存放所有类静态成员变量需要的内存大小
ptr += header.staticFieldsSize * sizeof(DexField);
else
result->staticFields = NULL;
if (header.instanceFieldsSize != 0)
// 设置存放实例类成员变量的内存起始地址
result->instanceFields = (DexField*) ptr;
ptr += header.instanceFieldsSize * sizeof(DexField);
else
result->instanceFields = NULL;
if (header.directMethodsSize != 0)
// 设置存放直接类成员方法的内存起始地址
result->directMethods = (DexMethod*) ptr;
ptr += header.directMethodsSize * sizeof(DexMethod);
else
result->directMethods = NULL;
if (header.virtualMethodsSize != 0)
// 设置存放虚拟类成员方法的内存起始地址
result->virtualMethods = (DexMethod*) ptr;
else
result->virtualMethods = NULL;
// 获取DexClassData中类静态变量和类实例变量以及类直接方法和类虚方法的数据
// 存放到指定的申请内存空间中
for (uint32_t i = 0; i < header.staticFieldsSize; i++)
dexReadClassDataField(pData, &result->staticFields[i]);
for (uint32_t i = 0; i < header.instanceFieldsSize; i++)
dexReadClassDataField(pData, &result->instanceFields[i]);
for (uint32_t i = 0; i < header.directMethodsSize; i++)
dexReadClassDataMethod(pData, &result->directMethods[i]);
for (uint32_t i = 0; i < header.virtualMethodsSize; i++)
dexReadClassDataMethod(pData, &result->virtualMethods[i]);
return result;
// 将整个DexClassData所表示的类数据leb128编码写入到申请的内存空间中
uint8_t* dexEncodeClassData(DexClassData *pData, int& len)
len=0;
// 获取整个DexClassData所表示类数据的内存占用大小
len+=unsignedLeb128Size(pData->header.staticFieldsSize);
len+=unsignedLeb128Size(pData->header.instanceFieldsSize);
len+=unsignedLeb128Size(pData->header.directMethodsSize);
len+=unsignedLeb128Size(pData->header.virtualMethodsSize);
if (pData->staticFields)
for (uint32_t i = 0; i < pData->header.staticFieldsSize; i++)
len+=unsignedLeb128Size(pData->staticFields[i].delta_fieldIdx);
len+=unsignedLeb128Size(pData->staticFields[i].accessFlags);
if (pData->instanceFields)
for (uint32_t i = 0; i < pData->header.instanceFieldsSize; i++)
len+=unsignedLeb128Size(pData->instanceFields[i].delta_fieldIdx);
len+=unsignedLeb128Size(pData->instanceFields[i].accessFlags);
if (pData->directMethods)
for (uint32_t i=0; i<pData->header.directMethodsSize; i++)
len+=unsignedLeb128Size(pData->directMethods[i].delta_methodIdx);
len+=unsignedLeb128Size(pData->directMethods[i].accessFlags);
len+=unsignedLeb128Size(pData->directMethods[i].codeOff);
if (pData->virtualMethods)
for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++)
len+=unsignedLeb128Size(pData->virtualMethods[i].delta_methodIdx);
len+=unsignedLeb128Size(pData->virtualMethods[i].accessFlags);
len+=unsignedLeb128Size(pData->virtualMethods[i].codeOff);
// 根据整个DexClassData所示类数据的大小申请内存空间
uint8_t * store = (uint8_t *) malloc(len);
if (!store)
// 申请内存失败的情况
return NULL;
uint8_t * result=store;
// 将整个DexClassData所表示的类数据写入到申请的内存空间中
writeUnsignedLeb128(&store,pData->header.staticFieldsSize);
writeUnsignedLeb128(&store,pData->header.instanceFieldsSize);
writeUnsignedLeb128(&store,pData->header.directMethodsSize);
writeUnsignedLeb128(&store,pData->header.virtualMethodsSize);
// 类静态成员变量
if (pData->staticFields)
for (uint32_t i = 0; i < pData->header.staticFieldsSize; i++)
writeUnsignedLeb128(&store,pData->staticFields[i].delta_fieldIdx);
writeUnsignedLeb128(&store,pData->staticFields[i].accessFlags);
// 类实例变量
if (pData->instanceFields)
for (uint32_t i = 0; i < pData->header.instanceFieldsSize; i++)
writeUnsignedLeb128(&store,pData->instanceFields[i].delta_fieldIdx);
writeUnsignedLeb128(&store,pData->instanceFields[i].accessFlags);
// 类直接方法
if (pData->directMethods)
for (uint32_t i=0; i<pData->header.directMethodsSize; i++)
writeUnsignedLeb128(&store,pData->directMethods[i].delta_methodIdx);
writeUnsignedLeb128(&store,pData->directMethods[i].accessFlags);
writeUnsignedLeb128(&store,pData->directMethods[i].codeOff);
// 类虚拟方法
if (pData->virtualMethods)
for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++)
writeUnsignedLeb128(&store,pData->virtualMethods[i].delta_methodIdx);
writeUnsignedLeb128(&store,pData->virtualMethods[i].accessFlags);
writeUnsignedLeb128(&store,pData->virtualMethods[i].codeOff);
free(pData);
return result;
// 根据try...catch的处理语句计算DexFile::CodeItem结构体的结束地址
uint8_t* codeitem_end(const uint8_t **pData)
uint32_t num_of_list = DecodeUnsignedLeb128(pData);
for (;num_of_list>0;num_of_list--)
int32_t num_of_handlers=DecodeSignedLeb128(pData);
int num=num_of_handlers;
if (num_of_handlers<=0)
num=-num_of_handlers;
for (; num > 0; num--)
DecodeUnsignedLeb128(pData);
DecodeUnsignedLeb128(pData);
if (num_of_handlers<=0)
DecodeUnsignedLeb128(pData);
return (uint8_t*)(*pData);
// 内存dump加固的dex文件的类数据信息
void* DumpClass(void *parament)
while (timer_flag)
sleep(5);
// 获取当前art虚拟机的运行时Runtime
Runtime* runtime = Runtime::Current();
// 附加到art虚拟机线程
runtime->AttachCurrentThread("ClassDumper", false, NULL,false);
// 获取当前art虚拟机的线程描述结构体Thread
Thread *self=Thread::Current();
#ifdef LOGI
LOG(INFO)<<"GOT IT DumpingClass";
#endif
#ifdef LOGI
// 获取当前时间
uint64_t time = MilliTime();
LOG(INFO)<<"GOT IT begin "<<time<<" ms";
#endif
// 当前Dex文件的内存镜像OAT描述相关的结构体
const DexFile &dex_file=(*((struct arg*)parament)->dex_file);
mirror::ClassLoader *class_loader=((struct arg*)parament)->class_loader;
ClassLinker *cl = ((struct arg*)parament)->cl;
char *path = new char[100];
strcpy(path, dumppath);
// 拼接字符串得到文件路径 xxxx/classdef
strcat(path, "classdef");
// 打开文件xxxx/classdef
FILE *fp = fopen(path, "wb+");
strcpy(path, dumppath);
// 拼接字符串得到文件路径xxxx/extra
strcat(path, "extra");
// 打开文件xxxx/extra
FILE *fp1 = fopen(path,"wb+");
uint32_t mask=0x3ffff;
char padding=0;
const char* header="Landroid";
bool pass=false;
// 锁
Locks::mutator_lock_->SharedLock(self);
// 获取当前dex文件的ClassDef的数量
size_t num_class_defs = dex_file.NumClassDefs();
// 获取整个OAT文件的文件大小
uint32_t total_pointer = dex_file.Size();
uint32_t rec = total_pointer;
// 获取OAT文件内存4字节对齐后的文件大小
while (total_pointer&3)
total_pointer++;
// 获取OAT文件4字节对齐需要填充'0'的数量
int inc = total_pointer - rec;
// 获取OAT文件里dex文件的ClassDef结构体表Table的结束地址
uint32_t start = dex_file.class_defs_off_ + sizeof(DexFile::ClassDef)*num_class_defs;
// 获取OAT文件的文件大小即文件偏移
uint32_t end = dex_file.Size();
// 遍历获取OAT文件里的dex文件的ClassDef并加载
for (size_t i = 0; i < num_class_defs; i++)
// 获取OAT文件里的dex文件的第i个ClassDef结构体
const DexFile::ClassDef &class_def = dex_file.GetClassDef(i);
// 获取ClassDef结构体描述的类的类签名字符串,例如:Landroid/xxx/yyy;
const char* descriptor = dex_file.GetClassDescriptor(class_def);
// 描述dump的类是否需要额外的类数据信息
bool need_extra = false;
// ART下dex文件的类内存加载后的描述结构体为mirror::Class
mirror::Class* klass=NULL;
const byte * data=NULL;
DexClassData* pData = NULL;
#ifdef LOGI
LOG(INFO) << "GOT IT " << descriptor;
#endif
// 过滤掉Android系统("Landroid")的类和没有类数据的无效类(这两中情况不处理)
if(!strncmp(header, descriptor, 8)||!class_def.class_data_off_)
pass = true;
// 此种情况,need_extra = false
goto classdef;
// 查找指定类签名描述的目标类
/** mirror::Class* **/klass = cl->FindClass(descriptor, class_loader);
// 查找的目标类没有找到的情况
if (!klass)
#ifdef LOGI
LOG(INFO)<<"GOT IT class Find Fail";
#endif
// 异常处理
self->ClearException();
continue;
// 成功查找到指定类签名描述的目标类
if(klass)
if(cl->EnsureInitialized(klass, true, true))
#ifdef LOGI
LOG(INFO)<<"GOT IT "<<descriptor<<" Initialized";
#endif
else
self->ClearException();
// OAT文件里dex文件的Class_Data数据存放在OAT文件的结尾或者Class_Def结构体表开始地址之前位置
if(class_def.class_data_off_<start || class_def.class_data_off_>end)
#ifdef LOGI
LOG(INFO)<<"GOT IT class data off exceeding "<<descriptor;
#endif
// 需要额外的处理Class_Def
need_extra=true;
// 获取Class_Def描述的类的ClassData数据class_data_item
data = dex_file.GetClassData(class_def);
// 读取OAT文件里dex文件的类描述DexClassData描述的类成员和类方法的数据到申请的内存空间中
pData = dexReadClassData(&data);
if (!pData)
// 失败情况
continue;
// 针对类直接方法的处理
if (pData->directMethods)
// 遍历art下类的直接方法
for (uint32_t i = 0; i < pData->header.directMethodsSize; i++)
// art::mirror::ArtMethod是art下dex文件类经过内存加载后的描述结构体
// 获取指定类的第i个直接类方法描述结构体art::mirror::ArtMethod
art::mirror::ArtMethod *method = klass->GetDirectMethod(i);
// 获取类方法的函数属性值AccessFlag
uint32_t ac = (method->GetAccessFlags()) & mask;
// 获取类方法的字节码偏移地址CodeItemOffset
uint32_t codeitem_off = method->GetCodeItemOffset();
// 通过类方法的DexMethodIndex获取到类方法的名称字符串
uint32_t dex_method_idx = method->GetDexMethodIndex();
const char * name = dex_file.GetMethodName(dex_file.GetMethodId(dex_method_idx));
// 判断dex文件中的类方法函数属性值AccessFlag与实际类运行时art::mirror::ArtMethod中函数属性值是否一致
if (ac != pData->directMethods[i].accessFlags)
#ifdef LOGI
LOG(INFO)<<"GOT IT direct method AF changed "<<name;
#endif
// 腾讯加固有这种情况,运行时native函数恢复为java函数
need_extra=true;
// 进行类方法函数属性的更正(以运行时为准)
pData->directMethods[i].accessFlags=ac;
// 根据类方法实际运行时的codeitem_off与dex文件中的codeOff偏移地址的不同,进行类方法字节码DexCode偏移地址的修正
if (codeitem_off != pData->directMethods[i].codeOff&&((codeitem_off>=start&&codeitem_off<=end)||codeitem_off==0))
#ifdef LOGI
LOG(INFO)<<"GOT IT direct method code changed "<<name;
#endif
need_extra=true;
// 以类方法实际运行时的codeitem_off为准,对dex文件中的类方法字节码DexCode的偏移地址进行修正
pData->directMethods[i].codeOff=codeitem_off;
// 类方法的字节码DexCode被存放在了OAT文件的文件末尾或者dex文件的ClassDef结构体表开始地址之前的位置
// 阿里加固出现过这种情况
if ((codeitem_off<start || codeitem_off>end) && codeitem_off!=0)
#ifdef LOGI
LOG(INFO)<<"GOT IT direct method code changed "<<name;
#endif
need_extra=true;
// 统计将dex文件的类方法字节码DexFile::CodeItem从OAT文件结尾的位置开始存放(注意内存4字节对齐)
pData->directMethods[i].codeOff = total_pointer;
// 获取类方法字节码描述结构体指针DexFile::CodeItem
const DexFile::CodeItem * code = dex_file.GetCodeItem(codeitem_off);
// 类方法字节码描述结构体DexFile::CodeItem存放的起始地址
uint8_t *item=(uint8_t *) code;
int code_item_len = 0;
// 判断类方法是否有try...catch语句
if (code->tries_size_)
// 获取有try...catch语句情况下,类方法的字节码DexFile::CodeItem结构体的结束地址
const byte *handler_data = (const byte *)(DexFile::GetTryItems(*code, code->tries_size_));
uint8_t * tail = codeitem_end(&handler_data);
// 有try...catch语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
code_item_len = (int)(tail - item);
else
// 无try...catch语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
code_item_len = 16+code->insns_size_in_code_units_*2;
// 将不在正常dex文件偏移存放位置的类方法字节码DexFile::CodeItem结构体的数据写入到xxxx/extra文件中
fwrite(item, 1, code_item_len, fp1);
fflush(fp1);
// 更新存放类方法字节码DexFile::CodeItem的文件偏移指针
total_pointer += code_item_len;
// 进行内存4字节对齐的填充处理
while (total_pointer&3)
fwrite(&padding,1,1,fp1);
fflush(fp1);
total_pointer++;
#ifdef LOGI
LOG(INFO)<<"GOT IT total_pointer "<<total_pointer;
#endif
// 针对类虚方法的处理(和上面类直接方法的处理一样)
if (pData->virtualMethods)
// 遍历类的虚方法
for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++)
art::mirror::ArtMethod *method = klass->GetVirtualMethod(i);
uint32_t ac = (method->GetAccessFlags()) & mask;
uint32_t codeitem_off = method->GetCodeItemOffset();
uint32_t dex_method_idx = method->GetDexMethodIndex();
const char * name = dex_file.GetMethodName(dex_file.GetMethodId(dex_method_idx));
// 类方法函数属性accessFlags的修复
if (ac != pData->virtualMethods[i].accessFlags)
#ifdef LOGI
LOG(INFO)<<"GOT IT virtual method AF changed "<<name;
#endif
need_extra=true;
pData->virtualMethods[i].accessFlags=ac;
// 类方法codeitem_off的修正,以类方法运行时的数据为准
if (codeitem_off!=pData->virtualMethods[i].codeOff&&((codeitem_off>=start&&codeitem_off<=end)||codeitem_off==0))
#ifdef LOGI
LOG(INFO)<<"GOT IT virtual method code changed "<<name;
#endif
need_extra=true;
pData->virtualMethods[i].codeOff=codeitem_off;
// 类方法的字节码DexCode被存放在了OAT文件的文件末尾或者dex文件的ClassDef结构体表开始地址之前的位置
if ((codeitem_off<start || codeitem_off>end)&&codeitem_off!=0)
#ifdef LOGI
LOG(INFO)<<"GOT IT virtual method code changed "<<name;
#endif
need_extra=true;
// 统计将dex文件的类方法字节码DexFile::CodeItem从OAT文件结尾的位置开始存放(注意内存4字节对齐)
pData->virtualMethods[i].codeOff = total_pointer;
const art::DexFile::CodeItem * code = dex_file.GetCodeItem(codeitem_off);
uint8_t *item=(uint8_t *) code;
int code_item_len = 0;
// 判断类方法是否有try...catch语句
if (code->tries_size_)
const byte *handler_data = (const byte *)(DexFile::GetTryItems(*code, code->tries_size_));
uint8_t * tail=codeitem_end(&handler_data);
// 有try...catch语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
code_item_len = (int)(tail-item);
else
// 无try...catch语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
code_item_len = 16+code->insns_size_in_code_units_*2;
// 将不在正常dex文件偏移存放位置的类方法字节码DexFile::CodeItem结构体的数据写入到xxxx/extra文件中
fwrite(item,1,code_item_len,fp1);
fflush(fp1);
// 更新存放类方法字节码DexFile::CodeItem的文件偏移指针
total_pointer+=code_item_len;
// 进行内存4字节对齐的填充处理
while (total_pointer&3)
fwrite(&padding,1,1,fp1);
fflush(fp1);
total_pointer++;
#ifdef LOGI
LOG(INFO)<<"GOT IT total_pointer "<<total_pointer;
#endif
// 上面是针对dex文件类方法的字节码DexFile::CodeItem的修正处理并保存到xxxx/extra文件中
// 以及对dex文件的ClassDef对应的ClassData的数据修正处理(以运行时的类描述信息为准)
// 下面是针对dex文件的
classdef:
DexFile::ClassDef *temp = (DexFile::ClassDef*) malloc(sizeof(DexFile::ClassDef));
if (!temp)
continue;
temp->class_idx_ = class_def.class_idx_;
temp->pad1_=class_def.pad1_;
temp->pad2_=class_def.pad2_;
temp->access_flags_=class_def.access_flags_;
temp->annotations_off_= class_def.annotations_off_;
temp->class_data_off_=class_def.class_data_off_;
temp->interfaces_off_=class_def.interfaces_off_;
temp->source_file_idx_=class_def.source_file_idx_;
temp->static_values_off_=class_def.static_values_off_;
temp->superclass_idx_=class_def.superclass_idx_;
if (pass)
// Android系统类和无效类的情况处理
temp->class_data_off_=0;
temp->annotations_off_=0;
uint8_t *p = (uint8_t *)temp;
if (need_extra)
// dex文件的类DexClassData需要修正情况
int class_data_len = 0;
// 将DexClassData所表示的类数据pData进行leb128编码写入到申请的内存空间中
// class_data_len为保存DexClassData进行leb128编码后的数据长度
uint8_t *out = dexEncodeClassData(pData, class_data_len);
if (!out)
continue;
// 修正DexFile::ClassDef中指向DexClassData的文件偏移指针
temp->class_data_off_ = total_pointer;
#ifdef LOGI
LOG(INFO)<<"GOT IT write extra";
#endif
// 将正确修正后的dex文件的类DexClassData数据保存到xxxx/extra文件中
fwrite(out, 1, class_data_len, fp1);
fflush(fp1);
// 更新在xxxx/extra文件中存放DexClassData或者DexFile::CodeItem的文件偏移指针
total_pointer += class_data_len;
// 内存4字节对齐的填充处理
while (total_pointer&3)
fwrite(&padding, 1, 1, fp1);
fflush(fp1);
total_pointer++;
#ifdef LOGI
LOG(INFO)<<"GOT IT total_pointer "<<total_pointer;
#endif
free(out);
else
if (pData)
free(pData);
#ifdef LOGI
LOG(INFO)<<"GOT IT write classdef";
#endif
// 将根据上面xxxx/extra文件中存放的DexClassData数据修正的DexFile::ClassDef结构体数据保存到xxxx/classdef文件中
fwrite(p, sizeof(DexFile::ClassDef), 1, fp);
fflush(fp);
free(temp);
// 释放锁
Locks::mutator_lock_->SharedUnlock(self);
// 关闭文件
fclose(fp1);
fclose(fp);
#ifdef LOGI
LOG(INFO)<<"GOT IT ClassDumped";
#endif
self->SetState(kSleeping);
// 取消对art虚拟机线程的附加
runtime->DetachCurrentThread();
// 需要脱壳的dex文件的内存dump已经全部完成,后面是dump后的dex文件重组处理
// 拼接字符串得到文件路径xxxx/whole.dex
strcpy(path,dumppath);
strcat(path,"whole.dex");
// 打开文件xxxx/whole.dex(用以存放dump重组后的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,"part0");
// 打开文件xxxx/part0
fp1 = fopen(path,"rb");
char reg=0;
// 读取文件xxxx/part0中的数据内容保存到文件xxxx/whole.dex中
for (uint32_t i = 0; i < 16; i++)
fread(®, 1, 1, fp1);
fwrite(®, 1, 1, fp);
fflush(fp);
fclose(fp1);
strcpy(path,dumppath);
strcat(path,"part1");
// 打开文件xxxx/part1
fd = open(path, O_RDONLY, 0666);
if (fd == -1)
return NULL;
// 获取文件xxxx/part1的数据大小
r = fstat(fd, &st);
if(r == -1)
close(fd);
return NULL;
len = st.st_size;
// 为文件xxxx/part1创建内存映射进行文件的读取操作
addr = (char*)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
// 将文件xxxx/part1的数据内容写入保存到文件xxxx/whole.dex中
fwrite(addr, 1, len, fp);
// 刷新文件流
fflush(fp);
// 取消内存映射
munmap(addr,len);
close(fd);
#ifdef LOGI
LOG(INFO)<<"GOT IT part1 over ";
#endif
strcpy(path, dumppath);
strcat(path, "classdef");
// 打开文件xxxx/classdef
fd = open(path, O_RDONLY, 0666);
if (fd==-1)
return NULL;
// 获取文件xxxx/classdef的数据大小
r = fstat(fd,&st);
if(r==-1)
close(fd);
return NULL;
len=st.st_size;
// 为文件xxxx/classdef创建内存映射实现文件的读取操作
addr = (char*)mmap(NULL,len,PROT_READ,MAP_PRIVATE,fd,0);
// 将文件xxxx/classdef的数据内容写入到文件xxxx/whole.dex中
fwrite(addr,1,len,fp);
fflush(fp);
munmap(addr,len);
close(fd);
#ifdef LOGI
LOG(INFO)<<"GOT IT classdef over ";
#endif
strcpy(path,dumppath);
strcat(path,"data");
// 打开文件xxxx/data
fd = open(path, O_RDONLY, 0666);
if (fd==-1)
return NULL;
// 获取文件xxxx/data的文件大小
r = fstat(fd, &st);
if(r==-1)
close(fd);
return NULL;
len=st.st_size;
// 为文件xxxx/data创建文件内存映射
addr=(char*)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
// 将文件xxxx/data的数据内容写入到文件xxxx/whole.dex中
fwrite(addr,1,len,fp);
fflush(fp);
munmap(addr,len);
close(fd);
#ifdef LOGI
LOG(INFO)<<"GOT IT data over ";
#endif
// 对 xxxx/data 进行内存4字节对齐的'0'填充处理
while (inc>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;
// 获取文件xxxx/extra的文件大小
r = fstat(fd,&st);
if(r == -1)
close(fd);
return NULL;
len=st.st_size;
// 为文件xxxx/extra创建文件内存映射实现文件的读取操作
addr = (char*)mmap(NULL,len,PROT_READ,MAP_PRIVATE,fd,0);
// 将文件xxxx/extra的数据内容保存写入到文件xxxx/whole.dex中
fwrite(addr, 1, len, fp);
fflush(fp);
munmap(addr,len);
close(fd);
/****
xxxx/part0
xxxx/part1
xxxx/classdef (dex文件的重组)--------> xxxx/whole.dex
xxxx/data
xxxx/extra
*****/
#ifdef LOGI
LOG(INFO)<<"GOT IT extra over ";
#endif
fclose(fp);
delete path;
#ifdef LOGI
time = MilliTime();
LOG(INFO)<<"GOT IT end "<<time<<" ms";
#endif
return NULL;
//-----------------------added end-----------------------//
// art下的类加载函数
mirror::Class* ClassLinker::DefineClass(const char* descriptor,
mirror::ClassLoader* class_loader,
const DexFile& dex_file,
const DexFile::ClassDef& dex_class_def)
//-----------------------added begin-----------------------//
int uid=::art::GetUid();
// compiler用来指定当前要创建的ART虚拟机是用来将DEX字节码编译成本地机器指令的
// 因此排除掉Runtime::Current()->IsCompiler()为true的优化dex文件的art虚拟机情况
// 参考:http://blog.csdn.net/Luoshengyang/article/details/39307813
if (Runtime::Current()->IsCompiler())
goto there;
// 排除掉系统进程(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的信息
// 获取需要脱壳dex文件的加载路径dexname以及存放脱壳后dex文件dump文件路径dumppath
pthread_create(&read_thread, NULL, ReadThread, NULL);
else
pthread_mutex_unlock(&read_mutex);
// 排除掉uid==0的进程以及被dump的加固dex文件的内存加载路径不能为空
if(uid && strcmp(dexname,""))
// dex_file.GetLocation().c_str()获取到art下dex文件的加载路径
// 通过加固dex文件的加载路径判断是否是需要脱壳的dex文件
char * res = strstr(dex_file.GetLocation().c_str(), dexname);
if (res && flag)
pthread_mutex_lock(&mutex);
if (flag)
flag=false;
pthread_mutex_unlock(&mutex);
char * temp = new char[100];
strcpy(temp, dumppath);
// 拼接字符串得到文件路径 xxxx/part0
strcat(temp,"part0");
// 打开文件xxxx/part0
FILE *fp = fopen(temp, "wb+");
// ART下的OAT文件是一个私有的ELF文件格式
// 获取OAT文件的内存映射的起始地址即dex优化后私有ELF文件的内存映射地址
const byte *addr = dex_file.Begin();
int length = 16;
// 将OAT文件(即私有ELF文件的)前16字节数据保存到xxxx/part0中
for (int i = 0; i < 16; i++)
fwrite(addr+i, 1 ,1 ,fp);
fflush(fp);
fclose(fp);
strcpy(temp, dumppath);
// 得到路径字符串xxxx/part1
strcat(temp,"part1");
// 打开文件xxxx/part1
fp = fopen(temp, "wb+");
// 内存地址指针移动到OAT文件即私有ELF文件的第17个字节的位置
addr = dex_file.Begin() + 16;
// 获取到OAT文件里即私有ELF文件的第17个字节到dex文件class_defs_off_开始地址之间的数据长度
length = dex_file.class_defs_off_ - 16;
// 将OAT文件里即私有ELF文件的第17个字节到dex文件class_defs_off_开始地址的数据写入到xxxx/part1中
fwrite(addr, 1, length, fp);
// 刷新文件流
fflush(fp);
fclose(fp);
// 拼接得到字符串xxxx/data
strcpy(temp, dumppath);
strcat(temp,"data");
// 打开文件xxxx/data
fp = fopen(temp,"wb+");
// 将OAT文件里的dex文件指针移动到dex文件的ClassDef结构体表Table的结尾位置
addr = dex_file.Begin()+dex_file.class_defs_off_+sizeof(DexFile::ClassDef)*dex_file.NumClassDefs();
// 获取OAT文件里dex文件的ClassDef结构体表Table的结尾位置到OAT文件的结尾数据长度
length=dex_file.Size()-dex_file.class_defs_off_-sizeof(DexFile::ClassDef)*dex_file.NumClassDefs();
// 将OAT文件里dex文件的ClassDef结构体表Table的结束位置到OAT文件的结尾数据写入到xxxx/data文件中
fwrite(addr, 1, length, fp);
fflush(fp);
fclose(fp);
delete temp;
// 当前dex文件所在的class_loader
param.class_loader = class_loader;
// 当前dex文件的内存加载的镜像描述结构体
param.dex_file = &dex_file;
// 当前dex文件所在的ClassLinker
param.cl = this;
pthread_t dumpthread;
// 创建线程对需要脱壳dex的OAT文件里的类数据进行内存dump处理
pthread_create(&dumpthread, NULL, DumpClass, (void*)¶m);
else
pthread_mutex_unlock(&mutex);
//-----------------------added end-----------------------//
there:
Thread* self = Thread::Current();
SirtRef<mirror::Class> klass(self, NULL);
// Load the class from the dex file.
if (UNLIKELY(!init_done_))
// finish up init of hand crafted class_roots_
if (strcmp(descriptor, "Ljava/lang/Object;") == 0)
klass.reset(GetClassRoot(kJavaLangObject));
else if (strcmp(descriptor, "Ljava/lang/Class;") == 0)
klass.reset(GetClassRoot(kJavaLangClass));
else if (strcmp(descriptor, "Ljava/lang/String;") == 0)
klass.reset(GetClassRoot(kJavaLangString));
else if (strcmp(descriptor, "Ljava/lang/DexCache;") == 0)
klass.reset(GetClassRoot(kJavaLangDexCache));
else if (strcmp(descriptor, "Ljava/lang/reflect/ArtField;") == 0)
klass.reset(GetClassRoot(kJavaLangReflectArtField));
else if (strcmp(descriptor, "Ljava/lang/reflect/ArtMethod;") == 0)
klass.reset(GetClassRoot(kJavaLangReflectArtMethod));
else
klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def)));
else
klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def)));
if (UNLIKELY(klass.get() == NULL))
CHECK(self->IsExceptionPending()); // Expect an OOME.
return NULL;
klass->SetDexCache(FindDexCache(dex_file));
LoadClass(dex_file, dex_class_def, klass, class_loader);
// Check for a pending exception during load
if (self->IsExceptionPending())
klass->SetStatus(mirror::Class::kStatusError, self);
return NULL;
ObjectLock lock(self, klass.get());
klass->SetClinitThreadId(self->GetTid());
// Add the newly loaded class to the loaded classes table.
mirror::Class* existing = InsertClass(descriptor, klass.get(), Hash(descriptor));
if (existing != NULL)
// We failed to insert because we raced with another thread. Calling EnsureResolved may cause
// this thread to block.
return EnsureResolved(self, existing);
// Finish loading (if necessary) by finding parents
CHECK(!klass->IsLoaded());
if (!LoadSuperAndInterfaces(klass, dex_file))
// Loading failed.
klass->SetStatus(mirror::Class::kStatusError, self);
return NULL;
CHECK(klass->IsLoaded());
// Link the class (if necessary)
CHECK(!klass->IsResolved());
if (!LinkClass(klass, NULL, self))
// Linking failed.
klass->SetStatus(mirror::Class::kStatusError, self);
return NULL;
CHECK(klass->IsResolved());
/*
* We send CLASS_PREPARE events to the debugger from here. The
* definition of "preparation" is creating the static fields for a
* class and initializing them to the standard default values, but not
* executing any code (that comes later, during "initialization").
*
* We did the static preparation in LinkClass.
*
* The class has been prepared and resolved but possibly not yet verified
* at this point.
*/
Dbg::PostClassPrepare(klass.get());
return klass.get();
以上是关于DexHunter在ART虚拟机模式下的脱壳原理分析的主要内容,如果未能解决你的问题,请参考以下文章
基于Frida框架打造Art模式下的脱壳工具(OpenMemory)的原理分析
Android通用脱壳工具DexHunter的原理分析和使用说明
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | ART 虚拟机下 DexClassLoader 类加载器脱壳点总结 )