Frida 实战:Android JNI 数组 (jobjectArray) 操作全流程解析

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

前言

在 Android 的 Native 层,Java 中的 Object[] 类型参数会以 jobjectArray 的形式传递到 C/C++ 代码中。

与 JS 数组不同,你不能直接对 jobjectArray 进行索引访问或直接操作其元素。要获取或修改其中的内容,必须借助 JNI 提供的接口,例如获取数组长度、读取单个元素或创建新的数组等操作。

env.js

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

github.com/frida/frida...

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

bash 复制代码
let env = Java.vm.tryGetEnv()

文档:frida.re/docs/javasc...

获取数组长度

ini 复制代码
let arrLen = env.getArrayLength(objArray)
console.log('array length is: ' + arrLen);

元素类型判断

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

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

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

}

获取数组元素

ini 复制代码
let element = env.getObjectArrayElement(objArray, i)

Int 元素读取

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

Long 元素读取

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

Float 元素读取

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

Double 元素读取

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

字符串 元素读取

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

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

或者

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

Object 元素读取

javascript 复制代码
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 中所有元素

javascript 复制代码
/**
 * 打印 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 传参

javascript 复制代码
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))
    });
})

执行脚本

r 复制代码
frida -H 127.0.0.1:1234 -F -l hook_native_func.js

输出如下:

vbnet 复制代码
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 数组

javascript 复制代码
/**
 * 解析 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

javascript 复制代码
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);
            }
        }
    });
}

执行脚本

r 复制代码
frida -H 127.0.0.1:1234 -F -l hook_native_func.js

输出如下:

vbnet 复制代码
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

ini 复制代码
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);
    })
}

调用输出如下:

vbnet 复制代码
[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+...
相关推荐
阿巴斯甜7 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker7 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95278 小时前
Andorid Google 登录接入文档
android
黄林晴10 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android