深入探索Xposed与Dobby结合实现native层hook的实战指南 1. 为什么需要Xposed与Dobby结合在Android逆向工程领域Xposed框架就像瑞士军刀一样经典。它能轻松hook Java层方法修改应用行为但遇到native层代码时就显得力不从心。我遇到过不少案例关键加密算法、核心校验逻辑都被开发者藏进了so库这时候就需要Dobby这样的native hook框架来帮忙。Dobby的优势在于它的轻量化和跨平台特性。实测在ARM64架构的Android 12设备上它的指令修改成功率能达到98%以上。相比其他hook框架Dobby的API设计更符合C/C开发者的习惯像DobbyHook这个函数用起来就特别顺手后面我会用实际代码演示。2. 环境搭建的避坑指南2.1 准备Dobby框架首先到Dobby的GitHub仓库下载最新release版本。这里有个坑要注意别直接clone主分支代码我上次用dev分支的代码就遇到了ABI兼容问题。解压后你会看到这几个关键文件include/dobby.h核心头文件lib/arm64-v8aARM64架构静态库lib/armeabi-v7aARMv7架构静态库建议在Android Studio里新建Native C项目时直接把整个lib目录拖到app/src/main/cpp下。我习惯的目录结构是这样的app/ └── src/ └── main/ ├── cpp/ │ ├── lib/ # Dobby库文件 │ ├── CMakeLists.txt │ └── native-lib.cpp └── java/2.2 CMake配置技巧在CMakeLists.txt中要特别注意链接顺序。我常用的配置模板如下add_library( native-lib SHARED native-lib.cpp) # 添加Dobby静态库 add_library(dobby STATIC IMPORTED) set_target_properties(dobby PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/lib/${ANDROID_ABI}/libdobby.a) target_link_libraries( native-lib dobby log)遇到过最头疼的问题是忘记加log库导致__android_log_print报错折腾了半天才发现。3. Dobby实战Hook技巧3.1 基础Hook方法先看最简单的函数替换示例。假设我们要hook的so里有个加密函数encrypt_data// 原函数声明 void (*original_encrypt)(char* data); // 新函数实现 void my_encrypt(char* data) { __android_log_print(ANDROID_LOG_DEBUG, HOOK, 原始数据: %s, data); original_encrypt(data); // 调用原函数 __android_log_print(ANDROID_LOG_DEBUG, HOOK, 加密后: %s, data); } // Hook调用 DobbyHook( (void*)getAbsoluteAddress(libtarget.so, 0x1234), (void*)my_encrypt, (void**)original_encrypt);这里有几个关键点函数指针声明要保持与原函数一致getAbsoluteAddress需要自己实现后面会讲第三个参数用于保存原函数指针3.2 指令修改实战有时候我们需要直接修改机器指令。比如绕过某个校验uint8_t patch[] {0x1F, 0x20, 0x03, 0xD5}; // ARM64 NOP指令 DobbyCodePatch( (void*)getAbsoluteAddress(libtarget.so, 0x5678), patch, sizeof(patch));这里踩过的坑是字节序问题。有次在ARMv7设备上忘了转小端序导致指令执行异常直接崩溃。建议先用IDA确认指令的字节表示。4. Xposed注入的关键时机4.1 类加载器切换技巧在加固应用中正确的ClassLoader获取方式决定成败。以某主流加固为例ClassLoader targetLoader null; XposedHelpers.findAndHookMethod(com.stub.StubApp, lpparam.classLoader, attachBaseContext, new XC_MethodHook() { Override protected void afterHookedMethod(MethodHookParam param) { Context context (Context) param.args[0]; targetLoader context.getClassLoader(); } });这个时机比onCreate更早能确保在so加载前拿到控制权。我测试过5款主流加固方案有3款需要在这个点切入。4.2 SO注入的版本适配Android不同版本的so加载API变化很大String soPath /data/app/~~pkgPath/lib/arm64/libinject.so; if (Build.VERSION.SDK_INT 28) { XposedHelpers.callMethod( Runtime.getRuntime(), nativeLoad, soPath, targetLoader); } else { XposedHelpers.callMethod( Runtime.getRuntime(), doLoad, soPath, targetLoader); }特别注意高版本的文件路径随机化问题。我推荐用ApplicationInfo.sourceDir动态获取路径而不是硬编码。5. 完整案例破解某金融App签名校验最近分析的一个真实案例校验逻辑在libsecurity.so的verifySign函数。解决方案分三步用Xposed在StubApp.attachBaseContext切入注入包含Dobby hook的so库在JNI_OnLoad中植入hook代码关键hook代码int (*orig_verify)(char* sign); int my_verify(char* sign) { __android_log_print(ANDROID_LOG_DEBUG, BYPASS, 跳过签名校验); return 1; // 强制返回验证成功 } JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { DobbyHook( (void*)getAbsoluteAddress(libsecurity.so, 0xA1B2C), (void*)my_verify, (void**)orig_verify); return JNI_VERSION_1_6; }这个案例中最大的挑战是so的加载顺序问题。最终通过Xposed延迟注入解决了竞争条件具体是在ActivityThread.handleBindApplication回调中触发加载。6. 常见问题解决方案6.1 地址获取的可靠性对于无符号函数我封装了一个增强版地址获取工具void* getSymAddress(const char* soName, const char* symName) { void* handle dlopen(soName, RTLD_NOW); if (!handle) return NULL; void* addr dlsym(handle, symName); dlclose(handle); return addr; }配合IDA的符号分析能解决90%的定位问题。剩下10%需要结合反调试对抗比如处理函数混淆。6.2 多线程环境下的稳定性遇到过一个崩溃案例hook点恰好在高频调用的线程回调中。解决方案是使用DobbyDestroy清理旧hook添加线程锁保护替换为DobbyInstrument方式static pthread_mutex_t hook_lock PTHREAD_MUTEX_INITIALIZER; void thread_safe_hook() { pthread_mutex_lock(hook_lock); // Hook操作... pthread_mutex_unlock(hook_lock); }7. 性能优化建议在长时间运行的hook中发现Dobby的原始实现会有约5%的性能损耗。通过以下优化降到1%以内使用DobbyCodePatch替代频繁的DobbyHook对热路径函数启用FAST_HOOK模式避免在hook回调中执行复杂逻辑实测数据对比优化方式平均耗时(ms)CPU占用率原始hook12.38.2%优化后2.11.5%关键配置代码DobbyConfig config; config.accelerator ACCELERATOR_FAST_HOOK; DobbyHookEx(target_func, new_func, config);8. 安全防护应对策略现在很多应用会检测inline hook我们得做好反检测擦除hook痕迹DobbyDestroy(target_func);伪装原始指令DobbyCodePatch(target, backup_instructions, sizeof(backup_instructions));动态切换hook点最近遇到的一个检测方案是通过/proc/self/maps检查so修改解决办法是hook文件读取相关系统调用。这种攻防对抗就像下棋需要不断调整策略。