(JNI) 使用 GetMethodID 获取 Java 方法的内存地址

Posted

技术标签:

【中文标题】(JNI) 使用 GetMethodID 获取 Java 方法的内存地址【英文标题】:(JNI) Get Java method's memory address with GetMethodID 【发布时间】:2020-04-19 04:16:35 【问题描述】:

如何获取我拥有 JNI MethodID 的方法的内存位置?

我想通过使用 JNI 来挂钩或操作 Java 方法,因为众所周知,JVM 会重新定位此类方法,因此无法使用指向方法的静态指针。

所以我使用 C++ 和 JNI 从 JVM 中获取 MethodID。

MethodID 可以转换为整数,即十六进制内存地址。

我已经发现,在 MethodID 的内存位置,有一个指向 HEAP 地址的指针。这个堆地址指向一个

“jvm.dll.53A14DE8 方法:元数据:MetaspaceObj”

(这就是我的反向工具“ReClass.NET”所说的)

所以 jvm.dll.xxx 方法得到了一些函数指针,但这些不能是方法,因为它们由 3 个字节组成(太小)或者真的太大(30 条指令+)。 我想找到的方法只返回 1.0 的浮点数

这就是 ReClass.NET 中的 jvm.dll.xxx 方法:

或者有没有其他方法可以在没有 JVMTI 的情况下本地挂钩/操作 Java 方法?

【问题讨论】:

您可以查看 JNA (github.com/java-native-access/jna) 如何获取回调地址 为什么不是 JVM TI?这是操作 Java 方法的正确方式(我会说,唯一方式)。 我试图通过获取方法的地址来找出另一种操作方式。 JNA 和 JVM TI 已打补丁,会导致我想要操作的内容崩溃。 “JVM TI 已打补丁,会崩溃”是什么意思?这是标准 JVM 的基本部分,应该像 JNI 一样可用。 再说一遍:没有“方法地址”这样的东西。 Java 方法可能有一个字节码的地址,以及多个(可能为零)已编译的条目。即使您将它们全部修补,这仍然不能保证行为发生变化,因为该方法可能已经内联到其他一些编译方法中。后者尤其适用于像“return 1.0”这样的简单方法。在这种情况下,您必须找到该方法的所有调用站点,递归地修补它们等等......理论上这是可能的,但这是一项远远超出您最初问题的巨大研究。 【参考方案1】:

您不能像使用本地方法那样挂钩 Java 方法,即直接在内存中替换机器代码。

jmethodID 是对 Java 方法的不透明引用。它可能在不同的 JVM 中实现不同,甚至在同一个 JVM 的不同版本中实现。例如,随着 Metaspace 的出现,jmethodID 的内部表示在 JDK 7 和 JDK 8 之间发生了变化。

现在,在 HotSpot JVM 中,jmethodID 是指向元空间中的 Method 结构的指针。它不是Java方法的代码,而是代表JVM内部方法的内部结构。

请注意,Java 方法最初根本没有任何机器代码 - 相反,JVM 解释其字节码。由于 JIT 编译、重新编译或反优化,方法的机器代码可能会出现、更改或完全消失。此外,一个方法可能同时具有多个 JIT 编译版本。这就是为什么传统的挂钩技术不能应用于 Java 方法的原因。此外,一个方法可以内联到其他 JIT 编译的方法中,在这种情况下,jmethodID 将毫无用处。

然而,有一种标准技术可用于操作 Java 方法 - bytecode instrumentation。可通过标准 API 获取,即JVM TI 的RetransformClasses 和RedefineClasses 函数。

如果您使用 JNI,您也可以使用 JVM TI 函数。即使没有代理或特殊的 JVM 参数,JVM TI 也能正常工作;它可以从任何 JNI 上下文中获得。例如。如何从JNIEnv*获取jvmtiEnv*

JavaVM* vm;
(*env)->GetJavaVM(env, &vm);

jvmtiEnv* jvmti;
(*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION_1_0);

【讨论】:

我不知道 JNI 中的 JVM TI,谢谢。但这不是我需要的,因为这是可能的。这个视频里的人是这么干的:youtu.be/4Futai_P5gw 问题是视频显示了我想hook方法的方式的结果,但是并没有说明什么。 @yungcxn 东西是not that simple。如果你想深入研究编译 Java 方法的二进制代码,我建议从 -XX:+PrintAssembly JVM 选项开始 - 它会转储所有 JIT 编译代码以及方法名称、地址等。 好的...我明白了。我会像你说的那样做,但是你将如何继续在内存中找到那些方法实例?我不介意这有多难,我有时间:) 你有jvm ti和jni的资源吗?

以上是关于(JNI) 使用 GetMethodID 获取 Java 方法的内存地址的主要内容,如果未能解决你的问题,请参考以下文章

Android jni GetFieldID 和 GetMethodID 函数的说明

使用挂起异常java.lang.ClassNotFoundException调用JNI GetMethodID

Android Studio NDK开发-JNI调用Java方法

JNI中java类型的简写

在 JNI 中传递对象总是返回 NULL

Android JNI 学习:JNI 接口整理 — Calling Instance Methods Api