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 脚本

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. 写主动调用脚本
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;
}
  1. 制作 RPC
rpc.exports = {
    fridamethod01:fridamethod01,
    fridamethod02:fridamethod02,
}
  1. 暴露接口到公网

    详见内网穿透

  2. 优化

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

  1. 普通 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());
        }
    })
}
  1. 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"]))
}
  1. 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());
  1. 启动 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,
}

测试详见测试

  1. 暴露到公网

    详见内网穿透

# 测试

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

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

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

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

# 将 http server 暴露至公网

文章插图

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

# 将 frida server 暴露至公网

文章插图

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

# Tips

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

Todo