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)

# 连接安卓机上的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())      
})