Frida RPC 学习记录
# RPC 开发环境搭建
抄代码的地方 ===> https://github.com/frida/frida-python
由于我们使用的是 frida-rpc,RPC 环境其实就是 frida 环境,只是多了一个内网穿透的搭建,这里使用的是 NPS, 具体可以详见内网穿透
# Java 层主动调用
先写 hook
再写主动调用
在拿到一个 apk 时,首先要找一下 MainActivity, grep -ril "MainActivty" * 尤其是脱壳之后的 dex, 但是这里我们的 apk 比较简单,用 jadx 静态分析一下就可以.
objection 试试
搜索
android hooking search classes MainActivityhook 打参数及调用栈
android hooking watch class com.example.demoso1.MainActivity --dump-args --dump-backtrace --dump-return写 frida 脚本
function hook(){ | |
Java.perform(function(){ | |
var MainActivity = Java.use("com.example.demoso1.MainActivity"); | |
MainActivity.method01.implementation = function(str){ | |
var s1 = "123123" | |
var result = this.method01(s1) | |
console.log("str ===> ", str); | |
console.log("s1 ===> ", s1); | |
console.log("result ===> ", result); | |
return result | |
} | |
}) | |
} |
- 写主动调用脚本
function fridamethod01(plaintext){ | |
var result; | |
Java.perform(function(){ | |
var MainActivity = Java.use("com.example.demoso1.MainActivity"); | |
var JavaString = Java.use("java.lang.String"); | |
result = MainActivity.method01(JavaString.$new(plaintext)); | |
}) | |
return result; | |
} | |
function fridamethod02(ciphertext){ | |
var result; | |
Java.perform(function(){ | |
var JavaString = Java.use("java.lang.String"); | |
Java.choose("com.example.demoso1.MainActivity",{ | |
onMatch: function(instanca){ | |
result = instanca.method02(JavaString.$new(ciphertext)); | |
},onComplete(){} | |
}) | |
}) | |
return result; | |
} |
- 制作 RPC
rpc.exports = { | |
fridamethod01:fridamethod01, | |
fridamethod02:fridamethod02, | |
} |
暴露接口到公网
详见内网穿透
优化
由于
fridamethod02方法中需要使用到Java.choose(), 这个操作非常耗时,我们为了节省时间将这部分代码抽出,但是这种操作非常危险,万一获取不到 handle 会造成严重后果,由于 MainActivity 一定存在就这样干了,但是其他类也许可以也许不可以.
var MainActivityHandle; | |
Java.perform(function(){ | |
// var JavaString = Java.use("java.lang.String"); | |
// result = MainActivity.method02(JavaString.$new(ciphertext)); | |
Java.choose("com.example.demoso1.MainActivity",{ | |
onMatch: function(instanca){ | |
MainActivityHandle = instanca; | |
// result = instanca.method02(JavaString.$new(ciphertext)); | |
},onComplete(){} | |
}) | |
}) |
# So 层主动调用
普通 hook
replace hook
env, jclass,jobject,jsring 构造
启动 rpc 服务并测试
部署到公网
- 普通 hook
function hook_method(addr){ | |
Interceptor.attach(addr, { | |
onEnter:function(args){ | |
console.log("args[0] ===> ", args[0]); | |
console.log("args[1] ===> ", args[1]); | |
console.log("args[2] ===> ", Java.vm.getEnv().getStringUtfChars(args[2], null).readCString()); | |
},onLeave:function(retvel){ | |
console.log("retvel is ===> ",Java.vm.getEnv().getStringUtfChars(retvel, null).readCString()); | |
} | |
}) | |
} |
- replace hook
function replacehook(addr){ | |
var addrfunc =new NativeFunction(addr, "pointer", ["pointer","pointer","pointer"]); | |
Interceptor.replace(addr, new NativeCallback(function(arg1, arg2, arg3){ | |
console.log("args[2] ===> ", Java.vm.getEnv().getStringUtfChars(arg3, null).readCString()); | |
var result = addrfunc(arg1, arg2, arg3); | |
console.log("retvel is ===> ",Java.vm.getEnv().getStringUtfChars(result, null).readCString()); | |
return result; | |
},"pointer", ["pointer","pointer","pointer"])) | |
} |
env, jclass,jobject,jsring 构造
由于肉丝姐在上课时的案例中没有用到
jobject和jclass, 所以只要传个指针过去就行,jstring也是个指针,在实际情况中不建议这么做,构造代码初次骨头大佬
var javaClass = Java.use("com.example.demoso1.MainActivity") | |
var JCLASS = javaClass.$getClassHandle(Java.vm.tryGetEnv()); | |
var JOBJECT = javaClass.$getClassHandle(Java.vm.tryGetEnv()); |
- 启动 rpc 服务并测试
function invokemethod01(text){ | |
console.log("ENV is ===> ", ENV) | |
// console.log("method01 is ===> ", method01addr); | |
var method01 =new NativeFunction(method01addr, "pointer", ["pointer","pointer","pointer"]); | |
var NewStringUTF = new NativeFunction(addrNewStringUTF,'pointer',['pointer','pointer']) | |
var result; | |
// var result = method01(env, jclass, jstring); | |
Java.perform(function(){ | |
var javaClass = Java.use("com.example.demoso1.MainActivity") | |
// var JSTRING =Java.vm.getEnv().newStringUtf(Java.vm.getEnv(), Memory.allocUtf8String("123123123123")) | |
var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(text)) | |
var JCLASS = javaClass.$getClassHandle(Java.vm.tryGetEnv()); | |
// var result = method01(env, jclass, jstring); | |
result = method01(Java.vm.getEnv(), JCLASS, JSTRING); | |
// console.log("result is ===> ", result) | |
// console.log("readCString result is ===> ", Java.vm.getEnv().getStringUtfChars(result, null).readCString()) | |
// console.log("Memory.readCString result is ===> ", Memory.readCString(Java.vm.getEnv().getStringUtfChars(result, null))) | |
result = Java.vm.getEnv().getStringUtfChars(result, null).readCString(); | |
console.log(result); | |
}) | |
return result | |
} | |
function invokemethod02(textenc){ | |
console.log("ENV is ===> ", ENV) | |
// console.log("method02 is ===> ", method02addr); | |
var method02 =new NativeFunction(method02addr, "pointer", ["pointer","pointer","pointer"]); | |
var NewStringUTF = new NativeFunction(addrNewStringUTF,'pointer',['pointer','pointer']) | |
var result; | |
// var result = method02(env, jobject, jstring); | |
Java.perform(function(){ | |
// var JSTRING =Java.vm.getEnv().newStringUtf(Java.vm.getEnv(), Memory.allocUtf8String("123123123123")) | |
var javaClass = Java.use("com.example.demoso1.MainActivity") | |
var JSTRING = NewStringUTF(Java.vm.getEnv(),Memory.allocUtf8String(textenc)) | |
var JOBJECT = javaClass.$getClassHandle(Java.vm.tryGetEnv()); | |
// var result = method02(env, jobject, jstring); | |
result = method02(Java.vm.getEnv(), JOBJECT, JSTRING); | |
// console.log("result is ===> ", result) | |
// console.log("readCString result is ===> ", Java.vm.getEnv().getStringUtfChars(result, null).readCString()) | |
// console.log("Memory.readCString result is ===> ", Memory.readCString(Java.vm.getEnv().getStringUtfChars(result, null))) | |
result = Java.vm.getEnv().getStringUtfChars(result, null).readCString(); | |
}) | |
return result; | |
} | |
// RPC invoke SO step 4: | |
// make exports | |
rpc.exports = { | |
invoke1:invokemethod01, | |
invoke2:invokemethod02, | |
} |
测试详见测试
暴露到公网
详见内网穿透
# 测试
curl/postman
sige
# 连通性测试
# curl
在开启服务之后简单测试接口是否通,可以用 curl, 更多操作可以参考:https://curl.se/docs/manpage.html
笔者只会一条命令还是刚学会:
curl -X POST -H "Content-Type: application/json" -d '{"data": "zrail"}' http://127.0.0.1:5000/encrypt | |
curl -X POST -H "Content-Type: application/json" -d '{"data": "4a61203f8a1b640818230269bd1059ea"}' http://127.0.0.1:5000/decrypt |
# postman
由于开发在虚拟机,postman 测试需要 :
app.run(host="0.0.0.0", port=50000, threaded=True, debug=False) |
至于传参使用
formdata还是raw, 这就要看服务端怎么写了
import json | |
from flask import Flask, request | |
app = Flask(__name__) | |
@app.route('/encrypt', methods=['POST'])#url 加密 | |
def encrypt_class(): | |
data = request.get_data() | |
json_data = json.loads(data.decode("utf-8")) | |
postdata = json_data.get("data") | |
# print("data ===> ",postdata) | |
res = script.exports.invoke1(postdata) | |
# print("res ===> ", res) | |
return res | |
@app.route('/decrypt', methods=['POST'])#data 解密 | |
def decrypt_class(): | |
data = request.get_data() | |
json_data = json.loads(data.decode("utf-8")) | |
postdata = json_data.get("data") | |
res = script.exports.invoke2(postdata) | |
return res | |
if __name__ == '__main__': | |
# app.run(host="0.0.0.0", port=50000, threaded=True, debug=False) | |
app.run() |
# 压力测试
# siege
其他系统可能需要编译,但是 kali 自带,直接安装
具体操作参考: https://www.cnblogs.com/chenxiaomeng/p/13130526.html
siege 默认只支持 255 个并发数,可以自己自定义,修改 /root/.siege/siege.conf 下的 limit 数值。
命令:
siege -c200 -r10 "http://127.0.0.1:5000/decrypt POST <./textenc.json" | |
siege -c200 -r10 "http://127.0.0.1:5000/decrypt POST <./text.json" |
# text.json | |
{ | |
"data":"123123123123123123" | |
} |
# textenc.json | |
{ | |
"data":"53c6a31a21192365943a4f672fc97c7d" | |
} |
据说 Jmeter 也挺好用,没试过
# 内网穿透
将接口暴露至公网需要用到内网穿透工具,这里使用 NPS.github 地址:https://github.com/ehang-io/nps
# Tips
安装比较简单,文档也够详细,有手就行 (😿我手呢?😿). 这里记录几个坑点
- 端口一定要开放,我就是一开始端口开少了.
- 服务器开完端口之后需要等一会儿,这个时间长短不确定.
# 主动调用,内网穿透的两种方式
# 将 http server 暴露至公网

如果是此种方式,NPC 就是 linux 或者 windows 的版本
# 将 frida server 暴露至公网

此种方式 NPC 为 arm64 (手机). 模拟器应该是 x86 (没试过)
# Tips
- 暴露 frida server 比暴露 http server 快,但是也没快多少
- 感觉手机和网络是瓶颈,选什么框架并无关系
Todo