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
 13- 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
 }
 })
 }
- 写主动调用脚本 - 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- 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 - 1 
 2
 3
 4- rpc.exports = { 
 fridamethod01:fridamethod01,
 fridamethod02:fridamethod02,
 }
- 暴露接口到公网 - 详见内网穿透 
- 优化 - 由于 - fridamethod02方法中需要使用到- Java.choose(), 这个操作非常耗时,我们为了节省时间将这部分代码抽出,但是这种操作非常危险,万一获取不到 handle 会造成严重后果,由于 MainActivity 一定存在就这样干了,但是其他类也许可以也许不可以.- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11- 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 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11- 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 - 1 
 2
 3
 4
 5
 6
 7
 8
 9- 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也是个指针,在实际情况中不建议这么做,构造代码初次骨头大佬- 1 
 2
 3- var 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
 54- 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,
 }- 测试详见测试 
- 暴露到公网 - 详见内网穿透 
<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 测试需要 :
至于传参使用
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
