frida列印與參數構造

  • 2020 年 11 月 5 日
  • 筆記

title: frida列印與參數構造
categories: 逆向與協議分析
toc: true
mathjax: true
tags:


本文將系統討論,frida的列印與參數構造問題。

參數列印

bytes2Hex

function bytes2Hex(arrBytes){
    var str = "";
    for (var i = 0; i < arrBytes.length; i++) {
        var tmp;
        var num = arrBytes[i];
        if (num < 0) {
            //此處填坑,當byte因為符合位導致數值為負時候,需要對數據進行處理
            tmp = (255 + num + 1).toString(16);
        } else {
            tmp = num.toString(16);
        }
        if (tmp.length == 1) {
            tmp = "0" + tmp;
        }
        if(i>0){
            str += " "+tmp;
        }else{
            str += tmp;
        }
    }
    return str;
}

傳入的參數為byte[],example:[12, 0, 156, -127] return:[0c,00,9c,fe],注意別傳成了string。

string2Bytes

function string2Bytes(str) {
    var bytes = new Array();
    var len, c;
    len = str.length;
    for(var i = 0; i < len; i++) {
        c = str.charCodeAt(i);
        if(c >= 0x010000 && c <= 0x10FFFF) {
            bytes.push(((c >> 18) & 0x07) | 0xF0);
            bytes.push(((c >> 12) & 0x3F) | 0x80);
            bytes.push(((c >> 6) & 0x3F) | 0x80);
            bytes.push((c & 0x3F) | 0x80);
        } else if(c >= 0x000800 && c <= 0x00FFFF) {
            bytes.push(((c >> 12) & 0x0F) | 0xE0);
            bytes.push(((c >> 6) & 0x3F) | 0x80);
            bytes.push((c & 0x3F) | 0x80);
        } else if(c >= 0x000080 && c <= 0x0007FF) {
            bytes.push(((c >> 6) & 0x1F) | 0xC0);
            bytes.push((c & 0x3F) | 0x80);
        } else {
            bytes.push(c & 0xFF);
        }
    }
    return bytes;


}

bytes2String

 function bytes2String(arr) {
    if(typeof arr === 'string') {
        return arr;
    }
    var str = '',
        _arr = arr;
    for(var i = 0; i < _arr.length; i++) {
        var one = _arr[i].toString(2),
            v = one.match(/^1+?(?=0)/);
        if(v && one.length == 8) {
            var bytesLength = v[0].length;
            var store = _arr[i].toString(2).slice(7 - bytesLength);
            for(var st = 1; st < bytesLength; st++) {
                store += _arr[st + i].toString(2).slice(2);
            }
            try {
                str += String.fromCharCode(parseInt(store, 2));
            } catch (error) {
                str += parseInt(store, 2).toString(); 
                console.log(error);
            }
            
            i += bytesLength - 1;
        } else {
            try {
                str += String.fromCharCode(_arr[i]);
            } catch (error) {
                str += parseInt(store, 2).toString(); 
                console.log(error);
            }
            
        }
    }
    return str;
}

這個腳本沒問題,在chrome的console中隨便用,但是在frida script中如果不是在unicode的解碼範圍內會報錯,所以我直接用的python解的byte2string.後面那個通過java string函數的轉換可以直接用BytesToString

BytesToString

    function bytesToString(value) {
        var buffer = Java.array('byte', value);
        var StringClass = Java.use('java.lang.String');
        return StringClass.$new(buffer);
    }

char[]

Java.openClassFile("/data/local/tmp/r0gson.dex").load();
const gson = Java.use('com.r0ysue.gson.Gson');

Java.use("java.lang.Character").toString.overload('char').implementation = function(char){
    var result = this.toString(char);
    console.log("char,result",char,result);
    return result;
}

Java.use("java.util.Arrays").toString.overload('[C').implementation = function(charArray){
    var result = this.toString(charArray);
    console.log("charArray,result:",charArray,result)
    console.log("charArray Object Object:",gson.$new().toJson(charArray));
    return result;
}

byte[]

使用google的gson庫,來輔助構造,為防止app已包含此庫並混淆,干擾我們調用,將該庫提取出來,改名。

下載鏈接://raw.githubusercontent.com/r0ysue/AndroidSecurityStudy/master/FRIDA/r0gson.dex.zip
把文件push到系統和frida-server放在一起

使用方法:

Java.openClassFile("/data/local/tmp/r0gson.dex").load();
const gson = Java.use('com.r0ysue.gson.Gson');

Java.use("java.util.Arrays").toString.overload('[B').implementation = function(byteArray){
    var result = this.toString(byteArray);
    console.log("byteArray,result):",byteArray,result)
    console.log("byteArray Object Object:",gson.$new().toJson(byteArray));
    return result;
}

