深入分析 Agent 内存马

再探Agent内存马

之前只是简单了解了Agent,知道如何使用Agent技术进行简单的代码修改以及简单的Agent型内存马编写

这次从底层出发,从Agent的加载流程开始谈起

Agent加载流程分析

先看两种agent加载形式的底层代码

Agent_OnLoad

首先是Agent_OnLoad,对应了premain,源码太长了这边就不全粘了

简单分析一下,首先通过createNewJPLISAgent创建JPLISAgent(这个一会分析),判断返回值,进入if,先是做简单的判断,往下看重点部分,获取提供的参数的jarfile,对获取的参数做一个判断,如果为空那就回一个错误,继续向下,premainClass = getAttribute(attributes, "Premain-Class");这句很明显是获取premain的class名(推测

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
attributes = readAttributes(jarfile);
if (attributes == NULL) {
    fprintf(stderr, "Error opening zip file or JAR manifest missing : %s\n", jarfile);
    free(jarfile);
    if (options != NULL) free(options);
    return JNI_ERR;
}

premainClass = getAttribute(attributes, "Premain-Class");
if (premainClass == NULL) {
    fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s\n",
        jarfile);
    free(jarfile);
    if (options != NULL) free(options);
    freeAttributes(attributes);
    return JNI_ERR;
}

再向下,appendClassPath(agent, jarfile);将agent和jarfile做一个绑定,这个agent就是刚才提到的JPLISAgent类型的实例变量

向下,读取命令行的参数加载agent

1
2
3
4
 /*
 * Track (record) the agent class name and options data
 */
initerror = recordCommandLineData(agent, premainClass, options);

再往下也没什么有用的,整体流程看下来重点就是将传入的-javaagent参数的agent绑定到JPLISAgent中

Agent_OnAttach

其次是Agent_OnAttach,逻辑上对应了agentmain方法,简单分析一下

与上面不同的是首先判断vm是否已经加载了jni

1
2
result = (*vm)->GetEnv(vm, (void**)&jni_env, JNI_VERSION_1_2);
jplis_assert(result==JNI_OK);

然后就是下面这段,并没有从命令行中读取参数运行,而是首先创建Instrument实例,然后通过startJavaAgent启动agent

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 /*
 * Create the java.lang.instrument.Instrumentation instance
 */
success = createInstrumentationImpl(jni_env, agent);
jplis_assert(success);

/*
 *  Turn on the ClassFileLoadHook.
 */
if (success) {
    success = setLivePhaseEventHandlers(agent);
    jplis_assert(success);
}

/*
 * Start the agent
 */
if (success) {
    success = startJavaAgent(agent,
                             jni_env,
                             agentClass,
                             options,
                             agent->mAgentmainCaller);
}

JPLISAgent

下面来看一看JPLISAgent

跟踪到JPLISAgent.h,跟进看一下_JPLISAgent结构体,根据名称和注释大部分的成员变量都能大概知道干什么的,在下面做了简单的标注,涉及到了之前提到的java agent技术中的所有关键点,其实JPLISAgent就代表了agent

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
struct _JPLISAgent {
    JavaVM *                mJVM;// 指向JVM的指针                   /* handle to the JVM */
    JPLISEnvironment        mNormalEnvironment;//      /* for every thing but retransform stuff */
    JPLISEnvironment        mRetransformEnvironment;//  /* for retransform stuff only */
    jobject                 mInstrumentationImpl;// instrument类   /* handle to the Instrumentation instance */
    jmethodID               mPremainCaller;// InstrumentationImpl中调用premain方法         /* method on the InstrumentationImpl that does the premain stuff (cached to save lots of lookups) */
    jmethodID               mAgentmainCaller;// InstrumentationImpl中调用agentmain方法       /* method on the InstrumentationImpl for agents loaded via attach mechanism */
    jmethodID               mTransform;// transform方法             /* method on the InstrumentationImpl that does the class file transform */
    jboolean                mRedefineAvailable;// MANIFEST.MF配置文件中的Can-Redefine-Classes     /* cached answer to "does this agent support redefine" */
    jboolean                mRedefineAdded;         /* indicates if can_redefine_classes capability has been added */
    jboolean                mNativeMethodPrefixAvailable;// MANIFEST.MF配置文件中的Can-Set-Native-Method-Prefix  /* cached answer to "does this agent support prefixing" */
    jboolean                mNativeMethodPrefixAdded;     /* indicates if can_set_native_method_prefix capability has been added */
    char const *            mAgentClassName;        /* agent class name */
    char const *            mOptionsString;         /* -javaagent options string */
};

