深度剖析GadgetInspector执行逻辑(上)
GadgetInspector
该类是这个项目的主类
首先就是配置日志格式
这里是使用的log4j进行控制台日志的输出,分别设置了了布局格式 / 日志级别 / 激活配置 等等操作
之后在主类中就是进行GIConfig
接口的实现类
这里优先获取的是默认的反序列化规则,什么意思呢?我们跟进getConfig
方法中看看(在gadgetinspector.config包的解释中)
之后我们这里返回的是普通的反序列化方式
之后继续回到主类的分析
这一部分主要是用来对添加参数的判断,怎么判断的呢?
首先通过判断在传入的参数中使用具有--
这个关键词,如果没有会直接退出这部分代码进行接下来的jar类提取
而对于--resume
这个参数,默认的resume
值是为false
这个布尔类型的,如果添加了该参数,将会将这个布尔值置为true,至于这个参数的作用是用来判断上次解析之后的结果还需不需要重复使用的标志,后面会一次提到
之后如果存在有--config
这个参数,这个参数使用来指定是否是什么类型的反序列化,如果没有指定特定的参数,使用的是前面获取的普通的反序列化方式
直接接着看接下来的流程
这里有两种格式,支持war / jar
格式的提取,首先判断的是.war
格式的文件
通过调用Util.getWarClassLoader
方法来获取这个ClassLoader对象
但是如果不是.war
的格式,将会调用Util.getJarClassLoader
方法来创建一个ClassLoader对象,其中的参数为jar包的path数组
对于Util类的解释可以看后面的内容
之后就是将得到的classLoader
传入ClassResourceEnumerator
类的构造方法中,创建了一个ClassResourceEnumerator
对象
对于该对象的解释在后面也可以找到
接着就是根据前面的resume
的配置来判断是否删除已经存在的结果
接下来就是进行从传入的jar包中或者war中提取我们需要的数据并保存
这一步的目的是获取类 / 方法 / 继承图 等等的数据并保存
接下来的一步就是数据流的调用图
后面就是通过类似的结构生成了callgraph.dat / sources.dat
的文件
最后通过调用GadgetChainDiscovery#discover
方法来生成利用链
gadgetinspector.config包
首先大致来看一下包下有哪些类或者接口
GIConfig接口
我们可以看到在这个包下包含有一个接口GIConfig
定义了一些方法getSerializableDecider / getImplementationFinder / getSourceDiscovery
,分别用来进行对应的序列化选择器 / 实现类的方法 / 资源的发现这些功能
我们在前面提到了默认是通过调用ConfigRepository.getConfig("jserial");
的方法来获取默认的配置
通过不同的name来返回对应的反序列化配置,原始的gadgetInspector
中内置了normal / Jackson / XStream
这三种不同的反序列化方式
gadgetinspector.javaserial / jackson / xstream包
这三个包下面分别实现了不同类型的SerializableDecider / ImplementationFinder / SourceDiscovery
SerializableDecider
对于普通的SerializableDecider,为SimpleSerializableDecider类的实现
构造方法传入的是一个类的继承关系图
gadgetinpector.Util类
这个类主要是作者设定的工具类
里面主要存在有四个方法
他们分别的作用为:
getWarClassLoader
: 获取war包中的jar包依赖,返回一个ClassLoader对象getJarClassLoader
: 获取jar包的存放路径的ClassLoader对象deleteDirectory
: 递归删除根目录和其中所有的类型copy
: 将输入流复制到输出流
这里可以仔细看看其中的逻辑
对于第二个方法来说,逻辑很简单,就是通过将传入的jar包路径转为URL对象之后将所有的jar包的路径放在了一个List
集合中
之后创建了一个URLClassLoader
对象进行返回
相比与使用jar包路径作为路径的方式,如果使用war作为路径当作参数进行传入
将会调用getWarClassLoader
方法来获取其中war包中的所有jar的URL创建一个URLClassLoader进行返回
在这个方法中有几个关键步骤
- 需要提取war包中的jar文件
- 创建一个临时文件夹存放提取的所有jar包
- 将这些jar包作为URL创建一个URLCLassLoader进行返回
来看看作者是怎么实现这些功能的
通过Files类创建了一个临时文件夹exploded-war
用于存放jar包
这里设定了将会在程序执行结束的时候删掉我们创建的临时目录和其中的所有内容
是通过deleteDirectory
方法进行实现的
作者是通过使用Files#walkFileTree
方法将文件夹作为一种树形结构进行访问,创建了一个SimpleFileVisitor的匿名对象
重写了其中的两个方法进行文件或者文件加的删除
接下来看看从war包中提取文件的方式
这里主要是通过创建了一个JarInputStream
输入流对象获取每一个JarEntry
之后通过调用copy
方法将输入流复制进入输出流,实现了文件内容的写入,完整的提取了war包中的所有内容
之后就是创建一个URLClassLoader进行返回
这里的tmpDir就是我们创建的临时文件夹,通过拼接WEB-INF/classes
目录来获取war中的类文件,添加进入了classPathUrls中
同时通过遍历WEB-INF/lib
文件夹下的war包中包含的所有jar包,将其路径添加进入classPathUrls
中
gadgetinspector.ClassResourceEnumerator类
这个类是一个类资源的枚举器
在这个类中存在有三个方法
构造方法是传入的一个jar包的URLClassLoader对象
其中的getRuntimeClasses
方法是用来提取当前JDK的 rt.jar包中的类
这一截是用来获取JDK8及以下版本的rt.jar类
作者这里主要是通过获取String.class
的资源路径,进而通过JarURLConnection#getJarFileURL
来获取JDK中的rt.jar这个jar包
其中对于jar包中类的提取主要是通过guava
这个Google开源的开源项目来进行反射操作,主要是通过将前面得到的classLoader传入, 使用ClassPath.from
方法获取ClassPath对象之后通过调用了getAllClasses
方法来获取当前的class path下所有的可以加载的类
其中返回的ClassPath.ClassInfo
这个类是用来代表一个可以通过Load加载的类
之后通过传入了rt.jar的URLClassLoader和每个类ClassPath.ClassInfo
的资源名(xx/xxx/xx/xxxx.class这种格式的类文件)创建了一个ClassLoaderClassResource
对象,添加进入了result中
正好来看看ClassLoaderClassResource
这个静态内部类把
主要是记录了资源名和类加载器,其中对应类的输入流是通过类加载器配合资源名进行获取的
之后就返回了我们从rt.jar包中的提取的ClassResource
对象集合
如果你的JDK版本是8及一下到这里就结束了,但是,如果你的JDK是大于8版本的,使用这种方法进行jar包中类的获取是行不通的
作者在这里使用另一种思路来获取JDK的类
这里更加直接,直接通过遍历classpath下jrt:/
根下的所有的以.class
结尾的资源,使用PathClassResource
这个内部类进行封装了之后添加进入了result这个集合
好了,对于getRuntimeClasses
方法获取JDK的类文件就在这里结束了
回到getAllClasses
方法来获取所有的类文件的实现
这里,首先是获取了JDK中可加载的所有类资源,之后通过ClassPath.from(classLoader).getAllClasses
方法来获取所有jar包中的类资源
这里的classloader
是我们在前面从war或者jar文件中获取的URLClassLoader类对象
之后采取从JDK获取类资源相同的方法,将获取的资源添加进入result这个集合中,进行返回
gadgetInspector.MethodDiscovery类
这个类主要是用来进行类方法的发现和保存的功能
存在有两个方法,分别是discover / save
两个
discover
首先来看看discover方法是如何发现的
在获取所有类的输入流之后创建了一个asm
jar包的ClassReader
对象
之后调用该对象的accept
方法使给定的访问者访问传递给此构造函数的 JVMS ClassFile 结构
作者这里继承了ClassVisitor
类来实现自己的逻辑
这里定义了多个属性,并重写了ClassVisitor
类的几个方法visit / visitField / visitMethod / visitEnd
方法分别在遭遇到类之前 / 遭遇属性 / 遭遇方法 / 类的结尾的时候执行响应的逻辑
其中的visit
方法
主要是记录下该类资源的名称 / 父类名 / 存在的接口 / 是否是接口 / 类的句柄
其中的visitField
方法
主要是将遭遇的属性进行记录
其中的visitMethod
方法
在创建了一个MethodReference
对象之后将其添加进入discoveredMethods属性中
其中的visitEnd
方法
主要是将收集到的类和其成员属性添加进discoveredClasses
属性中
save
而其中的save
方法
- 调用了
DataLoader.saveData
方法进行数据的存储 - 分别将类相关的存入了
classes.dat
, 和方法相关的放入methods.dat
文件 - 接下来就是进行继承关系图的计算了,怎么生成的呢?主要是通过遍历前面已经找到的所有的类,也即是
ClassReference
对象 - 将该对象的句柄Handle和该对象传入HashMap中进行保存,之后通过将这个map对象传入
InheritanceDeriver#derive
方法创建一个InheritanceMap
对象,即是继承图 - 调用这个返回的
InheritanceMap
对象的save
方法进行关系图的保存
gadgetinspector.data.ClassReference类
这个类使用记录类的引用的
用来存储类名/ 父类名 / 接口 / 是否是接口 / 类成员
同时具备获取对应值的getter方法
Member类
我们首先来看看其Member
这个内部类
其属性分别是用来记录该成员的名称 / 权限 / 句柄
Handle类
其中Handle
这个内部类
Factory类
其中还存在有Factory
这个内部类,实现了DataFactory\<ClassReference>
作为类引用的工厂实现类
实现了serialize
方法,自定义对Class相关参考使用特定的格式进行构造
总结一下格式
- 对于存在的接口,通过
,
来连接接口 - 对于member属性, 通过
!
来进行连接,连接的顺序分别为属性名 / 权限 / 属性类型 - 返回一个字符串对象数组,返回的是类名 / 父类 / 接口 / 是否是接口 / member属性