内核模式内存读写——kernel.getModuleBase + readDword/writeDword
核心三步骤
内核模式下操作目标进程内存,永远是这三步:
// 1. 拿 SO 库基址
var base = kernel.getModuleBase('libil2cpp.so');
// 2. 算实际地址:基址 + 偏移
var targetAddr = parseInt(base, 16) + 0x1A2B3C4;
// 3. 读或写
var health = kernel.readFloat('0x' + targetAddr.toString(16));
kernel.writeFloat('0x' + targetAddr.toString(16), 999.0);
完整示例:写无敌模式
if (runtime.isKernel) {
// 等 libil2cpp.so 加载好
var timer = setInterval(function () {
var il2cpp = kernel.getModuleBase('libil2cpp.so');
if (il2cpp && il2cpp !== '0x0') {
clearInterval(timer);
console.log('[+] libil2cpp base: ' + il2cpp);
// 假设生命值变量在基址 + 0x1A2B3C4 的位置(从 dump.cs 或内存扫描拿到)
var healthAddr = parseInt(il2cpp, 16) + 0x1A2B3C4;
var healthHex = '0x' + healthAddr.toString(16);
console.log('[+] 生命值地址: ' + healthHex);
// 读当前值
var currentHealth = kernel.readFloat(healthHex);
console.log('[+] 当前生命值: ' + currentHealth);
// 每 3 秒改成 999
setInterval(function () {
kernel.writeFloat(healthHex, 999.0);
console.log('[+] 生命值已设为 999');
}, 3000);
}
}, 100);
}
支持的读写类型
| API | 类型 | 说明 |
|---|---|---|
kernel.readDword(address) | int(4 字节整数) | 读 32 位整数 |
kernel.writeDword(address, value) | int | 写 32 位整数 |
kernel.readFloat(address) | float(4 字节浮点数) | 读 IEEE 754 浮点数 |
kernel.writeFloat(address, value) | float | 写 IEEE 754 浮点数 |
address 参数统一用字符串格式的十六进制,如 '0x7f8a12345000'。
怎么拿到偏移量
内核模式下无法用 Memory.scan 或 Module.findBaseAddress——观察者进程的地址空间和目标进程完全不同。
所以偏移量的获取只能在普通模式下先做一次:
方法一:普通模式下用 Frida 找基址
// 普通模式下运行,找到地址后记录下来
var addr = Module.findBaseAddress('libil2cpp.so');
console.log('[+] libil2cpp base: ' + addr);
// 然后用 Interceptor 或 Memory.scan 找到目标变量的偏移
方法二:用 Il2CppDumper 导出的 dump.cs
dump.cs 里包含了所有类、方法、字段的偏移量列表。直接查就行。
方法三:MCP 工具配合 VS Code Copilot
JsHook 的内置 MCP 服务可以让 AI 辅助定位内存地址。详见 MCP 可用工具清单。
getModuleBase 返回 0x0 怎么办
var base = kernel.getModuleBase('libil2cpp.so');
console.log('base: ' + base); // 输出 0x0
排查顺序:
- 目标应用有没有在运行? SO 库没加载的话基址就是 0x0
- SO 名对不对? 区分大小写,用实际的 SO 文件名
- 内核驱动正常工作吗? 检查驱动加载日志
- 是观察者进程的问题? 看看
/data/adb/jshook/.kernel/下有没有kernel_*.log
readDword / writeDword 用什么场景
- 整数变量(金币数量、等级、计数器)→ readDword / writeDword
- 浮点数变量(生命值、位置坐标、速度倍率)→ readFloat / writeFloat
- boolean(实际存储是 int,0 = false,非 0 = true)→ readDword / writeDword
注意事项
- 内存地址每次应用重启都会变(ASLR),基址每次都要重新获取
- 写操作风险较高——写错地址可能直接让目标应用崩溃
- 先读后写:写之前先读一下,确认地址是对的