运行时动态修复dex

    0x00

    本文的源代码已经上传至github,地址为https://github.com/jltxgcy/DynamicFixDex。在Android2.3模拟器上可以运行。

    ForceApkObj:用于动态加载的apk。类似于Android中的Apk的加固(加壳)原理解析和实现一文中的ForceApkObj工程。

    FixDex:用于分离ForceApkObj里面的classes.dex,把他分离为classes_fix.dex和data.so,具体情况我们后来介绍。

    DynamicDex:用于动态加载ForceApkObj工程生成的ForceApkObj.apk,也就是脱壳程序,类似于Android中的Apk的加固(加壳)原理解析和实现一文中的ReforceApk工程。


    0x01

    使用步骤:

    1、将ForceApkObj工程生成的classes.dex拷贝到/sdcard/payload/目录下。

    2、Run FixDex工程,点击Button按钮,此时在/sdcard/payload/目录下生成了classes_fix.dex和data.so。

    3、把classes_fix.dex更名为classes.dex,然后替换ForceApkObj.apk里面的classes.dex,然后重新签名,生成新签名的ForceApkObj.apk。

    4、把ForceApkObj.apk和data.so放入/sdcard/payload/目录中,运行DynamicDex工程。

  

    0x02

    ForceApkObj工程很简单,主MainActivity界面点击屏幕会打开SubActivity,SubActivity的代码如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class SubActivity extends Activity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         handleException();  
  7.     }  
  8.   
  9.     public void handleException() {  
  10.         Toast.makeText(this"成功映射", Toast.LENGTH_LONG).show();  
  11.     }  
  12.   
  13. }  


    0x03

    FixDex用于用于分离ForceApkObj里面的classes.dex,把他分离为classes_fix.dex和data.so。代码如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public void onClick(View arg0) {  
  2.     int codeoff = FindCode.findCode("Lcom/example/forceapkobj/SubActivity;""handleException");  
  3.     Log.d("jltxgcy""codeoff:" + codeoff);  
  4.     try {  
  5.         File file = new File("/sdcard/payload/classes.dex");  
  6.         byte[] dexByte = readFileBytes(file);  
  7.         String strso = "/sdcard/payload/data.so";  
  8.         writeFile(strso, dexByte, codeoff - 16, exceptionCode.length + 16);  
  9.         System.arraycopy(exceptionCode, 0, dexByte, codeoff, exceptionCode.length);  
  10.         //修改DEX file size文件头  
  11.         fixFileSizeHeader(dexByte);  
  12.         //修改DEX SHA1 文件头  
  13.         fixSHA1Header(dexByte);  
  14.         //修改DEX CheckSum文件头  
  15.         fixCheckSumHeader(dexByte);  
  16.         String str = "/sdcard/payload/classes_fix.dex";  
  17.         writeFile(str, dexByte, 0, dexByte.length);  
  18.     } catch (Exception e) {  
  19.         // TODO Auto-generated catch block  
  20.         e.printStackTrace();  
  21.     }  
  22. }  
    首先找到SubActivity类的handleException方法的偏移,这个方法是一个native方法,具体的请参考源代码,看源代码前,请先了解 apk自我保护的一种实现方式——运行时自篡改dalvik指令。核心的原理我解释下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. const DexCode  *code =  
  2.         dexFindClassMethod(&gDexFile, className, methodName);  
  3.     if(code == NULL){  
  4.         ALOGE("Error can not found setScoreHidden");  
  5.         return 0;  
  6.     }  
  7.     position = (u4 *)code - (u4 *)dexBase;  
  8.     ALOGD("codeoff:%d", ((u4 *)code - (u4 *)dexBase));  
  9.     return position * 4 + 16;  
    找到handleException在内存中的偏移,然后再减去dex头部的基地址,得到handleException在dex中本地偏移,那为什么要加上16呢?我们先看一个DexCode的结构。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. struct DexCode {  
  2.     u2  registersSize;  
  3.     u2  insSize;  
  4.     u2  outsSize;  
  5.     u2  triesSize;  
  6.     u4  debugInfoOff;       /* file offset to debug info stream */  
  7.     u4  insnsSize;          /* size of the insns array, in u2 units */  
  8.     u2  insns[1];  
  9.     /* followed by optional u2 padding */  
  10.     /* followed by try_item[triesSize] */  
  11.     /* followed by uleb128 handlersSize */  
  12.     /* followed by catch_handler_item[handlersSize] */  
  13. };  
    加上16其实就是insns[1]的偏移,也就是真正执行的指令的偏移。


    找到了真正执行的指令的偏移,然后我们把这个指令整个的DexCode拷贝到data.so中。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. writeFile(strso, dexByte, codeoff - 16, exceptionCode.length + 16);  
    codeoff-16其实就是DexCode的偏移。exceptionCode.length是insns[1]长度(指令的长度),16是registersSize+insSize+outsSize+triesSize+debugInfoOff+insnsSize长度和。


    然后把原来classes.dex中handleException的DexCode中insns[1],也就是真正执行的指令全部置为exceptionCode,也就是全0。这样如果不动态修复,当执行到这个方法时,就会报错。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. System.arraycopy(exceptionCode, 0, dexByte, codeoff, exceptionCode.length);  


    最后把修复后内容写入到classes_fix.dex中。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. String str = "/sdcard/payload/classes_fix.dex";  
  2. writeFile(str, dexByte, 0, dexByte.length);  


    0x03

    把classes_fix.dex更名为classes.dex,然后替换ForceApkObj.apk里面的classes.dex,然后重新签名,生成新签名的ForceApkObj.apk。

    然后把ForceApkObj.apk和data.so放入/sdcard/payload/目录中,运行DynamicDex工程。

    在Android加壳native实现一文中,我们讲述了如何在native加载ForceApkObj.apk,并替换原Application,执行ForceApkObj中的主MainActivity。在这里我们也可以用同样的方式加载ForceApkObj,执行ForceApkObj中的主MainActivity,但是执行到SubActivity时,由于在SubActivity类的onCreate方法中调用handleException方法,而这个方法指令在0x02步已经设置为全零。那么就会报错。

    如果想要执行正确,我们有一个思路,也就是代码中的实现,如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. static void nativeParserDex(const char *className, const char *methodName)  
  2. {  
  3.     void *base = NULL;  
  4.     int module_size = 0;  
  5.     char filename[512];  
  6.     char path[512];  
  7.     int fd=-1;  
  8.     int r=-1;  
  9.     int len=0;  
  10.     struct stat st;  
  11.     u4 *addr;  
  12.     int newCodeOffPosition;  
  13.     u1 *store = (u1 *) malloc(2);  
  14.   
  15.   
  16.     // simple test code  here!  
  17.     for(int i=0; i<2; i++){  
  18.         sprintf(filename, "/mnt/sdcard/payload_odex/ForceApkObj.dex");  
  19.   
  20.         base = get_module_base(-1, filename);  
  21.         if(base != NULL){  
  22.             break;  
  23.         }  
  24.     }  
  25.   
  26.     if(base == NULL){  
  27.         ALOGE("Can not found module: %s", filename);  
  28.         return ;  
  29.     }  
  30.   
  31.     module_size = get_module_size(-1, filename);  
  32.   
  33.     // search dex from odex  
  34.     void *dexBase = searchDexStart(base);  
  35.     ALOGD("found dex start[%p]", dexBase);  
  36.     if(checkDexMagic(dexBase) == false){  
  37.         ALOGE("Error! invalid dex format at: %p", dexBase);  
  38.         return ;  
  39.     }  
  40.   
  41.     DexHeader *dexHeader = (DexHeader *)dexBase;  
  42.   
  43.     gDexFile.baseAddr   = (u1*)dexBase;  
  44.     gDexFile.pHeader    = dexHeader;  
  45.     gDexFile.pStringIds = (DexStringId*)((u4)dexBase+dexHeader->stringIdsOff);  
  46.     gDexFile.pTypeIds   = (DexTypeId*)((u4)dexBase+dexHeader->typeIdsOff);  
  47.     gDexFile.pMethodIds = (DexMethodId*)((u4)dexBase+dexHeader->methodIdsOff);  
  48.     gDexFile.pFieldIds  = (DexFieldId*)((u4)dexBase+dexHeader->fieldIdsOff);  
  49.     gDexFile.pClassDefs = (DexClassDef*)((u4)dexBase+dexHeader->classDefsOff);  
  50.     gDexFile.pProtoIds  = (DexProtoId*)((u4)dexBase+dexHeader->protoIdsOff);  
  51.   
  52.   
  53.     //dumpDexHeader(dexHeader);  
  54.     //dumpDexStrings(&gDexFile);  
  55.     //dumpDexTypeIds(&gDexFile);  
  56.     //dumpDexProtos(&gDexFile);  
  57.     //dumpFieldIds(&gDexFile);  
  58.     //dumpClassDefines(&gDexFile);  
  59.   
  60.   
  61.     // 2. found Dex Class!  
  62.     const DexCode  *code =  
  63.         dexFindClassMethod(&gDexFile, className, methodName);  
  64.     if(code == NULL){  
  65.         ALOGE("Error can not found setScoreHidden");  
  66.         return ;  
  67.     }  
  68.     codeOffPosition = ((u4 *)code - (u4 *)dexBase) * 4;  
  69.     ALOGD("codeOffPosition:%d", codeOffPosition);  
  70.     dexFindClassData(&gDexFile, className);  
  71.     u1 *codeData = (u1 *)codeOffPoint;  
  72.     ALOGD("codeOffPoint:%p,codeOffPointByte:%d,codeOffPointData:%d,%d", codeOffPoint, codeOffPointByte, *codeData,*(codeData + 1));  
  73.     sprintf(path, "/mnt/sdcard/payload_odex/data.so");  
  74.     fd = open(path,O_RDONLY,0666);  
  75.     if (fd==-1) {  
  76.         return ;  
  77.     }  
  78.   
  79.     r=fstat(fd,&st);  
  80.     if(r==-1){  
  81.         close(fd);  
  82.         return ;  
  83.     }  
  84.   
  85.     len=st.st_size;  
  86.     addr=(u4 *)mmap(NULL,len,PROT_READ,MAP_PRIVATE,fd,0);  
  87.     ALOGD("addr:%p", addr);  
  88.     newCodeOffPosition = ((u4 *)addr - (u4 *)dexBase) * 4;  
  89.     writeLeb128(store, newCodeOffPosition);  
  90.     ALOGD("newCodeOffPosition:%d, store:%d,%d", newCodeOffPosition, *store,*(store + 1));  
  91.     ALOGD("mprotect in");  
  92.     *codeData = *store;  
  93.     codeData++;  
  94.     *codeData = *(store + 1);  
  95.     ALOGD("codeOffPointData:%d,%d", *(codeData-1),*(codeData));  
  96. }  
    还记得我们在0x02步提取的data.so,实际上就是原来的handleException方法的DexCode结构体内容。我们只要找到目前指向DexCode结构体(当前方法指令置为全零)指针codeOff,我们再把data.so映射到内存,让codeOff指向这个地址,就完成了动态修复。因为这时候还没有loadClass,所以在此之前修复,loadClass就能形成正确的ClassObject,从而正确的执行指令。

    讲的是大概的原理,大家参考源码多看看就能理解,如果有不懂,请留言。我在Android 2.3模拟器试验成功!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值