算法转发和unidbg

利用python库实现frida

  1. 附加方式实现frida

    #-*- 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: ', retval);
                return retval;
            }
        });
    """
    process = frida.get_usb_device().attach('com.dodonew.online')
    script = process.create_script(jsCode)
    script.load()
    sys.stdin.read()
    
  2. 自启动实现frida

    #-*- 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: ', retval);
                return retval;
            }
        });
    """
    device = frida.get_usb_device()
    print(device)
    pid = device.spawn(["com.dodonew.online"])
    process =  device.attach(pid)
    script = process.create_script(jsCode)
    script.load()
    device.resume(pid)
    sys.stdin.read()
    
  3. 非标准端口连接

    process=frida.get_device_manager().add_remote_device("192.168.68.234:8888").attach('com.dodonew.online')
    #process1=frida.get_device_manager().add_remote_device("192.168.68.234:8888").attach('com.dodonew.online')
    script = process.create_script(jsCode)
    #script1 = process.create_script(jsCode1)
    script.load()
    #script1.load()
    sys.stdin.read()
    #这里使用的端口是 ./data/local/tmp/fsarm64 -l 0.0.0.0:8888  本机的8888端口,所以要add_remote_device、
    #这样的结果就是可以使用多个脚本来连接多个设备,实现自动化
    

利用frida实现JS和python的数据交互

  1. send()利用这个函数,可以将JS的数据传入python进行交互(send这个函数的功能是通过开启事件来触发的,所以需要事件处理函数来执行

            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端发送数据   
                console.log('retval: ', retval);
                return retval;
                }
            //这里利用js的fridaHook去输出了这个函数的结果
    ---------------------------------------------------------------------------------------
    以上是JS,以下是python
            def func(message, data):
                if message["type"] == 'send':
                    print(u"[*] {0}".format(message['payload']))
                else:
                    print(message)
    
    
            process = frida.get_usb_device().attach('com.dodonew.online')
            script = process.create_script(jsCode)
            script.on('message', func)//这里去处理事件
            script.load()
            sys.stdin.read()
    
  2. recv()可以通过python来实现修改JS中数据,从而实现利用python库来修改数据,实现数据转发

            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);   //这里同样去发送数据
                console.log('retval: ', retval);
                recv(function(obj){
                    console.log(obj.num)
                    retval = obj.num
                }).wait()   //这里去处理数据,并且.wait()等待数据的处理
                return retval;
            }
    js 
    --------------------------------------------------------------------------------------------
    python
    
        def func(message, data):
            if message["type"] == 'send':
                print(u"[*] {0}".format(message['payload']))
                script.post({'num':"fcdb3349a492dcb9e480962138c57a84"})  //这里是往recv()里面去传处理之后的数据,实现数据python转JS
            else:
                print(message)
    
        process = frida.get_usb_device().attach('com.dodonew.online')
        script = process.create_script(jsCode)
        script.on('message', func)//同样触发事件处理
        script.load()
        sys.stdin.read()
    
    1. rpc远程调用
    # -*- 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: ', retval);
                return retval;
            }
        });
        
        function test(data){
            var result = "";
            Java.perform(function(){
                result = Java.use('com.dodonew.online.util.Utils').md5(data);
            });
            return result;
        }
        
        rpc.exports = {
            rpcfunc: test
        };
        
    """
    
    # get_usb_device
    # 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)  # 加载完脚本, 恢复进程运行
    
    result = script.exports.rpcFUnc('equtype=ANDROID&loginImei=Androidnull&timeStamp=1626790668522&userPwd=a12345678&username=15968079477&key=sdlkjsdljf0j2fsjk')
    print(result)
    print("开始运行")
    sys.stdin.read()
    
    

算法转发

com.dodonew.online 伪造请求包,进行算法转发

这里的加密全是主动调用的程序原本的加密库来进行的

# -*- coding: UTF-8 -*-
from fastapi import FastAPI
import json
import frida
import sys

import requests

jsCode ="""
    function dodonew_Post(username,userPwd)
    {
        var result;
        Java.perform(function()
        {
            var time = new Date().getTime();
            var sign_data = 'equtype=ANDROID&loginImei=Androidnull&timeStamp='+time+'&userPwd='+userPwd+'&username='+username+'key=sdlkjsdljf0j2fsjk';
            console.log(sign_data);
            var sign = Java.use("com.dodonew.online.util.Utils").md5(sign_data);
            console.log(sign);
            ---------------------------------------这个构造的是sign进行md5之前的数据
            //"equtype":"ANDROID","loginImei":"Androidnull","sign":"D2C0747D62B016963E301ADBBC5C1C97","timeStamp":"1729045869892","userPwd":"password777","username":"15828712515"
            var  post_data = '{"equtype":"ANDROID","loginImei":"Androidnull","sign":"'+sign+'","timeStamp":"'+time+'","userPwd":"'+userPwd+'","username":"'+username+'"}';
            console.log(post_data);
            ---------------------------------------这个构造的是请求包中Encrypt的cipherText
            var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil');
            var Encrypt = RequestUtil.encodeDesMap(post_data,'65102933','32028092');
            console.log("Encrypt:"+Encrypt);
            return Encrypt;
        });
    }
    rpc.exports ={
        rpcfunc:dodonew_Post
    }
"""  
process = frida.get_device_manager().add_remote_device('192.168.30.234:8888').attach('com.dodonew.online')
script = process.create_script(jsCode)
script.load()
print('[*] Running com.dodonew.online')
cipherText = script.exports.rpcfunc('15828712515','password777')

headers = {
    "content-type": "application/json; charset=utf-8",
    "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 10; Pixel XL Build/QP1A.191005.007.A3)"
}
url = 'http://api.dodovip.com/api/user/login'
data = json.dumps({"Encrypt": cipherText})
r = requests.post(url=url, data=data, headers=headers)
print(r)
print(r.text)
print(type(r.text))
print(r.content)

构造服务器 将算法转发到服务器上,通过传入参数之间获取结果

from fastapi import FastAPI
import uvicorn
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.30.234:8888').attach("com.dodonew.online")
script = process.create_script(jsCode)
print('[*] Running')
script.load()

app = FastAPI()#获取这个服务器


@app.get("/get")#创建这个服务器的/get目录   比如 127.0.0.1:8080/get 
async def getEchoApi(item_id, item_user, item_pass):
    result = script.exports.rpcfunc(item_user, item_pass)
    return {"item_id": item_id,"item_user":item_user,"item_pass":item_pass, "item_retval": result}
#这里是要进行传参的,不然你不知道值:
# 127.0.0.1:8080/get?item_id=100&item_user=1528712515&item_pass=password777


if __name__ == '__main__':#监听本机的8080端口,也就是我们开启服务的端口
    uvicorn.run(app, port=8080)

通过构造post请求来实现

get和post请求的区别:

GET 请求通过 URL 的查询字符串将数据发送到服务器,参数以 ? 后面跟随键值对的形式出现。(数据传输量小)

POST 请求通过 HTTP 请求体(body)传递数据,而不是通过 URL。数据在 URL 中不可见。(数据传输量大)

from fastapi import FastAPI
import uvicorn
import frida
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.30.234:8888').attach("com.dodonew.online")
script = process.create_script(jsCode)
print('[*] Running 小肩膀')
script.load()

app = FastAPI()


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


@app.get("/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)

利用fiddler script 对于抓包的数据进行过滤,并利用挂载到对应本机的post目录实现数据的收集

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel

"""
        FiddlerObject.log("xiaojianbang1");
		//过滤无关请求,只关注特定请求
		if (oSession.fullUrl.Contains("functionId=pc_club_productPageComments"))
		{
            FiddlerObject.log("xiaojianbang2");
			var html = oSession.GetResponseBodyAsString()
			FiddlerObject.log(html);
			if(html.Contains("productPageComments")){
				//数据统计开始:把内容通过ajax http发送其它地方
				var _xhr = new ActiveXObject('Microsoft.XMLHTTP');
				var url = 'http://127.0.0.1:8000/sendData';
				//发送的数据参数
				var jsonString = oSession.GetResponseBodyAsString();
				var requestHeaders = oSession.oRequest.headers.ToString();
				var responseHeaders=oSession.oResponse.headers.ToString();
		
				var str='{}';//构造自己的JSON http请求的信息及返回的结果
				var data = Fiddler.WebFormats.JSON.JsonDecode(str);
	
				data.JSONObject["requestHeaders"]=requestHeaders;
				data.JSONObject["responseHeaders"]=responseHeaders;
				data.JSONObject["responseBody"] = jsonString;
				data.JSONObject["url"] = oSession.fullUrl;
				data.JSONObject["response_code"] = oSession.responseCode;
				
				if(oSession.oRequest.headers.Exists("Cookie")){
					data.JSONObject["requestCookie"] = oSession.oRequest.headers['Cookie'];
				}else{
					data.JSONObject["requestCookie"] = 'request no Cookie';
				};
				
				if(oSession.oResponse.headers.Exists("Cookie")){
					data.JSONObject["responseCookie"] = oSession.oResponse.headers['Cookie'];	
				}else{
					data.JSONObject["responseCookie"] = 'response no Cookie';
				};
				jsonString = Fiddler.WebFormats.JSON.JsonEncode(data.JSONObject)

				FiddlerObject.log(jsonString);
				
				_xhr.onreadystatechange=function(){
						if (_xhr.readyState==4){
							FiddlerObject.log(_xhr.responseText);
						}	
				};
				_xhr.open('POST', url, true);
				_xhr.send(jsonString);
				//----数据统计结束-----
			}else{
				 //弹窗报错
				FiddlerObject.alert("抓取出错!");
			} // if end
		} // if controll end
