首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
3
1
[分享][原创]frida中Java和js之间需要转换的数据
发表于: 2026-7-11 08:09
1168
[分享][原创]frida中Java和js之间需要转换的数据
陈芸欣
2026-7-11 08:09
1168
# 一、说明 本文所使用的fria版本为17.9.6。如果文章有错误希望指出,不吝赐教。 在Frida中,Java层和Js层之间的数据并不是完全一致的,虽然frida-java-bridge会自动完成绝大多数类型转换,但有些类型需要手动转换或使用专门的API。下表列出了Java和js之间数据是否hi自动转换以及常用的API。 | Java类型 | JS中表现 | 是否自动转换 | 常用API | | --------- | ---------------------------- | ------ | ------------------------- | | boolean | Boolean | ✅ | 无 | | byte | Number | ✅ | 无 | | short | Number | ✅ | 无 | | int | Number | ✅ | 无 | | float | Number | ✅ | 无 | | double | Number | ✅ | 无 | | long | Int64对象(不是普通Number) | ⚠️ | `.toString()`、`valueOf()` | | char | String(长度1) | ✅ | 无 | | String | String 或 Java String Wrapper | ✅ | `$new()` | | Object | Java Wrapper | ⚠️ | `Java.cast()` | | 数组 | JS数组 / PrimitiveArray | ⚠️ | `Java.array()` | | List | Java Wrapper | ❌ | 调用 `.get()` `.size()` | | Map | Java Wrapper | ❌ | 调用 Java API | | Set | Java Wrapper | ❌ | 调用 Java API | | byte[] | PrimitiveArray | ⚠️ | `Java.array('byte', ...)` | | Object[] | JS Array | ⚠️ | `Java.array()` | | Enum | Java Wrapper | ❌ | `.name()` `.ordinal()` | | Class | Java Wrapper | ❌ | `Java.cast()` | | Throwable | Java Wrapper | ❌ | `.getMessage()` | # 一、基本数类型 ```java int boolean float double char short byte ``` 这些在Java中式基本类型的变量,进入js后依然是普通变量: ```js Java.perform(function(){ var Test Java.use("com.test.Test"); Test.test.overload("int").implementation = function(i){ console.log(typeof i);//number console.log(i);//123 return this.test.call(this,i+1); } }) ``` 这些基本类型几乎可以不考虑转换问题。 # 二、long类型 在Java中定义`long id = 1234567890123L;`,在js中输出:`console.log(i);`,输出的不是普通对象,而是一个Int64对象。例如: ```js console.log(id.toString()); console.log(id.valueOf()); console.log(id.equals(100)); ``` 因为JS的Number最大只能精确表示53位整数,而Java的long是64位,所以Frida使用专门的对象来避免精度丢失。 # 三、String Java中`String s = "Hello";`,在js里通常可以直接当字符串使用: ```JS console.log(s); console.log(s.length); console.log(s.toString()); ``` 至于第三段代码调用`toString()`方法的原因: 通常情况下,Java的String对象JS可以直接调用: ```JS Java.perform(function() { var String = Java.use("java.lang.String"); var str = String.$new("Hello Frida"); console.log(str); // 输出: Hello Frida console.log("内容: " + str); // 输出: 内容: Hello Frida console.log(str.toString()); // 输出: Hello Frida }); ``` ## 1、避免对象引用输出 ```js Java.perform(function() { var String = Java.use("java.lang.String"); var str = String.$new("Hello"); console.log(str); // 可能输出: java.lang.String // 强制转换为 JavaScript 字符串 console.log(str.toString()); // 输出: Hello }); ``` 某些情况下,直接输出可能会是Java的对象引用 ## 2、在复杂表达式中确保类型正确 ```js Java.perform(function() { var String = Java.use("java.lang.String"); var str = String.$new("Frida"); if (str === "Frida") { console.log("相等"); // 可能不会执行! } if (str.toString() === "Frida") { console.log("相等"); // 会执行 } }); ``` 直接比较可能会出现问题,比如比较的是对象引用。 ## 3、传递给需要纯字符串的API ```js Java.perform(function() { var String = Java.use("java.lang.String"); var str = String.$new("test@example.com"); // 某些 JavaScript API 可能不接受 Java 对象 var parts = str.split("@"); // 可能报错! // 转换为纯 JavaScript 字符串 var parts = str.toString().split("@"); // 正常工作 }); ``` 在JS中创建Java String对象: ```js var JString = Java.use("java.lang.String"); var str = JString.$new("hello") ``` # 四、Java Object Java中`User user`,JS收到的是`Java Wrapper`,所以不能直接访问所有的字段,需要: ```js console.log(user.$className); //$className的作用是打印user的完整包名+类名 console.log(user.getName()); console.log(user.getAge()); ``` Frida访问Java类中字段需要按照特定格式:<span style="color:red">访问Java字段用.找到字段,再用.value获取或设置字段值</span>。 ## 1、访问实例字段 假设有Java类: ```Java public class User { public String name = "Alice"; public int age = 20; } ``` Frida: ```js Java.perform(function () { var User = Java.use("com.test.User"); var obj = User.$new(); console.log(obj.name.value); console.log(obj.age.value); }); //Alice //20 ``` 这里: ```text obj │ ├── name ---> Java Field Wrapper │ │ │ └── value ---> "Alice" │ └── age ---> Java Field Wrapper │ └── value ---> 20 ``` 也就是说`obj.name`得到的是Field Wrapper,而不是真正的字段值。真正的数据在`obj.name.value`中。 ## 2、修改字段值 修改字段值只能通过下面的方式来修改: ```js user.age.value = 30; ``` ## 3、访问静态字段 Java: ```java public class Config { public static int VERSION = 10; } ``` Frida中需要定位到这个类,并且也需要使用.value: ```js var Config = Java.use("com.test.Config"); console.log(Config.VERSION.value); Config.VERSION.value = 20; ``` ## 4、如果字段是对象 例如: ```java public User user; ``` Frida: ``` var u = obj.user.value ``` 得到的依然是一个Java Wrapper。 需要继续访问: ```js console.log(u.name.value); console.log(u.age.value); //也可以链式访问 console.log(obj.user.value.name.value); ``` ## 5、如果字段和方法重名 这是Frida的特殊规则,例如Java中有“ ```java class Test { public int m; public int m() { return 1; } } ``` 如果直接`obj.m`,那么frida默认认为你想要访问方法。 字段需要写成`obj._m.value`,也就是在字段名前加"`_`"。 # 五、数组 Java数组返回方法后,通常不需要主动转换。Frida已经帮助你转换成了一个可以直接在JS中操作的对象,只是不同类型的数组表现形式略有不同。 ## 1、遍历数组 Frida可以获取所有从Java层得到的数组的length属性,也可以遍历这些数组,只不过根据数组形式的不同,遍历方式也不同。比如下面展示遍历从Java层获得的byte数组和Object数组: ```js //byte数组 for (var i = 0; i < result.length; i++){ console.log(result[i]); } //Object数组 for (var j = 0; j < result.length; j++){ console.log(result[i].age.value); } ``` ## 2、打印数组 Frida中直接打印数组的时候`console.log(result)`,输出可能是`[object Object]`或类型的东西。这是因为它是一个PrimitiveArray对象,不是普通的JS Array。在这种情况下可以自己遍历这个数组,然后转移到JS的数组中: ```js var arr = []; for (var i = 0; i < result.length; i++){ arr.push(result[i]); } console.log(arr); ``` ## 3、转换byte数组 在Frida中真正常见的是`byte[]`转字符串和byte[]转十六进制: 例如Java中:`byte[] = 12 5A FF 01 ...`,那么JS中需要: ```js var hex = []; for (var i = 0; i < result.length; i++) { var b = result[i]; if (b < 0) b += 256; hex.push(("0" + b.toString(16)).slice(-2)); } console.log(hex.join(" ")); //输出0c 5a ff 01 ``` 因为Java的byte是有符号的(-128~127),所以遇到负数时通常先加256再格式化。 如果知道`byte[]`数组中存的是UTF-8文本,可以直接借助Java的String类输出: ```js var JString = Java.use("java.lang.String"); var str = JString.$new(result); console.log(str); ``` 如果不是UTF-8而是AES密文等二进制数据的时候,就不能这样转换,否则会出现乱码。 ## 4、二进制数据转UTF-8 在Frida中也可以经常看到这样的工具函数: ```js var ByteString = Java.use("com.android.okhttp.okio.ByteString"); //工具函数,用于将ASCII码转换为UTF8编码的字符串 function toUtf8(data){ console.log(ByteString.of(data).utf8()); } ``` `ByteString.of(data)`是`ByteString`类的一个静态方法。这个方法的作用是将原始的Java byte[]对象,包装成一个`ByteString`对象。 `.uft8`是`ByteString`的一个实例方法,该方法的功能是尝试将包装的二进制数据用`UTF-8`字符集解码成一个Java字符串。 ## 5、JS向Java传递数组 有时也需要在Frida中向Java层传递一个数组: ```js var User = Java.use("com.test.User"); var u1 = User.$new(); var u2 = User.$new(); var arr = Java.array( "com.test.User", [u1,u2] ); ``` `Java.array(type, elements)`是Frida创建Java数组的标准方式,支持基本类型数组和对象数组 ## 6、一个实例 下面的例子几乎涵盖了实际逆向中的需求: ```js Java.perform(function () { var cls = Java.use("com.test.Test"); cls.encrypt.implementation = function (input) { var result = this.encrypt(input); // Java byte[] -> JS Array var bytes = []; for (var i = 0; i < result.length; i++) { var b = result[i]; if (b < 0) b += 256; bytes.push(b); } console.log(bytes); console.log(bytes.length); return result; }; }); ``` # 六、List 例如Java代码:`ArrayList<String>`,不会自动变成JS Array,它仍然是一个Java对象。但是Frida也可以像操作Java数组那样,获取Java List的长度以及通过.get(下标)的方法得到对应的元素: ```js console.log(list.size()); console.log(list.get(0)); console.log(list.get(1)); var jsArray = []; for (var i = 0; i < list.size(); i++) { jsArray.push(list.get(i)); } ``` # 七、Map 诸如Java的`HashMap`等,在Frida中获取到后依然是Java对象,但可以通过以下方法操作: ```js console.log(map.get("name")); console.log(map.keySet()); console.log(map.values()); var iterator = map.entrySet().iterator(); while(iterator.hasNext()){ var entry = iterator.next(); console.log(entry.getKey()); console.log(entry.getValue()); } ``` # 八、Class Frida中可以这样操作: ```js console.log(cls.getName()); console.log(cls.getSuperclass()); var Class = Java.use("java.lang.Class"); var c = Java.cast(handle, Class); ``` # 九、Throwable Frida中可以这样操作异常对象: ```js console.log(e.getMessage()); console.log(e.toString()); console.log( e.getStackTrace() ); ``` # 十、JNI Handle 有时 Native Hook拿到的是: ``` jobject jstring jclass ``` 这些实际上JNI Handle,要在Java层使用,需要:`Java.cast(handle, SomeClass);`。 ## 1、JNI Handle本质 那么JNI Handle的本质是什么?在学习中,最容易建立正确模型的比喻是:<span style="color:red">“JNI Handle更像是native层一个指向Java层对象的指针”</span>。但实际上来讲,JNI Handle是Native层保存的一个“Java对象引用”,它与指针的区别在于:<span style="color:red">JNI规范故意没有保证jobject一定就是对象的内存地址</span>。 可以试着这样理解:把JVM想象成一个仓库 ``` Java Heap ┌────────────┐ │ User对象 │ │ name="Tom" │ │ age=18 │ └────────────┘ ``` Native里面不会直接拿到它的首地址,而是拿到一个"Handle #25",或者"Reference #25",而JVM内部知道: ``` Handle #25 │ ▼ Java Heap │ ▼ User对象 ``` 因此:`Native->jobject->JVM->整整的Java对象`。这也是为什么JNI官方文档一直使用“Reference(引用)”这个术语。 还有一部分原因是:Java有: - `GC`(垃圾回收机制) - `Heap Compact`(堆压缩机制) - 对象移动(Object Moving) 因此,<span style="color:red">Java对象在堆中的地址是可以改变的;而在 JNI 语境下,为了安全,不允许 C 语言代码直接保存或使用指向 Java 堆内存的真实指针地址。</span>。 这样看的话,它们之间的关系更像是: |操作系统|JNI| |---|---| |虚拟地址(Virtual Address)|`JNI Handle`(`jobject`、`jstring` 等)| |MMU + 页表|JVM(准确地说是 JVM 的引用管理和 GC 系统)| |物理页(Physical Page)|Java Heap 中真正的 Java 对象| ## 2、Frida中拿到JNI Handle后的转化 假设在Java中有: ```java package com.demo; public class User { public String name = "Alice"; public String getName() { return name; } public native void test(); } ``` 可以看到在User类中注册了一个native函数test。对应JNI: ```C JNIEXPORT void JNICALL Java_com_demo_User_test(JNIEnv *env, jobject thiz) { // thiz 就是 JNI Handle(jobject) } ``` 那么就可以通过Frida代码得到JNI Handle: ```js Interceptor.attach(addr, { onEnter(args) { var jobjectHandle = args[1]; console.log(jobjectHandle); } }); ``` 拿到了JNI Handle以后就可以转换成Java对象: ```js Java.perform(function () { var User = Java.use("com.demo.User"); Interceptor.attach(addr, { onEnter(args) { var handle = args[1]; var obj = Java.cast(handle, User); console.log(obj); } }); }); ``` 至于为什么Java.cast()可以转化为Java对象是因为: ``` Java.cast() │ JNI Handle │ ▼ JNI API │ ▼ 找到对应Java对象 │ ▼ 创建Java Wrapper ``` 所以Java.cast()并不是把一块内存“强制转换”成对象,而是告诉Frida:<span style="color:red">这个JNI 引用实际上对应的就是User类型,请为我创建一个对应的Java包装对象(Wrapper)“</span>。 ## 3、转换后能做什么? 再此之前先看看Oracle官方JNI规范给出的完整的引用类型继承关系: ```text jobject │ ├── jclass ├── jstring ├── jthrowable │ └── jarray │ ├── jobjectArray ├── jbooleanArray ├── jbyteArray ├── jcharArray ├── jshortArray ├── jintArray ├── jlongArray ├── jfloatArray └── jdoubleArray ``` 值得注意的是,所有的普通java实例在JNI函数中都会变为`jobject`。既然如此,`jclass`的存在意义是什么? 先说结论:<span style="color:red">`jclass`的存在不是因为JVM不能用`jobject`表示,而是因为`java.lang.Class`在Java中本身就是一中特殊现象,它代表类的元数据,而不是普通实例</span>。换句话说: - `jobject`表示某个类的实例 - `jclass`表示这个类本身。 ### (1)`jobject`转换后: ```js console.log(obj.name.value);//读取字段 obj.name.value = "Bob";//修改字段 obj.login();//调用实例方法 obj.getClass();//获取class //强制类型转化 var ArrayList = Java.use("java.util.ArrayList"); var list = Java.cast(obj, ArrayList); console.log(list.size()); ``` ### (2)`jstring`转换后: ```js console.log(s.length());//返回字符串长度 console.log(s.charAt(0));//返回字符串索引0位置的字符,类型位char console.log(s.contains("abc"));//判断字符s中是否包含子串"abc" console.log(s.substring(1));//返回从索引1开始到结尾的字串 console.log(s.hashCode());//返回字符串的哈希码(一个int值) ``` ### (3)`jbyteArray`一般不转化 更多的是利用JNI API: ```js env.getByteArrayElements() env.getArrayLength() //或者自己遍历数组,然后转化为js数组 ``` ### (4)`jclass`转换后: 既然它代表class对象,那么能否修改类呢?答案是:<span style="color:red">不能</span>。 原因很简单:因为java.lang.Class对象里面保存的是:类名、方法列表、字段列表、父类、接口、Annotation和Modifier。这些是类的元数据。所以转换后可以做很多的反射。 ```js console.log(clazz.getName());//获取类名 clazz.getSuperclass();//获取父类 clazz.getInterfaces();//获取接口 clazz.getDeclaredFields();//获取字段 clazz.getDeclaredMethods();//获取方法 clazz.getDeclaredConstructors();//获取Constructor clazz.isInterface();//判断是不是接口 clazz.isArray();//判断是不是数组 clazz.getModifiers();//获取modifier ```
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于
2026-7-11 13:40 被陈芸欣编辑 ,原因:
#问题讨论
#其他内容
收藏
・
3
点赞
・
1
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
wx_0.0_522
你的分享对大家帮助很大,非常感谢!
2026-7-11 09:54
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
1
)
mb_cfjwplfo
雪 币:
284
能力值:
( LV1,RANK:0 )
在线值:
发帖
6
回帖
92
粉丝
20
关注
私信
mb_cfjwplfo
2
楼
可以可以,有时候想看某个值不知道怎么取,就得网上搜索半天脚本,这下直接总结完了,太棒了
2026-7-11 11:40
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
陈芸欣
2
发帖
2
回帖
10
RANK
关注
私信
他的文章
[分享][原创]frida中Java和js之间需要转换的数据
1168
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部