Frida拦截HermesRuntimeImpl::call触发访问违例错误求助
拦截HermesRuntimeImpl::call时调用valueToString触发访问违例的解决方案
问题概述
用Frida拦截React Native安卓应用的HermesRuntimeImpl::call方法,意图提取参数并执行额外操作,但调用jsi::Value::toString时出现访问违例:
Error: access violation accessing 0x500000cf0
现有代码问题分析
核心错误点
- Runtime实例误用:当前代码把
makeHermesRuntime的函数地址直接当作Runtime实例传入valueToString,但makeHermesRuntime是创建Runtime的工厂函数,并非实例本身,直接传递必然触发非法内存访问。 - 成员函数调用逻辑错误:
jsi::Value::toString是C++成员函数,第一个参数应为jsi::Value的this指针,第二个是Runtime实例指针,且返回的jsi::String不能直接读取内存,需要调用其utf8()方法获取可读取的字符串。 - Runtime实例获取方式无效:当前环境中
makeHermesRuntime未被调用,说明Runtime是通过Native层直接传递等其他途径创建的。
原始拦截代码(带错误调用)
let libhermesBaseAddress = Module.findBaseAddress("libhermes.so"); let hermesRuntimeImplCallAddress = libhermesBaseAddress.add(0x1f3931 - 0x00100000); let runtimePtr = Module.findExportByName("libhermes.so", "_ZN8facebook6hermes17makeHermesRuntimeERKN6hermes2vm13RuntimeConfigE"); let valueToStringAddr = Module.findExportByName("libjsi.so", "_ZNK8facebook3jsi5Value8toStringERNS0_7RuntimeE"); Interceptor.attach(hermesRuntimeImplCallAddress, { onEnter: function (args) { console.log("HermesRuntimeImpl::call intercepted"); let func = args[1]; let jsThis = args[2]; let jsArgs = args[3]; let count = args[4]; console.log(`Function: ${func}`); console.log(`jsThis: ${jsThis}`); console.log(`jsArgs: ${jsArgs}`); console.log(`Count: ${count}`); console.log(`runtimePtr: ${runtimePtr}`); console.log(`valueToStringAddr: ${valueToStringAddr}`); let valueToString = new NativeFunction(valueToStringAddr, 'pointer', ['pointer', 'pointer']); let resultPtr = valueToString(jsThis, runtimePtr); let resultString = Memory.readUtf8String(resultPtr); console.log("Value to String Result:", resultString); } });
修正方案
1. 从拦截上下文获取正确的Runtime实例
HermesRuntimeImpl::call是成员函数,args[0]就是HermesRuntimeImpl的this指针,而HermesRuntimeImpl继承自jsi::Runtime,直接用args[0]作为Runtime实例即可。
2. 修正valueToString的调用逻辑
jsi::Value::toString返回的是jsi::String指针,需要调用jsi::String::utf8()方法才能获取C风格字符串,不能直接读取指针内存。
3. 批量Hook两个HermesRuntimeImpl::call方法
存在两个同名方法(分别处理普通调用和构造函数调用),需同时Hook避免遗漏。
修正后的完整代码
// 加载依赖库基地址 const libhermesBase = Module.findBaseAddress("libhermes.so"); const libjsiBase = Module.findBaseAddress("libjsi.so"); // 替换为你分析得到的两个HermesRuntimeImpl::call偏移(相对于libhermes基地址) const callOffsets = [ 0x1f3931 - 0x00100000, // 普通调用偏移 0xXXXXXX - 0x00100000 // 构造函数调用偏移,自行补充 ]; // 获取jsi相关函数地址 const valueToStringAddr = Module.findExportByName("libjsi.so", "_ZNK8facebook3jsi5Value8toStringERNS0_7RuntimeE"); const stringUtf8Addr = Module.findExportByName("libjsi.so", "_ZNK8facebook3jsi6String4utf8ERNS0_7RuntimeE"); // 定义Native函数 const valueToString = new NativeFunction(valueToStringAddr, 'pointer', ['pointer', 'pointer']); const getStringUtf8 = new NativeFunction(stringUtf8Addr, 'pointer', ['pointer', 'pointer']); // 批量Hook两个call方法 callOffsets.forEach(offset => { const callAddr = libhermesBase.add(offset); Interceptor.attach(callAddr, { onEnter(args) { console.log(`[+] Intercepted HermesRuntimeImpl::call at ${callAddr}`); // args[0]就是HermesRuntime实例 const runtime = args[0]; const jsThis = args[2]; const jsArgs = args[3]; const argCount = args[4].toUInt32(); // 转换jsThis为字符串 try { const strPtr = valueToString(jsThis, runtime); if (strPtr) { const utf8Ptr = getStringUtf8(strPtr, runtime); const jsThisStr = Memory.readUtf8String(utf8Ptr); console.log(`[jsThis] ${jsThisStr}`); } } catch (e) { console.log(`[-] Failed to convert jsThis: ${e.message}`); } // 遍历所有参数并转换 for (let i = 0; i < argCount; i++) { const argPtr = jsArgs.add(i * Process.pointerSize); const argValue = argPtr.readPointer(); try { const strPtr = valueToString(argValue, runtime); if (strPtr) { const utf8Ptr = getStringUtf8(strPtr, runtime); const argStr = Memory.readUtf8String(utf8Ptr); console.log(`[Arg ${i}] ${argStr}`); } } catch (e) { console.log(`[-] Failed to convert arg ${i}: ${e.message}`); } } } }); });
注意事项
- 偏移地址适配:不同版本的
libhermes.so偏移不同,需用IDA/Ghidra重新分析获取正确的HermesRuntimeImpl::call偏移。 - 异常处理:添加try-catch防止单个参数转换失败导致拦截逻辑崩溃。
- jsi对象生命周期:不要长时间持有jsi对象指针,避免内存回收导致的访问错误。
内容的提问来源于stack exchange,提问作者Thuwarakan Mohanraj
相关产品推荐
相关产品推荐

