Android逆向学习笔记——使用Python库调用Frida

1、为什么使用frida的Python库

1、之前学习的frida更多是用于手工调试阶段,如果要用代码自动化处理,还需要其他语言接入,比如Python

2、 后续学习的frida算法转发方案和frida的rpc也需要用到Python,算法转发和rpc能给逆向带来无比便捷的体验

3、frida可以实时与python进行数据交互,可以把数据发送给python,等待python处理完后,接收返回值,frida再接着往下执行代码。Python提供的各种库,让代码编写更为简单

2、API学习

2.1、包名附加

import frida, sys
jsCode = """ ...... """
process = frida.get_usb_device().attach('进程名')#老版本frida用包名,新版本frida用进程名
script = process.create_script(jsCode)
script.load()
sys.stdin.read()

我这里以嘟嘟牛在线为例

# -*- coding: UTF-8 -*-
import frida,sys
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);
            console.log(retval);
            return retval;
        }
    });
"""
process = frida.get_usb_device().attach('嘟嘟牛在线')
script=process.create_script(jscode);
script.load();
sys.stdin.read();

在pycharm的控制台中已经打印出数据了

2.2、PID附加

先通过ps -A|grep dodo,获取PID

由于有的应用是双进程,这个时候使用包名附加就会冲突,因此可以使用PID附加

process = frida.get_usb_device().attach(11518)
script=process.create_script(jscode);
script.load();
sys.stdin.read();

很简单,只需要把attach里的包名/应用名改为PID进行了

2.3、spawn方式附加

# -*- coding: UTF-8 -*-
import frida,sys
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);
            console.log(retval);
            return retval;
        }
    });
"""
#get_remote_device
device = frida.get_usb_device()
print("device:",device);
pid = device.spawn(["com.dodonew.online"])#以挂起的方式创建进程
print("pid:",pid);
process = device.attach(pid);
print("process:",process);
script = process.create_script(jscode)
script.load()
device.resume(pid)#加载完脚本,恢复进程运行
sys.stdin.read()

先创建device,然后通过device.spawn挂起进程,通过device.attach(pid)附加到进程,加载脚本后通过resume恢复进程运行

2.4、连接非标准端口

先查看手机的ip

然后frida-server用指定端口的方法启动

process = frida.get_device_manager().add_remote_device('192.168.0.104:8888').attach('嘟嘟牛在线');
script = process.create_script(jscode);
script.load()
print("开始hook")
sys.stdin.read()

2.5、frida与Python的交互(send)

我们用frida去做hook的时候,js代码中通过console.log打印出想要的值,但是这个值无法交给python继续使用,因此需要用到send函数来将值传递给python中,具体操作如下:

# -*- coding: UTF-8 -*-
import frida,sys
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);
            return retval;
        }
    });
"""
def messageFunc(message, data):
    print(message)
    if message["type"] == 'send':
        print(u"[*] {0}".format(message['payload']))
    else:
        print(message)

process = frida.get_usb_device().attach('嘟嘟牛在线')
script = process.create_script(jscode)
script.load()
script.on('message', messageFunc)
print("开始hook")
sys.stdin.read()

我们通过script.on(‘message’, messageFunc)接受js里发来的消息,类似监听,然后messageFunc是回调函数,对message进行处理

我们打印出message,格式是key+value,通过判断key为send,打印出payload

2.6、frida与Python的交互(recv)

# -*- coding: UTF-8 -*-
import time

import frida,sys
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);
            recv(
            function(obj){
                console.log(obj.data)
                retval=obj.data
            }
            ).wait();
            return retval;
        }
    });
"""
def messageFunc(message, data):
    print(message)
    if message["type"] == 'send':
        print(u"[*] {0}".format(message['payload']))
    else:
        print(message)

process = frida.get_usb_device().attach('嘟嘟牛在线')
script = process.create_script(jscode)
script.load()
script.on('message', messageFunc)
time.sleep(10)
script.post({'data': '12345678123456781234567812345678'})
print("开始hook")
sys.stdin.read()

我们在接收到message时,通过messageFunc处理,打印出了现在的md5加密后的结果,然后通过time.sleep(10)暂停10秒后再发送数据给js,发送的值为12345678123456781234567812345678。js中通过recv函数接受python发来的数据并作处理。

recv(
           function(obj){
               console.log(obj.data)
               retval=obj.data
           }
           ).wait();

recv的参数是一个回调函数,里面先打印了传来的数据,然后又把这个数据赋值给retval

2.7、frida的rpc

# -*- coding: UTF-8 -*-
import time

