该类是这个项目的主类
首先就是配置日志格式
这里是使用的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方法来生成利用链
首先大致来看一下包下有哪些类或者接口
我们可以看到在这个包下包含有一个接口GIConfig
定义了一些方法
getSerializableDecider / getImplementationFinder / getSourceDiscovery
分别用来进行对应的序列化选择器 / 实现类的方法 / 资源的发现这些功能
我们在前面提到了默认是通过调用ConfigRepository.getConfig("jserial");的方法来获取默认的配置
通过不同的name来返回对应的反序列化配置,原始的gadgetInspector中内置了normal/Jackson/XStream这三种不同的反序列化方式
这三个包下面分别实现了不同类型的SerializableDecider/ImplementationFinder/ SourceDiscovery
对于普通的SerializableDecider
为SimpleSerializableDecider类的实现
构造方法传入的是一个类的继承关系图
这个类主要是作者设定的工具类
里面主要存在有四个方法
他们分别的作用为:
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中
这个类是一个类资源的枚举器
在这个类中存在有三个方法
构造方法是传入的一个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这个集合中,进行返回
这个类主要是用来进行类方法的发现和保存的功能
存在有两个方法,分别是discover / save两个
首先来看看discover方法是如何发现的
在获取所有类的输入流之后创建了一个asmjar包的ClassReader对象
之后调用该对象的accept方法使给定的访问者访问传递给此构造函数的JVMS ClassFile 结构
作者这里继承了ClassVisitor类来实现自己的逻辑
这里定义了多个属性,并重写了ClassVisitor类的几个方法
visit / visitField / visitMethod / visitEnd方法
分别在遭遇到类之前 / 遭遇属性 / 遭遇方法 / 类的结尾的时候执行响应的逻辑
其中的visit方法
主要是记录下该类资源的名称/父类名/存在的接口/是否是接口/类的句柄
其中的visitField方法
主要是将遭遇的属性进行记录
其中的visitMethod方法
在创建了一个MethodReference对象之后将其添加进入discoveredMethods属性中
其中的visitEnd方法
主要是将收集到的类和其成员属性添加进discoveredClasses属性中
而其中的save方法
调用了DataLoader.saveData方法进行数据的存储
分别将类相关的存入了classes.dat, 和方法相关的放入methods.dat文件
接下来就是进行继承关系图的计算了,怎么生成的呢?主要是通过遍历前面已经找到的所有的类,也即是ClassReference对象
将该对象的句柄Handle和该对象传入HashMap中进行保存,之后通过将这个map对象传入InheritanceDeriver#derive方法创建一个InheritanceMap对象,即是继承图
调用这个返回的InheritanceMap对象的save方法进行关系图的保存
这个类使用记录类的引用的
用来存储类名/父类名/接口/是否是接口/类成员
同时具备获取对应值的getter方法
我们首先来看看其Member这个内部类
其属性分别是用来记录该成员的名称 / 权限 / 句柄
其中Handle这个内部类
其中还存在有Factory这个内部类,实现了DataFactory\作为类引用的工厂实现类
实现了serialize方法,自定义对Class相关参考使用特定的格式进行构造
总结一下格式
对于存在的接口,通过","来连接接口
对于member属性,通过!来进行连接,连接的顺序分别为属性名/权限/属性类型
返回一个字符串对象数组,返回的是类名/父类/接口/是否是接口/member属性
往期推荐