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 试试

  1. 搜索
    android hooking search classes MainActivity

  2. hook 打参数及调用栈

    android hooking watch class com.example.demoso1.MainActivity --dump-args --dump-backtrace --dump-return

  3. 写 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
    }
    })
    }

  4. 写主动调用脚本

    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;
    }

  5. 制作 RPC

    1
    2
    3
    4
    rpc.exports = {
    fridamethod01:fridamethod01,
    fridamethod02:fridamethod02,
    }

  6. 暴露接口到公网

    详见内网穿透

  7. 优化

    由于 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 服务并测试
部署到公网

  1. 普通 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());
    }
    })
    }

  2. 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"]))
    }

  3. env, jclass,jobject,jsring 构造

    由于肉丝姐在上课时的案例中没有用到 jobjectjclass , 所以只要传个指针过去就行, 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());

  4. 启动 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,
    }

    测试详见测试

  5. 暴露到公网

    详见内网穿透

<h2 id="测试"> 测试 </h2>

curl/postman

sige

# 连通性测试

# curl

在开启服务之后简单测试接口是否通,可以用 curl, 更多操作可以参考:https://curl.se/docs/manpage.html

笔者只会一条命令还是刚学会:

1
2
3
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 测试需要 :

1
app.run(host="0.0.0.0", port=50000, threaded=True, debug=False)

至于传参使用 formdata 还是 raw , 这就要看服务端怎么写了

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
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 数值。

命令:

1
2
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"

1
2
3
4
# text.json
{
"data":"123123123123123123"
}

1
2
3
4
# textenc.json
{
"data":"53c6a31a21192365943a4f672fc97c7d"
}

据说 Jmeter 也挺好用,没试过

<h2 id="内网穿透"> 内网穿透 </h2>

将接口暴露至公网需要用到内网穿透工具,这里使用 NPS.github 地址:https://github.com/ehang-io/nps

# Tips

安装比较简单,文档也够详细,有手就行 (😿我手呢?😿). 这里记录几个坑点

  1. 端口一定要开放,我就是一开始端口开少了.
  2. 服务器开完端口之后需要等一会儿,这个时间长短不确定.

# 主动调用,内网穿透的两种方式

# 将 http server 暴露至公网

如果是此种方式,NPC 就是 linux 或者 windows 的版本

# 将 frida server 暴露至公网

此种方式 NPC 为 arm64 (手机). 模拟器应该是 x86 (没试过)

# Tips

  1. 暴露 frida server 比暴露 http server 快,但是也没快多少
  2. 感觉手机和网络是瓶颈,选什么框架并无关系

Todo

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

折花载酒z 微信支付

微信支付

折花载酒z 支付宝

支付宝

折花载酒z 贝宝

贝宝