import frida,sys
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);
            console.log('retval: ', retval);
            return retval;
        }
    });
   
  function test(data) {
        return new Promise(function(resolve, reject) {
             Java.perform(function() {   
                    var result = Java.use('com.dodonew.online.util.Utils').md5(data);
                    console.log('result1: ', result);
                    resolve(result);
                 });
         }).then(function(result) {
                console.log('result2: ', result);
                return result;
  });
}

    
     rpc.exports = {
        rpcfunc: test
    };
    
"""
device = frida.get_usb_device()
print("device:",device);
pid = device.spawn(["com.dodonew.online"])#以挂起的方式创建进程
print("pid:",pid);
process = device.attach(pid);
print("process:",process);
script = process.create_script(jscode)
script.load()
device.resume(pid)#加载完脚本,恢复进程运行

result = script.exports_sync.rpcfunc('equtype=ANDROID&loginImei=Androidnull&timeStamp=1626790668522&userPwd=a12345678&username=15968079477&key=sdlkjsdljf0j2fsjk')
print("开始hook")
print("result3:"+result)
sys.stdin.read()

通过rpc.exports导出函数,然后通过script.exports_sync.rpcfunc调用函数。这里要注意个问题,我们在js中定义函数test时,原本的代码如下:

function test(data){
       var result = "";
       Java.perform(function(){   
           result = Java.use('com.dodonew.online.util.Utils').md5(data);
           console.log('result1: ', result);
       });
       console.log('result2: ', result);
       return result;
   }

这里有个问题,test被执行时,并不是顺序执行的, Java.perform会异步操作,因此,在 Java.perform()调用之后,代码会立即执行 console.log(‘result2: ‘, result),然后执行 return result;。这意味着 Java.use(‘com.dodonew.online.util.Utils’).md5(data)的执行尚未完成,因此 result 变量的值仍然为空字符串,而不是预期的 MD5 值。利用chatgpt对代码进行了修改后就可以正常打印了

2.8、frida算法转发

import requests, json
import frida

jsCode = """
    function hookTest(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;
    }
    rpc.exports = {
        rpcfunc: hookTest
    };
"""

# 调用frida脚本
process = frida.get_device_manager().add_remote_device('192.168.1.106:8888').attach("嘟嘟牛在线")
script = process.create_script(jsCode)
print('[*] Running Whitebird')
script.load()
cipherText = script.exports.rpcfunc('18075042220', 'a12345678')
print(cipherText)

其实就是调用app中的加密算法

2.9、将python作为服务端转发

我们上面的测试代码都是用python写的,有的时候我们可能想用其他语言去实现,但是其他语言不一定有frida的rpc库,所以我们可以把python作为中间层,将python开启一个服务,这样就可以让别的语言访问,类似于函数的封装,然后提供一个接口。

pip3 install fastapi
pip3 install uvicorn

代码如下

import requests, json
import frida
import uvicorn
import fastapi
jsCode = """
    function hookTest(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;
    }
    rpc.exports = {
        rpcfunc: hookTest
    };
"""

# 调用frida脚本
process = frida.get_device_manager().add_remote_device('192.168.1.106:8888').attach("嘟嘟牛在线")
script = process.create_script(jsCode)
print('[*] Running Whitebird')
script.load()
cipherText = script.exports.rpcfunc('18075042220', 'a12345678')
print(cipherText)


app = fastapi.FastAPI()

@app.get("/get")
async def getEchoApi(item_id, item_user, item_pass):
    result = script.exports.rpcfunc(item_user, item_pass)
    return {"item_id": item_id, "item_retval": result}

if __name__ == '__main__':
    uvicorn.run(app, port = 8080)

构造访问的url:http://127.0.0.1:8080/get?item_id=100&item_user=18075042220&item_pass=a12345678

现在我们可以用其他语言直接发起get请求,传入对应的参数就可以得到加密后的结果

我们还可以写一个post请求

from curses import A_ALTCHARSET
import requests, json
import frida
import uvicorn
import fastapi
from pydantic import BaseModel
jsCode = """
    function hookTest(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;
    }
    rpc.exports = {
        rpcfunc: hookTest
    };
"""

# 调用frida脚本
process = frida.get_device_manager().add_remote_device('192.168.1.106:8888').attach("嘟嘟牛在线")
script = process.create_script(jsCode)
print('[*] Running Whitebird')
script.load()
cipherText = script.exports.rpcfunc('18075042220', 'a12345678')
print(cipherText)


class Item(BaseModel):
    item_id: str = None
    item_user: str = None
    item_pass: str = None

app = fastapi.FastAPI()
@app.post("/post")
async def getEchoApi(postData: Item):
    result = script.exports.rpcfunc(postData.item_user, postData.item_pass)
    return {"item_id": postData.item_id, "item_retval": result}

if __name__ == '__main__':
    uvicorn.run(app, port = 8080)


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 767778848@qq.com