前言 学习Java Agent内存马,需要看一看底层Native代码。
源码下载 我们安装的Oracle JDK只提供java和javax的源代码,所以我们还需要从OpenJDK下载源代码,下载地址 ,点击左边的zip即可下载。
Java InstrumentationImpl类 根据参考文章,加载代理最后就是为了产生Instrumentation对象,即该接口的实现InstrumentationImpl对象。InstrumentationImpl类中存在一个redefineClasses函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 ; } redefineClasses0(mNativeAgent, definitions); }
ClassDefinition是一个包括类名和类字节码的对象,也就是说这个函数可以通过一个ClassDefinition重新定义一个类。可以看到最后一行,函数进入了Native部分进行类的重定义,参数为一个long类型的私有属性和这个ClassDefinition对象,在源码中找到这个native函数:
1 2 3 4 5 6 7 8 9 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函数中第一句就是:
1 jvmtiEnv* jvmtienv = jvmti(agent);
jvmti是一个宏:
1 #define jvmti(a) a->mNormalEnvironment.mJVMTIEnv
作用是从agent结构的mNormalEnvironment-mJVMTIEnv中取出一个jvmtiEnv指针,然后这个agent指针就没有用了,后续就是通过这个jvmtienv指针重定义类:
1 2 jvmtiError errorCode = JVMTI_ERROR_NONE; errorCode = (*jvmtienv)->RedefineClasses(jvmtienv, numDefs, classDefs);
所以问题就从如何获取agent指针变成了如何获取jvmtiEnv指针,其中也同样定义了一个RedefineClasses函数(C语言下):
1 2 3 4 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内存马