DexHunter脫殼神器分析
0x00
0x01
DexHunter 實現中,只需要修改一處檔案:dalvik\vm\native\dalvik_system_DexFile.cpp
下面是BeyondCompare比對:
我們看到DexHunter的程式碼都位於系統原始碼vm目錄下,所以要執行DexHunter需要dalvik\vm\native\dalvik_system_DexFile.cpp程式碼,然後在原始碼環境下編譯,最後刷機執行。
0x02
核心原理請參考從Android執行時出發,打造我們的脫殼神器,簡單的從程式碼角度說,在Dalvik_dalvik_system_DexFile_defineClassNative中插入程式碼來修復被破壞的dex,原理有兩個:
1、在DVM中:
顯式載入:
ClassLoader.loadClass對應Dalvik_dalvik_system_DexFile_defineClassNative
Class.forName對應Dalvik_java_lang_Class_classForName
隱式載入:
對應dvmResolveClass
如下圖:
第一點說明了時機,也就是為什麼在Dalvik_dalvik_system_DexFile_defineClassNative插入程式碼。
2、執行dvmDefineClass,形成的ClassObject結構體的變數都是有效的。dvmDefineClass會在我們插入的程式碼中呼叫,詳見後面的分析。
0x03
下面用註釋的方式來分析原始碼。
分析之前先附上兩張圖,有助於分析。
圖 1
其中baseAddr指向DexHeader的首地址,圖中有錯誤。下文中經常看到mem->addr指向DexOptHeader的首地址。
再附上一張圖,分析時會用到:
圖 2
//------------------------added begin----------------------// #include <asm/siginfo.h> #include "libdex/DexClass.h" #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> 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{ DvmDex* pDvmDex; Object * loader; }param; void timer_thread(sigval_t) { timer_flag=false; timer_delete(timerId); ALOGI("GOT IT time up"); } void* ReadThread(void *arg){ FILE *fp = NULL; while (dexname[0]==0||dumppath[0]==0) { fp=fopen("/data/dexname", "r"); if (fp==NULL) { sleep(1); continue; } fgets(dexname,99,fp);//從/data/dexname獲取字串賦值給dexname,github中已經給出了,是/data/data/com.example.seventyfour.tencenttest/files/libmobisecy1.zip,這是用來脫阿里殼子時使用的 dexname[strlen(dexname)-1]=0; fgets(dumppath,99,fp);//這是生成4個檔案的總目錄/data/data/com.example.seventyfour.tencenttest/,後面我們會看到4個檔案分別是part1,data,classdef,extra 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) { pField->fieldIdx = readUnsignedLeb128(pData); pField->accessFlags = readUnsignedLeb128(pData); } void ReadClassDataMethod(const uint8_t** pData, DexMethod* pMethod) { pMethod->methodIdx = readUnsignedLeb128(pData); pMethod->accessFlags = readUnsignedLeb128(pData); pMethod->codeOff = readUnsignedLeb128(pData); } DexClassData* ReadClassData(const uint8_t** pData) { DexClassDataHeader header; if (*pData == NULL) { return NULL; } 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; } 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; } for (uint32_t i = 0; i < header.staticFieldsSize; i++) { ReadClassDataField(pData, &result->staticFields[i]); } for (uint32_t i = 0; i < header.instanceFieldsSize; i++) { ReadClassDataField(pData, &result->instanceFields[i]); } for (uint32_t i = 0; i < header.directMethodsSize; i++) { ReadClassDataMethod(pData, &result->directMethods[i]); } for (uint32_t i = 0; i < header.virtualMethodsSize; i++) { ReadClassDataMethod(pData, &result->virtualMethods[i]); } return result; } 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); 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); } } 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); } } 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); } } 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); 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); } } if (pData->instanceFields) { for (uint32_t i = 0; i < pData->header.instanceFieldsSize; i++) { writeLeb128(&store,pData->instanceFields[i].fieldIdx); writeLeb128(&store,pData->instanceFields[i].accessFlags); } } if (pData->directMethods) { for (uint32_t i=0; i<pData->header.directMethodsSize; i++) { writeLeb128(&store,pData->directMethods[i].methodIdx); writeLeb128(&store,pData->directMethods[i].accessFlags); writeLeb128(&store,pData->directMethods[i].codeOff); } } 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; } 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); } void* DumpClass(void *parament) { while (timer_flag) { sleep(5); } DvmDex* pDvmDex=((struct arg*)parament)->pDvmDex; Object *loader=((struct arg*)parament)->loader; DexFile* pDexFile=pDvmDex->pDexFile; MemMapping * mem=&pDvmDex->memMap; u4 time=dvmGetRelativeTimeMsec(); ALOGI("GOT IT begin: %d ms",time); char *path = new char[100]; strcpy(path,dumppath); strcat(path,"classdef"); FILE *fp = fopen(path, "wb+");//fp指向classdef檔案 strcpy(path,dumppath); strcat(path,"extra"); FILE *fp1 = fopen(path,"wb+");//fp1指向extra檔案 uint32_t mask=0x3ffff; char padding=0; const char* header="Landroid"; unsigned int num_class_defs=pDexFile->pHeader->classDefsSize;//class_def的數量 uint32_t total_pointer = mem->length-uint32_t(pDexFile->baseAddr-(const u1*)mem->addr);//末尾地址相對DexHeader頭部的偏移地址 uint32_t rec=total_pointer; while (total_pointer&3) { total_pointer++;//對齊 } int inc=total_pointer-rec; uint32_t start = pDexFile->pHeader->classDefsOff+sizeof(DexClassDef)*num_class_defs;//若干個class_def_item之後的地址相對於DexHeader頭部的偏移地址 uint32_t end = (uint32_t)((const u1*)mem->addr+mem->length-pDexFile->baseAddr);//末尾地址相對DexHeader頭部的偏移地址 for (size_t i=0;i<num_class_defs;i++)//遍歷class_def_item { bool need_extra=false; ClassObject * clazz=NULL; const u1* data=NULL; DexClassData* pData = NULL; bool pass=false; const DexClassDef *pClassDef = dexGetClassDef(pDvmDex->pDexFile, i);//找到了對應的class_def_item,如圖2 const char *descriptor = dexGetClassDescriptor(pDvmDex->pDexFile,pClassDef); if(!strncmp(header,descriptor,8)||!pClassDef->classDataOff) { pass=true; goto classdef; } clazz = dvmDefineClass(pDvmDex, descriptor, loader);//前面說過,脫殼程式能夠執行的很重要的原理是,這裡生成的ClassObject結構體中的變數都是有效的。 if (!clazz) { continue; } ALOGI("GOT IT class: %s",descriptor); if (!dvmIsClassInitialized(clazz)) { if(dvmInitClass(clazz)){ ALOGI("GOT IT init: %s",descriptor); } } if(pClassDef->classDataOff<start || pClassDef->classDataOff>end)//如果classDataOff的偏移落在範圍外,那就需要生成extra部分,詳解後面的程式碼 { need_extra=true; } data=dexGetClassData(pDexFile,pClassDef);//通過class_def獲取class_data_item,詳解圖2 pData = ReadClassData(&data);//讀取現在記憶體中的值,形成了DexClassData結構體pData,注意此時pData中的變數可能是錯誤的,很簡單的一個例子就是http://blog.csdn.net/jltxgcy/article/details/50581259,參考這篇文章 if (!pData) { continue; } if (pData->directMethods) { for (uint32_t i=0; i<pData->header.directMethodsSize; i++) { Method *method = &(clazz->directMethods[i]);//此時獲取Method結構體method裡面的變數是正確的 uint32_t ac = (method->accessFlags) & mask;//正確的accessFlags ALOGI("GOT IT direct method name %s.%s",descriptor,method->name); if (!method->insns||ac&ACC_NATIVE) {//正確的insns沒有指令,所以要調整 if (pData->directMethods[i].codeOff) { need_extra = true; pData->directMethods[i].accessFlags=ac; pData->directMethods[i].codeOff=0; } continue; } u4 codeitem_off = u4((const u1*)method->insns-16-pDexFile->baseAddr);//獲取正確的指令偏移 if (ac != pData->directMethods[i].accessFlags)//當前的accessFlag和正確的accessFlag不一致,所以要調整 { ALOGI("GOT IT method ac"); need_extra=true; pData->directMethods[i].accessFlags=ac; } if (codeitem_off!=pData->directMethods[i].codeOff&&((codeitem_off>=start&&codeitem_off<=end)||codeitem_off==0)) {//如果正確的codeitem_off不等於現在的codeitem_off,且正確的codeitem_off在範圍內,則需要調整 ALOGI("GOT IT method code"); need_extra=true; pData->directMethods[i].codeOff=codeitem_off; } if ((codeitem_off<start || codeitem_off>end) && codeitem_off!=0) {//在http://blog.csdn.net/jltxgcy/article/details/50581259,所屬的情況在這個判斷中,真是的codeitem_off在範圍(start--end)外。 need_extra=true; pData->directMethods[i].codeOff = total_pointer;//讓codeOff指向dex結尾的偏移 DexCode *code = (DexCode*)((const u1*)method->insns-16);//正確的DexCode 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;//正確的DexCode的大小 } ALOGI("GOT IT method code changed"); fwrite(item,1,code_item_len,fp1);//把DexCode寫入extra中 fflush(fp1); total_pointer+=code_item_len; while (total_pointer&3) { fwrite(&padding,1,1,fp1); fflush(fp1); total_pointer++; } } } } if (pData->virtualMethods) {//同理 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; } if ((codeitem_off<start || codeitem_off>end)&&codeitem_off!=0) { need_extra=true; 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"); 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++; } } } } classdef: DexClassDef temp=*pClassDef; uint8_t *p = (uint8_t *)&temp; if (need_extra) { ALOGI("GOT IT classdata before"); int class_data_len = 0; uint8_t *out = EncodeClassData(pData,class_data_len); if (!out) { continue; } temp.classDataOff = total_pointer;//class_def的classDataOff指向了新生成的class_data_item fwrite(out,1,class_data_len,fp1);//將這個class_data_item寫入extra,此class_data_item中的codeOff已經改變了 fflush(fp1); total_pointer+=class_data_len; while (total_pointer&3) { fwrite(&padding,1,1,fp1); fflush(fp1); total_pointer++; } free(out); ALOGI("GOT IT classdata written"); }else{ if (pData) { free(pData); } } if (pass) { temp.classDataOff=0; temp.annotationsOff=0; } ALOGI("GOT IT classdef"); fwrite(p, sizeof(DexClassDef), 1, fp);//將class_def入classdef檔案,改class_def中的classDataOff已經改變了 fflush(fp); } fclose(fp1); fclose(fp); //最後執行完畢後,把四個檔案按照先後順序合併在一起,依次是part1,classdef,data,extra,最後生成dex就是正確的dex strcpy(path,dumppath); strcat(path,"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"); 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); fwrite(addr,1,len,fp); fflush(fp); munmap(addr,len); close(fd); strcpy(path,dumppath); strcat(path,"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); fwrite(addr,1,len,fp); fflush(fp); munmap(addr,len); close(fd); strcpy(path,dumppath); strcat(path,"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); fwrite(addr,1,len,fp); fflush(fp); munmap(addr,len); close(fd); while (inc>0) { fwrite(&padding,1,1,fp); fflush(fp); inc--; } strcpy(path,dumppath); strcat(path,"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); 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----------------------// 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(); if (uid) { if (readable) { pthread_mutex_lock(&read_mutex); if (readable) {//只執行一次,避免多次執行 readable=false; pthread_mutex_unlock(&read_mutex); pthread_t read_thread; pthread_create(&read_thread, NULL, ReadThread, NULL);//在新的執行緒中執行ReadThread,ReadThread在上面的程式碼中 }else{ pthread_mutex_unlock(&read_mutex); } } } if(uid&&strcmp(dexname,"")){ char * res=strstr(pDexOrJar->fileName, dexname);//這個是dexname的用途,用來比較,只有匹配的dex才會執行後面的程式碼 if (res&&flag) { pthread_mutex_lock(&mutex); if (flag) { flag = false; pthread_mutex_unlock(&mutex); DexFile* pDexFile=pDvmDex->pDexFile; MemMapping * mem=&pDvmDex->memMap; char * temp=new char[100]; strcpy(temp,dumppath); strcat(temp,"part1"); FILE *fp = fopen(temp, "wb+"); const u1 *addr = (const u1*)mem->addr; int length=int(pDexFile->baseAddr+pDexFile->pHeader->classDefsOff-addr);//class_defs之前的內容的長度,mem->addr,和baseAddr是理解這個長度的關鍵,在程式碼前的紅字部分有說明和圖解。 fwrite(addr,1,length,fp);//class_defs之前的內容寫入part1 fflush(fp); fclose(fp); strcpy(temp,dumppath); strcat(temp,"data"); fp = fopen(temp, "wb+"); addr = pDexFile->baseAddr+pDexFile->pHeader->classDefsOff+sizeof(DexClassDef)*pDexFile->pHeader->classDefsSize; length=int((const u1*)mem->addr+mem->length-addr);//class_defs之後的內容的長度 fwrite(addr,1,length,fp);//class_defs之後的內容寫入data中 fflush(fp); fclose(fp); delete temp; param.loader=loader; param.pDvmDex=pDvmDex; pthread_t dumpthread; dvmCreateInternalThread(&dumpthread,"ClassDumper",DumpClass,(void*)param);//在新的執行緒中執行DumpClass,引數是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); }
0x04
如果是執行時動態修復dex這篇文章執行的DynamicDex.apk,上面的程式碼解釋也著重分析了,解析後會形成下圖的結構(正確的dex)。
第一部分是part1,第二部分是classdef,第三部分是data,第四部分是extra。
第二部分的其中一個class_def_item中的classDataOff指向了extra中class_data_item。
extra中的class_data_item中的codeOff指向了extra中DexCode。