【app逆向】Frida-rpc 参数类型转换实战指南

张开发
2026/4/18 22:01:41 15 分钟阅读

分享文章

【app逆向】Frida-rpc 参数类型转换实战指南
1. Frida-RPC参数类型转换的核心挑战当你第一次尝试用Frida-RPC跨语言调用时大概率会遇到这样的场景Python传了个字符串给JavaScript结果Java层方法报类型错误或者JavaScript返回的对象在Python端变成了一堆看不懂的十六进制值。这种问题我遇到过太多次了甚至有一次调试到凌晨三点才发现是int32和int64的转换问题。跨语言调用最大的痛点就是类型系统不匹配。JavaScript用动态类型Java是强类型Python又是另一套规则。比如Java方法需要byte[]但Python传过来的是bytesJavaScript这边就得做转换。常见的坑包括数值类型的位数差异32位/64位字符串编码问题UTF-8/UTF-16对象与基本类型的自动装箱数组和列表的表示差异2. JavaScript与Java的类型转换实战2.1 基础类型转换技巧在Java层看到一个int参数时JavaScript这边可以直接传numberrpc.exports { calculate: function(num) { return Java.use(com.example.Calculator).add(num, 10); // num自动转为Java的int } }但遇到long类型时就要小心了。有次我传了个超过2^32的数结果高位被截断。正确做法是用long构造器Java.use(java.lang.Long).$new(123456789012345) // 处理大整数字符串处理更是个暗坑。Android用的UTF-16而JavaScript字符串是UTF-8。遇到过中文乱码问题的可以这样解决Java.use(java.lang.String).$new(中文.split().map(c c.charCodeAt(0)).flatMap(x [x 0xff, x 8 0xff]))2.2 复杂对象处理方案当需要处理Java对象时Java.cast是你的好朋友。比如有个返回ListString的方法const ArrayList Java.use(java.util.ArrayList); const list ArrayList.$new(); list.add(item1); list.add(Java.use(java.lang.String).$new(item2)); // 显式构造String对象遇到自定义对象时记得先获取类引用。有次我卡了半天就因为类名写错了大小写const MyClass Java.use(com.example.MyClass); const instance MyClass.$new(); instance.setValue(42); // 调用实例方法3. Python与JavaScript的交互细节3.1 参数传递的隐藏规则Python调用JavaScript的RPC方法时参数会经过JSON序列化。这意味着Python的bytes会变成base64字符串datetime对象会变成ISO格式字符串numpy数组会被展平实测发现这样的转换会出问题script.exports.process(b\x01\x02) # 错误变成AQI字符串应该改用bytearrayscript.exports.process(bytearray([1, 2])) # 正确保持二进制3.2 返回值的处理技巧JavaScript返回Java对象时Python端会收到字典表示。但遇到特殊类型需要手动转换result script.exports.getUser() # 返回Java对象 user_id result[id].value # 获取Java Long的实际值处理二进制数据时更要注意编码data script.exports.getBytes() # 返回base64字符串 real_bytes base64.b64decode(data) # 需要解码4. 实战案例加密函数调用全流程来看一个真实项目的加密函数调用过程这里要处理三种类型转换Python端准备参数params { timestamp: int(time.time()), data: bytearray(敏感数据.encode(utf-8)), flag: 0x20 }JavaScript层类型转换rpc.exports { encrypt: function(params) { const jParams Java.use(org.json.JSONObject).$new(); jParams.put(timestamp, params.timestamp); const jData Java.array(byte, Array.from(params.data)); const Crypt Java.use(com.example.Crypto); return Crypt.encrypt(jParams.toString(), jData, params.flag); } }Java层最终处理public byte[] encrypt(String json, byte[] data, int flag) { // 实际加密逻辑 }这个案例中踩过的坑包括Python的dict需要手动转JSON字符串bytearray要转换成Java的byte[]flag的十六进制表示要正确处理5. 调试技巧与性能优化5.1 类型检查工具在JavaScript层插入类型检查很管用function checkType(value, expected) { if (expected jstring !(value instanceof Java.use(java.lang.String))) { throw new Error(Expected String got ${value.getClass()}); } // 其他类型检查... }5.2 性能敏感场景处理大量数据传输时发现性能瓶颈试试这些优化避免频繁跨语言调用批量处理数据使用Java.scheduleOnMainThread处理UI相关操作二进制数据用base64传输比逐字节转换快3倍rpc.exports { bulkProcess: function(items) { const results []; const MyClass Java.use(com.example.Processor); items.forEach(item { results.push(MyClass.process(item)); }); return results; // 一次性返回 } }6. 特殊场景解决方案6.1 回调函数处理需要Java调用JavaScript回调时这样实现const callback new Java.use(com.example.Callback).$new({ onEvent: function(event) { send({type: event, data: event}); } });6.2 多线程环境下的坑在非主线程调用Java方法会导致崩溃记得加保护Java.perform(function() { // 这里操作Java对象是线程安全的 });遇到过最诡异的bug是某个对象只在特定线程可用解决方案是const obj Java.scheduleOnMainThread(() { return Java.use(com.example.Singleton).getInstance(); }).wait();

更多文章