You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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));
    }
}

使用方式

  1. 将代码打包为Agent JAR,在MANIFEST.MF中添加Premain-Class: AntiCheatAgent;
  2. 启动游戏时添加JVM参数:-javaagent:anti-cheat-agent.jar。

注意事项

  • 性能优化:只对核心游戏类进行哈希验证,缓存已验证的JAR哈希,避免重复计算;
  • 动态类识别:通过getResource返回null、类路径为空等特征判定动态生成类;
  • 跨平台适配:处理Windows/Linux不同的文件路径格式。

内容的提问来源于stack exchange,提问作者150149

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 13:12:53