Java 代码解密:使用 Frida 还原 JVMTI Agent 加密保护的java类
字数 1435 2025-08-22 12:23:19

使用 Frida 还原 JVMTI Agent 加密保护的 Java 类 - 详细教学文档

1. 背景与问题描述

在 Java 应用程序安全分析中,经常会遇到使用 JVMTI Agent 对类文件进行加密保护的情况。这种保护方式通过在类加载时动态解密字节码,使得直接反编译 JAR 文件时无法获取原始类内容。

典型特征:

  • 启动参数中包含 -agentpath 指向 .so 文件(Linux)或 .dll 文件(Windows)
  • 反编译工具返回空白结果或异常
  • 使用 JVMTI(JVM Tool Interface)技术而非普通 Java Agent

2. JVMTI Agent 工作原理

JVMTI Agent 通过注册 ClassFileLoadHook 回调函数拦截类加载过程:

void JNICALL ClassFileLoadHook(jvmtiEnv *jvmti_env, JNIEnv *jni_env, 
    jclass class_being_loaded, 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) {
    // 解密逻辑在此执行
    *new_class_data_len = class_data_len;
    *new_class_data = const_cast<unsigned char *>(class_data);
}

关键点:

  1. Agent 在 Agent_OnLoad 中注册回调
  2. JVM 加载类时触发回调
  3. Agent 可以修改 new_class_data 返回解密后的字节码

3. 技术分析路线

3.1 逆向分析难点

  • 直接逆向 .so 文件可能遇到反编译困难
  • 解密函数定位困难
  • 需要理解 JVM 内部工作机制

3.2 替代方案

通过分析 JVM 源码(OpenJDK)找到关键函数:

  • post_class_file_load_hook (在 jvmtiExport.cpp 中)
  • 这是 JVM 调用 ClassFileLoadHook 的实际入口

4. Frida 动态分析方案

4.1 环境准备

  1. 目标机器:

    • 上传 frida-server 可执行文件
    • 启动服务:frida-server -l 0.0.0.0:27042
  2. 分析机器:

    • 安装 Frida 工具包:pip install frida-tools
    • 验证连接:frida-ps -H <target_ip>

4.2 关键 Hook 点

4.2.1 模块加载检测

由于 libjvm.so 可能延迟加载,需要 Hook dlopen

const moduleName = "libjvm.so";
var dlopen = Module.findExportByName(null, "dlopen");

Interceptor.attach(dlopen, {
    onEnter: function(args) {
        var soName = args[0].readCString();
        if(soName.indexOf(moduleName) != -1) {
            this.hook = true;
        }
    },
    onLeave: function(retval) {
        if(this.hook) {
            hookFunction();
        }
    }
});

4.2.2 Hook post_class_file_load_hook

函数签名:

static void post_class_file_load_hook(Symbol* h_name, Handle class_loader, 
    Handle h_protection_domain, unsigned char **data_ptr, 
    unsigned char **end_ptr, JvmtiCachedClassFileData **cache_ptr)

关键参数:

  • h_name: 类名(Symbol 对象)
  • data_ptr/end_ptr: 类字节码的起始和结束指针

4.2.3 Symbol 对象解析

Symbol 内存结构:

_refcount (2 bytes) | _length (2 bytes) | 
_identity_hash (4 bytes) | _body[1] (variable length)

读取类名实现:

function getSymbolString(symbolAddr) {
    const length = ptr(symbolAddr).add(2).readU16();
    const symbolBodyAddr = ptr(symbolAddr).add(8); // _body 偏移8字节
    const byteArray = symbolBodyAddr.readByteArray(length);
    return Memory.readUtf8String(byteArray, length);
}

4.2.4 完整 Hook 实现

function hookFunction() {
    const baseAddress = Module.findBaseAddress(moduleName);
    const targetAddress = baseAddress.add(functionOffset); // 函数偏移
    
    Interceptor.attach(targetAddress, {
        onEnter: function(args) {
            this.ptrAddress = args[3]; // data_ptr 的地址
            this.endPtrAddress = args[4]; // end_ptr 的地址
            this.name = getSymbolString(args[0]);
        },
        onLeave: function(retval) {
            const ptr = this.ptrAddress.readPointer();
            const endPtr = this.endPtrAddress.readPointer();
            const length = endPtr.sub(ptr).toInt32();
            
            if (length > 0) {
                const data = ptr.readByteArray(length);
                const dumpPath = "/tmp/"+this.name.replaceAll("/", ".")+".class";
                const file = new File(dumpPath, "wb");
                file.write(data);
                file.close();
            }
        }
    });
}

