GadgetInspector 是 JackOfMostTrades 在 2018 BLACKHAT USA 上发布的一个自动化反序列化链挖掘工具,它通过对字节码形式的JAVA项目进行污点分析,挖掘可能存在的反序列化链。
我们从GadgetInspector的源代码进行分析。根据源代码的逻辑结构,可以看出其分成六个部分:
ClassResourceEnumerator 读取类文件,保存到自定义的数据结构里
MethodDiscovery 分析类文件包含的的类、方法信息
PassthrougthDiscovery 对方法进行局部污点分析
CallGraphDiscovery 枚举污点边
SourceDiscovery 枚举源点
GadgetChainDicovery 生成反序列化链
六个部分层层递进,最终完成反序列化链挖掘。
0x11 ClassResourceEnumerator
GadgetInspector的核心入口类是 GadgetInspector
这个类。它的main方法根据输入参数的区别,可以生成war包或者jar包对应的 ClassLoader
,并利用 ClassLoader
作为参数构造 ClassResourceEnumerator
对象
final ClassLoader classLoader;
if (args.length == argIndex+1 && args[argIndex].toLowerCase().endsWith(".war")) {
Path path = Paths.get(args[argIndex]);
classLoader = Util.getWarClassLoader(path);
} else {
final Path[] jarPaths = new Path[args.length - argIndex];
for (int i = 0; i < args.length - argIndex; i++) {
Path path = Paths.get(args[argIndex + i]).toAbsolutePath();
jarPaths[i] = path;
}
classLoader = Util.getJarClassLoader(jarPaths);
}
final ClassResourceEnumerator classResourceEnumerator = new ClassResourceEnumerator(classLoader);
ClassResourceEnumerator
对象主要的作用就是获取用于分析的类文件,我们看一下它的主要方法 getAllClasses
。可以看到首先调用 getRuntimeClasses
获取JDK中的类文件,然后使用刚才创建的 ClassLoader
读取作为分析对象的JAVA项目的类文件
public Collection<ClassResource> getAllClasses() throws IOException {
Collection<ClassResource> result = new ArrayList<>(getRuntimeClasses());
for (ClassPath.ClassInfo classInfo : ClassPath.from(classLoader).getAllClasses()) {
result.add(new ClassLoaderClassResource(classLoader, classInfo.getResourceName()));
}
return result;
}
在读取了类文件以后,GadgetInspector通过5个阶段的discovery来获取反序列化链,它们的实现框架大致相同:都是根据上一个阶段的分析结果,进行进一步分析,然后保存这个阶段的分析结果。
// Perform the various discovery steps
if (!Files.exists(Paths.get("classes.dat")) || !Files.exists(Paths.get("methods.dat"))
|| !Files.exists(Paths.get("inheritanceMap.dat"))) {
MethodDiscovery methodDiscovery = new MethodDiscovery();
methodDiscovery.discover(classResourceEnumerator);
methodDiscovery.save();
}
对应到具体的方法就是 discover
和 save
方法。这里先仔细分析MethodDiscovery部分的 discover
方法。
这里使用上一步创建的 ClassResourceEnumerator
对象获取类输入流
public void discover(final ClassResourceEnumerator classResourceEnumerator) throws Exception {
for (ClassResourceEnumerator.ClassResource classResource : classResourceEnumerator.getAllClasses()) {
try (InputStream in = classResource.getInputStream()) {
ClassReader cr = new ClassReader(in);
cr.accept(new MethodDiscoveryClassVisitor(), ClassReader.EXPAND_FRAMES);
}
}
}
注意这里的 ClassReader
和 MethodDiscoveryClassVistor
用到JAVA中的ASM技术,用来读取类文件并进行分析。
ASM是一种JAVA字节码操作,读取,增强技术。它直接分析类文件中的各种信息,例如常量池,操作码,要求其使用者对于JAVA类文件的结构有一定了解。其主要的类操作API为ClassVisitor类。使用者对ClassVisitor进行个性化实现,满足自己的需求。例如下面这一段代码就是ClassVisitor的API的使用步骤
java public static void main(String[] args) throws Exception{ //实现一个自己的ClassVistor类型的对象 ClassVisitor classVisitor=new ClassVisitor(Opcodes.ASM9) { //重载一个回调方法,当访问字段时会打印字段名称 @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { System.out.println(name); return super.visitField(access, name, desc, signature, value); } }; //获取一个类文件,这里是Hello这个测试类 ClassLoader classLoader= VisitClass.class.getClassLoader(); String path= Hello.class.getCanonicalName().replace(".","/")+".class"; InputStream inputStream=classLoader.getResourceAsStream(path); //使用类文件构造一个ClassReader类型的对象 ClassReader classReader=new ClassReader(inputStream); //使用自定义的ClassVisitor类型的对象并解析ClassReader里面保存的对象流 classReader.accept(classVisitor,0); }
编译运行以后就会打印 Hello
这个类的所有字段名称
ASM主要的方法操作API为MethodVisitor类,使用者对MethodVisitor进行个性化实现,可以进一步解析类方法。MethodVisitor的用法下面再介绍
我们需要知道的就是 cr.accept
接收一个 ClassVistor
类型的对象,并且在分析类文件过程中回调式地触发 ClassVistor
类型的对象实现的各种方法。那么我们继续查看 MethodDiscoveryClassVistor
。里面两个关键的方法如下
@Override
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
if ((access & Opcodes.ACC_STATIC) == 0) {
Type type = Type.getType(desc);
String typeName;
if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
typeName = type.getInternalName();
} else {
typeName = type.getDescriptor();
}
// 记录成员信息
members.add(new ClassReference.Member(name, access, new ClassReference.Handle(typeName)));
}
return super.visitField(access, name, desc, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String d