ByteBuffer

Java.openClassFile("/data/local/tmp/r0gson.dex").load();
const gson = Java.use('com.r0ysue.gson.Gson');

my_class.b.overload('java.nio.ByteBuffer', 'java.nio.ByteBuffer').implementation = function(x0,x1) {

    var result = this.b(x0,x1);
    //Suppose the byte[] in the Bytebuffer has a key value of 'hb'.
    var bytesArray = bytesBuffer2bytesArray(x0, 'hb');
    var tmp = gson.$new().toJson(x0);
    tmp = JSON.parse(tmp);
    console.log("tmp:"+typeof(tmp));
    console.log("decrypt --> message: ",tmp["backingArray"]);
    // console.log("decrypt --> message*hexdump: ",hexdump(gson.$new().toJson(x0)["backingArray"]));
    // console.log("decrypt --> decryptTo: ",gson.$new().toJson(x1));
    // console.log("decrypt --> decryptTo*hexdump: ",hexdump(gson.$new().toJson(x1)["backingArray"]));
    return result;
}


function bytesBuffer2bytesArray(bytesBuffer, key) {
    Java.openClassFile("/data/local/tmp/r0gson.dex").load();
    const gson = Java.use('com.r0ysue.gson.Gson');
    var tmp = gson.$new().toJson(bytesBuffer);
    var dataKey = key
    tmp = JSON.parse(tmp);
    tmp = new Array(tmp[dataKey]);
    tmp = tmp.toString();
    var tmp_array = tmp.split(",");
    var tmp_int_array=[];
    tmp_int_array=tmp_array.map(function(data){  
        return +data;  
    });  
    return tmp_int_array
}

example

下面給一個樣例,hook的xxqg的chacha20poly1305演算法。

python:

import time
import frida
import sys
import binascii