5. 执行与结果

启动命令:

frida -H <target_ip> -l hook.js -f /bin/java -- -agentpath:./agent.so -jar app.jar

结果:

  • 解密后的类文件将保存到 /tmp/ 目录
  • 文件名格式为完整类名(包路径转换为点分隔)

6. 进阶技巧

6.1 触发所有类加载

为了完整解密所有类,可以:

  1. 编写特殊 ClassLoader 强制加载所有类
  2. 使用反射扫描 JAR 文件并尝试加载

6.2 注意事项

  1. 确保使用与目标环境完全一致的 JDK 版本
  2. 函数偏移量需要通过 IDA 分析目标 libjvm.so 确定
  3. 生产环境可能需要静态编译 Frida-server

7. 总结

本方法通过 Frida Hook JVM 内部函数,在加密 Agent 解密后获取原始类字节码,具有以下优势:

  • 不依赖逆向分析加密逻辑
  • 可获取完整解密后的类文件
  • 即使类加载失败也能获取字节码
  • 适用于各种 JVMTI 加密方案

通过这种动态分析方法,可以有效绕过基于 JVMTI 的 Java 类保护机制,为安全分析和研究提供有力工具。

使用 Frida 还原 JVMTI Agent 加密保护的 Java 类 - 详细教学文档 1. 背景与问题描述 在 Java 应用程序安全分析中,经常会遇到使用 JVMTI Agent 对类文件进行加密保护的情况。这种保护方式通过在类加载时动态解密字节码,使得直接反编译 JAR 文件时无法获取原始类内容。 典型特征: 启动参数中包含 -agentpath 指向 .so 文件(Linux)或 .dll 文件(Windows) 反编译工具返回空白结果或异常 使用 JVMTI(JVM Tool Interface)技术而非普通 Java Agent 2. JVMTI Agent 工作原理 JVMTI Agent 通过注册 ClassFileLoadHook 回调函数拦截类加载过程: 关键点: Agent 在 Agent_OnLoad 中注册回调 JVM 加载类时触发回调 Agent 可以修改 new_class_data 返回解密后的字节码 3. 技术分析路线 3.1 逆向分析难点 直接逆向 .so 文件可能遇到反编译困难 解密函数定位困难 需要理解 JVM 内部工作机制 3.2 替代方案 通过分析 JVM 源码(OpenJDK)找到关键函数: post_class_file_load_hook (在 jvmtiExport.cpp 中) 这是 JVM 调用 ClassFileLoadHook 的实际入口 4. Frida 动态分析方案 4.1 环境准备 目标机器: 上传 frida-server 可执行文件 启动服务: frida-server -l 0.0.0.0:27042 分析机器: 安装 Frida 工具包: pip install frida-tools 验证连接: frida-ps -H <target_ip> 4.2 关键 Hook 点 4.2.1 模块加载检测 由于 libjvm.so 可能延迟加载,需要 Hook dlopen : 4.2.2 Hook post_ class_ file_ load_ hook 函数签名: 关键参数: h_name : 类名(Symbol 对象) data_ptr / end_ptr : 类字节码的起始和结束指针 4.2.3 Symbol 对象解析 Symbol 内存结构: 读取类名实现: 4.2.4 完整 Hook 实现 5. 执行与结果 启动命令: 结果: 解密后的类文件将保存到 /tmp/ 目录 文件名格式为完整类名(包路径转换为点分隔) 6. 进阶技巧 6.1 触发所有类加载 为了完整解密所有类,可以: 编写特殊 ClassLoader 强制加载所有类 使用反射扫描 JAR 文件并尝试加载 6.2 注意事项 确保使用与目标环境完全一致的 JDK 版本 函数偏移量需要通过 IDA 分析目标 libjvm.so 确定 生产环境可能需要静态编译 Frida-server 7. 总结 本方法通过 Frida Hook JVM 内部函数,在加密 Agent 解密后获取原始类字节码,具有以下优势: 不依赖逆向分析加密逻辑 可获取完整解密后的类文件 即使类加载失败也能获取字节码 适用于各种 JVMTI 加密方案 通过这种动态分析方法,可以有效绕过基于 JVMTI 的 Java 类保护机制,为安全分析和研究提供有力工具。