版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

在 native 层,Object[] 类型参数会以 jobjectArray 形式传递进来。你不能直接拿它当作 JS 数组来访问,必须通过 JNI 的方式逐个取出。

env.js

常用的 JNI 函数在 frida 的 env.js 中都已经定义好了

https://github.com/frida/frida-java-bridge/blob/main/lib/env.js

word/media/image1.png

通过下面代码获取 JNIEnv 引用,就可以调用相关的 JNI 函数

let env = Java.vm.tryGetEnv()

word/media/image2.png

文档:https://frida.re/docs/javascript-api/

获取数组长度

let arrLen = env.getArrayLength(objArray)
console.log('array length is: ' + arrLen);

元素类型判断

通过 getObjectClassName 可以获取到对象的类名进而判断该元素的类型。

// 获取对象的类名
let className = env.getObjectClassName(objArray)
console.log('className: ' + className);

// 判断是否 jobjectArray
if (className === '[Ljava.lang.Object;') {

}

获取数组元素

let element = env.getObjectArrayElement(objArray, i)

Int 元素读取

let intElement = Java.cast(env.getObjectArrayElement(objArray, i), Java.use('java.lang.Integer'))
console.log(`element ${i} value: ${intElement}`);

Long 元素读取

let longElement = Java.cast(env.getObjectArrayElement(objArray, i), Java.use('java.lang.Long'))
console.log(`element ${i} value: ${longElement}`);

Float 元素读取

let floatElement = Java.cast(env.getObjectArrayElement(objArray, i), Java.use('java.lang.Float'))
console.log(`element ${i} value: ${floatElement}`);

Double 元素读取

let doubleElement = Java.cast(env.getObjectArrayElement(objArray, i), Java.use('java.lang.Double'))
console.log(`element ${i} value: ${doubleElement}`);

字符串 元素读取

通过 env.js 中定义的 stringFromJni 函数可以直接获取到字符串对象的值

word/media/image3.png

let stringElement = env.stringFromJni(env.getObjectArrayElement(objArray, i))
console.log(`element ${i} value: ${stringElement}`);

或者

let stringElement = Java.cast(env.getObjectArrayElement(objArray, i), Java.use('java.lang.String'))
console.log(`element ${i} value: ${stringElement}`);

Object 元素读取

let element = env.getObjectArrayElement(objArray, i)

let elementClassName = env.getObjectClassName(element)

// 元素类型转换
let castElement = Java.cast(element, Java.use(elementClassName))

console.log(`element ${i} value: ${castElement}`);

打印 jobjectArray

打印 jobjectArray 中所有元素

/**
 * 打印 jobjectArray 中所有元素
 * 
 * @param objArray
 * @returns {string|null}
 */
function printObjectArray(objArray) {
    if (objArray.isNull()) {
        console.log('Object array is null');
        return null;
    }

    // 获取 JNIEnv
    let env = Java.vm.tryGetEnv();
    let className = env.getObjectClassName(objArray);

    // 不是 jobjectArray,则直接打印类型
    if (!className.startsWith('[L')) {
        return `Argument is not a jobjectArray, actual type: ${className}`;
    }

    let arrLen = env.getArrayLength(objArray);
    let result = `Object array of type ${className}, length: ${arrLen}\n`;

    for (let i = 0; i < arrLen; i++) {
        let element = env.getObjectArrayElement(objArray, i);
        let elementClassName = env.getObjectClassName(element);
        let castElement = Java.cast(element, Java.use(elementClassName));

        result += `  [${i}] ${elementClassName}: ${castElement}\n`;
    }

    return result.trim() + '\n';
}

hook native 函数并打印 jobjectArray 传参

function hook_native_func(targetAddress) {
    // Hook 目标地址
    Interceptor.attach(targetAddress, {
        onEnter: function (args) {
            this.log = 'Entering native function at: ' + targetAddress + '\n';
            this.log += printObjectArray(args[2])
        },

        onLeave: function (retval) {
            // 检查是否包含 "283"
            if (this.log.includes("283") && !retval.isNull()) {
                // 类型转换
                let className = Java.vm.tryGetEnv().getObjectClassName(retval)
                retval = Java.cast(retval, Java.use(className));
            }
            this.log += 'Leaving native function,retval: ' + retval
            console.log(this.log);
        }
    });
}

setImmediate(function () {
    Java.perform(function () {
        var baseAddress = Module.findBaseAddress("libGameVMP.so");
        hook_native_func(baseAddress.add(0xdfa8))
    });
})

执行脚本

frida -H 127.0.0.1:1234 -F -l hook_native_func.js

输出如下:

