200字
[编程] JuiceAgent实现原理 【1】
2025-12-12
2025-12-13

JuiceAgent是一个基于JVMTI的工具库,可以在运行时修改、获取Java字节码,即使在DisableAttachMechanism开启时也能运行,使用Dll 注入技术。

本文章不解释dll注入部分,ReflectiveDLLInjection 已经实现dll注入,See Inject.c

截至目前(2025-12-12),JuiceAgent仅支持Windows,故只介绍Windows下的实现

A. 加载器

1. 入口: DllMain

注入任何Dll(以及JNI方式加载dll)的时候,都会触发DllMain, JuiceAgent借此特性来初始化。

使用ReflectiveDLLInjection注入Dll时,可以通过lpReserved 来传递参数,JuiceAgent中传递的参数是当前的运行目录路径。

以下是关键实现代码

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ) {
    switch( fdwReason ) 
    { 
        case DLL_PROCESS_ATTACH: {
            InitLogger();
            PLOGI << "DllMain: DLL_PROCESS_ATTACH";
            PLOGD.printf("lpReserved: %p", lpReserved);

            const char* runtime_dir = (const char*)lpReserved;
            PLOGD << "runtime_dir: " << runtime_dir;

            // 下一步: 初始化JuiceAgent
            InitJuiceAgent(runtime_dir);

            break;
        }

        case DLL_THREAD_ATTACH: {
            break;
        }

        case DLL_THREAD_DETACH: {
            break;
        }
            

        case DLL_PROCESS_DETACH: {
            PLOGI << "DllMain: DLL_PROCESS_DETACH";
            PLOGD.printf("lpReserved: %p", lpReserved);
            break;
        }
    }
    return TRUE;
}

2. 初始化: InitJuiceAgent

这部分以读取配置、获取JNI/JVMTI环境为主。

关键点在于,在jvm进程中可以使用JNI_GetCreatedJavaVMs获取jvm,获取jvm之后可以使用GetEnv来获取jni和jvmti环境,以实现调用Java函数、redefine、retransform等操作。

示例:获取JVM环境,需要在JAVA中运行(即注入到JAVA程序中的DLL将会运行以下代码)

#include <jvm/jni.h>
#include <jvm/jvmti.h>

......

//变量声明
JavaVM *jvm = NULL;
// 获取JVM环境
JNI_GetCreatedJavaVMs(&jvm, 1, NULL);

接下来是获取JNI和JVMTI环境

// jvmti变量
jvmtiEnv *jvmti = NULL;
// jni变量
JNIEnv *env = NULL;

// 获取jni环境,版本为1.8 
jvm->GetEnv((void**)&env, JNI_VERSION_1_8);
// 获取jvmti环境,版本为1.2
jvm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_2);

可选的JNI/JVMTI版本,各个版本编程API各有不同

#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006
#define JNI_VERSION_1_8 0x00010008
#define JNI_VERSION_9   0x00090000
#define JNI_VERSION_10  0x000a0000
#define JNI_VERSION_19  0x00130000
#define JNI_VERSION_20  0x00140000
#define JNI_VERSION_21  0x00150000

enum {
    JVMTI_VERSION_1   = 0x30010000,
    JVMTI_VERSION_1_0 = 0x30010000,
    JVMTI_VERSION_1_1 = 0x30010100,
    JVMTI_VERSION_1_2 = 0x30010200,
    JVMTI_VERSION_9   = 0x30090000,
    JVMTI_VERSION_11  = 0x300B0000,
    JVMTI_VERSION_19  = 0x30130000,
    JVMTI_VERSION_21  = 0x30150000,

    JVMTI_VERSION = 0x30000000 + (21 * 0x10000) + ( 0 * 0x100) + 0  /* version: 21.0.0 */
};

成功获取环境之后就是注入JuiceLoader Jar

JuiceAgent.jvmti->AddToSystemClassLoaderSearch(InjectionInfo.JuiceLoaderJarPath)

之后调用JuiceLoader Init (Java函数),这部分省略。

B. JuiceLoader

JuiceLoader的作用在于提供编程接口,与JVMTI交互。

JuiceLoader Init会加载libjuiceloader,因为直接通过JNI调用java的System.load("xxx.dll"); 会报错java.lang.UnsatisfiedLinkError,所以需要中间层JuiceLoader来加载,这样的做的好处还有可以在程序直接加载libjuiceloader而不用手动注入。

【未完待续】

Comment