运行时动态修复dex
来源:互联网 发布:java通用版手机qq 编辑:程序博客网 时间:2024/06/17 14:44
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的代码如下:
public class SubActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);handleException();}public void handleException() {Toast.makeText(this, "成功映射", Toast.LENGTH_LONG).show();}}
FixDex用于用于分离ForceApkObj里面的classes.dex,把他分离为classes_fix.dex和data.so。代码如下:
public void onClick(View arg0) {int codeoff = FindCode.findCode("Lcom/example/forceapkobj/SubActivity;", "handleException");Log.d("jltxgcy", "codeoff:" + codeoff);try {File file = new File("/sdcard/payload/classes.dex");byte[] dexByte = readFileBytes(file);String strso = "/sdcard/payload/data.so";writeFile(strso, dexByte, codeoff - 16, exceptionCode.length + 16);System.arraycopy(exceptionCode, 0, dexByte, codeoff, exceptionCode.length);//修改DEX file size文件头fixFileSizeHeader(dexByte);//修改DEX SHA1 文件头fixSHA1Header(dexByte);//修改DEX CheckSum文件头fixCheckSumHeader(dexByte);String str = "/sdcard/payload/classes_fix.dex";writeFile(str, dexByte, 0, dexByte.length);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}首先找到SubActivity类的handleException方法的偏移,这个方法是一个native方法,具体的请参考源代码,看源代码前,请先了解apk自我保护的一种实现方式——运行时自篡改dalvik指令。核心的原理我解释下:
const DexCode *code = dexFindClassMethod(&gDexFile, className, methodName); if(code == NULL){ ALOGE("Error can not found setScoreHidden"); return 0; } position = (u4 *)code - (u4 *)dexBase; ALOGD("codeoff:%d", ((u4 *)code - (u4 *)dexBase)); return position * 4 + 16;找到handleException在内存中的偏移,然后再减去dex头部的基地址,得到handleException在dex中本地偏移,那为什么要加上16呢?我们先看一个DexCode的结构。
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] */};加上16其实就是insns[1]的偏移,也就是真正执行的指令的偏移。
找到了真正执行的指令的偏移,然后我们把这个指令整个的DexCode拷贝到data.so中。
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。这样如果不动态修复,当执行到这个方法时,就会报错。
System.arraycopy(exceptionCode, 0, dexByte, codeoff, exceptionCode.length);
String str = "/sdcard/payload/classes_fix.dex";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步已经设置为全零。那么就会报错。
如果想要执行正确,我们有一个思路,也就是代码中的实现,如下:
static void nativeParserDex(const char *className, const char *methodName){void *base = NULL;int module_size = 0;char filename[512];char path[512];int fd=-1;int r=-1;int len=0;struct stat st;u4 *addr;int newCodeOffPosition;u1 *store = (u1 *) malloc(2);// simple test code here!for(int i=0; i<2; i++){sprintf(filename, "/mnt/sdcard/payload_odex/ForceApkObj.dex");base = get_module_base(-1, filename);if(base != NULL){break;}} if(base == NULL){ ALOGE("Can not found module: %s", filename); return ; } module_size = get_module_size(-1, filename);// search dex from odexvoid *dexBase = searchDexStart(base);ALOGD("found dex start[%p]", dexBase);if(checkDexMagic(dexBase) == false){ALOGE("Error! invalid dex format at: %p", dexBase);return ;}DexHeader *dexHeader = (DexHeader *)dexBase; gDexFile.baseAddr = (u1*)dexBase; gDexFile.pHeader = dexHeader; gDexFile.pStringIds = (DexStringId*)((u4)dexBase+dexHeader->stringIdsOff); gDexFile.pTypeIds = (DexTypeId*)((u4)dexBase+dexHeader->typeIdsOff); gDexFile.pMethodIds = (DexMethodId*)((u4)dexBase+dexHeader->methodIdsOff); gDexFile.pFieldIds = (DexFieldId*)((u4)dexBase+dexHeader->fieldIdsOff); gDexFile.pClassDefs = (DexClassDef*)((u4)dexBase+dexHeader->classDefsOff); gDexFile.pProtoIds = (DexProtoId*)((u4)dexBase+dexHeader->protoIdsOff); //dumpDexHeader(dexHeader); //dumpDexStrings(&gDexFile); //dumpDexTypeIds(&gDexFile); //dumpDexProtos(&gDexFile); //dumpFieldIds(&gDexFile); //dumpClassDefines(&gDexFile); // 2. found Dex Class! const DexCode *code = dexFindClassMethod(&gDexFile, className, methodName); if(code == NULL){ ALOGE("Error can not found setScoreHidden"); return ; } codeOffPosition = ((u4 *)code - (u4 *)dexBase) * 4; ALOGD("codeOffPosition:%d", codeOffPosition); dexFindClassData(&gDexFile, className); u1 *codeData = (u1 *)codeOffPoint; ALOGD("codeOffPoint:%p,codeOffPointByte:%d,codeOffPointData:%d,%d", codeOffPoint, codeOffPointByte, *codeData,*(codeData + 1)); sprintf(path, "/mnt/sdcard/payload_odex/data.so"); fd = open(path,O_RDONLY,0666);if (fd==-1) {return ;}r=fstat(fd,&st);if(r==-1){close(fd);return ;}len=st.st_size;addr=(u4 *)mmap(NULL,len,PROT_READ,MAP_PRIVATE,fd,0);ALOGD("addr:%p", addr);newCodeOffPosition = ((u4 *)addr - (u4 *)dexBase) * 4;writeLeb128(store, newCodeOffPosition);ALOGD("newCodeOffPosition:%d, store:%d,%d", newCodeOffPosition, *store,*(store + 1));ALOGD("mprotect in");*codeData = *store;codeData++;*codeData = *(store + 1);ALOGD("codeOffPointData:%d,%d", *(codeData-1),*(codeData));}还记得我们在0x02步提取的data.so,实际上就是原来的handleException方法的DexCode结构体内容。我们只要找到目前指向DexCode结构体(当前方法指令置为全零)指针codeOff,我们再把data.so映射到内存,让codeOff指向这个地址,就完成了动态修复。因为这时候还没有loadClass,所以在此之前修复,loadClass就能形成正确的ClassObject,从而正确的执行指令。
讲的是大概的原理,大家参考源码多看看就能理解,如果有不懂,请留言。我在Android 2.3模拟器试验成功!
- 运行时动态修复dex
- 运行时动态修复dex
- Android热修复三部曲之动态加载补丁.dex文件
- Android热补丁动态修复技术(一)dex分包原理
- Android热修复三部曲之动态加载补丁.dex文件
- android dex热修复
- Android热补丁动态修复技术(一):从Dex分包原理到热补丁
- Android热补丁动态修复技术(一):从Dex分包原理到热补丁
- Android热补丁动态修复技术(一):从Dex分包原理到热补丁
- Android热补丁动态修复技术(一):从Dex分包原理到热补丁
- Android动态加载热更新修复功能 加载多个dex
- 替换dex实现热修复
- 动态load dex
- Dex动态加载
- 动态加载DEX
- 动态加载dex笔记
- 动态加载dex
- 动态加载dex
- 【转】深入理解JDBC Scrollable ResultSet
- hashmap与hashtable的区别,以及实现hashmap的同步操
- 第三方支付宝支付
- 【转】MySQL命令大全
- 【转】IntelliJ IDEA 10.0 64位运行方法
- 运行时动态修复dex
- 【转】2003服务器终端服务器超出最大连接数
- 【转】MySQL负载均衡功能实现概要
- 图像处理(十)基于特征线的图像变形-Siggraph 1992
- 【转】mysql负载均衡的配置过程
- android graphic(5)—surfaceflinger和Vsync (简化)
- 【转】OpenGL开发环境配置
- Android中webview加载网络资源
- 【转】MySQL忘记root密码