FRIDA RPC

简介

  • 无需做具体的算法还原,利用App来当成加密机器,传入对应的参数返回加密结果
  • 适用于加密无法还原或者不好还原的时候采用此方法
  • 跟Python爬虫结合来用的

基本姿势

  • 获取设备:Device(id="FA6970303786", name="Pixel", type='usb')

    • 获取USB设备:frida.get_usb_device() 参数可以设置超时时间 一般可以不用给参数
    • 获取远程设备:frida.get_remote_device()
  • 注入:Session(pid=应用PID)

    • 包名注入:frida.get_usb_device().attach("包名")
    • PID注入:frida.get_usb_device().attach(PID) 双进程应用就用PID注入
  • Spwan:PID =frida.get_usb_device().spwan(["包名"]) 以挂起的方式创建进程 返回应用PID
  • FRIDA与Python交互

    • send:在JS代码中发送代码给Python,与console的区别在于,console仅仅只是输出,send可以把代码发送给Python再次处理
    • recv:在JS代码里阻塞接收Python发来的数据 recv(function(obj){csole.log(obj.data)}).wait();
    • script.post():script.post({"data": "value"})Python代码里发送数据到JS配合recv使用
  • Rpc:主动调用 传入对应参数获取返回值
  • rpc.exports = {rpcfunc:xx}开放让外部访问的接口
  • script.exports.rpcfunc() Python调用
  • 阻塞控制台防止程序结束:

    • input("已开启服务监听 输入任意数字回车退出程序 >> ")
    • sys.stdin.read()

PS:在py文件里修改JS代码需要重新注入,python里没有自动注入功能

注入

# -*- coding: UTF-8 -*-
import frida, sys

with codecs.open('./hook.js', 'r', 'utf-8') as f:
    source = f.read()

process = frida.get_usb_device().attach('com.dodonew.online')  # 包名注入
process = frida.get_usb_device().attach(9999)  # PID注入
script = process.create_script(source )
script.load()
print("开始运行")
sys.stdin.read()

Spwan

# -*- coding: UTF-8 -*-
import frida, sys

with codecs.open('./hook.js', 'r', 'utf-8') as f:
    source = f.read()

device = frida.get_usb_device()
print("device: ", device)
pid = device.spawn(["com.dodonew.online"])    # 以挂起方式创建进程 得到应用PID
print("pid: ", pid)
process = device.attach(pid)  # 注入进程
print("process: ", process)
script = process.create_script(source )  # 创建脚本
script.load()  # 加载脚本
device.resume(pid)  # 加载完脚本, 恢复进程运行
print("开始运行")
sys.stdin.read()

非标准端口及多设备连接

  • 非标准端口

    # -*- coding: UTF-8 -*-
    import frida, sys
    with codecs.open('./hook.js', 'r', 'utf-8') as f:
        source = f.read()
    
    process = frida.get_device_manager().add_remote_device('192.168.x.xx:8888').attach('包名')
    script = process.create_script(source)
    script.load()
    print("开始运行")
    sys.stdin.read()
  • 多设备

    # -*- coding: UTF-8 -*-
    import frida, sys
    with codecs.open('./hook.js', 'r', 'utf-8') as f:
        source = f.read()
    
    process1 = frida.get_device_manager().add_remote_device('192.168.x.xx1:8888').attach('包名')
    process2 = frida.get_device_manager().add_remote_device('192.168.x.xx2:8888').attach('包名')
    script1 = process.create_script(source)
    script2 = process.create_script(source)
    script1.load()
    script2.load()
    print("开始运行")
    sys.stdin.read()

send 打印输出

  • send是在python层定义的on_message回调函数,jscode内所有的信息都被监控script.on('message', on_message),当输出信息的时候on_message函数会拿到其数据再通过format转换, 其最重要的功能也是最核心的是能够直接将数据以json格式输出,当然数据是二进制的时候也依然是可以使用send,十分方便,我们来看代码示例以及执行效果。

    • 可以看出这里两种方式输出的不同的效果,console直接输出了[object Object],无法输出其正常的内容,因为jni_env实际上是一个对象,但是使用send的时候会自动将对象转json格式输出。通过对比,我们就知道send的好处啦~
    • console的区别在于,console仅仅只是输出,send可以把代码发送给Python再次处理
    # -*- coding: utf-8 -*-
    import frida
    import sys
    
    def on_message(message, data):
        if message['type'] == 'send':
            print("[*] {0}".format(message['payload']))
        else:
            print(message)
    
    jscode = """
        Java.perform(function () 
        {
            var jni_env = Java.vm.getEnv();
            console.log(jni_env);
            send(jni_env);
        });
     """
    
    process = frida.get_usb_device().attach('com.roysue.roysueapplication')
    script = process.create_script(jscode)
    script.on('message', on_message)  # 注册事件 参数1:message固定值
    script.load()
    sys.stdin.read()
    
    运行脚本效果如下:
    
    roysue@ubuntu:~/Desktop/Chap09$ python Chap03.py 
    [object Object]
    [*] {'handle': '0xdf4f8000', 'vm': {}}

