Java游戏反作弊:如何用JVMTI/JNI/JVM函数监控类加载并追踪来源JAR
解决Java游戏反作弊中类加载来源追踪与合法性验证问题
针对你遇到的JAR未签名、JVMTI回调适配难、自定义类加载器覆盖不全的问题,以下是两种直接可行的实现方案:
方案1:基于JVMTI的全场景类加载追踪
JVMTI的ClassFileLoadHook回调可以拦截所有类的加载过程,并且通过jvmtiGetClassLocation接口直接获取类的来源路径,完全覆盖自定义类加载器和动态生成类的场景。
核心Native代码实现
#include <jvmti.h> #include <string.h> #include <stdio.h> void JNICALL ClassFileLoadHook(jvmtiEnv *jvmti, JNIEnv* jni, jclass class_being_redefined, jobject loader, const char* name, jobject protection_domain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) { char* location = NULL; jvmtiError err = jvmti->GetClassLocation(class_being_redefined, &location); if (err == JVMTI_ERROR_NONE && location != NULL) { // 处理JAR包来源的类 if (strstr(location, "jar:") != NULL) { char jar_path[1024]; // 提取JAR文件路径(适配jar:file://xxx.jar!/格式) sscanf(location, "jar:file://%[^!]!", jar_path); // 这里调用哈希计算逻辑,验证JAR合法性 calculate_jar_hash(jar_path); } // 处理动态生成的类(路径为空或无有效文件路径) else if (strlen(location) == 0 || strstr(location, "memory:") != NULL) { // 直接使用类字节码计算哈希 calculate_class_hash(class_data, class_data_len); } // 释放JVMTI分配的内存 jvmti->Deallocate((unsigned char*)location); } // 不修改类字节码,直接返回原数据 *new_class_data_len = 0; *new_class_data = NULL; } // 哈希计算示例函数(需自行实现SHA-256等逻辑) void calculate_jar_hash(const char* jar_path) { // 读取JAR文件并计算哈希,与合法值对比 printf("Verifying JAR: %s\n", jar_path); } void calculate_class_hash(const unsigned char* data, int len) { // 计算类字节码哈希,与合法值对比 printf("Verifying dynamic class, length: %d\n", len); }
关键说明
- 该回调会触发所有类的加载事件,包括自定义类加载器加载的类;
jvmtiGetClassLocation返回的路径为空时,可判定为动态生成类;- 需要将代码编译为Native库,通过JVMTI agent挂载到目标JVM进程。
方案2:基于Instrumentation API的纯Java实现
Instrumentation是JVM提供的字节码增强接口,ClassFileTransformer会被所有类加载器调用,无需适配自定义加载器,纯Java实现更易维护。
核心Transformer实现
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.net.URL; import java.security.MessageDigest; import java.nio.file.Files; import java.nio.file.Paths; public class AntiCheatTransformer implements ClassFileTransformer { private final Instrumentation inst; public AntiCheatTransformer(Instrumentation inst) { this.inst = inst; } @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, java.security.ProtectionDomain protectionDomain, byte[] classfileBuffer) { String resourceName = className.replace('.', '/') + ".class"; if (loader != null) { URL resource = loader.getResource(resourceName); if (resource != null) { String urlStr = resource.toString(); // 处理JAR包来源的类 if (urlStr.startsWith("jar:")) { String jarPath = urlStr.substring(4, urlStr.indexOf('!')); // 适配不同系统的file://前缀 if (jarPath.startsWith("file:///")) jarPath = jarPath.substring(8); else if (jarPath.startsWith("file://")) jarPath = jarPath.substring(7); verifyJarHash(jarPath); } // 处理普通文件类(按需验证) else if (urlStr.startsWith("file:")) { // 可选:验证单个class文件哈希 } } else { // 无法获取资源,判定为动态生成类 verifyClassHash(classfileBuffer); } } else { // 系统类加载器加载的类,同样处理 verifyClassHash(classfileBuffer); } // 返回原字节码,不修改类结构 return classfileBuffer; } private void verifyJarHash(String jarPath) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] buffer = new byte[8192]; int read; try (var fis = Files.newInputStream(Paths.get(jarPath))) { while ((read = fis.read(buffer)) != -1) { md.update(buffer, 0, read); } } byte[] hash = md.digest(); // 将计算出的哈希与预存的合法哈希对比 } catch (Exception e) { // 验证失败逻辑(如标记作弊) e.printStackTrace(); } } private void verifyClassHash(byte[] classData) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(classData); byte[] hash = md.digest(); // 对比动态类的合法哈希值 } catch (Exception e) { e.printStackTrace(); } } }
Agent启动类
import java.lang.instrument.Instrumentation; public class AntiCheatAgent { public static void premain(String args, Instrumentation inst) { inst.addTransformer(new AntiCheatTransformer(inst)); } }
使用方式
- 将代码打包为Agent JAR,在MANIFEST.MF中添加
Premain-Class: AntiCheatAgent; - 启动游戏时添加JVM参数:
-javaagent:anti-cheat-agent.jar。
注意事项
- 性能优化:只对核心游戏类进行哈希验证,缓存已验证的JAR哈希,避免重复计算;
- 动态类识别:通过
getResource返回null、类路径为空等特征判定动态生成类; - 跨平台适配:处理Windows/Linux不同的文件路径格式。
内容的提问来源于stack exchange,提问作者150149
相关产品推荐
相关产品推荐

