Frida 中解析 Native 层 jobjectArray 转换 JS 数组
版权归作者所有,如有转发,请注明文章出处: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
通过下面代码获取 JNIEnv 引用,就可以调用相关的 JNI 函数
let env = Java.vm.tryGetEnv()
文档: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 函数可以直接获取到字符串对象的值
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+...