Frida拦截Android原生方法后如何读取任意参数及判断类型?
关于Frida拦截JNI方法时的参数枚举与类型读取问题
假设存在地址为expfunction.address的原生方法,我用Frida的Interceptor.attach()拦截该方法调用并尝试获取参数,核心代码如下:
Interceptor.attach(expfunction.address, { onEnter(args) { console.log("\n-----\n[*] Function is called!"); for(let i in args) { ... 读取args[i]的值 ... } }, onLeave(retval) { console.log("\t\n-----\n[*] Function returns"); console.log("\t[+] Returns:", retval); } })
我现在遇到两个问题:
- 该方法的参数可能是
jboolean、jstring、jobject等任意JNI类型,如何准确判断args[i]的数据类型并正确读取其值? for(let i in args) { ... }是不是枚举args数组的正确方式?因为尝试获取args.length时会触发RangeError: invalid array index错误。
我试过两种方式,但都返回空值:
- 尝试方案1(失败)
onEnter(args) { for(let i in args) { var tmp = new NativePointer(args[i]); console.log(tmp.readPointer()); } }
- 尝试方案2(失败)
function getType(value) { return Object.prototype.toString.call(value).toLowerCase(); } ... onEnter(args) { for(let i in args) { var tmp = new NativePointer(args[i]); console.log("Type:", getType(tmp)); } }
解答
一、正确枚举args的方式
Frida中的args不是标准JavaScript数组,而是一个类数组对象,不能直接用for...in遍历(会遍历到对象的内置属性,导致非参数内容被处理),也不能直接读取length属性。
正确的做法是使用args.count()获取参数总数,再配合普通for循环遍历:
onEnter(args) { console.log("\n-----\n[*] Function is called!"); // 通过args.count()拿到参数数量 for (let i = 0; i < args.count(); i++) { const arg = args[i]; // 处理当前参数 } }
二、判断JNI参数类型并读取值
JNI参数的类型无法被Frida自动识别,需要结合函数签名或针对常见类型做针对性处理:
1. 优先使用函数签名(最准确)
如果能拿到目标函数的JNI签名(比如(ZLjava/lang/String;Ljava/lang/Object;)V),可以直接按签名顺序对应参数类型处理,这是最可靠的方式。
2. 常见JNI类型的读取方法
如果不知道签名,可以针对常见JNI类型做判断和读取:
- jboolean:本质是1字节的无符号值,用
arg.readU8() !== 0获取布尔结果 - jstring:通过
Java.String(arg)转换为JavaScript字符串 - jobject:用
Java.cast(arg, Java.use("java.lang.Object"))转换为Java对象,之后可以调用对象的toString()或其他方法查看内容;如果是数组类型(如jintArray),则用Java.cast(arg, Java.use("int[]"))转换后读取数组内容 - 基本数值类型:
- jint/jshort/jbyte:
arg.readInt()/arg.readShort()/arg.readU8() - jlong:
arg.readLong() - jfloat:
arg.readFloat() - jdouble:
arg.readDouble()
- jint/jshort/jbyte:
示例代码:
onEnter(args) { console.log("\n-----\n[*] Function is called!"); for (let i = 0; i < args.count(); i++) { const arg = args[i]; console.log(`[+] Arg ${i}: ${arg}`); // 尝试识别常见类型 try { // 先尝试转换为jstring const strVal = Java.String(arg); console.log(` -> 类型:jstring,值:${strVal}`); } catch (e) { // 不是jstring,尝试基本类型 try { const boolVal = arg.readU8() !== 0; console.log(` -> 类型:jboolean,值:${boolVal}`); } catch (e) {} try { const intVal = arg.readInt(); console.log(` -> 类型:jint,值:${intVal}`); } catch (e) {} try { const longVal = arg.readLong(); console.log(` -> 类型:jlong,值:${longVal}`); } catch (e) {} // 尝试转换为jobject try { const objVal = Java.cast(arg, Java.use("java.lang.Object")); console.log(` -> 类型:jobject,值:${objVal.toString()}`); } catch (e) {} } } }
为什么你的尝试方案失败?
- 方案1:
args[i]本身就是NativePointer类型,不需要再用new NativePointer()包装。直接调用readPointer()会把参数值当成内存地址去读取,而非参数本身的值,所以返回空。 - 方案2:
Object.prototype.toString()只能判断出它是[object nativepointer],无法区分具体的JNI类型——所有JNI参数在Frida中都表现为NativePointer,必须通过JNI相关API转换才能识别具体类型。
内容的提问来源于stack exchange,提问作者Lam Nguyen Chi
相关产品推荐
相关产品推荐

