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 MainActivity
hook 打参数及调用栈
android hooking watch class com.example.demoso1.MainActivity --dump-args --dump-backtrace --dump-return
写 frida 脚本
1
2
3
4
5
6
7
8
9
10
11
12
13function 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
}
})
}写主动调用脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function 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
1
2
3
4rpc.exports = {
fridamethod01:fridamethod01,
fridamethod02:fridamethod02,
}暴露接口到公网
详见内网穿透
优化
由于
fridamethod02
方法中需要使用到Java.choose()
, 这个操作非常耗时,我们为了节省时间将这部分代码抽出,但是这种操作非常危险,万一获取不到 handle 会造成严重后果,由于 MainActivity 一定存在就这样干了,但是其他类也许可以也许不可以.1
2
3
4
5
6
7
8
9
10
11var 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
1
2
3
4
5
6
7
8
9
10
11function 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
1
2
3
4
5
6
7
8
9function 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
也是个指针,在实际情况中不建议这么做,构造代码初次骨头大佬1
2
3var javaClass = Java.use("com.example.demoso1.MainActivity")
var JCLASS = javaClass.$getClassHandle(Java.vm.tryGetEnv());
var JOBJECT = javaClass.$getClassHandle(Java.vm.tryGetEnv());启动 rpc 服务并测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54function 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,
}测试详见测试
暴露到公网
详见内网穿透
<h2 id="测试"> 测试 </h2>
curl/postman
sige
# 连通性测试
# curl
在开启服务之后简单测试接口是否通,可以用 curl, 更多操作可以参考:https://curl.se/docs/manpage.html
笔者只会一条命令还是刚学会:
1 | curl -X POST -H "Content-Type: application/json" -d '{"data": "zrail"}' http://127.0.0.1:5000/encrypt |
# postman
由于开发在虚拟机,postman 测试需要 :
1 app.run(host="0.0.0.0", port=50000, threaded=True, debug=False)
至于传参使用
formdata
还是raw
, 这就要看服务端怎么写了
1 | import json |
# 压力测试
# siege
其他系统可能需要编译,但是 kali 自带,直接安装
具体操作参考: https://www.cnblogs.com/chenxiaomeng/p/13130526.html
siege 默认只支持 255 个并发数,可以自己自定义,修改 /root/.siege/siege.conf 下的 limit 数值。
命令:
1 | siege -c200 -r10 "http://127.0.0.1:5000/decrypt POST <./textenc.json" |
1 | # text.json |
1 | # textenc.json |
据说 Jmeter 也挺好用,没试过
<h2 id="内网穿透"> 内网穿透 </h2>
将接口暴露至公网需要用到内网穿透工具,这里使用 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