JNIEnv函数表Hook在Android模拟器失效问题求助
解决模拟器中Hook JNI GetStringUTFChars的JNI错误问题
从你的报错日志里的libhoudini.so能看出来,你用的是Intel x86架构的模拟器,它依赖Houdini兼容层来转译ARM指令到x86。这个兼容层会对JNIEnv做一层特殊封装,你直接Hookenv->functions->GetStringUTFChars的方式会破坏Houdini的内部调用逻辑,导致后续JNI调用出现类型混淆——把String对象当成Array处理,触发了GetArrayLength的错误。
下面是具体的修复方案:
1. 问题根源拆解
在原生ARM真机上,JNIEnv的函数表直接指向libart.so中的JNI实现函数;但在x86模拟器的Houdini层中,JNIEnv被做了中转包装,函数表的指针指向Houdini的内部处理函数,直接Hook会干扰它的参数校验和类型转换流程,进而引发JNI错误。
2. 修复方案:Hook底层JNI实现函数
不要直接Hook JNIEnv函数表中的指针,转而Hook libart.so里对应的JNI底层实现函数,具体步骤如下:
步骤1:定位目标函数
- 针对Android 5的x86模拟器,用IDA打开
/system/lib/libart.so,查找符号_ZN3art3JNI17GetStringUTFCharsEP7_JNIEnvP8_jstringP8_jboolean(对应C++函数art::JNI::GetStringUTFChars);如果不想硬编码符号,也可以用dlsym动态获取函数地址。 - 不同Android版本的符号名可能略有差异,需要根据模拟器的系统版本调整。
步骤2:修改Hook代码
调整你的JNI_OnLoad逻辑,改为Hook libart中的实际实现:
#include <dlfcn.h> const char* (*oGetStringUTFChars)(JNIEnv*, jstring, jboolean*); const char* GetStringUTFChars(JNIEnv* env, jstring str, jboolean* copy) { auto result = oGetStringUTFChars(env, str, copy); LOGI("GetStringUTFChars: %s", result); return result; } JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void * reserved) { JNIEnv *env; vm->GetEnv((void **)&env, JNI_VERSION_1_4); // 加载libart并获取目标函数地址 void* libart_handle = dlopen("/system/lib/libart.so", RTLD_NOW); if (libart_handle) { // 根据Android版本调整符号名 void* target_func = dlsym(libart_handle, "_ZN3art3JNI17GetStringUTFCharsEP7_JNIEnvP8_jstringP8_jboolean"); if (target_func) { MSHookFunction(target_func, (void *)GetStringUTFChars, (void **)&oGetStringUTFChars); } else { LOGW("Failed to locate GetStringUTFChars symbol in libart"); } dlclose(libart_handle); } else { LOGW("Failed to open libart.so"); } return JNI_VERSION_1_4; }
3. 跨环境兼容适配
如果需要同时兼容真机和模拟器,可以在代码中先检测是否处于Houdini环境,再选择对应Hook策略:
bool isRunningOnHoudini() { FILE* maps_file = fopen("/proc/maps", "r"); if (!maps_file) return false; char buffer[256]; bool found_houdini = false; while (fgets(buffer, sizeof(buffer), maps_file)) { if (strstr(buffer, "libhoudini.so")) { found_houdini = true; break; } } fclose(maps_file); return found_houdini; }
然后在JNI_OnLoad中分支处理:
if (isRunningOnHoudini()) { // 模拟器环境:Hook libart底层函数 // ... 上面的libart Hook逻辑 } else { // 真机环境:原来的JNIEnv函数表Hook逻辑 MSHookFunction((void *)env->functions->GetStringUTFChars, (void *)GetStringUTFChars, (void **)&oGetStringUTFChars); }
4. 额外调试建议
- 用
LOGI打印env->functions->GetStringUTFChars的地址,对比真机和模拟器的差异,确认是否被Houdini重定向。 - 检查Hook函数的参数类型是否完全匹配,避免栈对齐或者参数传递错误。
- 可以先用Frida等动态调试工具验证目标函数的调用逻辑,再调整Substrate的Hook代码。
内容的提问来源于stack exchange,提问作者Aimar Adhitya
相关产品推荐
相关产品推荐

