title: frida打印与参数构造

categories: 逆向与协议分析

toc: true

mathjax: true

tags:

  • frida
  • HOOK
  • 逆向

    widgets:
  • type: toc

    position: left
  • type: profile

    position: left

    author: Runope

    Author title

    author_title: 不知不论,不做不论

    Author's current location

    location: Nanjin,jiangsu

    URL or path to the avatar image

    avatar: https://en.gravatar.com/userimage/194935117/7129e2095de79a9dd97e5cc344acaba2?size=200

    Whether show the rounded avatar image

    avatar_rounded: false

    Email address for the Gravatar

    gravatar: 275358499@qq.com

    URL or path for the follow button

    follow_link: 'https://github.com/runope'

  • type: recent_posts

    position: left

本文将系统讨论,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已包含此库并混淆,干扰我们调用,将该库提取出来,改名。

下载链接:https://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

https://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

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来强制转换对象的类型。

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

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())
})

最新文章

  1. tomcat处理中文文件名的访问(乱码)
  2. Windows 10 RTM 官方正式版
  3. mysql新建用户的方法
  4. JAVA程序改错 (易错题)
  5. android linker (1) —— __linker_init()
  6. Objective-C 协议(接口)
  7. dump报文转换为wrieshark报文
  8. Vue中应用CORS实现AJAX跨域,及它在 form data 和 request payload 的小坑处理
  9. JavaWeb学习总结(二)——Tomcat服务器学习和使用(一)(转)
  10. ubuntu 13.10 install wireshark
  11. 使用scrapy爬虫,爬取17k小说网的案例-方法一
  12. github爬虫100项目
  13. Far manager界面混乱问题解决
  14. jenkins 多版本 jdk
  15. 网站JS控制的QQ悬浮
  16. Unity3d之树木创建的参数设定
  17. Python:安装MYSQL Connector
  18. Usbhub驱动编译
  19. 洛谷 P5078 Tweetuzki 爱军训
  20. AndroidManifest.xml中的注册组件

热门文章

  1. P3378 堆(模板)
  2. Java知识系统回顾整理01基础04操作符07Scanner
  3. VS2013 c++ 生成和调用DLL动态链接库(.def 方法已验证OK)
  4. 安装haproxy
  5. windows上启动docker容器报错:standard_init_linux.go:211: exec user process caused “no such file or directory” - Docker
  6. 磁盘 IOPS(每秒读写次数) 的计算方法
  7. gorm学习地址
  8. lumen-ioc容器测试 (4)
  9. swoft 切面AOP尝试
  10. Martyr2项目实现——Number部分问题求解(3) Prime Factorization