"""


class Item(BaseModel):
    url: str
    response_code: str
    responseBody: str
    requestHeaders: str
    responseHeaders: str
    requestCookie: str
    responseCookie: str


app = FastAPI()


@app.post("/sendData")
async def post_info1(request_data: Item):
    print("url:", request_data.url)
    print("response_code:", request_data.response_code)
    print("responseBody:", request_data.responseBody)
    print('requestHeaders:',request_data.requestHeaders)
    print('responseHeaders:',request_data.responseHeaders)
    print('requestCookie:',request_data.requestCookie)
    print('responseCookie:',request_data.responseCookie)
    return 'ok'

if __name__ == '__main__':
    uvicorn.run(app=app, host='127.0.0.1', port=8000)

这里中间的js,放到fiddler script里面,实现数据的过滤

unidbg入门

unidbg是Java开发的基于unicorn开发的仿真框架,它利用了 Java 的跨平台特性,通过使用 QEMU 来仿真 ARM 指令集,并通过 Java 代码去执行模拟 Android 或 iOS 的本地代码运行环境

通过模拟Android的运行环境来实现我们的内部函数的调用和相关寄存器等数据的读取和分析

unicorn好比是unidbg的一个CPU,可以模拟执行各种指令提供了很多编程语言接口,可以操作内存、寄存器等但它不是一个系统,内存管理、文件管理、系统调用等都需要自己来实现

unidbg
支持模拟JNI调用
支持模拟系统调用指令
支持ARM32和ARM64
支持Hookzz、Dobby、xHook、原生unicorn Hook等Hook方式
支持Android、iOS
好比是在CPU上搭建了一个系统,因此可以很方便地在PC端模拟运行so
学习成本较低,不需要复现so算法,补环境后直接运行即可

代码基本框架会自动调用init和init_arrayJNI_OnLoad的调用可以自己选择,可以不调用

Inspector.inspect(..., false) 便捷打印Java的byte数组

        dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数

callStaticJniMethodObject

该函数比callFunction多封装了一些代码不需要自己寻找函数地址不需要自己包装参数

        if (logging) {
            emulator.attach(DebuggerType.ANDROID_SERVER_V7); // 附加IDA android_server,可输入c命令取消附加继续运行
        }
        byte[] data = new byte[16];
        ByteArray array = TTEncryptUtils.callStaticJniMethodObject(emulator, "ttEncrypt([BI)[B", new ByteArray(vm, data), data.length); // 执行Jni方法
        return array.getValue();

这里是在test中的调用callStaticJniMethodObject的地方,在其内部是调用的callJniMethod来实现的,内部去call对应的method的object其实是需要对于相应的函数地址进行获取的

UnidbgPointer fnPtr = objectType.findNativeFunction(emulator, method);
//去获取对应的函数地址,先利用传进来的method比对是否是动态注册的函数,假如不是就利用传进来的类名的路径去拼接静态注册的地址,来实现函数地址的寻找
vm.addLocalObject(thisObj);
//加载对应的类——>获取thisobj的对象
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());//获取env
list.add(thisObj.hashCode());//通过thisobj的对象进行hashCode的索引得到jobject

这里是获取对应函数的函数地址以及相应的参数Env以及对应的Obj(object)

函数指针的获取:

public final UnidbgPointer findNativeFunction(Emulator<?> emulator, String method) {
    UnidbgPointer fnPtr = nativesMap.get(method);
    int index = method.indexOf('(');
    if (fnPtr == null && index == -1) {
        index = method.length();
    }
   //这里以上是对于动态注册的fnPtr的获取
    --------------------------------------------------------------------
   //这里以下是对于静态注册的函数取对应的函数注册名比如
   //Java_com_bytedance_frameworks_core_encrypt_TTEncryptUtils_function 这样的字眼
    StringBuilder builder = new StringBuilder();
    builder.append("Java_");
    mangleForJni(builder, getClassName());
    builder.append("_");
    mangleForJni(builder, method.substring(0, index));
    String symbolName = builder.toString();
    if (fnPtr == null) {
        for (Module module : emulator.getMemory().getLoadedModules()) {
            Symbol symbol = module.findSymbolByName(symbolName, false);
            if (symbol != null) {
                fnPtr = (UnidbgPointer) symbol.createPointer(emulator);
                break;
            }
        }
    }

利用unidbg进行主动的函数调用:

public static native int add(int i, int i2, int i3);

调用函数(利用findSymbolByName):

由于这里的函数是通过静态注册的函数,那么我们就利用函数名直接取实现函数调用

Symbol symbol = module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_add");
Number number = symbol.call(emulator, vm.getJNIEnv(), vm.addLocalObject(NativeHelper), 100, 200, 300);
System.out.println("result:"+number.intValue());

同理再次去调用一次函数jclass

Symbol symbol = module.findSymbolByName("_Z7_strcatP7_JNIEnvP7_jclass");
Number number = symbol.call(emulator, vm.getJNIEnv(), vm.addLocalObject(NativeHelper));
int value = number.intValue();//vm.getObject(value)将对应的类型返回回去
//注意一下这里的vm.getObject(value),相当于 vm.addLocalObject(NativeHelper)的逆过程,把对应的基础对象返回回来去调用getValue()获取对应的值
System.out.println("result:"+vm.getObject(value).getValue());

调用函数(利用callStaticJniMethodObject):

        StringObject md5Result = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", "xiaojianbang");
        System.out.println("md5result = " + md5Result.getValue());
        StringObject encodeResult = NativeHelper.callStaticJniMethodObject(emulator, "encode()Ljava/lang/String;");
        System.out.println("md5result = " + encodeResult.getValue());

这里注意一个问题,我们要通过对应的callStaticJniMethodObject函数是需要对应的so进行了加载之后的结果,也就是对应的以下的操作

//        DalvikModule dmA = vm.loadLibrary(new File("unidbg-android/src/test/java/xiaojianbang/ndk/libxiaojianbangA.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/xiaojianbang/ndk/libxiaojianbang.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
//        dmA.callJNI_OnLoad(emulator);
//        dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
        module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
        NativeHelper = vm.resolveClass("com/xiaojianbang/ndk/NativeHelper");

我们通过上面那个代码其实只能进行md5Result = NativeHelper.callStaticJniMethodObject的调用,而encodeResult = NativeHelper.callStaticJniMethodObject不能进行调用的原因是因为对应的so文件并没有进行加载

因为encode的native代码是在xiaojianbangA.so里面的

处理so调用其他so4.1 如果被调用的函数,需要先执行JNI_OnLoad或者其他函数

那就按顺序调用,如果so中调用了其他的so,只需按顺序加载所需的so即可,dlopen会自己处理,因为unidbg加载了libdl.so4.4 C/C++标准库也会自己处理,因为unidbg加载了libc.so、libc++.so

注意的callStaticJniMethodObject:

callStaticJniMethodObject

module.findSymbolByName
Symbol symbol = module.findSymbolByName(...);
Number[] numbers = symbol.call(...); 
int retval = numbers[0].intValue(); //返回Number的数组,要第0个

获取到Symbol以后,最好先判断下是否为null

//注意:
得到Java的int数据后,根据这个数据去内存中捞对象或者数据
比如返回的是Java对象
	vm.getObject(retval)	//相当于vm.addLocalObject的反过程
比如返回的是地址
	emulator.getMemory().pointer(retval).getByteArray(..., ...);
比如返回的是长度
	emulator.getMemory().getByteArray(..., retval);

日志开启:

将Unidbg的日志全开
src/test/resources/log4j.properties中INFO全改成DEBUG

通过偏移调用函数

Number number = module.callFunction(emulator, 0x0001A8C, vm.getJNIEnv(), vm.addLocalObject(NativeHelper));
int number_result = number.intValue();
System.out.println(vm.getObject(number_result).getValue());
直接利用偏移去找到函数,然后打印返回值

MD5的调用:以及值的传递

UnidbgPointer context = (UnidbgPointer) emulator.getMemory().malloc(200, false).getPointer();
//申请一个context空间,调用md5init(对于创建的context进行传值)
module.callFunction(emulator, 0x0002140,context );
UnidbgPointer painText = (UnidbgPointer) emulator.getMemory().malloc(200, false).getPointer();
byte[] buffer ="chen_chen_chen".getBytes();
painText.write(buffer);
//这里取申请空间,写入数据,再调用md5upadte
module.callFunction(emulator, 0x00021B0,context,painText,buffer.length);
UnidbgPointer cipherText = (UnidbgPointer) emulator.getMemory().malloc(16, false).getPointer();
//调用md5final
module.callFunction(emulator,0x003988,context,cipherText);
byte[] result = cipherText.getByteArray(0, 16);
Inspector.inspect(result,"MD5result");

unicode的源生HOOK

函数HOOK:

IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
        hookZz.wrap(module.findSymbolByName("_Z9MD5UpdateP7MD5_CTXPhj"), new WrapCallback<RegisterContext>() { // inline wrap导出函数
            @Override//这里是利用的symbol来进行寻址HOOK的
            public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
                //函数调用之前
                byte[] plainText_new = "chen_chen_chen123".getBytes(StandardCharsets.UTF_8);
                int length1 = plainText_new.length;
                Pointer new_pointer = emulator.getMemory().malloc(length1, false).getPointer();

                context_pointer = ctx.getPointerArg(0);
                Pointer plainText = ctx.getPointerArg(1);
                int length = ctx.getIntArg(2);
                Inspector.inspect(context_pointer.getByteArray(0,64),"preCall : context_pointer: ");
                Inspector.inspect(plainText.getByteArray(0,length),"plainText : ");
            }
            @Override
            public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
                //函数调用之后
                Inspector.inspect(context_pointer.getByteArray(0,64),"postCall : context_pointer: ");
            }
        });
        StringObject md5Result = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", "xiaojianbang");
        System.out.println("md5result = " + md5Result.getValue());

汇编代码HOOK 汇编级别的打印数据

  1. 单地址HOOK
hookZz.instrument(module.base + 0x01A2C, new InstrumentCallback<Arm64RegisterContext>() {
    @Override
    public void dbiCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) { // 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
        System.out.println("w8=0x" + Long.toHexString(ctx.getXInt(8)) + ", w9=0x" + Long.toHexString(ctx.getXInt(9)));
    }
}
  1. 汇编群HOOK
emulator.getBackend().hook_add_new(new CodeHook() {
    @Override
    public void hook(Backend backend, long address, int size, Object user) {
        emulator.getUnwinder().unwind();


        RegisterContext context = emulator.getContext();
        if(address ==module.base+0x01F04){
            Pointer pointerArg = context.getPointerArg(0);
            Inspector.inspect(pointerArg.getByteArray(0,64),"MD5 coontext:");
            Pointer plainText = context.getPointerArg(1);
            int length = context.getIntArg(2);
            Inspector.inspect(plainText.getByteArray(0,length),"MD5 plainText::");
        }
        else if(address ==module.base+0x00001F14 ){
            Pointer pointerArg = context.getPointerArg(0);
            Inspector.inspect(pointerArg.getByteArray(0,64),"MD5 coontext:");
            Pointer cipherText = context.getPointerArg(1);
            Inspector.inspect(cipherText.getByteArray(0,16),"MD5 cipherText::");

        }
    }

    @Override
    public void onAttach(UnHook unHook) {

    }

    @Override
    public void detach() {

    }
}, module.base+0x0001EF0,module.base+0x01F14,null);//HOOK( module.base+0x0001EF0----module.base+0x01F14 )

        DvmObject<?> xiaojianbang = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", "xiaojianbang");
        System.out.println("md5result = " + xiaojianbang.getValue());

函数替换

  IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
        hookZz.replace(module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_md5"), new ReplaceCallback() {
            @Override
            public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                System.out.println("123456789");   
//                return super.onCall(emulator, context, originFunction);
//                return HookStatus.RET(emulator, originFunction);
                return HookStatus.LR(emulator,100);//调用原函数并且返回100的返回值
            }
        });

利用unidbg进行的程序的附加和动态调试

               DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/xiaojianbang/ndk/libxiaojianbang.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
//        dmA.callJNI_OnLoad(emulator);
//        dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
        module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
		Debugger attach = emulator.attach();
        attach.addBreakPoint(module.base+0x0001E98);
        attach.addBreakPoint(module.base+0x0001F04);
        attach.addBreakPoint(module.base+0x000001F10);

读写监控以及对应的执行流汇编代码的获取

读写监控可以查看在哪个位置对于数据进行了读写操作以及emulator.traceCode的方法可以得到对应真实得到的执行流程中的汇编代码(反混淆)

        String File_name= "unidbg_trace";
        PrintStream Outputfilestream = new PrintStream(new FileOutputStream(File_name));
        emulator.traceRead(module.base, module.base+module.size).setRedirect(Outputfilestream);
        emulator.traceWrite(module.base, module.base+module.size).setRedirect(Outputfilestream);
        emulator.traceCode(module.base, module.base+ module.size).setRedirect(Outputfilestream);
//        DvmObject<?> xiaojianbang = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", "xiaojianbang");
//        System.out.println("md5result = " + xiaojianbang.getValue());

        Symbol symbol = module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_add");
        Number number = symbol.call(emulator, vm.getJNIEnv(), vm.addLocalObject(NativeHelper), 100, 200, 300);
        System.out.println("result:"+number.intValue());

处理so调用自写Java类

由于我们unidbg是通过加载so来实现so中函数的处理,在过程中,可能接收到的数据是来自于Java层传入的数据,而我们直接去加载so不会去得到很多数据或者是环境,那么我们就需要去补数据,补环境来实现运用。

补对象:

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        if ("java/lang/Class->getClassLoader()Ljava/lang/ClassLoader;".equals(signature)) {
            return vm.resolveClass("java/lang/ClassLoader").newObject(null);
        }
    @Override
    public DvmObject<?> allocObject(BaseVM vm, DvmClass dvmClass, String signature) {
        if("com/xiaojianbang/ndkdemo/NDKDemo->allocObject".equals(signature)) {
            return vm.resolveClass("com/xiaojianbang/ndkdemo/NDKDemo").newObject(null);
        }
            return super.allocObject(vm, dvmClass, signature);
    }

这里报错是callObjectMethodV()的java/lang/Class->getClassLoader()Ljava/lang/ClassLoader所以我们直接补对象,传个null就可以了,只要能过报错就行,同理补allocObject

补newObjectV:

@Override
public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    if("com/xiaojianbang/ndkdemo/NDKDemo-><init>(Ljava/lang/String;I)V".equals(signature)) {
        StringObject objectArg = vaList.getObjectArg(0);
        int intArg = vaList.getIntArg(1);
        System.out.println(objectArg.getValue());
        System.out.println(intArg);
        NDKDemo demo = new NDKDemo(objectArg.getValue(), intArg);
        return vm.resolveClass("com/xiaojianbang/ndkdemo/NDKDemo").newObject(demo);
    }
        return super.newObjectV(vm, dvmClass, signature, vaList);
}

这里是有传入参数的,函数目的就是创建新的类,我们直接去获取参数,创建类就好了(因为这里是init函数)

补类:

@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
    if("com/xiaojianbang/ndkdemo/NDKDemo->privateStaticStringField:Ljava/lang/String;".equals(signature)) {
        return new StringObject(vm,NDKDemo.privateStaticStringField);
    }
        return super.getStaticObjectField(vm, dvmClass, signature);
}

我们这里直接去创建了一个新类,因为我们的so文件并没有程序里面的有些类,这里就是没NDKDemo的类,我们就去创建类,并且我们可以把一些变量(比如private——>public)直接就可以访问,然后直接去传对应的函数就可以了

补callStaticObjectMethodV:

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        if("com/xiaojianbang/ndkdemo/NDKDemo->privateStaticFunc([Ljava/lang/String;)[I".equals(signature)) {
            ArrayObject arrayObjectArg = vaList.getObjectArg(0);
            int[] intarray =NDKDemo.privateStaticFunc(new String[]{arrayObjectArg.toString()});
            return new  IntArray(vm,intarray);
//            return new IntArray(vm,NDKDemo.privateStaticFunc(stringArray.getValue()))
        }
            return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }

这里我们是主动调用得到结果,然后直接去传结果过去就好了

将自写的 Java 类,放到 unidbg 工程中
包名最好与原包名一致,避免歧义
类中有用到 android 相关类,需要用 Java 去实现
代码不需要完全一致,只需函数处理结果符合预期即可

访问修饰符的问题

JNI 调用 Java 函数不需要理会访问修饰符
unidbg 用 Java 开发,在调用函数时需要注意访问修饰符
解决方法可以用反射,或者直接将 private 改成 public

处理 so 调用自写 Java 类
参数的获取

DvmClass 也是 DvmObject
VaList.getObject(0) 、 VaList.getInt(1)
这里的 index 指的是索引,表示第几个参数

数组参数

intArray   ArrayObject  toString
getValue( 赋值给 DvmObject[]) ,再取出 unidbg 包装前的 Java 数据

APP实例实现unidbg转发

马蜂窝旅游

这个APP抓包有证书检测的,但是只是单向的检测,直接用相应的HOOK是可以HOOK掉的,我们将HOOK代码和算法HOOK的代码合并到一起,把算法HOOK的代码存到function里面去,直接搞

Java.perform(function() {

    /*
    hook list:
    1.SSLcontext
    2.okhttp
    3.webview
    4.XUtils
    5.httpclientandroidlib
    6.JSSE
    7.network\_security\_config (android 7.0+)
    8.Apache Http client (support partly)
    9.OpenSSLSocketImpl
    10.TrustKit
    11.Cronet
    */
    
        // Attempts to bypass SSL pinning implementations in a number of
        // ways. These include implementing a new TrustManager that will
        // accept any SSL certificate, overriding OkHTTP v3 check()
        // method etc.
        var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
        var HostnameVerifier = Java.use('javax.net.ssl.HostnameVerifier');
        var SSLContext = Java.use('javax.net.ssl.SSLContext');
        var quiet_output = false;
    
        // Helper method to honor the quiet flag.
    
        function quiet_send(data) {
    
            if (quiet_output) {
    
                return;
            }
    
            send(data)
        }
    
    
        // Implement a new TrustManager
        // ref: https://gist.github.com/oleavr/3ca67a173ff7d207c6b8c3b0ca65a9d8
        // Java.registerClass() is only supported on ART for now(201803). 所以android 4.4以下不兼容,4.4要切换成ART使用.
        /*
    06-07 16:15:38.541 27021-27073/mi.sslpinningdemo W/System.err: java.lang.IllegalArgumentException: Required method checkServerTrusted(X509Certificate[], String, String, String) missing
    06-07 16:15:38.542 27021-27073/mi.sslpinningdemo W/System.err:     at android.net.http.X509TrustManagerExtensions.<init>(X509TrustManagerExtensions.java:73)
            at mi.ssl.MiPinningTrustManger.<init>(MiPinningTrustManger.java:61)
    06-07 16:15:38.543 27021-27073/mi.sslpinningdemo W/System.err:     at mi.sslpinningdemo.OkHttpUtil.getSecPinningClient(OkHttpUtil.java:112)
            at mi.sslpinningdemo.OkHttpUtil.get(OkHttpUtil.java:62)
            at mi.sslpinningdemo.MainActivity$1$1.run(MainActivity.java:36)
    */
        var X509Certificate = Java.use("java.security.cert.X509Certificate");
        var TrustManager;
        try {
            TrustManager = Java.registerClass({//创建自定义的 TrustManager
                name: 'org.wooyun.TrustManager',
                implements: [X509TrustManager],
                methods: {
                    checkClientTrusted: function(chain, authType) {},//checkClientTrusted 和 checkServerTrusted 方法被重写以不执行任何检查。
                    checkServerTrusted: function(chain, authType) {},
                    getAcceptedIssuers: function() {
                        // var certs = [X509Certificate.$new()];
                        // return certs;
                        return [];
                    }
                }
            });
        } catch (e) {
            quiet_send("registerClass from X509TrustManager >>>>>>>> " + e.message);
        }
    
        // Prepare the TrustManagers array to pass to SSLContext.init()
        var TrustManagers = [TrustManager.$new()];
    
        try {
            // Prepare a Empty SSLFactory修改 SSLContext 的初始化,载入新修改的TrustManagers
            var TLS_SSLContext = SSLContext.getInstance("TLS");
            TLS_SSLContext.init(null, TrustManagers, null);
            var EmptySSLFactory = TLS_SSLContext.getSocketFactory();
        } catch (e) {
            quiet_send(e.message);
        }
    
        send('Custom, Empty TrustManager ready');
    
        // Get a handle on the init() on the SSLContext class
        var SSLContext_init = SSLContext.init.overload(
            '[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom');
    
        // Override the init method, specifying our new TrustManager
        SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {
    
            quiet_send('Overriding SSLContext.init() with the custom TrustManager');
    
            SSLContext_init.call(this, null, TrustManagers, null);
        };
    
        /*** okhttp3.x unpinning ***/
    
    
        // Wrap the logic in a try/catch as not all applications will have
        // okhttp as part of the app.
        try {
    
            var CertificatePinner = Java.use('okhttp3.CertificatePinner');
            quiet_send('OkHTTP 3.x Found');
            CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function() {
                quiet_send('OkHTTP 3.x check() called. Not throwing an exception.');
            }
    
            var OkHttpClient$Builder = Java.use('okhttp3.OkHttpClient$Builder');
            quiet_send('OkHttpClient$Builder Found');
            console.log("hostnameVerifier", OkHttpClient$Builder.hostnameVerifier);
            OkHttpClient$Builder.hostnameVerifier.implementation = function () {
                quiet_send('OkHttpClient$Builder hostnameVerifier() called. Not throwing an exception.');
                return this;
            }
    
            var myHostnameVerifier = Java.registerClass({
                name: 'com.xiaojianbang.MyHostnameVerifier',
                implements: [HostnameVerifier],
                methods: {
                    verify: function (hostname, session) {
                        return true;
                    }
                }
            });
    
            var OkHttpClient = Java.use('okhttp3.OkHttpClient');
            OkHttpClient.hostnameVerifier.implementation = function () {
                quiet_send('OkHttpClient hostnameVerifier() called. Not throwing an exception.');
                return myHostnameVerifier.$new();
            }
    
        } catch (err) {
    
            // If we dont have a ClassNotFoundException exception, raise the
            // problem encountered.
            if (err.message.indexOf('ClassNotFoundException') === 0) {
    
                throw new Error(err);
            }
        }
    
        // Appcelerator Titanium PinningTrustManager
    
        // Wrap the logic in a try/catch as not all applications will have
        // appcelerator as part of the app.
        try {
    
            var PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager');
    
            send('Appcelerator Titanium Found');
    
            PinningTrustManager.checkServerTrusted.implementation = function() {
    
                quiet_send('Appcelerator checkServerTrusted() called. Not throwing an exception.');
            }
    
        } catch (err) {
    
            // If we dont have a ClassNotFoundException exception, raise the
            // problem encountered.
            if (err.message.indexOf('ClassNotFoundException') === 0) {
    
                throw new Error(err);
            }
        }
    
        /*** okhttp unpinning ***/
    
    
        try {
            var OkHttpClient = Java.use("com.squareup.okhttp.OkHttpClient");
            OkHttpClient.setCertificatePinner.implementation = function(certificatePinner) {
                // do nothing
                quiet_send("OkHttpClient.setCertificatePinner Called!");
                return this;
            };
    
            // Invalidate the certificate pinnet checks (if "setCertificatePinner" was called before the previous invalidation)
            var CertificatePinner = Java.use("com.squareup.okhttp.CertificatePinner");
            CertificatePinner.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function(p0, p1) {
                // do nothing
                quiet_send("okhttp Called! [Certificate]");
                return;
            };
            CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(p0, p1) {
                // do nothing
                quiet_send("okhttp Called! [List]");
                return;
            };
        } catch (e) {
            quiet_send("com.squareup.okhttp not found");
        }
    
        /*** WebView Hooks ***/
    
        /* frameworks/base/core/java/android/webkit/WebViewClient.java */
        /* public void onReceivedSslError(Webview, SslErrorHandler, SslError) */
        var WebViewClient = Java.use("android.webkit.WebViewClient");
    
        WebViewClient.onReceivedSslError.implementation = function(webView, sslErrorHandler, sslError) {
            quiet_send("WebViewClient onReceivedSslError invoke");
            //执行proceed方法
            sslErrorHandler.proceed();
            return;
        };
    
        WebViewClient.onReceivedError.overload('android.webkit.WebView', 'int', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c, d) {
            quiet_send("WebViewClient onReceivedError invoked");
            return;
        };
    
        WebViewClient.onReceivedError.overload('android.webkit.WebView', 'android.webkit.WebResourceRequest', 'android.webkit.WebResourceError').implementation = function() {
            quiet_send("WebViewClient onReceivedError invoked");
            return;
        };
    
        /*** JSSE Hooks ***/
    
        /* libcore/luni/src/main/java/javax/net/ssl/TrustManagerFactory.java */
        /* public final TrustManager[] getTrustManager() */
        /* TrustManagerFactory.getTrustManagers maybe cause X509TrustManagerExtensions error  */
        // var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
        // TrustManagerFactory.getTrustManagers.implementation = function(){
        //     quiet_send("TrustManagerFactory getTrustManagers invoked");
        //     return TrustManagers;
        // }
    
        var HttpsURLConnection = Java.use("com.android.okhttp.internal.huc.HttpsURLConnectionImpl");
        HttpsURLConnection.setSSLSocketFactory.implementation = function(SSLSocketFactory) {
            quiet_send("HttpsURLConnection.setSSLSocketFactory invoked");
        };
        HttpsURLConnection.setHostnameVerifier.implementation = function(hostnameVerifier) {
            quiet_send("HttpsURLConnection.setHostnameVerifier invoked");
        };
    
        /*** Xutils3.x hooks ***/
        //Implement a new HostnameVerifier
        var TrustHostnameVerifier;
        try {
            TrustHostnameVerifier = Java.registerClass({
                name: 'org.wooyun.TrustHostnameVerifier',
                implements: [HostnameVerifier],
                method: {
                    verify: function(hostname, session) {
                        return true;
                    }
                }
            });
    
        } catch (e) {
            //java.lang.ClassNotFoundException: Didn't find class "org.wooyun.TrustHostnameVerifier"
            quiet_send("registerClass from hostnameVerifier >>>>>>>> " + e.message);
        }
    
        try {
            var RequestParams = Java.use('org.xutils.http.RequestParams');
            RequestParams.setSslSocketFactory.implementation = function(sslSocketFactory) {
                sslSocketFactory = EmptySSLFactory;
                return null;
            }
    
            RequestParams.setHostnameVerifier.implementation = function(hostnameVerifier) {
                hostnameVerifier = TrustHostnameVerifier.$new();
                return null;
            }
    
        } catch (e) {
            quiet_send("Xutils hooks not Found");
        }
    
        /*** httpclientandroidlib Hooks ***/
        try {
            var AbstractVerifier = Java.use("ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier");
            AbstractVerifier.verify.overload('java.lang.String', '[Ljava.lang.String', '[Ljava.lang.String', 'boolean').implementation = function() {
                quiet_send("httpclientandroidlib Hooks");
                return null;
            }
        } catch (e) {
            quiet_send("httpclientandroidlib Hooks not found");
        }
    
        /***
    android 7.0+ network_security_config TrustManagerImpl hook
    apache httpclient partly
    ***/
        var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
        // try {
        //     var Arrays = Java.use("java.util.Arrays");
        //     //apache http client pinning maybe baypass
        //     //https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#471
        //     TrustManagerImpl.checkTrusted.implementation = function (chain, authType, session, parameters, authType) {
        //         quiet_send("TrustManagerImpl checkTrusted called");
        //         //Generics currently result in java.lang.Object
        //         return Arrays.asList(chain);
        //     }
        //
        // } catch (e) {
        //     quiet_send("TrustManagerImpl checkTrusted nout found");
        // }
    
        try {
            // Android 7+ TrustManagerImpl
            TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
                quiet_send("TrustManagerImpl verifyChain called");
                // Skip all the logic and just return the chain again :P
                //https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2017/november/bypassing-androids-network-security-configuration/
                // https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#L650
                return untrustedChain;
            }
        } catch (e) {
            quiet_send("TrustManagerImpl verifyChain nout found below 7.0");
        }
        // OpenSSLSocketImpl
        try {
            var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl');
            OpenSSLSocketImpl.verifyCertificateChain.implementation = function(certRefs, authMethod) {
                quiet_send('OpenSSLSocketImpl.verifyCertificateChain');
            }
    
            quiet_send('OpenSSLSocketImpl pinning')
        } catch (err) {
            quiet_send('OpenSSLSocketImpl pinner not found');
        }
        // Trustkit
        try {
            var Activity = Java.use("com.datatheorem.android.trustkit.pinning.OkHostnameVerifier");
            Activity.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function(str) {
                quiet_send('Trustkit.verify1: ' + str);
                return true;
            };
            Activity.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function(str) {
                quiet_send('Trustkit.verify2: ' + str);
                return true;
            };
    
            quiet_send('Trustkit pinning')
        } catch (err) {
            quiet_send('Trustkit pinner not found')
        }
    
        try {
            //cronet pinner hook
            //weibo don't invoke
    
            var netBuilder = Java.use("org.chromium.net.CronetEngine$Builder");
    
            //https://developer.android.com/guide/topics/connectivity/cronet/reference/org/chromium/net/CronetEngine.Builder.html#enablePublicKeyPinningBypassForLocalTrustAnchors(boolean)
            netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.implementation = function(arg) {
    
                //weibo not invoke
                console.log("Enables or disables public key pinning bypass for local trust anchors = " + arg);
    
                //true to enable the bypass, false to disable.
                var ret = netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.call(this, true);
                return ret;
            };
    
            netBuilder.addPublicKeyPins.implementation = function(hostName, pinsSha256, includeSubdomains, expirationDate) {
                console.log("cronet addPublicKeyPins hostName = " + hostName);
    
                //var ret = netBuilder.addPublicKeyPins.call(this,hostName, pinsSha256,includeSubdomains, expirationDate);
                //this 是调用 addPublicKeyPins 前的对象吗? Yes,CronetEngine.Builder
                return this;
            };
    
        } catch (err) {
            console.log('[-] Cronet pinner not found')
        }
});
function call_ciphers(){
    
Java.perform(function () {

    function showStacks() {
        console.log(
            Java.use("android.util.Log")
                .getStackTraceString(
                    Java.use("java.lang.Throwable").$new()
                )
        );
    }
    function stringToBytes(str){
        return hexToBytes(stringToHex(str));
    }
    
    // Convert a ASCII string to a hex string
    function stringToHex(str) {
        return str.split("").map(function(c) {
            return ("0" + c.charCodeAt(0).toString(16)).slice(-2);
        }).join("");
    }
    
    function hexToBytes(hex) {
        for (var bytes = [], c = 0; c < hex.length; c += 2)
            bytes.push(parseInt(hex.substr(c, 2), 16));
        return bytes;
    }
    
    // Convert a hex string to a ASCII string
    function hexToString(hexStr) {
        var hex = hexStr.toString();//force conversion
        var str = '';
        for (var i = 0; i < hex.length; i += 2)
            str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
        return str;
    }
    function logOutPut(msg) {
        Java.use("android.util.Log").d("xiaojianbang", "frida inject: " + msg);
    }

    var ByteString = Java.use("com.android.okhttp.okio.ByteString");
    function toBase64(tag, data) {
        //logOutPut(tag + " Base64: " + ByteString.of(data).base64());
        console.log(tag + " Base64: " + ByteString.of(data).base64());
    }
    function toHex(tag, data) {
        //logOutPut(tag + " Hex: " + ByteString.of(data).hex());
        console.log(tag + " Hex: " + ByteString.of(data).hex());
    }
    function toUtf8(tag, data) {
        //logOutPut(tag + " Utf8: " + ByteString.of(data).utf8());
        console.log(tag + " Utf8: " + ByteString.of(data).utf8());
    }
    // toBase64([48,49,50,51,52]);
    // toHex([48,49,50,51,52]);
    // toUtf8([48,49,50,51,52]);
    //console.log(Java.enumerateLoadedClassesSync().join("\n"));

    var messageDigest = Java.use("java.security.MessageDigest");
    messageDigest.update.overload('byte').implementation = function (data) {
        console.log("MessageDigest.update('byte') is called!");
        showStacks();
        return this.update(data);
    }
    messageDigest.update.overload('java.nio.ByteBuffer').implementation = function (data) {
        console.log("MessageDigest.update('java.nio.ByteBuffer') is called!");
        showStacks();
        return this.update(data);
    }
    messageDigest.update.overload('[B').implementation = function (data) {
        console.log("MessageDigest.update('[B') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================");
        return this.update(data);
    }
    messageDigest.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("MessageDigest.update('[B', 'int', 'int') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================", start, length);
        return this.update(data, start, length);
    }
    messageDigest.digest.overload().implementation = function () {
        console.log("MessageDigest.digest() is called!");
        showStacks();
        var result = this.digest();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest result";
        toHex(tag, result);
        toBase64(tag, result);
        console.log("=======================================================");
        return result;
    }
    messageDigest.digest.overload('[B').implementation = function (data) {
        console.log("MessageDigest.digest('[B') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.digest(data);
        var tags = algorithm + " digest result";
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================");
        return result;
    }
    messageDigest.digest.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("MessageDigest.digest('[B', 'int', 'int') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.digest(data, start, length);
        var tags = algorithm + " digest result";
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================", start, length);
        return result;
    }

    var mac = Java.use("javax.crypto.Mac");
    mac.init.overload('java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (key, AlgorithmParameterSpec) {
        console.log("Mac.init('java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");
        return this.init(key, AlgorithmParameterSpec);
    }
    mac.init.overload('java.security.Key').implementation = function (key) {
        console.log("Mac.init('java.security.Key') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " init Key";
        var keyBytes = key.getEncoded();
        toUtf8(tag, keyBytes);
        toHex(tag, keyBytes);
        toBase64(tag, keyBytes);
        console.log("=======================================================");
        return this.init(key);
    }
    mac.update.overload('byte').implementation = function (data) {
        console.log("Mac.update('byte') is called!");
        return this.update(data);
    }
    mac.update.overload('java.nio.ByteBuffer').implementation = function (data) {
        console.log("Mac.update('java.nio.ByteBuffer') is called!");
        return this.update(data);
    }
    mac.update.overload('[B').implementation = function (data) {
        console.log("Mac.update('[B') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================");
        return this.update(data);
    }
    mac.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("Mac.update('[B', 'int', 'int') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================", start, length);
        return this.update(data, start, length);
    }
    mac.doFinal.overload().implementation = function () {
        console.log("Mac.doFinal() is called!");
        var result = this.doFinal();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " doFinal result";
        toHex(tag, result);
        toBase64(tag, result);
        console.log("=======================================================");
        return result;
    }

    var cipher = Java.use("javax.crypto.Cipher");
    cipher.init.overload('int', 'java.security.cert.Certificate').implementation = function () {
        console.log("Cipher.init('int', 'java.security.cert.Certificate') is called!");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.SecureRandom').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.SecureRandom') is called!");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom').implementation = function () {
        console.log("Cipher.init('int', 'java.security.cert.Certificate', 'java.security.SecureRandom') is called!");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom') is called!");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom') is called!");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters') is called!");
        return this.init.apply(this, arguments);
    }

    cipher.init.overload('int', 'java.security.Key').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " init Key";
        var className = JSON.stringify(arguments[1]);
        if(className.indexOf("OpenSSLRSAPrivateKey") === -1){
            var keyBytes = arguments[1].getEncoded();
            toUtf8(tag, keyBytes);
            toHex(tag, keyBytes);
            toBase64(tag, keyBytes);
        }
        console.log("=======================================================");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " init Key";
        var keyBytes = arguments[1].getEncoded();
        toUtf8(tag, keyBytes);
        toHex(tag, keyBytes);
        toBase64(tag, keyBytes);
        var tags = algorithm + " init iv";
        var iv = Java.cast(arguments[2], Java.use("javax.crypto.spec.IvParameterSpec"));
        var ivBytes = iv.getIV();
        toUtf8(tags, ivBytes);
        toHex(tags, ivBytes);
        toBase64(tags, ivBytes);
        console.log("=======================================================");
        return this.init.apply(this, arguments);
    }

    cipher.doFinal.overload('java.nio.ByteBuffer', 'java.nio.ByteBuffer').implementation = function () {
        console.log("Cipher.doFinal('java.nio.ByteBuffer', 'java.nio.ByteBuffer') is called!");
        showStacks();
        return this.doFinal.apply(this, arguments);
    }
    cipher.doFinal.overload('[B', 'int').implementation = function () {
        console.log("Cipher.doFinal('[B', 'int') is called!");
        showStacks();
        return this.doFinal.apply(this, arguments);
    }
    cipher.doFinal.overload('[B', 'int', 'int', '[B').implementation = function () {
        console.log("Cipher.doFinal('[B', 'int', 'int', '[B') is called!");
        showStacks();
        return this.doFinal.apply(this, arguments);
    }
    cipher.doFinal.overload('[B', 'int', 'int', '[B', 'int').implementation = function () {
        console.log("Cipher.doFinal('[B', 'int', 'int', '[B', 'int') is called!");
        showStacks();
        return this.doFinal.apply(this, arguments);
    }
    cipher.doFinal.overload().implementation = function () {
        console.log("Cipher.doFinal() is called!");
        showStacks();
        return this.doFinal.apply(this, arguments);
    }

    cipher.doFinal.overload('[B').implementation = function () {
        console.log("Cipher.doFinal('[B') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " doFinal data";
        var data = arguments[0];
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.doFinal.apply(this, arguments);
        var tags = algorithm + " doFinal result";
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================");
        return result;
    }
    cipher.doFinal.overload('[B', 'int', 'int').implementation = function () {
        console.log("Cipher.doFinal('[B', 'int', 'int') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " doFinal data";
        var data = arguments[0];
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.doFinal.apply(this, arguments);
        var tags = algorithm + " doFinal result";
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================", arguments[1], arguments[2]);
        return result;
    }

    var signature = Java.use("java.security.Signature");
    signature.update.overload('byte').implementation = function (data) {
        console.log("Signature.update('byte') is called!");
        return this.update(data);
    }
    signature.update.overload('java.nio.ByteBuffer').implementation = function (data) {
        console.log("Signature.update('java.nio.ByteBuffer') is called!");
        return this.update(data);
    }
    signature.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("Signature.update('[B', 'int', 'int') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================", start, length);
        return this.update(data, start, length);
    }
    signature.sign.overload('[B', 'int', 'int').implementation = function () {
        console.log("Signature.sign('[B', 'int', 'int') is called!");
        return this.sign.apply(this, arguments);
    }
    signature.sign.overload().implementation = function () {
        console.log("Signature.sign() is called!");
        var result = this.sign();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " sign result";
        toHex(tag, result);
        toBase64(tag, result);
        console.log("=======================================================");
        return result;
    }
});
} 

抓包的结果:

POST /rest/app/user/login/ HTTP/1.1
User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; Pixel XL Build/QP1A.191005.007.A3) mfwappcode/com.mfw.roadbook mfwappver/8.1.6 mfwversioncode/535 mfwsdk/20140507 channel/GROWTH-WAP-LC-3 mfwjssdk/1.1 mfwappjsapi/1.5
Connection: close
X-HTTP-METHOD-OVERRIDE: PUT
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: mapi.mafengwo.cn
Accept-Encoding: gzip
Cookie: mfw_uuid=66f26266-1c4d-1dbc-c45a-b01dd11fcf99; __openudid=40:4E:36:24:41:C1; PHPSESSID=ugsr811seo9bj2ligv3hdum3f4; oad_n=a%3A3%3A%7Bs%3A3%3A%22oid%22%3Bi%3A1029%3Bs%3A2%3A%22dm%22%3Bs%3A16%3A%22mapi.mafengwo.cn%22%3Bs%3A2%3A%22ft%22%3Bs%3A19%3A%222024-10-25+11%3A02%3A25%22%3B%7D
Content-Length: 671


x_auth_username=1528712517&o_lat=30.886964&device_type=android&oauth_version=1.0&oauth_signature_method=HMAC-SHA1&screen_height=2392&open_udid=40%3A4E%3A36%3A24%3A41%3AC1&put_style=default&app_version_code=535&x_auth_mode=client_auth&sys_ver=10&o_lng=103.593712&brand=google&app_code=com.mfw.roadbook&screen_scale=3.5&screen_width=1440&time_offset=480&device_id=40%3A4E%3A36%3A24%3A41%3AC1&
oauth_signature=jcm3Tsb%2BMGYijEWw3%2BrW0zbeDps%3D&x_auth_password=password777&oauth_consumer_key=5&
oauth_timestamp=1729825328&oauth_nonce=ae757416-bbe3-43ef-8383-38df694a3a77&mfwsdk_ver=20140507&app_ver=8.1.6&hardware_model=Pixel+XL&channel_id=GROWTH-WAP-LC-3&after_style=default&

我们找到sign的加密过程

package com.mfw.tnative;
import android.content.Context;
/* loaded from: classes4.dex */
public class AuthorizeHelper {
    private static AuthorizeHelper instance;
    private String packageName;
    private native boolean signatureChecked(Context context, String str);
    private native String xAuthencode(Context context, String str, String str2, String str3, boolean z);
    private AuthorizeHelper(String name) {
        this.packageName = name;
    }
    public static AuthorizeHelper getInstance(String name) {
        if (instance == null) {
            instance = new AuthorizeHelper(name);
        }
        return instance;
    }
    public String cryptoParams(Context context, String source, String key, boolean isLogin) {
        return xAuthencode(context, source, key, this.packageName, isLogin);
    }
    public boolean isAppSingedCorrect(Context context) {
        return signatureChecked(context, this.packageName);
    }
    static {
        System.loadLibrary("mfw");
    }
}

private native String xAuthencode(Context context, String str, String str2, String str3, boolean z);是这里的native化的函数

unidbg调用so文件

package MFW;
import com.alibaba.fastjson.util.IOUtils;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import java.io.File;
import unicorn.ArmConst;
public class AuthorizeHelper extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private final DvmClass AuthorizeHelper_class;
    private final boolean logging;
    AuthorizeHelper(boolean logging) {
        this.logging = logging;
        emulator = AndroidEmulatorBuilder.for32Bit()
                .setProcessName("com.mfw.roadbook")
                .addBackendFactory(new Unicorn2Factory(true))
                .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
        vm = emulator.createDalvikVM(); // 创建Android虚拟机
        vm.setVerbose(logging); // 设置是否打印Jni调用细节
        vm.setJni(this);
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/MFW/libmfw.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
//        dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
        module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
        AuthorizeHelper_class = vm.resolveClass("com/mfw/tnative/AuthorizeHelper");
    }

    void destroy() {
        IOUtils.close(emulator);
        if (logging) {
            System.out.println("destroy");
        }
    }
    public static void main(String[] args) throws Exception {
        AuthorizeHelper test = new AuthorizeHelper(true);

        test.call_func();

        test.destroy();
    }
    void call_func() {
        emulator.attach().addBreakPoint(module.base + 0x0000914C, new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_PC,address+4+1);
                emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0,1);
                return false;
            }
        });
        String data ="PUT&https%3A%2F%2Fmapi.mafengwo.cn%2Frest%2Fapp%2Fuser%2Flogin%2F&after_style%3Ddefault%26app_code%3Dcom.mfw.roadbook%26app_ver%3D8.1.6%26app_version_code%3D535%26brand%3Dgoogle%26channel_id%3DGROWTH-WAP-LC-3%26device_id%3D40%253A4E%253A36%253A24%253A41%253AC1%26device_type%3Dandroid%26hardware_model%3DPixel%2520XL%26mfwsdk_ver%3D20140507%26oauth_consumer_key%3D5%26oauth_nonce%3D1b6be857-bec5-4302-97be-6778d3bed316%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1727240426%26oauth_version%3D1.0%26open_udid%3D40%253A4E%253A36%253A24%253A41%253AC1%26put_style%3Ddefault%26screen_height%3D2392%26screen_scale%3D3.5%26screen_width%3D1440%26sys_ver%3D10%26time_offset%3D480%26x_auth_mode%3Dclient_auth%26x_auth_password%3Dpassword777%2520%252F%26x_auth_username%3D15838712517";
        StringObject stringObject = AuthorizeHelper_class.callStaticJniMethodObject(emulator, "xAuthencode(Landroid.content.Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", vm.resolveClass("android.content.Context"),data, "","com.mfw.roadbook",true); // 执行Jni方法
        System.out.println(stringObject.getValue());
        return;
    }
    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        if("java/lang/Class->getPackageManager()Landroid/content/pm/PackageManager".equals(signature)) {
            return vm.resolveClass("java/lang/Class->getPackageManager").newObject(null);
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }
}

这里有HOOK代码的地方:

        emulator.attach().addBreakPoint(module.base + 0x0000914C, new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_PC,address+4+1);
                emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0,1);
                return false;
            }
        });

这里的位置是去调用 if(signatureChecked(a1, a2, a3, a6) != 1) 的代码,是对于sign检测的位置,我们直接HOOK这里的汇编代码,把pc指针指向了下一个地址,并且将返回值R0的值设置为1了,所以直接HOOK掉了

就可以直接得到加密结果了

JNIEnv->GetStringUtfChars("com.mfw.roadbook") was called from RX@0x120087bf[libmfw.so]0x87bf
JNIEnv->GetStringUtfChars("") was called from RX@0x120087bf[libmfw.so]0x87bf
JNIEnv->GetStringUtfChars("PUT&https%3A%2F%2Fmapi.mafengwo.cn%2Frest%2Fapp%2Fuser%2Flogin%2F&after_style%3Ddefault%26app_code%3Dcom.mfw.roadbook%26app_ver%3D8.1.6%26app_version_code%3D535%26brand%3Dgoogle%26channel_id%3DGROWTH-WAP-LC-3%26device_id%3D40%253A4E%253A36%253A24%253A41%253AC1%26device_type%3Dandroid%26hardware_model%3DPixel%2520XL%26mfwsdk_ver%3D20140507%26oauth_consumer_key%3D5%26oauth_nonce%3D1b6be857-bec5-4302-97be-6778d3bed316%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1727240426%26oauth_version%3D1.0%26open_udid%3D40%253A4E%253A36%253A24%253A41%253AC1%26put_style%3Ddefault%26screen_height%3D2392%26screen_scale%3D3.5%26screen_width%3D1440%26sys_ver%3D10%26time_offset%3D480%26x_auth_mode%3Dclient_auth%26x_auth_password%3Dpassword777%2520%252F%26x_auth_username%3D15838712517") was called from RX@0x120087bf[libmfw.so]0x87bf
JNIEnv->ReleaseStringUTFChars("PUT&https%3A%2F%2Fmapi.mafengwo.cn%2Frest%2Fapp%2Fuser%2Flogin%2F&after_style%3Ddefault%26app_code%3Dcom.mfw.roadbook%26app_ver%3D8.1.6%26app_version_code%3D535%26brand%3Dgoogle%26channel_id%3DGROWTH-WAP-LC-3%26device_id%3D40%253A4E%253A36%253A24%253A41%253AC1%26device_type%3Dandroid%26hardware_model%3DPixel%2520XL%26mfwsdk_ver%3D20140507%26oauth_consumer_key%3D5%26oauth_nonce%3D1b6be857-bec5-4302-97be-6778d3bed316%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1727240426%26oauth_version%3D1.0%26open_udid%3D40%253A4E%253A36%253A24%253A41%253AC1%26put_style%3Ddefault%26screen_height%3D2392%26screen_scale%3D3.5%26screen_width%3D1440%26sys_ver%3D10%26time_offset%3D480%26x_auth_mode%3Dclient_auth%26x_auth_password%3Dpassword777%2520%252F%26x_auth_username%3D15838712517") was called from RX@0x120087eb[libmfw.so]0x87eb
JNIEnv->ReleaseStringUTFChars("") was called from RX@0x120087eb[libmfw.so]0x87eb
JNIEnv->ReleaseStringUTFChars("com.mfw.roadbook") was called from RX@0x120087eb[libmfw.so]0x87eb
JNIEnv->NewStringUTF("b0nNedgOwdVRN12uLzlSI7APAgk=") was called from RX@0x12008793[libmfw.so]0x8793
b0nNedgOwdVRN12uLzlSI7APAgk=
destroy

Virtual Module (虚拟so文件函数)

对于so之间的相互调用,我们在处理一个SO的过程中可能会出现,导入了其他so文件的函数,需要实现加载其他so的过程,但是对于so之间的相互交互会使得我们要导入多个so文件,所以通常我们使用vitrual module来实现某个so中的函数,从而不去导入新的so文件

在unidbg中的java/com.github.unidbg/virtualmodule.android中的androidmodule类实现了对于虚拟so文件函数的实例

public AndroidModule(Emulator<?> emulator, VM vm) {
    super(emulator, vm, "libandroid.so");
}

标注对应虚拟实现的so文件

protected void onInitialize(Emulator<?> emulator, final VM vm, Map<String, UnidbgPointer> symbols) {
    boolean is64Bit = emulator.is64Bit();
    SvcMemory svcMemory = emulator.getSvcMemory();
    symbols.put("AAssetManager_fromJava", svcMemory.registerSvc(is64Bit ? new Arm64Svc() {
        @Override
        public long handle(Emulator<?> emulator) {
            return fromJava(emulator, vm);
        }
    } : new ArmSvc() {
        @Override
        public long handle(Emulator<?> emulator) {
            return fromJava(emulator, vm);
        }
    }));

通过对应的symbol找到对应so文件的函数符号,从而实现函数的复写,同时会去实例化对应程序位数的对象 new

Arm64Svc() {
        @Override
        public long handle(Emulator<?> emulator) {
            return fromJava(emulator, vm);//进入实现函数
        }
private static long fromJava(Emulator<?> emulator, VM vm) {
    RegisterContext context = emulator.getContext();
    Pointer env = context.getPointerArg(0);
    UnidbgPointer assetManager = context.getPointerArg(1);
    DvmObject<?> obj = vm.getObject(assetManager.toIntPeer());
    if (log.isDebugEnabled()) {
        log.debug("AAssetManager_fromJava env={}, assetManager={}, LR={}", env, obj.getObjectType(), context.getLRPointer());
    }
    return assetManager.peer;
}

这里就是实现对应函数的位置

so与系统之间交互的 文件访问(读写访问,内存访问)

public class NativeHelper extends AbstractJni implements IOResolver 

这里去implement了IOResolver的类

emulator.getSyscallHandler().addIOResolver(this);

这里的函数注册,是对应的文件交互类的处理函数的注册

@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
    System.out.println("print pathname:"+pathname);
    return null;
}

这里是对于resolve函数的处理函数,对于读写的文件访问的时候会进入这个函数,所以就打印对应的pathname

内存访问

对应内存的访问监控,可以看到对应内存的使用过程,但是不会直接去查看到对于手机内存的读写操作和内存使用,因为没有程序去读取了对于程序的进程的map内容,所以我们可以通过在开启进程之后对于程序进程的maps进行拷贝

cp /proc/进程号/maps  /sdcard/maps

在resovle函数中可以处理对应的实现读取文件夹的操作:

public FileResult resolve(Emulator emulator, String pathname, int oflags) {
    if (("/proc/self/maps").equals(pathname)) {
        return FileResult.success(new SimpleFileIO(oflags, new File("xxx"), pathname));//这里就是去利用拷贝下来的maps的文件填写的位置,这样就是按照手机中对应的进程实现内存申请的过程了
        //return FileResult.success(new ByteArrayFileIO(oflags, pathname, "xxx".getBytes()));
        //这个函数是返回对应的文件输出和内容输出
    }
    //emulator.attach().debug();
    System.out.println("xiaojianbang: " + pathname);
    return null;
}

对于so与系统交互的环境变量PATH

在/src/main/java/com/github/unidbg/linux/AndroidElfLoader.javad 的位置保存着unidbg中的PATH环境变量的静态编写

        this.environ = initializeTLS(new String[] {
                "ANDROID_DATA=/data",
                "ANDROID_ROOT=/system",
                "PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin",
                "NO_ADDR_COMPAT_LAYOUT_FIXUP=1"
        });
        this.setErrno(0);

我们可以自己去修改,或者是通过通过libc里的setenv设置环境变量

Symbol setenv = module.findSymbolByName("setenv", true); setenv.call(emulator, "PATH", "PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin", 0);

同时程序获取的Env也就是path的路径

HookListener是对于程序执行位置时候的HOOK,比如获取到getEnv的时候

public long hook(SvcMemory svcMemory, String libraryName, String symbolName, final long old) {
    if ("libc.so".equals(libraryName)) {
        if ("getenv".equals(symbolName)) {
            log.debug("Hook {}", symbolName);
            if (emulator.is64Bit()) {
                return svcMemory.registerSvc(new Arm64Hook() {
                    @Override
                    protected HookStatus hook(Emulator<?> emulator) {
                        RegisterContext context = emulator.getContext();
                        int index = 0;
                        Pointer pointer = context.getPointerArg(index);
                        String key = pointer.getString(0);
                        System.out.println("getenv key:" + key);
                        return HookStatus.RET(emulator,old);
                    }
                }).peer;
            } else {
                return svcMemory.registerSvc(new ArmHook() {
                    @Override
                    protected HookStatus hook(Emulator<?> emulator) {
                        RegisterContext context = emulator.getContext();
                        int index = 0;
                        Pointer pointer = context.getPointerArg(index);
                        String key = pointer.getString(0);
                        System.out.println("getenv key:" + key);
                        return HookStatus.RET(emulator,old);
                    }
                }).peer;
            }
        }
    }
    return old;
}}

同时需要对于函数进行注册

GetEnvHook getEnvHook = new GetEnvHook(emulator);
memory.addHookListener(getEnvHook);

或者是通过简便方式进行单个的处理

SystemPropertyHook systemPropertyHook = new SystemPropertyHook(emulator);
systemPropertyHook.setPropertyProvider(new SystemPropertyProvider() {
    @Override
    public String getProperty(String key) {
        switch (key) {
            case "getenv":
                return System.getenv(key);
        }
        return "";
    };
});
GetEnv

自己实现内核的syscall

unidbg对于syscall的内核函数有进行处理和使用,src/main/java/com/github/unidbg/linux/ARM64SyscallHandler.java,对于使用syscall的函数按照对应的系统调用号进行了相应的处理

我们同样可以使用该unidbg写的内核处理的东西进行复写来实现对应的操作,比如:

package xiaojianbang.ndk;

import com.github.unidbg.Emulator;
import com.github.unidbg.arm.context.EditableArm32RegisterContext;
import com.github.unidbg.linux.ARM32SyscallHandler;
import com.github.unidbg.linux.ARM64SyscallHandler;
import com.github.unidbg.memory.SvcMemory;
import com.sun.jna.Pointer;

public class MySyscallHandler extends ARM64SyscallHandler {

    public MySyscallHandler(SvcMemory svcMemory) {
        super(svcMemory);
    }

    @Override
    protected boolean handleUnknownSyscall(Emulator<?> emulator, int NR) {
        EditableArm32RegisterContext context = emulator.getContext();
        if (NR == 114) {
            int pid = context.getR0Int();
            Pointer wstatus = context.getR1Pointer();
            int options = context.getR2Int();
            Pointer rusage = context.getR3Pointer();
            System.out.println("wait4 pid=" + pid + ", wstatus=" + wstatus + ", options=0x" + Integer.toHexString(options) + ", rusage=" + rusage);
            return true;
        }
        return super.handleUnknownSyscall(emulator, NR);
    }

}

通过去继承一个这个处理函数的类,继承父类,并且复写handleUnknownSyscall来实现对于没有进行系统处理的函数,比如这里的NR==114

使用方法:

AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(false) {
    public AndroidEmulator build() {
        return new AndroidARMEmulator(processName, rootDir, backendFactories) {
            @Override
            protected UnixSyscallHandler<AndroidFileIO> createSyscallHandler(SvcMemory svcMemory) {
                return new MySyscallHandler(svcMemory);
            }
        };
    }
};
emulator = builder.setProcessName("xxx")
    .setRootDir(new File("xxx"))
    .build();

去创建我们自己的buider实现对于的操作

posted @   fisherman-ovo  阅读(113)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
点击右上角即可分享
微信分享提示