frida 常用方法 java jni native 层操作

java to frida 类型

java type frida type
int int
float float
boolean boolean
string java.lang.String
byte [B

frida 构建不同类型的 array

java type so type
int I
float F
boolean Z
string java.lang.String
byte B
long J
short S
double D
char C

在Frida中用 [ 表示数组。
例如是 int 类型的数组,写法为:[I
如果是 String 类型的数组,则写法为: [java.lang.String;
注意:后面还有个 ; 号

frida 常用工具对应版本

frida-server= 12.9.4
frida=12.11.18
frida-tools=7.2.2
jnitrace=3.0.1
objection=1.8.4

frida-server=14.2.18
frida=14.2.18
frida-tools=9.2.5
jnitrace=3.2.2
objection=1.11.0

frida-server=15.0.18
frida=15.0.18
frida-tools=10.2.1
jnitrace=3.2.2
objection=1.11.0

frida hook

hook 构造函数

JavaClass.$init.implementation = function () {}

hook 实例化

JavaClass.$new.implementation = function () {}

hook 普通函数

JavaClass.funcA.implementation = function () {}

hook so 文件函数 - 导出符号

var native_func = Module.findExportByName("${so file name}", "${so export function name}");

Interceptor.attach(native_func, {
    // 函数开始
    onEnter: function (args) {},
    // 函数结束
    onLeave: function (return_val) {}
});

hook so 文件函数 - 偏移地址

var native_func_addr = Module.findBaseAddress("${so file name}");
var native_addr = native_func_addr.add(${so 函数偏移地址});

Interceptor.attach(native_addr, {
    // 函数开始
    onEnter: function (args) {
        // 读取 r0 的数据
        this.context.r0.readCString()
    },
    // 函数结束
    onLeave: function (return_val) {}
});

frida call

创建 new 一个实例对象

var javaString = Java.use("java.lang.String");
javaString.$new();

调用静态函数

var javaString = Java.use("java.lang.String");
javaString.valueOf(1);

调用实例函数

var javaString = Java.use("java.lang.String");
javaString.$new().toString();

frida 常用操作

frida 获取 android context

var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var context = currentApplication.getApplicationContext();

frida 调用 JNIEnv 函数

// 获取 JNIEnv
var env = Java.vm.getEnv();
var jstring = env.newStringUtf('xiaojianbang');

frida byte array to string

// 方法一
var JavaString = Java.use("java.lang.String");
JavaString.$new('byte array').toString();

// 方法二
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
// 转成 hex 在转 string
ByteString.of('byte array').hex();

frida java 类型转换

// 使用 Java.cast 把 java byet 转成 java Object
var javaBytes = Java.use('java.lang.String').$new("aaaaa").getBytes();
var javaBytesClass = Java.cast(javaBytes, Java.use('java.lang.Object')).getClass();

// 使用 Java.array 把 js array 转成 java Object array
var params = [
    Java.use('java.lang.String').$new('str1'),
    Java.use('java.lang.String').$new('str2'),
    Java.use('java.lang.Boolean').$new(false),
    Java.use('java.lang.Integer').$new(0)
];
var ps = Java.array('Ljava.lang.Object;', params);

frida 注册 java 类

Java.openClassFile('/data/local/tmp/androidAsync-2.2.1.dex').load();
const HttpServerRequestCallback = Java.use('com.koushikdutta.async.http.server.HttpServerRequestCallback');

// 构建一个默认请求
const RequestTestCallback = Java.registerClass({
    name: "RequestTestCallback",
    implements: [HttpServerRequestCallback],
    methods: {
        onRequest: function (request, response) {
            // 主动调用代码直接写这里
            response.send(JSON.stringify({
                "code": 0,
                "message": " 服务已经注册成功, 默认端口8181"
            }));
        }
    }
});

frida hook 动态 dex 切换 classLoader

// 方法一
var className = 'com.taobao.wireless.security.adapter.JNICLibrary';
Java.enumerateClassLoaders({
    onMatch: function (loader) {
        try {
            if (loader.findClass(className)) {
                Java.classFactory.loader = loader;
            }
        } catch (error) {}
    },
    onComplete: function () {}
})

// 方法二
var classApplication = Java.use('android.app.Application');
classApplication.onCreate.implementation = function () {
    Java.enumerateClassLoadersSync().forEach(function (loader) {
        try {
            if (loader.loadClass(className)) {
                Java.classFactory.loader = loader;
            }
        } catch (error) {}
    });
}

frida map 转 js json

function getMapData(mapSet) {
    try {
        var result = {};
        var key_set = mapSet.keySet();
        var it = key_set.iterator();
        while (it.hasNext()) {
            var key_str = it.next().toString();
            result[key_str] = mapSet.get(key_str).toString();
        }
        return result
    } catch (error) {
        return mapSet
    }
}

frida bytes to hex

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;
}

frida string to bytes

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;
}

frida bytes to string

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;
}

frida bytes to base64

function bytes2Base64(e) {
    var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    var r, a, c, h, o, t;
    for (c = e.length, a = 0, r = ''; a < c;) {
        if (h = 255 & e[a++], a == c) {
            r += base64EncodeChars.charAt(h >> 2),
                r += base64EncodeChars.charAt((3 & h) << 4),
                r += '==';
            break
        }
        if (o = e[a++], a == c) {
            r += base64EncodeChars.charAt(h >> 2),
                r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
                r += base64EncodeChars.charAt((15 & o) << 2),
                r += '=';
            break
        }
        t = e[a++],
            r += base64EncodeChars.charAt(h >> 2),
            r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
            r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
            r += base64EncodeChars.charAt(63 & t)
    }
    return r
}

frida hook class 的所有方法

function hookLoggerAllMethod() {
    const cls = Java.use('com.virjar.sekiro.business.api.log.SekiroLogger');
    const mhd_array = cls.class.getDeclaredMethods();

    // hook 类所有方法 (所有重载方法也要hook)
    for (var i = 0; i < mhd_array.length; i++) {
        // 当前方法签名
        const mhd_cur = mhd_array[i];
        // 当前方法名
        const str_mhd_name = mhd_cur.getName();

        // 当前方法重载方法的个数
        const n_overload_cnt = cls[str_mhd_name].overloads.length;
        console.log('n_overload_cnt: ', n_overload_cnt);

        for (var index = 0; index < n_overload_cnt; index++) {
            cls[str_mhd_name].overloads[index].implementation = function () {
                // 参数个数
                var n_arg_cnt = arguments.length;
                for (var idx_arg = 0; idx_arg < n_arg_cnt; n_arg_cnt++) {
                    console.log(arguments[idx_arg]);
                }
                console.log(str_mhd_name + ' --- ' + n_arg_cnt);
                return this[str_mhd_name].apply(this, arguments);
            }
        }
    }
}

frida js sleep

function sleep(numberMillis) {
    var now = new Date();
    var exitTime = now.getTime() + numberMillis;
    while (true) {
        now = new Date();
        if (now.getTime() > exitTime)
            return;
    }
}

// 毫秒级别
sleep(1000)

frida hook 常见加密算法

Java.perform(function () {
    var ByteString = Java.use("com.android.okhttp.okio.ByteString");
    var MessageDigest = Java.use('java.security.MessageDigest');
    var StringClass = Java.use("java.lang.String");

    // hook md5
    md.getInstance.overload("java.lang.String").implementation = function (str) {
        return this.getInstance(str)
    }
    md.update.overload('[B').implementation = function (a) {
        var result = this.update(a);
        return result;
    }
    md.digest.overload().implementation = function () {
        var result = this.digest();
        return result;
    }

    // hook aes
    var secretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');
    var iv = Java.use("javax.crypto.spec.IvParameterSpec");
    var cipher = Java.use("javax.crypto.Cipher");
    // hook aes key
    secretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (a, b) {
        console.log("secretKeySpec.$init - UTF8密钥: " + StringClass.$new(a));
        console.log("secretKeySpec.$init - hex密钥 : " + ByteString.of(a).hex());
        console.log("secretKeySpec.$init - 算法类型: " + b);
        var result = this.$init(a, b);
        return result;
    }
    // hook aes 偏移
    iv.$init.overload('[B').implementation = function (a) {
        console.log("IvParameterSpec.$init - 向量偏移量utf8: ", StringClass.$new(a));
        console.log("IvParameterSpec.$init - 向量偏移量hex: ", bytes2Hex(a))
        var result = this.$init(a);
        return result;
    }
    // hook aes 加密模式
    cipher.getInstance.overload('java.lang.String').implementation = function (a) {
        console.log("cipher.getInstance - 加密模式、填充类型: ", a)
        var result = this.getInstance(a);
        return result;
    }
    cipher.doFinal.overload('[B').implementation = function (a) {
        console.log("Cipher.doFinal - 待加密字符串: ", bytes2Hex(a))
        var result = this.doFinal(a);
        console.log("Cipher.doFinal - 加密后字符串: ", bytes2Hex(result))
        return result;
    }
})

最后

目前就搜集到这么多,后面会持续更新

暂无评论
本文作者:
本文链接: https://www.qinless.com/?p=347
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 qinless 的博客!
100

发表评论

返回顶部