Entering native function at: 0x7802455fa8
Object array of type [Ljava.lang.Object;, length: 3
  [0] java.lang.Integer: 283
  [1] com.shizhuang.duapp.modules.app.DuApplication: com.shizhuang.duapp.modules.app.DuApplication@d3fdf19
  [2] java.lang.String: cipherParamuserNamecountryCode86loginTokenpasswordca85e501ec201e140c97d4480a724cffplatformandroidtimestamp1243532540699typepwduserName4860cc943262bab5ef4712e3bf0db355_1uuidac6abb3d17c8fb63v5.43.0
Leaving native function,retval: dWWoXlbR3K87j2N27Dkv4uOPUnOIN8Kof9Gm2x1kil7S/jpBEVaMS8QgdCHBIMPhVX/bK7s5MFUyLCOl
B7InMGNA682aYZfSsu0VK8TERMuSq3Bg3C3ATNGKaJPVMWtogFXteBS1/CxbFUdhtv0v1U8zrQCT6QLeaQvM8nBmXDKSOvivdG7xhLLNWmSGP8gdVL0CuAKDFH2Xj9Krb/0jNsPgNnA==

jobjectArray 转换 JS 数组

封装一个方法将解析 jobjectArray 并返回元素转换为真实类型后的 JS 数组

/**
 * 解析 jobjectArray 并返回元素转换为真实类型后的 JS 数组
 *
 * @param objArray          jobjectArray
 * @returns {array|null}    数组
 */
function parseObjectArray(objArray) {
    if (objArray.isNull()) {
        console.log('Object array is null');
        return null;
    }

    // 获取 JNIEnv
    let env = Java.vm.tryGetEnv();
    let className = env.getObjectClassName(objArray);

    // 不是 jobjectArray
    if (!className.startsWith('[L')) {
        console.log(`Argument is not a jobjectArray, actual type: ${className}`);
        return null;
    }

    let arrLen = env.getArrayLength(objArray);
    let result = []

    for (let i = 0; i < arrLen; i++) {
        let element = env.getObjectArrayElement(objArray, i);
        let elementClassName = env.getObjectClassName(element);
        let castElement = Java.cast(element, Java.use(elementClassName));

        result.push(castElement)
    }

    return result;
}

比如 jobjectArray 中第一个元素是 java.lang.Integer,经过 parseObjectArray 后可以直接访问元素中的 intValue() 方法判断 值是否等于 283

function hook_native_func(targetAddress) {
    // Hook 目标地址
    Interceptor.attach(targetAddress, {
        onEnter: function (args) {
            let arr = parseObjectArray(args[2])
            // 判断数组中第一个元素是否 283
            if (arr[0].intValue() === 283) {
                this.log = 'Entering native function at: ' + targetAddress + '\n';
                this.log += printObjectArray(args[2])
            }
        },

        onLeave: function (retval) {
            if (this.log) {
                if (!retval.isNull()) {
                    let className = Java.vm.tryGetEnv().getObjectClassName(retval)
                    retval = Java.cast(retval, Java.use(className));
                }
                this.log += 'Leaving native function,retval: ' + retval
                console.log(this.log);
            }
        }
    });
}

执行脚本

frida -H 127.0.0.1:1234 -F -l hook_native_func.js

输出如下:

Entering native function at: 0x6f22493fa8
Object array of type [Ljava.lang.Object;, length: 3
  [0] java.lang.Integer: 283
  [1] com.abc.duapp.modules.app.DuApplication: com.abc.duapp.modules.app.DuApplication@b68b4ae
  [2] java.lang.String: cipherParamuserNamecountryCode86loginTokenpassword675d0c16c532e8dc96ab17490beplatformandroidtimestamp2195743174404typepwduserNamef573fa1fa140cf340018011db67c963cd733_1uuid84c3a328fb63819bv5.43.0
Leaving native function,retval: dWWoXlbR3K87j2N27Dkv4uOPUnOsh...

创建 jobjectArray

通过 frida 创建 jobjectArray 填充数据,调用 NativeFunction

function NCall_IL() {
    Java.perform(function () {
        let targetAddress = Module.findBaseAddress("libGameVMP.so").add(0xdfa8)

        let IL = new NativeFunction(
            ptr(targetAddress),                 // 函数地址
            'pointer',                          // 返回值类型:jstring
            ['pointer', 'pointer', 'pointer']   // 参数类型列表(JNIEnv* , jclass, jobjectArray)
        );

        // 获取 env 和 jclass
        const env = Java.vm.tryGetEnv();
        const clazz = env.findClass("java.lang.Object");

        // 构造元素
        const arg0 = 283;
        const arg1 = Java.use("com.cyrus.duapp.modules.app.DuApplication").instance.value; // 读取 static 字段 instance
        const arg2 = "cipherParamuserNamecoun...";

        // 获取 java.lang.Object class (jclass)
        const objectClass = env.findClass("java.lang.Object");

        // 创建一个长度为 3 的 jobjectArray(即 Object[])
        const arrayLength = 3;
        const objectArray = env.newObjectArray(arrayLength, objectClass, ptr(0));

        // int 参数
        const intArg = Memory.alloc(4);
        intArg.writeS32(arg0);

        // 填充数据
        env.setObjectArrayElement(objectArray, 0, intArg);
        env.setObjectArrayElement(objectArray, 1, arg1.$handle); //  $handle 是 Java 层对象的 native JNI 指针表示
        env.setObjectArrayElement(objectArray, 2, env.newStringUtf(arg2));

        // 调用
        let result = IL(env.handle, clazz, objectArray);
        console.log("函数返回值:", result);
    })
}

调用输出如下:

[Remote::CYRUS]-> NCall_IL()
Entering native function at: 0x6f22499fa8
Object array of type [Ljava.lang.Object;, length: 3
  [0] java.lang.Integer: 283
  [1] com.cyrus.duapp.modules.app.DuApplication: com.cyrus.duapp.modules.app.DuApplication@89c9f57
  [2] java.lang.String: appKey1e4e9a461f9b4fb09d6a4ae12c1eca83loginTokenplatformandroidsymbol...
Leaving native function,retval: AC8aG5GIwLORLMYNGmc6BE2c3IgGXgoBn3fqYpySA+...