def on_message(message , data): #定義錯誤處理
    if message['type'] == 'send':
        # print("[*] {0}".format(message['payload']))
        print()
        hex_str = message['payload']
        hex_str_len = len(hex_str)
        
        // 這個包尾部的0x00有點多,看著不舒服,所以這個是去除尾部00的空bytebuffer的
        n_0 = 0
        for k in hex_str[::-1]:
            if k == '0':
                n_0 += 1
            else:
                break
        
        print(hex_str[0:hex_str_len-n_0])
        n_0 = n_0//2
        hex_str_remove_zero = hex_str
        for i in range(0,hex_str_len//2-n_0):
            // 這裡是核心的bytes[] to string,這裡一個個字元解,以及try/catch都是為了防止不可見編碼影響效果
            try:
                hex0 = hex_str[2*i:2*i+2].encode('utf-8')
                str_bin = binascii.unhexlify(hex0)
                print(str_bin.decode('utf-8'),end="")   
            except:
                pass

        # file.write("[*] {0}".format(message['payload']))
    else:
        print(message)
        # file.write(message)

# 連接Android機上的frida-server
device = frida.get_usb_device()
# 啟動app
pid = device.spawn(["cn.xuexi.android"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
# 載入腳本
with open("./xxqg.js",encoding='UTF-8') as f:
    script = session.create_script(f.read())

script.on("message" , on_message) #調用錯誤處理
script.load()

# 腳本會持續運行等待輸入
sys.stdin.read()

JavaScript:

console.log("Script loaded successfully ");
Java.perform(function x() {
    
    console.log("Inside java perform function");
    
    var my_class = Java.use("com.laiwang.protocol.android.x");
    console.log("Java.Use.Successfully!");//定位類成功!
    //在這裡更改類的方法的實現(implementation)
    console.log("Inside java perform function");
    
    Java.openClassFile("/data/local/tmp/r0gson.dex").load();
    const gson = Java.use('com.r0ysue.gson.Gson');

    my_class.b.overload('java.nio.ByteBuffer', 'java.nio.ByteBuffer').implementation = function(x0,x1) {

        var result = this.b(x0,x1);
        var bytesArray = bytesBuffer2bytesArray(x1, 'hb');
        send(bytes2Hex_nin_zero(bytesArray));

        return result;
    }


    my_class.a.overload('java.nio.ByteBuffer', 'java.nio.ByteBuffer').implementation = function(x0,x1) {

        var result = this.a(x0,x1);
        var bytesArray = bytesBuffer2bytesArray(x0, 'hb');
        send(bytes2Hex_nin_zero(bytesArray));

        return result;
    }


})


function bytesBuffer2bytesArray(bytesBuffer, key) {
    
    Java.openClassFile("/data/local/tmp/r0gson.dex").load();
    const gson = Java.use('com.r0ysue.gson.Gson');
    var tmp = gson.$new().toJson(bytesBuffer);
    var dataKey = key
    tmp = JSON.parse(tmp);
    tmp = new Array(tmp[dataKey]);
    tmp = tmp.toString();
    var tmp_array = tmp.split(",");
    var tmp_int_array=[];
    tmp_int_array=tmp_array.map(function(data){  
        return +data;  
    });  
    return tmp_int_array
}

function bytes2Hex(arrBytes){
    
    var str = "";
    for (var i = 0; i < arrBytes.length; i++) {
        var tmp;
        var num = arrBytes[i];
        if (num < 0) {
            //此處填坑,當byte因為符合位導致數值為負時候,需要對數據進行處理
            tmp = (255 + num + 1).toString(16);
        } else {
            tmp = num.toString(16);
        }
        if (tmp.length == 1) {
            tmp = "0" + tmp;
        }
        if(i>0){
            str += " "+tmp;
        }else{
            str += tmp;
        }
    }
    return str;
}

function bytes2Hex_nin_zero(arrBytes){
    
    var str = "";
    for (var i = 0; i < arrBytes.length; i++) {
        var tmp;
        var num = arrBytes[i];
        if (num < 0) {
            //此處填坑,當byte因為符合位導致數值為負時候,需要對數據進行處理
            tmp = (255 + num + 1).toString(16);
        } else {
            tmp = num.toString(16);
        }
        if (tmp.length == 1) {
            tmp = "0" + tmp;
        }
        if(i>0){
            str += ""+tmp;
        }else{
            str += tmp;
        }
    }
    return str;
}

function string2Bytes(str) {
    
    var bytes = new Array();
    var len, c;
    len = str.length;
    for(var i = 0; i < len; i++) {
        c = str.charCodeAt(i);
        if(c >= 0x010000 && c <= 0x10FFFF) {
            bytes.push(((c >> 18) & 0x07) | 0xF0);
            bytes.push(((c >> 12) & 0x3F) | 0x80);
            bytes.push(((c >> 6) & 0x3F) | 0x80);
            bytes.push((c & 0x3F) | 0x80);
        } else if(c >= 0x000800 && c <= 0x00FFFF) {
            bytes.push(((c >> 12) & 0x0F) | 0xE0);
            bytes.push(((c >> 6) & 0x3F) | 0x80);
            bytes.push((c & 0x3F) | 0x80);
        } else if(c >= 0x000080 && c <= 0x0007FF) {
            bytes.push(((c >> 6) & 0x1F) | 0xC0);
            bytes.push((c & 0x3F) | 0x80);
        } else {
            bytes.push(c & 0xFF);
        }
    }
    return bytes;
}

 function bytes2String(arr) {
   
     if(typeof arr === 'string') {
        return arr;
    }
    var str = '',
        _arr = arr;
    for(var i = 0; i < _arr.length; i++) {
        var one = _arr[i].toString(2),
            v = one.match(/^1+?(?=0)/);
        if(v && one.length == 8) {
            var bytesLength = v[0].length;
            var store = _arr[i].toString(2).slice(7 - bytesLength);
            for(var st = 1; st < bytesLength; st++) {
                store += _arr[st + i].toString(2).slice(2);
            }
            try {
                str += String.fromCharCode(parseInt(store, 2));
            } catch (error) {
                str += parseInt(store, 2).toString(); 
                console.log(error);
            }
            
            i += bytesLength - 1;
        } else {
            try {
                str += String.fromCharCode(_arr[i]);
            } catch (error) {
                str += parseInt(store, 2).toString(); 
                console.log(error);
            }
            
        }
    }
    return str;
}


hexdump

var _fillUp = function (value, count, fillWith) {
    var l = count - value.length;
    var ret = "";
    while (--l > -1)
        ret += fillWith;
    return ret + value;
}


hexdump = function (arrayBuffer, offset, length) {

    var view = new DataView(arrayBuffer);
    offset = offset || 0;
    length = length || arrayBuffer.byteLength;

    var out = _fillUp("Offset", 8, " ") + "  00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n";
    var row = "";
    for (var i = 0; i < length; i += 16) {
        row += _fillUp(offset.toString(16).toUpperCase(), 8, "0") + "  ";
        var n = Math.min(16, length - offset);
        var string = "";
        for (var j = 0; j < 16; ++j) {
            if (j < n) {
                var value = view.getUint8(offset);
                string += value >= 32 ? String.fromCharCode(value) : ".";
                row += _fillUp(value.toString(16).toUpperCase(), 2, "0") + " ";
                offset++;
            }
            else {
                row += "   ";
                string += " ";
            }
        }
        row += " " + string + "\n";
    }
    out += row;
    return out;
};

列印memorybuffer

    // 列印記憶體
    var view = new DataView(this.context.r0.readByteArray(12));
    var value = '0x' + view.getUint8(11).toString(16) + view.getUint8(10).toString(16) + view.getUint8(9).toString(16) + view.getUint8(8).toString(16);

列印non-ascii

//api-caller.com/2019/03/30/frida-note/#non-ascii
類名非ASCII字元串時,先編碼列印出來, 再用編碼後的字元串去 hook.

//場景hook cls.forName尋找目標類的classloader。
    cls.forName.overload('java.lang.String', 'boolean', 'java.lang.ClassLoader').implementation = function (arg1, arg2, arg3) {
        var clsName = cls.forName(arg1, arg2, arg3);
        console.log('oriClassName:' + arg1)
        var base64Name = encodeURIComponent(arg1)
        console.log('encodeName:' + base64Name);
        //通過日誌確認base64後的非ascii字元串,下面對比並列印classloader
        //clsName為特殊字元o.ÎÉ«
        if ('o.%CE%99%C9%AB' == base64Name) {
            //列印classloader
            console.log(arg3);
        }
        return clsName;
    }

hook enum

enum Signal {
    GREEN, YELLOW, RED
}
public class TrafficLight {
    public static Signal color = Signal.RED;
    public static void main() {
        Log.d("4enum", "enum "+ color.getClass().getName().toString());
        switch (color) {
            case RED:
                color = Signal.GREEN;
                break;
            case YELLOW:
                color = Signal.RED;
                break;
            case GREEN:
                color = Signal.YELLOW;
                break;
        }
    }
}
Java.perform(function(){
        Java.choose("com.r0ysue.a0526printout.Signal",{
            onMatch:function(instance){
                console.log("instance.name:",instance.name());
                console.log("instance.getDeclaringClass:",instance.getDeclaringClass());                
            },onComplete:function(){
                console.log("search completed!")
            }
        })
    })

列印hash map

Java.perform(function(){
        Java.choose("java.util.HashMap",{
            onMatch:function(instance){
                if(instance.toString().indexOf("ISBN")!= -1){
                    console.log("instance.toString:",instance.toString());
                }
            },onComplete:function(){
                console.log("search complete!")
            }
        })
    })

參數構造

Java array構造

如果不只是想列印出結果,而是要替換原本的參數,就要先自己構造出一個charArray,使用Java.arrayAPI

image-20201023092221628

Java.use("java.util.Arrays").toString.overload('[C').implementation = function(charArray){
    var newCharArray = Java.array('char', [ '一','去','二','三','里' ]);
    var result = this.toString(newCharArray);
    console.log("newCharArray,result:",newCharArray,result)
    console.log("newCharArray Object Object:",gson.$new().toJson(newCharArray));
    var newResult = Java.use('java.lang.String').$new(Java.array('char', [ '煙','村','四','五','家']))
    return newResult;
}

類的多態:轉型/Java.cast

可以通過getClass().getName().toString()來查看當前實例的類型。
找到一個instance,通過Java.cast來強制轉換對象的類型。

image-20201023092503515

public class Water { // 水 類
    public static String flow(Water W) { // 水 的方法
        // SomeSentence
        Log.d("2Object", "water flow: I`m flowing");
        return "water flow: I`m flowing";
    }

    public String still(Water W) { // 水 的方法
        // SomeSentence
        Log.d("2Object", "water still: still water runs deep!");
        return "water still: still water runs deep!";
    }
}
...
public class Juice extends Water { // 果汁 類 繼承了水類

    public String fillEnergy(){
        Log.d("2Object", "Juice: i`m fillingEnergy!");
        return "Juice: i`m fillingEnergy!";
    }
var JuiceHandle = null ;
Java.choose("com.r0ysue.a0526printout.Juice",{
    onMatch:function(instance){
        console.log("found juice instance",instance);
        console.log("juice instance call fill",instance.fillEnergy());
        JuiceHandle = instance;
    },onComplete:function(){
        console.log("juice handle search completed!")
    }
})
console.log("Saved juice handle :",JuiceHandle);
var WaterHandle = Java.cast(JuiceHandle,Java.use("com.r0ysue.a0526printout.Water"))
console.log("call Waterhandle still method:",WaterHandle.still(WaterHandle));

interface/Java.registerClass

frida可以構建一個新的class

image-20201023094427053

public interface liquid {
    public String flow();
}

首先獲取要實現的interface,然後調用registerClass來實現interface。

Java.perform(function(){
    var liquid = Java.use("com.r0ysue.a0526printout.liquid");
    var beer = Java.registerClass({
        name: 'com.r0ysue.a0526printout.beer',
        implements: [liquid],
        methods: {
            flow: function () {
                console.log("look, beer is flowing!")
                return "look, beer is flowing!";
            }
        }
    });
    console.log("beer.bubble:",beer.$new().flow())      
})