再简单看一下JPLISEnviroment,对应结构体_JPLISEnviroment

1
2
3
4
5
struct _JPLISEnvironment {
    jvmtiEnv *              mJVMTIEnv;// jvmti环境              /* the JVM TI environment */
    JPLISAgent *            mAgent;                 /* corresponding agent */
    jboolean                mIsRetransformer;// MANIFEST.MF配置文件中的Can-Retransform-Classes       /* indicates if special environment */
};

对于JVM TI,官方文档如下解释,简单来说就是JVM提供的编程接口,能够监控、修改JVM中正在运行的方法,这也是java agent技术的核心

The JVM tool interface (JVM TI) is a native programming interface for use by tools. It provides both a way to inspect the state and to control the execution of applications running in the Java virtual machine (JVM). JVM TI supports the full breadth of tools that need access to JVM state, including but not limited to: profiling, debugging, monitoring, thread analysis, and coverage analysis tools.

jvmtiEnv可以理解为一个个连接到JVM工具或代理(监控、调试、分析等,比如监控JVM的工具、对jvm执行Agent相关操作),每有一个连接到JVM上,java就会为其创建一个jvmtiEnv实例来绑定它,以一个链状来表示,链主要用于事件广播

然后上面分析到Agent_OnAttach方法中推测通过startJavaAgent启动agent,也就是启动JPLISAgent,跟进看一下

首先调用commandStringIntoJavaStrings,顾名思义做了简单的命令转换,然后调用invokeJavaAgentMainMethod,跟进

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
jboolean
startJavaAgent( JPLISAgent *    agent,
                JNIEnv *        jnienv,
                const char *    classname,
                const char *    optionsString,
                jmethodID       agentMainMethod) {
    jboolean    success = JNI_FALSE;
    jstring classNameObject = NULL;
    jstring optionsStringObject = NULL;

    success = commandStringIntoJavaStrings(    jnienv,
                                               classname,
                                               optionsString,
                                               &classNameObject,
                                               &optionsStringObject);

    if (success) {
        success = invokeJavaAgentMainMethod(   jnienv,
                                               agent->mInstrumentationImpl,
                                               agentMainMethod,
                                               classNameObject,
                                               optionsStringObject);
    }

    return success;
}

同样返回值是jBoolean,其中做了一个断言mainCallingMethod != null,mainCallingMethod为传入的agentClass,也就是premain或agentmain,进入if,jni的CallVoidMethod函数,传入instrument、agentmain方法,这个函数用于调用一个返回为void的方法,跟进一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
jboolean
invokeJavaAgentMainMethod( JNIEnv *    jnienv,
                           jobject     instrumentationImpl,
                           jmethodID   mainCallingMethod,
                           jstring     className,
                           jstring     optionsString) {
    jboolean errorOutstanding = JNI_FALSE;

    jplis_assert(mainCallingMethod != NULL);
    if ( mainCallingMethod != NULL ) {
        (*jnienv)->CallVoidMethod(  jnienv,
                                    instrumentationImpl,
                                    mainCallingMethod,
                                    className,
                                    optionsString);
        errorOutstanding = checkForThrowable(jnienv);
        if ( errorOutstanding ) {
            logThrowable(jnienv);
        }
        checkForAndClearThrowable(jnienv);
    }
    return !errorOutstanding;
}

找到了这个方法的原型,再往下找一下实在没找到

