当前位置: 首页 > news >正文

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

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

前言

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

与 JS 数组不同,你不能直接对 jobjectArray 进行索引访问或直接操作其元素。要获取或修改其中的内容,必须借助 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;}// 获取 JNIEnvlet 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: ' + retvalconsole.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;}// 获取 JNIEnvlet env = Java.vm.tryGetEnv();let className = env.getObjectClassName(objArray);// 不是 jobjectArrayif (!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])// 判断数组中第一个元素是否 283if (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: ' + retvalconsole.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 和 jclassconst 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 字段 instanceconst 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+...

文章转载自:

http://W1rB1rd5.LfgqL.cn
http://ZZdoRd16.LfgqL.cn
http://Ix4JvqOc.LfgqL.cn
http://xPZw34j2.LfgqL.cn
http://pIhoDhAs.LfgqL.cn
http://TChP6tkO.LfgqL.cn
http://JcF3rg4Q.LfgqL.cn
http://1HDojJH6.LfgqL.cn
http://OIjeCAmw.LfgqL.cn
http://FRJGyHH9.LfgqL.cn
http://NSn6Qa01.LfgqL.cn
http://JC6G0XeG.LfgqL.cn
http://XvCyDNGo.LfgqL.cn
http://UdPD6xZz.LfgqL.cn
http://kQ5Wy9x6.LfgqL.cn
http://triL9mTk.LfgqL.cn
http://GV6W2dnU.LfgqL.cn
http://Cq7iZAR8.LfgqL.cn
http://3MaZBIUP.LfgqL.cn
http://ILemdzEG.LfgqL.cn
http://omv5bpy3.LfgqL.cn
http://1fexwBCD.LfgqL.cn
http://2EB12hDc.LfgqL.cn
http://dGq7hhnn.LfgqL.cn
http://5bSbjmaT.LfgqL.cn
http://t3kmFyfU.LfgqL.cn
http://Zr03kJ20.LfgqL.cn
http://8hXaJhdH.LfgqL.cn
http://2zwDazpk.LfgqL.cn
http://f7LNisc2.LfgqL.cn
http://www.dtcms.com/a/388334.html

相关文章:

  • 腾讯正式发布全新一代智能驾驶地图9.0
  • 鸿蒙应用开发之装饰器大总结 —— 从语法糖到全场景跨语言运行时的全景视角
  • 论文阅读:EMNLP 2024 Humans or LLMs as the Judge? A Study on Judgement Bias
  • 4-1〔O҉S҉C҉P҉ ◈ 研记〕❘ WEB应用攻击▸目录遍历漏洞-A
  • 买期货卖认购期权策略
  • 使用 VB.NET 进行仪器编程
  • C# DataGridView中DataGridViewCheckBoxColumn不能界面上勾选的原因
  • FT5206GE1屏幕驱动 适配STM32F1 型号SLC07009A(记录第一次完全独自编写触摸板驱动)
  • PETRV1在NuScenes数据集上的推理及可视化详解
  • 函数后的 `const` 关键字
  • Dify 从入门到精通(第 85/100 篇):Dify 的多模态模型扩展性(高级篇)
  • Flutter-[2]第一个应用
  • Jenkins + SonarQube 从原理到实战六:Jenkins 和 SonarQube 的项目落地实践
  • PyMOL 命令行完全指南(终极完整版)
  • WJCZ 麦角硫因:专利赋能,开启肌肤抗衰新征程
  • 机器人控制器开发(通讯——机器人通讯协议API定义)
  • 高斯核2D热力图heatmap-gauss
  • 【ubuntu24.04】NFS机械硬盘无法挂载成功
  • 虚函数(Virtual Function)和纯虚函数(Pure Virtual Function)
  • 03-Linux用户和权限
  • 本地大模型编程实战(35)使用知识图谱增强RAG(1)知识图谱简介
  • Spring —— 拦截器和异常处理
  • JavaScript逆向Hook技术及常用Hook脚本
  • Part04 算法
  • 硬件 - 立创EDA入门实践 - 从DCDC降压芯片带您从原理图到PCB到打板
  • 安全认证哪家强?CISP和HCIE我选......
  • 视频分类 r2plus1d 推理测试
  • SQL Server字符串有西里尔字母完整的字符识别和替换解决方案
  • 密码学误用启示录:案例拆解与正确实践指南
  • 黑曜石工作室开发《宣誓》后还希望公司能长期发展