You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 08:52:51