📌 前置知识:Frida 基础用法、Python 基础🎯 目标:通过 RPC 在 Python 中调用 Android App 的加解密函数
一、为什么需要 RPC?
传统方式的痛点
以前我们分析 App 时,通常这样做:
1. 写 Frida 脚本 → 注入 App2. 手动触发 App 行为3. 查看 console 输出4. 重复步骤 2-3...
问题:需要手动操作 App,无法自动化批量调用。
RPC 带来的变革
有了 RPC,Python 可以直接调用 App 内部的函数:
┌─────────────────────────────────────────────────────────────────┐│ RPC 调用流程 │├─────────────────────────────────────────────────────────────────┤│ ││ Python 脚本 ││ │ ││ │ rpc.encrypt("hello") ──▶ ││ ▼ ││ ┌─────────────────────────────────────┐ ││ │ Frida RPC Server │ ││ │ (运行在 Android 进程中) │ ││ └──────────────┬──────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────┐ ││ │ App 内部函数被调用 │ ││ │ encrypt("hello") │ ││ └──────────────┬──────────────────────┘ ││ │ ││ ▼ 返回密文 ││ ◀───────────────────────────────── ││ │└─────────────────────────────────────────────────────────────────┘
二、RPC 核心概念
2.1 工作原理
| 组件 | 作用 |
|---|
| rpc.exports | 暴露函数给 Python 调用 |
| Python 端 | 创建 session,加载脚本,调用 exports |
| 数据传递 | 自动序列化:JS ↔ Python |
2.2 支持的数据类型
| JavaScript 类型 | Python 类型 | 说明 |
|---|
| Number | int/float | 自动转换 |
| String | str | UTF-8 编码 |
| Boolean | bool | 直接映射 |
| Array | list | 数组转换 |
| Object | dict | 对象转字典 |
| Uint8Array | bytes | 二进制数据 |
| NativePointer | int | 内存地址 |
三、快速入门
3.1 第一步:编写 Frida 脚本
// rpc_agent.js// 通过 rpc.exports 导出函数rpc.exports= {// 简单函数add: function(a, b) {returna+b; },// 获取进程信息getInfo: function() {return {pid: Process.id,name: Process.enumerateModules()[0].name }; },// 调用 Java 层函数encrypt: function(plaintext) {varresult=null;Java.perform(function() {varCryptoUtils=Java.use("com.example.app.SecurityUtils");result=CryptoUtils.encrypt(plaintext); });returnresult; },// 异步函数decryptAsync: asyncfunction(ciphertext) {returnnewPromise(function(resolve) {Java.perform(function() {varresult=Java.use("com.example.app.SecurityUtils") .decrypt(ciphertext);resolve(result); }); }); }};3. 第二步:Python 调用
# rpc_client.pyimportfridaimportsys# 附加到目标 Appsession = frida.attach("com.example.app")# 加载 RPC 脚本script = session.create_script(open("rpc_agent.js").read())script.load()# 获取导出对象rpc = script.exports# 调用 RPC 函数print("1 + 2 =", rpc.add(1, 2))print("进程信息:", rpc.getInfo())print("加密结果:", rpc.encrypt("hello"))print("解密结果:", rpc.decryptAsync("xxx"))session.detach()3.3 运行
python rpc_client.py
四、实战:自动调用加解密
场景
假设有一个 App,内部有复杂的加密算法。我们想批量调用这个加密函数,不需要理解算法细节。
4.1 Frida 脚本
// crypto_rpc.jsrpc.exports= {// 加密encrypt: function(plaintext) {varresult=null;Java.perform(function() {varAesUtil=Java.use("com.example.app.utils.AesUtil");result=AesUtil.encrypt(plaintext); });returnresult; },// 解密decrypt: function(ciphertext) {varresult=null;Java.perform(function() {varAesUtil=Java.use("com.example.app.utils.AesUtil");result=AesUtil.decrypt(ciphertext); });returnresult; },// 生成签名sign: function(data, key) {varresult=null;Java.perform(function() {varSignUtil=Java.use("com.example.app.utils.SignUtil");result=SignUtil.sign(data, key); });returnresult; }};4.2 Python 批量调用脚本
# batch_encrypt.pyimportfridadefmain():# 连接目标 Appsession = frida.attach("com.example.app")# 加载脚本script = session.create_script(open("crypto_rpc.js").read())script.load()rpc = script.exports# 批量加密测试test_data = ["hello world","123456","test@email.com","订单号: 12345", ]print("========== 批量加密测试 ==========")forplaintextintest_data:try:encrypted = rpc.encrypt(plaintext)print(f"[+] {plaintext} -> {encrypted}")exceptExceptionase:print(f"[!] {plaintext} 失败: {e}")print("\n========== 批量解密测试 ==========")# 假设我们知道一个密文cipher = "U29tZXRoaW5nIHRoYXQgbmVlZCB0byBiZSBkZWNyeXB0ZWQ="try:decrypted = rpc.decrypt(cipher)print(f"[+] {cipher} -> {decrypted}")exceptExceptionase:print(f"[!] 解密失败: {e}")session.detach()if__name__ == "__main__":main()4.3 运行效果
========== 批量加密测试 ==========[+] hello world -> x7q2K9pLmN8...[+] 123456 -> a1b2c3d4e5f6...[+] test@email.com -> ZXN0cnlAbWFpbC5jb20...[+] 订单号: 12345 -> S2lYaWxsT3JkZXI...========== 批量解密测试 ==========[+] U29tZXRoaW5nIHRoYXQgbmVlZCB0byBiZSBkZWNyeXB0ZWQ -> Something that needs to be decrypted
五、高级:处理复杂参数
5.1 传递 Java 对象
// 处理 Java 对象参数rpc.exports= {login: function(username, password) {varresult=null;Java.perform(function() {varLoginRequest=Java.use("com.example.app.model.LoginRequest");varrequest=LoginRequest.$new();request.setUsername(username);request.setPassword(password);varApiClient=Java.use("com.example.app.network.ApiClient");result=ApiClient.login(request); });returnresult; }};5.2 处理二进制数据
// 处理 bytes[] 参数rpc.exports= {hash: function(data_hex) {varresult=null;Java.perform(function() {// 十六进制字符串转字节数组varbytes=Java.use("java.util.Hex") .decode(data_hex);varMessageDigest=Java.use("java.security.MessageDigest");varmd=MessageDigest.getInstance("SHA-256");varhash=md.digest(bytes);result=Java.use("java.util.Hex") .encodeToString(hash); });returnresult; }};5.3 返回二进制数据
// 返回 bytes[] 给 Pythonrpc.exports= {getCert: function() {varcert=null;Java.perform(function() {varcertObj=Java.use("java.security.cert.X509Certificate") .$new(/** 参数 **/);cert=certObj.getEncoded(); });// 返回 bytes (Uint8Array -> Python bytes)returncert; }};
六、完整案例:自动化签名生成
场景
App 的签名算法很复杂,但我们只需要批量生成签名用于测试。
6.1 Frida 脚本
// sign_rpc.jsrpc.exports= {// 获取签名getSign: function(params) {varresult=null;Java.perform(function() {varSignHelper=Java.use("com.example.app.SecurityHelper");// params 是 dict,转为 Java MapvarMap=Java.use("java.util.HashMap");varmap=Map.$new();for (varkeyinparams) {map.put(key, params[key]); }result=SignHelper.getSign(map); });returnresult; },// 验证签名verifySign: function(params, sign) {varresult=false;Java.perform(function() {varMap=Java.use("java.util.HashMap");varmap=Map.$new();for (varkeyinparams) {map.put(key, params[key]); }varSignHelper=Java.use("com.example.app.SecurityHelper");result=SignHelper.verifySign(map, sign); });returnresult; }};6.2 Python 调用
# auto_sign.pyimportfridaimportjsonsession = frida.attach("com.example.app")script = session.create_script(open("sign_rpc.js").read())script.load()rpc = script.exports# 模拟批量请求requests = [ {"appId": "app1", "timestamp": "1700000001", "nonce": "abc"}, {"appId": "app2", "timestamp": "1700000002", "nonce": "def"}, {"appId": "app3", "timestamp": "1700000003", "nonce": "ghi"},]print("========== 批量生成签名 ==========")forreqinrequests:sign = rpc.getSign(req)print(f"参数: {json.dumps(req)}")print(f"签名: {sign}\n")
七、常见问题汇总
| 问题 | 原因 | 解决方案 |
|---|
rpc is not defined | 未加载脚本 | 确保 script.load() 已执行 |
| 参数类型错误 | Java 对象需要构造 | 使用 $new() 创建实例 |
| 返回 null | 函数执行失败 | 添加 try-catch 打印错误 |
| 异步返回 | Promise 未解决 | 使用 async/await 或回调 |
| 内存地址转换 | Pointer 需要处理 | 使用 parseInt(ptr) 转整数 |
八、总结
RPC 核心流程:
1️⃣ 编写 rpc.exports ──▶ 暴露内部函数2️⃣ Python 加载脚本 ──▶ session.create_script()3️⃣ 调用 exports ──────▶ script.exports.函数名()4️⃣ 获取返回值 ───────▶ 自动反序列化为 Python 类型
使用场景:
🔐 自动化加解密调用
✍️ 批量签名生成
🧪 自动化安全测试
🔄 批量接口测试
📊 数据批量采集
🔗 关联阅读:《Frida 环境搭建》《Java 层 Hook 基础》《登录 & 加密实战》💬 有问题? 评论区聊聊!
本文仅供学习与安全研究使用,请勿用于非法目的。