1
2
void (JNICALL *CallVoidMethod)
      (JNIEnv *env, jobject obj, jmethodID methodID, ...);

这篇文章讲的挺好的,也就是说首先会找到instrumentImpl类,调用构造方法,然后调用InstrumentationImpl中调用agentmain的方法,也就是loadClassAndCallAgentmain,并且传入参数agentmain的classname,以及options,回到java,看一下

1
2
3
4
5
6
loadClassAndCallAgentmain(  String  classname,
                            String  optionsString)
        throws Throwable {

    loadClassAndStartAgent( classname, "agentmain", optionsString );
}

跟进loadClassAndStartAgent,首先通过反射获取agentmain的class,然后获取agentmain方法,做一些简单的判断之后invoke调用方法。premain同理,调用链串起来了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// Attempt to load and start an agent
private void
loadClassAndStartAgent( String  classname,
                        String  methodname,
                        String  optionsString)
        throws Throwable {

    ClassLoader mainAppLoader   = ClassLoader.getSystemClassLoader();
    Class<?>    javaAgentClass  = mainAppLoader.loadClass(classname);

    Method m = null;
    NoSuchMethodException firstExc = null;
    boolean twoArgAgent = false;

    // The agent class must have a premain or agentmain method that
    // has 1 or 2 arguments. We check in the following order:
    //
    // 1) declared with a signature of (String, Instrumentation)
    // 2) declared with a signature of (String)
    //
    // If no method is found then we throw the NoSuchMethodException
    // from the first attempt so that the exception text indicates
    // the lookup failed for the 2-arg method (same as JDK5.0).

    try {
        m = javaAgentClass.getDeclaredMethod( methodname,
                             new Class<?>[] {
                                 String.class,
                                 java.lang.instrument.Instrumentation.class
                             }
                           );
        twoArgAgent = true;
    } catch (NoSuchMethodException x) {
        // remember the NoSuchMethodException
        firstExc = x;
    }

    if (m == null) {
        // now try the declared 1-arg method
        try {
            m = javaAgentClass.getDeclaredMethod(methodname,
                                             new Class<?>[] { String.class });
        } catch (NoSuchMethodException x) {
            // none of the methods exists so we throw the
            // first NoSuchMethodException as per 5.0
            throw firstExc;
        }
    }

    // reject non-public premain or agentmain method
    if (!Modifier.isPublic(m.getModifiers())) {
        String msg = "method " + classname + "." +  methodname + " must be declared public";
        throw new IllegalAccessException(msg);
    }

    if (!Modifier.isPublic(javaAgentClass.getModifiers()) &&
        !javaAgentClass.getModule().isNamed()) {
        // If the java agent class is in an unnamed module, the java agent class can be non-public.
        // Suppress access check upon the invocation of the premain/agentmain method.
        setAccessible(m, true);
    }

    // invoke the 1 or 2-arg method
    if (twoArgAgent) {
        m.invoke(null, new Object[] { optionsString, this });
    } else {
        m.invoke(null, new Object[] { optionsString });
    }
}

attach 流程分析