recv

  • 等待Python返回结果:recv(function(obj){csole.log(obj.data)}).wait();
  • Python传递数据给 JS:script.post({'data': 100}
# -*- coding: UTF-8 -*-
import frida, sys
import time

jsCode = """
    Java.perform(function(){
        var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil');
        RequestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c){
            console.log('data: ', a);
            console.log('desKey: ', b);
            console.log('desIV: ', c);
            var retval = this.encodeDesMap(a, b, c);
            console.log('retval: ', retval);
            return retval;
        }
        var Utils = Java.use('com.dodonew.online.util.Utils');
        Utils.md5.implementation = function(a){
            console.log('MD5 string: ', a);
            var retval = this.md5(a);
            send(retval);  // 发送数据到Python
            recv(function(obj){  // 接收返回数据
                console.log(JSON.stringify(obj));
                console.log("Python:", obj.data);
                retval = obj.data;
            }).wait();
            return retval;
        }
    });
"""

def messageFunc(message, data):
   print(message)
   if message["type"] == 'send':
       print(u"[*] {0}".format(message['payload']))
       time.sleep(10)
       script.post({"data": "0e8315152843b943563031945032e957"})  # python 传递数据到js
   else:
       print(message)

process = frida.get_usb_device().attach('com.dodonew.online')
script = process.create_script(jsCode)
script.on('message', messageFunc)
script.load()
print("开始运行")
sys.stdin.read()

Rpc 调用

  • 主动调用 传入对应参数获取返回值

    • rpc.exports = {rpcfunc:xx} 在JS代码里需要开放让外部访问的接口
    • script.exports.rpcfunc() Python调用

PS:如果导出函数为 rpcFunc 那么调用就要加下划线 script.exports.rpc_func

# -*- coding: UTF-8 -*-
import codecs, frida
jscode = """
    function call_dodonew(data) {
        var retval = ""
        Java.perform(function () {
            // 主动调用
            retval = Java.use('com.dodonew.online.util.Utils').md5(data);
        })
        return retval
    }
    
    // 接口导出
    rpc.exports = {
        call: call_dodonew  // 导出名(可自定义):导出方法
    }
"""
device = frida.get_usb_device()
PID = device.spawn(["com.dodonew.online"])
process = device.attach(PID)
script = process.create_script(jscode)
script.load()
device.resume(PID)
# 传入参数 调用接口
result = script.exports.call("equtype=ANDROID&loginImei=Androidnull&timeStamp=1626790668522&userPwd=a12345678&username=15968079477&key=sdlkjsdljf0j2fsjk")
print("[+] result :=> ", result)
input("已开启服务监听 输入任意数字回车退出程序 >> ")

Python Rpc Spider

  • Python 爬虫RPC用法
import requests, json
import frida

jsCode = """
    function getParams(username, passward){
        var result;
        Java.perform(function(){

            var time = new Date().getTime();
            var signData = 'equtype=ANDROID&loginImei=Android352689082129358&timeStamp=' + 
            time + '&userPwd=' + passward + '&username=' + username + '&key=sdlkjsdljf0j2fsjk';
            var Utils = Java.use('com.dodonew.online.util.Utils');
            var sign = Utils.md5(signData).toUpperCase();
            // console.log('sign: ', sign);

            var encryptData = '{"equtype":"ANDROID","loginImei":"Android352689082129358","sign":"'+ 
            sign +'","timeStamp":"'+ time +'","userPwd":"' + passward + '","username":"' + username + '"}';
            var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil');
            var Encrypt = RequestUtil.encodeDesMap(encryptData, '65102933', '32028092');
            // console.log('Encrypt: ', Encrypt);
            result = Encrypt;

        });
        return result;
    }
    
    function deCipher(CipherText) {
        // 响应解密
        var retval = ""
        Java.perform(function () {
            // 主动调用
            var desKey = "65102933"
            var desIv = "32028092"
            retval = Java.use('com.dodonew.online.http.RequestUtil').decodeDesJson(CipherText, desKey, desIv)
        })
        return retval
    }
    
    rpc.exports = {
        getparams: getParams,
        decipher: deCipher
    };
"""

process = frida.get_usb_device().attach("com.dodonew.online")
script = process.create_script(jsCode)
print('[*] Running ...')
script.load()

# Rpc getParams
cipherText = script.exports.getParams('16688889999', 'a12345678')
url = 'http://api.dodovip.com/api/user/login'
data = json.dumps({"Encrypt": cipherText})
r = requests.post(url=url, data=data, headers={
    "content-type": "application/json; charset=utf-8",
    "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 10; Pixel Build/QP1A.191005.007.A3)"
})
print("[*] 请求参数 :=> " + data)
print("[*] 响应 :=> " + r.text)
print("[*] 响应解密 :=> " + script.exports.deCipher(r.text))

输出结果如下:
[*] Running ...
[*] 请求参数 :=> {"Encrypt": "NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii02ggCULFNaejvFwr3Wqwkal\nNRmYlAjU4jhnxbz9qIVFiYpm44XB2z9x8NGrTBjlh5Qgamn8ersP7XCKbLrLJuo5/eUS0yYua7Az\nIN51JHzz6atnjN6SfD9T5QHa14rrvyzd3EL9qll5kjLIrtpnM1NCkjM5dVFrKW8gmECUIV+37R7+\n7l3q0oA=\n"}
[*] 响应 :=> 2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0=
[*] 响应解密 :=> {"code":-1,"message":"账号或密码错误","data":{}}

RCP FASTAPI 本地服务部署

  • 安装库

    • pip install fastapi
    • pip install uvicorn
import codecs
import frida
from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn

jsCode = """
        function getParams(username, passward) {
        var result;
        Java.perform(function () {
    
            var time = new Date().getTime();
            var signData = 'equtype=ANDROID&loginImei=Android352689082129358&timeStamp=' +
                time + '&userPwd=' + passward + '&username=' + username + '&key=sdlkjsdljf0j2fsjk';
            var Utils = Java.use('com.dodonew.online.util.Utils');
            var sign = Utils.md5(signData).toUpperCase();
            // console.log('sign: ', sign);
    
            var encryptData = '{"equtype":"ANDROID","loginImei":"Android352689082129358","sign":"' +
                sign + '","timeStamp":"' + time + '","userPwd":"' + passward + '","username":"' + username + '"}';
            var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil');
            var Encrypt = RequestUtil.encodeDesMap(encryptData, '65102933', '32028092');
            // console.log('Encrypt: ', Encrypt);
            result = Encrypt;
    
        });
        return result;
    }
    
    function deCipher(CipherText) {
        // 响应解密
        var retval = ""
        Java.perform(function () {
            // 主动调用
            var desKey = "65102933"
            var desIv = "32028092"
            retval = Java.use('com.dodonew.online.http.RequestUtil').decodeDesJson(CipherText, desKey, desIv)
        })
        return retval
    }
    
    rpc.exports = {
        getparams: getParams,
        decipher: deCipher
    };
"""

process = frida.get_usb_device().attach("com.dodonew.online")
script = process.create_script(jsCode)
script.load()
app = FastAPI()  # 初始化

@app.get("/getParams")  # get请求触发
async def getEchoApi(user, pwd):
    # http://127.0.0.1:8080/getParams?user=16688889999&pwd=a12345678
    result = script.exports.getParams(user, pwd)
    return {"Encrypt": result}

# 声明参数模型
class Item(BaseModel):
    data: str

@app.post("/deCipher")
async def getEchoApi(item: Item):
    result = script.exports.decipher(item.data)
    return result

if __name__ == '__main__':
    uvicorn.run(app, port=8080)  # 本地监听端口

Fiddler数据转发

  • 什么是fiddler数据转发

    • 使用fiddler拦截数据包,把数据包发送到指定服务器
    • 优点:更加万能,不需要逆向,只需抓包找到相应数据链接即可
    • 缺点:这是个半自动的爬取数据的方案
  • 什么是fiddler script

    • 想要实现fiddler数据转发,就需要使用fiddler script,这是fiddler功能的扩展,用代码去控制fiddler

本文链接:

https://www.linqi.net.cn/index.php/archives/293/