Frida05 - 高级API用法

参考文档

https://api-caller.com/2019/03/30/frida-note/

https://frida.re/docs/javascript-api/#frida

数组打印

测试代码:

private static class Bean {
    String a;
    int b;
    float c;
}

private void test() {
    Bean[] beans = new Bean[3];
    beans[0] = new Bean();
    beans[0].a = "a";
    beans[0].b = 1;
    beans[0].c = 1f;

    beans[1] = new Bean();
    beans[1].a = "b";
    beans[1].b = 2;
    beans[1].c = 2f;

    beans[2] = new Bean();
    beans[2].a = "c";
    beans[2].b = 2;
    beans[2].c = 3f;

    Arrays.toString(beans);
}

想要 hook Arrays.toString() 方法很简单:

function main() {
    Java.perform(function () {

        Java.use("java.util.Arrays").toString.overload('[Ljava.lang.Object;').implementation = function (x) {
            var result = this.toString(x);
            console.log("params=", x);
            console.log("result=", result)
            return result
        }

    })
}

setImmediate(main)

输出的结果很多:

可以看到,输出的数组里是一个对象,那有没有什么好办法将对象转成字符串显示出来呢?

答案就是Gson,所以使用开发的思想来做逆向是很重要的。

项目里面不一定引入了Gson,所以我们需要自己编译一个gons库,放到手机里面,然后使用frida加载一下就可以使用了。

加载外部DEX

var dexPath = "/data/local/tmp/r0gson.dex";
Java.openClassFile(dexPath).load();

已经有大佬编译好了的dex,我们可以直接用:

https://github.com/r0ysue/AndroidSecurityStudy/blob/master/FRIDA/r0gson.dex.zip

为了避免类重复,还特意换了包名,具体可看:

https://bbs.kanxue.com/thread-259186.htm

重新写脚本:

function main() {
    Java.perform(function () {

        Java.use("java.util.Arrays").toString.overload('[Ljava.lang.Object;').implementation = function (x) {
            var result = this.toString(x);
            if (x.length > 0 && x[0].getClass().getName() == "com.example.demo2.MainActivity$Bean") {
                Java.openClassFile("/data/local/tmp/r0gson.dex").load();
                const gsonClass = Java.use('com.r0ysue.gson.Gson');
                for (var i=0; i<x.length; i++) {
                    console.log("entry=", gsonClass.$new().toJson(x[i]));
                }
            }
            return result;
        }

    })
}

setImmediate(main)

看下输出结果:

Spawned `com.example.demo2`. Resuming main thread!                      
[Pixel::com.example.demo2]-> entry= {"a":"a","b":1,"c":1.0}
entry= {"a":"b","b":2,"c":2.0}
entry= {"a":"c","b":2,"c":3.0}

构造数组

有时候为了替换返回值,需要一个数组类型,可以使用如下方式,构造一个数组:

const values = Java.array('int', [ 1003, 1005, 1007 ]);

const JString = Java.use('java.lang.String');
const str = JString.$new(Java.array('byte', [ 0x48, 0x65, 0x69 ]));

类型转换

测试代码:

Father father = new Father();
Son son = new Son();
Father father2 = new Son();

脚本:

Java.choose('com.example.demo2.Father', {
    onMatch: function (instance) {
        console.log('found instance', instance);
        var son = Java.cast(instance, Java.use('com.example.demo2.Son'))
        console.log('cast instance', son.test());
    }, onComplete: function () { }

})

Java.choose('com.example.demo2.Son', {
    onMatch: function (instance) {
        console.log('found instance', instance);
        var father = Java.cast(instance, Java.use('com.example.demo2.Father'))
        console.log('cast instance', father.test());
    }, onComplete: function () { }

})

输出结果:

found instance com.example.demo2.Father@daf1aad
found instance com.example.demo2.Son@c7d41e2
cast instance Son
found instance com.example.demo2.Son@2341973
cast instance Son
Error: Cast from 'com.example.demo2.Father' to 'com.example.demo2.Son' isn't possible
    at cast (frida/node_modules/frida-java-bridge/lib/class-factory.js:131)
    at cast (frida/node_modules/frida-java-bridge/index.js:270)
    at onMatch (/demo2.js:20)
    at _chooseObjectsArtPreA12 (frida/node_modules/frida-java-bridge/lib/class-factory.js:298)
    at <anonymous> (frida/node_modules/frida-java-bridge/lib/class-factory.js:250)
    at vt (frida/node_modules/frida-java-bridge/lib/android.js:573)

符合直觉,子类可以转成父类类型,但是调用的方法还是子类的。

注册一个类

有时候,我们想做一个AOPhook的时候,就需要实现一个接口,我们可以使用 registerClass 方法来做到。

测试代码:

public interface IBook {

    String id();

    int size();

    boolean test(int input);

}

// -------------------------

public class SimpleBook implements IBook {

    @Override
    public String id() {
        return UUID.randomUUID().toString();
    }

    @Override
    public int size() {
        return 100;
    }

    @Override
    public boolean test(int input) {
        Log.e("SimpleBook", "input = " + input);
        return false;
    }

}

// -------------------------

IBook book = new SimpleBook();

脚本代码:

Java.registerClass({
    name: 'com.example.demo2.SimpleBook2',
    implements: [Java.use('com.example.demo2.IBook')],
    fields: {
        proxy: 'com.example.demo2.IBook',
    },
    methods: {
        '<init>': [{
            returnType: 'void',
            argumentTypes: ['com.example.demo2.IBook'],
            implementation: function (proxy) {
                this.$super.$init();
                this.proxy.value = proxy;
            }
        }],
        id() {
            return this.proxy.value.id();
        },
        size() {
            return this.proxy.value.size();
        },
        test(input) {
            this.proxy.value.test(input);
            return true;
        },
    }
})

Java.choose('com.example.demo2.MainActivity', {
    onMatch: function (instance) {
        console.log('found MainActivity instance', instance);
        var oldBook = instance.book.value;
        instance.book.value = Java.use('com.example.demo2.SimpleBook2').$new(oldBook);
        console.log('book test id = ', instance.book.value.id());
        console.log('book test size = ', instance.book.value.size());
        console.log('book test result = ', instance.book.value.test(1));
    }, onComplete: function () { }

})

这里我们注册了一个类,com.example.demo2.SimpleBook2,并且实现了一个代理类,将 MainActivity 的字段替换之后,我们就可以让代理类来托管逻辑,做很多操作。

打印Map

与开发时的写法是一样的:

var result = "";
var keyset = map.keySet();
var it = keyset.iterator();
while(it.hasNext()){
    var keystr = it.next().toString();
    var valuestr = map.get(keystr).toString();
    result += valuestr;
}

Non-ASCII 方法名处理

比如说

int ֏(int x) {
    return x + 100;
}

甚至有一些不可视, 所以可以先编码打印出来, 再用编码后的字符串去 hook。

var methods = Java.use('com.example.demo2.MainActivity').class.getDeclaredMethods();
for (var i in methods) {
    console.log('origin method name -> ' + methods[i].toString());
    console.log('encode method name ->' + encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1")));
}

Java.use('com.example.demo2.MainActivity')[decodeURIComponent("%D6%8F")].implementation = function() {
    console.log('method invoke');
    return 200;
}

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值