分析了以一下这两行代码做了什么,也就是根据id号返回指定jvm,然后对其执行loadAgent,参数为我们打包好带有agentmain的jar包(mac平台

1
2
VirtualMachine virtualMachine = VirtualMachine.attach(virtualMachineDescriptor.id());
virtualMachine.loadAgent("jar/path");

第一行跟进,核心部分如下,检查attach许可后,通过 attach 测试来检测目标虚拟机能否 attach,最后返回ViretualMachineImpl实例

image-20240408223105592

这边需要注意最后创建返回 VirtualMachineImpl 时会检测 jdk.attach.allowAttachSelf 参数(默认为 false,也就是不允许attach自身)

image-20240408223013519

第二行代码,跟进后核心位置在Bsd#execute方法(部分源码如下),会通过提供的id,socket连接到目标JVM,使用write(native)向目标JVM发送命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// create UNIX socket
int s = socket();

// connect to target VM
try {
    connect(s, socket_path);
} catch (IOException x) {
    close(s);
    throw x;
}

IOException ioe = null;

// connected - write request
// <ver> <cmd> <args...>
try {
    writeString(s, PROTOCOL_VERSION);
    writeString(s, cmd);

    for (int i=0; i<3; i++) {
        if (i < args.length && args[i] != null) {
            writeString(s, (String)args[i]);
        } else {
            writeString(s, "");
        }
    }
} catch (IOException x) {
    ioe = x;
}
...

Retransform分析

上面简单实现并分析了利用addTransformer + retransformerClasses,配合javassist修改已加载类中否个方法

并且我们在分析到addTransformer中的native方法时,直接跳过了,现在我们从这里出发深入分析一下

看一下setHasRetransformableTransformers,调用方法retransformableEnvironment(),继续向下调用SerEventNofigiactionMode来设置事件通知类型,根据传入的has也就是java层addTransformer中的canRetransform参数来配置是否触发事件传递(true则传递)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void
setHasRetransformableTransformers(JNIEnv * jnienv, JPLISAgent * agent, jboolean has) {
    jvmtiEnv *          retransformerEnv     = retransformableEnvironment(agent);
    jvmtiError          jvmtierror;

    jplis_assert(retransformerEnv != NULL);
    jvmtierror = (*retransformerEnv)->SetEventNotificationMode(
                                                    retransformerEnv,
                                                    has? JVMTI_ENABLE : JVMTI_DISABLE,
                                                    JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
                                                    NULL /* all threads */);
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}

跟进retransformableEnvironment,首先判断传入agent是否具有retransformerEnv,有就直接返回,没有的话继续向下(每次agent都会创建一个新的JPLISAgent,所以这边不会进入),调用mJVM的GetEnv方法生成一个新的jvmtiEnv,接下来就是一些简单的配置,重点看下面的事件回调配置,给这个jvmtiEnv设置了一个事件回调,回调事件为eventHandlerClassFileLoadHook。总的来说就是构造了一个canRetransform的jvmtiEnv

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
jvmtiEnv *
retransformableEnvironment(JPLISAgent * agent) {
    jvmtiEnv *          retransformerEnv     = NULL;
    jint                jnierror             = JNI_OK;
    jvmtiCapabilities   desiredCapabilities;
    jvmtiEventCallbacks callbacks;
    jvmtiError          jvmtierror;

    if (agent->mRetransformEnvironment.mJVMTIEnv != NULL) {
        return agent->mRetransformEnvironment.mJVMTIEnv;
    }
    jnierror = (*agent->mJVM)->GetEnv(  agent->mJVM,
                               (void **) &retransformerEnv,
                               JVMTI_VERSION_1_1);
    if ( jnierror != JNI_OK ) {
        return NULL;
    }
    jvmtierror = (*retransformerEnv)->GetCapabilities(retransformerEnv, &desiredCapabilities);
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    desiredCapabilities.can_retransform_classes = 1;
    if (agent->mNativeMethodPrefixAdded) {
        desiredCapabilities.can_set_native_method_prefix = 1;
    }

    jvmtierror = (*retransformerEnv)->AddCapabilities(retransformerEnv, &desiredCapabilities);
    if (jvmtierror != JVMTI_ERROR_NONE) {
         /* cannot get the capability, dispose of the retransforming environment */
        jvmtierror = (*retransformerEnv)->DisposeEnvironment(retransformerEnv);
        jplis_assert(jvmtierror == JVMTI_ERROR_NOT_AVAILABLE);
        return NULL;
    }
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;
    jvmtierror = (*retransformerEnv)->SetEventCallbacks(retransformerEnv,
                                                        &callbacks,
                                                        sizeof(callbacks));
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    if (jvmtierror == JVMTI_ERROR_NONE) {
        // install the retransforming environment
        agent->mRetransformEnvironment.mJVMTIEnv = retransformerEnv;
        agent->mRetransformEnvironment.mIsRetransformer = JNI_TRUE;

        // Make it for ClassFileLoadHook handling
        jvmtierror = (*retransformerEnv)->SetEnvironmentLocalStorage(
                                                       retransformerEnv,
                                                       &(agent->mRetransformEnvironment));
        jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
        if (jvmtierror == JVMTI_ERROR_NONE) {
            return retransformerEnv;
        }
    }
    return NULL;
}

然后跟进看一下给的这个事件回调触发的回调方法eventHandlerClassFileLoadHook,通过jvmtiEnv拿到JPLISAgent后,调用了transformClassFile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void JNICALL
eventHandlerClassFileLoadHook(  jvmtiEnv *              jvmtienv,
                                JNIEnv *                jnienv,
                                jclass                  class_being_redefined,
                                jobject                 loader,
                                const char*             name,
                                jobject                 protectionDomain,
                                jint                    class_data_len,
                                const unsigned char*    class_data,
                                jint*                   new_class_data_len,
                                unsigned char**         new_class_data) {
    JPLISEnvironment * environment  = NULL;

    environment = getJPLISEnvironment(jvmtienv);

    /* if something is internally inconsistent (no agent), just silently return without touching the buffer */
    if ( environment != NULL ) {
        jthrowable outstandingException = preserveThrowable(jnienv);
        transformClassFile( environment->mAgent,
                            jnienv,
                            loader,
                            name,
                            class_being_redefined,
                            protectionDomain,
                            class_data_len,
                            class_data,
                            new_class_data_len,
                            new_class_data,
                            environment->mIsRetransformer);
        restoreThrowable(jnienv, outstandingException);
    }
}

这个transformClassFile有一个位置值得注意,CallObjectMethod方法传入agent->mInstrumentationImpl、agent->mTransform,上面分析我们知道分别代表了Java中的InstrumentationImpl类和transform方法,所以基本可以判断这个回调函数会触发我们自定义的transform方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
        transformedBufferObject = (*jnienv)->CallObjectMethod(
                                            jnienv,
                                            agent->mInstrumentationImpl,
                                            agent->mTransform,
                                            loaderObject,
                                            classNameStringObject,
                                            classBeingRedefined,
                                            protectionDomain,
                                            classFileBufferObject,
                                            is_retransformer);

分析到这里也就是说在addTransformer时,创建一个新的jvmtiEnv,并在事件回调上添加了一个eventHandlerClassFileLoadHook方法,执行该方法会调用我们定义的transform以及instrumentationImpl,这样就联系到java了

知道哪里触发transform方法了,那么如何触发jvmtiEnv的callback呢,先跟进retransformClasses0 native方法

直接看重点部分,调用jvmtiEnv的RetransformClasses方法,除retransformerEnv外另外两个参数分别是传入的Class的数量和class组成的数组

1
2
3
4
5
    if (!errorOccurred) {
        errorCode = (*retransformerEnv)->RetransformClasses(retransformerEnv,
                                                            numClasses, classArray);
        errorOccurred = (errorCode != JVMTI_ERROR_NONE);
    }

继续跟进,直接看for位置,对于传入的classes进行遍历,首先做一些判断,然后检查是否已经缓存了该类,有就直接用,没有就从InstanceKlass重新构建,然后下边是最终执行位置,可以发现这里最终还是调用VM_RedefineClasses来进行处理,这个是jvm提供的用于重新定义类的接口

1
2
VM_RedefineClasses op(class_count, class_definitions, jvmti_class_load_kind_retransform);
VMThread::execute(&op);

这里没有提及callback的调用

这边换一个路子,从jvm加载字节码说起,这边先介绍一个类jvmtiClassFileLoadHookPoster

jvmtiClassFileLoadHookPoster 是 Java 虚拟机工具接口 (JVMTI) 中的一个类,用于在类文件加载到 JVM 之前进行字节码转换。

jvmtiClassFileLoadHookPoster 类封装了有关正在加载的类的信息,例如类名、类加载器和字节码本身。它还包含一个 post 方法,用于通知注册的代理程序类加载事件并传递相关信息

触发jvmtiClassFileLoadHookPoster的加载流程如下

  1. 类文件准备加载到 JVM 中。
  2. JVM 触发 JVMTI_EVENT_CLASS_FILE_LOAD_HOOK 事件。
  3. jvmtiClassFileLoadHookPoster 实例被创建并填充相关信息。
  4. jvmtiClassFileLoadHookPoster.post() 方法被调用,通知注册的代理程序。
  5. 代理程序可以检查和修改字节码。
  6. 修改后的字节码被加载到 JVM 中。

因此我们跟踪到jvmtiClassFileLoadHookPoster.post()方法,其中触发类中post_all_envs()方法,首先会判断当前触发事件的形式,可以区别为retransform和非retransform(正常load class、redefineClass),这会影响到执行的post_to_env方法是传入的第二个变量。无论哪种方式都会遍历JvmtiEnv链,对每个jvmtienv执行post_to_env方法,继续跟进

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private:
  void post_all_envs() {
    if (_load_kind != jvmti_class_load_kind_retransform) {
      // for class load and redefine,
      // call the non-retransformable agents
      JvmtiEnvIterator it;
      for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
        if (!env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {
          // non-retransformable agents cannot retransform back,
          // so no need to cache the original class file bytes
          post_to_env(env, false);
        }
      }
    }
    JvmtiEnvIterator it;
    for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
      // retransformable agents get all events
      if (env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {
        // retransformable agents need to cache the original class file
        // bytes if changes are made via the ClassFileLoadHook
        post_to_env(env, true);
      }
    }
  }

进入post_to_env方法,发现如下代码也就是说他会对JvmTiEnv链上的每个jvmtienv调用callbacks方法中的ClassFileLoadHook,承接上面,这里就会触发我们自定义的transform方法

image-20240318200150804

再往下看,通过上一步如果生成了新类(判断方式是new data是否为空,但是前面的代码可以知道只要触发retransform正常执行这都不是空),如果new_data不为空则会对类进行缓存操作,再往下主要就是替换curr_len和curr_data来重置原本的类

image-20240716104501993

这样整个retransform修改字节码流程就串起来了,首先我们执行addTransform方法时,会利用retransformableEnvironment(agent),取出JPLISAgent中的JvmTiEnv(新agent都是构建),没有的话就会新构造一个JvmTiEnv,在新构造JvmTiEnv时,会为其中的callbacks -> ClassFileLoadHook赋值为eventHandlerClassFileLoadHook方法,方法中包含会触发我们的transform的代码,然后在jvm加载字节码时,会经过jvmtiClassFileLoadHookPoster的处理,其中会自动执行post方法,方法向下执行会自动调用JvmTiEnv链上每个JvmTiEnv的callbacks -> ClassFileLoadHook,从而触发到我们定义的transform方法,最后执行retransformerClasses方法会调用VM_RedefineClasses接口重新加载我们修改好的字节码

简单理解:

1
2
addTransformer:创建一个jvmtiEnv并挂一个事件回调函数 -->
retransformer:调用jvmtiClassFileLoadHookPoster并调用jvmtiEnv链的事件传递执行,触发挂好的回调函数,回调函数自动执行

这里顺带把redefineClasses修改字节码也说了,比较简单,直接调用VM_RedefineClasses接口将目标类修改为我们传入的新字节码

1
2
3
4
5
6
7
jvmtiError
JvmtiEnv::RedefineClasses(jint class_count, const jvmtiClassDefinition* class_definitions) {
//TODO: add locking
  VM_RedefineClasses op(class_count, class_definitions, jvmti_class_load_kind_redefine);
  VMThread::execute(&op);
  return (op.check_error());
} /* end RedefineClasses */

参考

https://goodapple.top/archives/1355

https://xz.aliyun.com/t/9450

https://xz.aliyun.com/t/13110

https://paoka1.top/2023/04/24/Tomcat-Agent-型内存马

0%