前言

学习Java Agent内存马,需要看一看底层Native代码。


源码下载

我们安装的Oracle JDK只提供java和javax的源代码,所以我们还需要从OpenJDK下载源代码,下载地址,点击左边的zip即可下载。

Java InstrumentationImpl类

根据参考文章,加载代理最后就是为了产生Instrumentation对象,即该接口的实现InstrumentationImpl对象。InstrumentationImpl类中存在一个redefineClasses函数:

public void redefineClasses(ClassDefinition...  definitions)
        throws  ClassNotFoundException {
    if (!isRedefineClassesSupported()) {
        throw new UnsupportedOperationException("redefineClasses is not supported in this environment");
    }
    if (definitions == null) {
        throw new NullPointerException("null passed as 'definitions' in redefineClasses");
    }
    for (int i = 0; i < definitions.length; ++i) {
        if (definitions[i] == null) {
            throw new NullPointerException("element of 'definitions' is null in redefineClasses");
        }
    }
    if (definitions.length == 0) {
        return; // short-circuit if there are no changes requested
    }

    redefineClasses0(mNativeAgent, definitions);
}

ClassDefinition是一个包括类名和类字节码的对象,也就是说这个函数可以通过一个ClassDefinition重新定义一个类。可以看到最后一行,函数进入了Native部分进行类的重定义,参数为一个long类型的私有属性和这个ClassDefinition对象,在源码中找到这个native函数:

/*
 * Class:     sun_instrument_InstrumentationImpl
 * Method:    redefineClasses0
 * Signature: ([Ljava/lang/instrument/ClassDefinition;)V
 */
JNIEXPORT void JNICALL Java_sun_instrument_InstrumentationImpl_redefineClasses0
  (JNIEnv * jnienv, jobject implThis, jlong agent, jobjectArray classDefinitions) {
    redefineClasses(jnienv, (JPLISAgent*)(intptr_t)agent, classDefinitions);
}

这个long类型的其实是个native的对象指针,也就是说如果我们能找到这个指针,可能就可以通过不落地Agent文件的方式修改类代码,

Native

看到redefineClasses函数中第一句就是:

jvmtiEnv* jvmtienv = jvmti(agent);

jvmti是一个宏:

#define jvmti(a) a->mNormalEnvironment.mJVMTIEnv

作用是从agent结构的mNormalEnvironment-mJVMTIEnv中取出一个jvmtiEnv指针,然后这个agent指针就没有用了,后续就是通过这个jvmtienv指针重定义类:

jvmtiError  errorCode = JVMTI_ERROR_NONE;
errorCode = (*jvmtienv)->RedefineClasses(jvmtienv, numDefs, classDefs);

所以问题就从如何获取agent指针变成了如何获取jvmtiEnv指针,其中也同样定义了一个RedefineClasses函数(C语言下):

/*   87 : Redefine Classes */
jvmtiError (JNICALL *RedefineClasses) (jvmtiEnv* env,
  jint class_count,
  const jvmtiClassDefinition* class_definitions);

常规手段好像是通过Native开发的dll在Native层面调用导出的JNI_GetCreatedJavaVMs函数获取JavaVM对象,再通过其GetEnv函数找到,然而这个手法需要落地一个dll文件,同样不开心。

根据参考文章,拿到这个jvmtienv指针的方式各平台各有不同。

Windows

WindowsVirtualMachine的Native函数enqueue可以直接注入并运行shellcode,使用System.loadLibrary加载attach库后即可使用。

ShellCode不会写。

Linux

解析libjvm.so文件计算函数偏移,再读取/proc/self/maps找到库加载基址,同样是编写shellcode,写入/proc/self/mem并执行。

还是ShellCode不会写。

拿到jvmtienv指针后,使用Unsafe申请内存并伪造agent对象,最后调用Java里面InstrumentationImpl类的redefineClasses函数完成类的重定义。


参考

JDK源码下载

Linux下内存马进阶植入技术

论如何优雅的注入Java Agent内存马


Web Java

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

Java ASM
Java Tomcat Executor/Processor 内存马