https://bbs.pediy.com/thread-225427.htm
DexHunter需要刷机,感觉太麻烦了,所以用xposed弄了一份。然后大部分解析逻辑都放到了java层,这样也有另一个好处,就是壳总不能修改java正常的api。所以define class之类的都可以直接调用java 层的classloader来初始化class。
注意:目前我只在dalvik上面实现,art还没有看。
1. 寻找两个dalvik虚拟机函数,dvmDecodeIndirectRef 和dvmThreadSelf
/** * 函数名称表根据4.4的Android版本设置的,不同Android版本映射可能存在差异,可以直接用ida查看维护 */ void initDvmFunctionTables() { void *libVMhandle = dlopen("libdvm.so", RTLD_GLOBAL | RTLD_LAZY); initDvmFunctionItem("_Z20dvmDecodeIndirectRefP6ThreadP8_jobject", (void **) (&dvmFunctionTables.dvmDecodeIndirectRef), libVMhandle); initDvmFunctionItem("_Z13dvmThreadSelfv", (void **) (&dvmFunctionTables.dvmThreadSelf), libVMhandle); dlclose(libVMhandle); }
2. 定位dex文件
其实我现在还不知道为啥dump内存需要那么麻烦,直接就能在内存中找到啊。看代码
extern "C" JNIEXPORT jobject JNICALL Java_com_virjar_xposedhooktool_unshell_Dumper_originDex(JNIEnv *env, jclass type, jclass loader) { //TODO check & throw exception ClassObject *clazz = (ClassObject *) dvmFunctionTables.dvmDecodeIndirectRef( dvmFunctionTables.dvmThreadSelf(), loader); DvmDex *dvm_dex = clazz->pDvmDex; return env->NewDirectByteBuffer(dvm_dex->memMap.addr, dvm_dex->memMap.length); }
这不就直接找到dex文件了嘛。
3. 创建dexFile模型,使用baksmaliAPI
private static DexBackedDexFile createMemoryDexFile(Class loader) { ByteBuffer byteBuffer = originDex(loader); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); byte[] buffer = new byte[byteBuffer.capacity()]; byteBuffer.get(buffer, 0, byteBuffer.capacity()); if (HeaderItem.verifyMagic(buffer, 0)) { return new DexBackedDexFile(Opcodes.forApi(apiLevel()), buffer); //a normal dex file } if (OdexHeaderItem.verifyMagic(buffer, 0)) { //this is a odex file try { ByteArrayInputStream is = new ByteArrayInputStream(buffer); DexUtil.verifyOdexHeader(is); is.reset(); byte[] odexBuf = new byte[OdexHeaderItem.ITEM_SIZE]; ByteStreams.readFully(is, odexBuf); int dexOffset = OdexHeaderItem.getDexOffset(odexBuf); if (dexOffset > OdexHeaderItem.ITEM_SIZE) { ByteStreams.skipFully(is, dexOffset - OdexHeaderItem.ITEM_SIZE); } return new DexBackedOdexFile(Opcodes.forApi(Dumper.apiLevel()), odexBuf, ByteStreams.toByteArray(is)); } catch (IOException e) { //while not happen throw new RuntimeException(e); } } throw new IllegalStateException("can not find out dex image in vm memory"); }
4. 使用smali APi rewrite功能,重构Method的指令数据
@Nonnull @Override public Method rewrite(@Nonnull final Method value) { if (!(value instanceof DexBackedMethod)) { return super.rewrite(value); } Class<?> definingClass; try { definingClass = classLoader.loadClass(value.getDefiningClass()); } catch (ClassNotFoundException e) { return super.rewrite(value); } if (definingClass.getClassLoader() != classLoader) { return super.rewrite(value); } final Class<?> searchClass = definingClass; final String methodDescriptor = ReferenceUtil.getMethodDescriptor(value); //覆盖 getImplementation return new RewrittenMethod(value) { @Nullable @Override public MethodImplementation getImplementation() { ByteBuffer byteBuffer = methodDataWithDescriptor(methodDescriptor, value.getName(), searchClass); if (byteBuffer == null) { return super.getImplementation(); } byteBuffer.order(ByteOrder.LITTLE_ENDIAN); byte[] buffer = new byte[byteBuffer.capacity()]; byteBuffer.get(buffer, 0, byteBuffer.capacity()); DexBackedMethodImplementation dexBackedMethodImplementation = new DexBackedMethodImplementation(new MethodSegmentDexFile(buffer, dexFile), (DexBackedMethod) value, 0); return rewriters.getMethodImplementationRewriter().rewrite(dexBackedMethodImplementation); } @Override public int getAccessFlags() { int accessFlags = getMethodAccessFlagsWithDescriptor(methodDescriptor, value.getName(), searchClass); if (accessFlags < 0) { //证明没有找到这个方法 return super.getAccessFlags(); } return accessFlags; } }; }
5. 使用dalvik dvmFindXXXMethodByDescriptor功能,寻找对应的method对象,进而扣出真实的指令,这里指令长度计算,借用了DexHunter的代码
extern "C" JNIEXPORT jobject JNICALL Java_com_virjar_xposedhooktool_unshell_Dumper_methodDataWithDescriptor(JNIEnv *env, jclass type, jstring methodDescriptor_, jstring methodName_, jclass searchClass) { const char *methodDescriptor = env->GetStringUTFChars(methodDescriptor_, 0); const char *methodName = env->GetStringUTFChars(methodName_, 0); ClassObject *clazz = (ClassObject *) dvmFunctionTables.dvmDecodeIndirectRef( dvmFunctionTables.dvmThreadSelf(), searchClass); jobject ret = NULL; Method *method = dvmFindDirectMethodByDescriptor(clazz, methodName, methodDescriptor); if (method == NULL) { method = dvmFindVirtualMethodByDescriptor(clazz, methodName, methodDescriptor); } if (method == NULL) { goto tail; } //check for native uint32_t ac = (method->accessFlags) & accessFlagsMask; if (method->insns == NULL || ac & ACC_NATIVE) { goto tail; } //why 16 // 2 byte for registersSize // 2 byte for insSize // 2 byte for outsSize // 2 byte for triesSize // 4 byte for debugInfoOff // 4 byte for insnsSize // and then ,the insns address 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 = &handler_data; uint8_t *tail = codeitem_end(phandler); code_item_len = (int) (tail - item); } else { //正确的DexCode的大小 code_item_len = 16 + code->insnsSize * 2; } ret = env->NewDirectByteBuffer(item, code_item_len); tail: env->ReleaseStringUTFChars(methodDescriptor_, methodDescriptor); env->ReleaseStringUTFChars(methodName_, methodName); return ret; }
6. binggo,使用smali api,直接解码
/** * 将指定loader的smali全部dump到硬盘,请异步执行该函数 * * @param loader loader */ public static void dissembleAllDex(Object loader) { Class loaderClass = resolveLoaderClass(loader); DexBackedDexFile memoryMethodDexFile = createMemoryDexFile(loaderClass); File dumpDir = resolveDumpDir(memoryMethodDexFile); DexFile reWritedDexFile = rewrite(memoryMethodDexFile, loaderClass.getClassLoader()); XposedBridge.log("脱壳目录:" + dumpDir.getAbsolutePath()); int jobs = Runtime.getRuntime().availableProcessors(); if (jobs > 6) { jobs = 6; } if (memoryMethodDexFile instanceof DexBackedOdexFile) { baksmaliOptions.inlineResolver = InlineMethodResolver .createInlineMethodResolver(((DexBackedOdexFile) memoryMethodDexFile).getOdexVersion()); } Log.i("weijia", "开始进行脱壳"); if (Baksmali.disassembleDexFile(reWritedDexFile, dumpDir, jobs, baksmaliOptions)) { Log.i("weijia", "脱壳完成,但是存在错误"); } else { Log.i("weijia", "脱壳成功,请在" + dumpDir + "中查看smali文件"); } Toast.makeText(SharedObject.context, "脱壳完成,请在" + dumpDir + "中查看smali文件", Toast.LENGTH_LONG).show(); }
7.binggo,使用smali API,输出dex文件
/** * 将对应class对应的dex文件的二进制dump出来 * * @param loader 该dex文件定义的任何一个class,或者class定义的object * @return 一个byteBuffer,包含了二进制数据 */ public static ByteBuffer dumpDex(Object loader) { Class loaderClass = resolveLoaderClass(loader); DexBackedDexFile memoryDexFile = createMemoryDexFile(loaderClass); byte[] buf = (byte[]) XposedHelpers.getObjectField(memoryDexFile, "buf"); DexFile dexFile = rewrite(memoryDexFile, loaderClass.getClassLoader()); final DexBuilder builder = new DexBuilder(Opcodes.forApi(apiLevel())); MemoryDataStore memoryDataStore = new MemoryDataStore(buf.length); for (ClassDef classDef : dexFile.getClasses()) { try { buildClassDef(classDef, builder); } catch (Exception e) { Log.i("weijia", "error when define class:" + classDef.getType() + " skipped for rebuild it"); } } try { builder.writeTo(memoryDataStore); } catch (IOException ioe) { //the memory writer,no ioe happend throw new RuntimeException(ioe); } return ByteBuffer.wrap(memoryDataStore.getData()); }
8.如何调用
XposedHelpers.findAndHookConstructor(Activity.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object activity = param.thisObject; if (activity == null) { return; } XposedBridge.log("hook class " + activity.getClass()); if (StringUtils.equalsIgnoreCase(activity.getClass().getName(), "com.xxx.xxx.MainActivity")) { Dumper.dumpDex(activity); } } });
9.对了你需要移植dalvik的dexlib模块代码,还有/vm/oo包下面的代码,到你的jni环境下。要不然没有dex的相关数据结构,也没有ClassObject的数据结构。
然后,不愿意放整个工程,不要求代码。论文放出来,还是自己实现一遍才能有收获。
其他:
相比原始DexHunter的优点:
1. 定位dex更加精准,DexHunter用过filename来确定当前dex是不是需要处理,很容易被加壳平台识别到这个特征。而且每次都要在放Android系统push一些文件,比较麻烦。我这个,直接通过class对象寻找,想找按个找那个。
2. 并发,可能没有写过Linux c语言程序,看那个pthread哪里看不懂。DexHunter为了防止多次重复处理同一个dex文件,写了比较复杂的加锁逻辑。这个放到java层,很简单实现吧。
3. 多dex,如果一个apk多个dex都需要处理。DexHunter不好处理,因为他输出就是whole.dex
4. dvmDefineClass失败,就算正常情况,一个classLoader下面的class,也不是全部可以正常load成功。DexHunter的流程是删除这些bad class,我还可以尽可能的使用原始dex数据进行解码。
5. Dalvik_dalvik_system_DexFile_defineClassNative被替换,这可能导致defineClassNative函数不被调用,这样DexHunter无法拦截到。我用classLoader.loadeClass,java标准接口,这个他永远没法替换。
6.其他数据结构加密调整,如果未来不光光是method的数据变化了。由于使用baksmali建立的模型,任何数据结构都很容易重构替换,毕竟是java,抽象封装很好用。
7.脱壳时机,脱壳输出控制更加方便。提供的是api,你调用就脱壳不调用就不脱壳。脱壳结果是java的二进制流,你想怎么编码、加密、转储都很方便。java层的api太多了。
8.可移植性,是一个普通的xposed项目,任何一个有xposed环境的Android机器都可以(当然现在还没有实现art,不过理论上没问题)。也不需要编译系统镜像。等几天再把xposed包装一下,免root脱壳。
当然,我对dex文件格式,并不是非常熟,反正没有DexHunter玩儿的那么溜,所以只有多用别人的api了。