Java安全之Javassist动态编程

作者 | nice_0e3
来源 | urlify.cn/M3U7Br
前言
在调试CC2链前先来填补知识盲区 , 先来了解一下Javassist具体的作用 。 在CC2链会用到Javassist以及PriorityQueue来构造利用链
1 Javassist 介绍Java 字节码以二进制的形式存储在 class 文件中 , 每一个 class 文件包含一个 Java 类或接口 。 Javaassist 就是一个用来处理 Java 字节码的类库 。
Javassist是一个开源的分析、编辑和创建Java字节码的类库 。
2 Javassist 使用这里主要讲一下主要的几个类:
ClassPoolClassPool:一个基于哈希表(Hashtable)实现的CtClass对象容器 , 其中键名是类名称 , 值是表示该类的CtClass对象(Hashtable和Hashmap类似都是实现map接口 , hashmap可以接收null的值 , 但是Hashtable不行) 。
常用方法:static ClassPool getDefault() 返回默认的类池 。 ClassPath insertClassPath(java.lang.String pathname)在搜索路径的开头插入目录或jar(或zip)文件 。 ClassPath insertClassPath(ClassPath cp)ClassPath在搜索路径的开头插入一个对象 。 java.lang.ClassLoader getClassLoader()获取类加载器toClass() , getAnnotations()在 CtClass等CtClass get(java.lang.String classname)从源中读取类文件 , 并返回对CtClass 表示该类文件的对象的引用 。 ClassPath appendClassPath(ClassPath cp)将ClassPath对象附加到搜索路径的末尾 。 CtClass makeClass(java.lang.String classname) 创建一个新的public类CtClassCtClass表示类 , 一个CtClass(编译时类)对象可以处理一个class文件 , 这些CtClass对象可以从ClassPoold的一些方法获得 。
常用方法:void setSuperclass(CtClass clazz) 更改超类 , 除非此对象表示接口 。 java.lang.Class toClass(java.lang.invoke.MethodHandles.Lookup lookup)将此类转换为java.lang.Class对象 。 byte[] toBytecode()将该类转换为类文件 。 void writeFile()将由此CtClass 对象表示的类文件写入当前目录 。 void writeFile(java.lang.String directoryName)将由此CtClass 对象表示的类文件写入本地磁盘 。 CtConstructor makeClassInitializer()制作一个空的类初始化程序(静态构造函数) 。 CtMethodCtMethod:表示类中的方法 。
CtConstructorCtConstructor的实例表示一个构造函数 。 它可能代表一个静态构造函数(类初始化器) 。
常用方法void setBody(java.lang.String src)设置构造函数主体 。 void setBody(CtConstructor src, ClassMap map)从另一个构造函数复制一个构造函数主体 。 CtMethod toMethod(java.lang.String name, CtClass declaring)复制此构造函数并将其转换为方法 。 ClassClassPath该类作用是用于通过 getResourceAsStream() 在 java.lang.Class 中获取类文件的搜索路径 。
构造方法:
ClassClassPath(java.lang.Class c)创建一个搜索路径 。 常见方法:java.net.URL find (java.lang.String classname)获取指定类文件的URL 。 java.io.InputStream openClassfile(java.lang.String classname)通过获取类文getResourceAsStream() 。 代码实例:ClassPool pool = ClassPool.getDefault();在默认系统搜索路径获取ClassPool对象 。
如果需要修改类搜索的路径需要使用insertClassPath方法进行修改 。
pool.insertClassPath(new ClassClassPath(this.getClass()));将本类所在的路径插入到搜索路径中
toBytecodepackage com.demo;import javassist.*;import java.io.IOException;import java.util.Arrays;public class testssit {public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {ClassPool pool = ClassPool.getDefault();pool.insertClassPath(new ClassClassPath(demo.class.getClass()));CtClass ctClass = pool.get("com.demo.test");ctClass.setSuperclass(pool.get("com.demo.test"));//System.out.println(ctClass);byte[] bytes = ctClass.toBytecode();String s = Arrays.toString(bytes);System.out.println(s);
Java安全之Javassist动态编程文章插图
toClassHello类:public class Hello {public void say() {System.out.println("Hello");}}Test 类public class Test {public static void main(String[] args) throws Exception {ClassPool cp = ClassPool.getDefault();//在默认系统搜索路径获取ClassPool对象 。CtClass cc = cp.get("com.demo.Hello");//获取hello类的CtMethod m = cc.getDeclaredMethod("say"); //获取hello类的say方法m.insertBefore("{ System.out.println(\"Hello.say():\"); }");//在正文的开头插入字节码Class c = cc.toClass();//将此类转换为java.lang.Class对象Hello h = (Hello)c.newInstance(); //反射创建对象并进行强转h.say();调用方法say}}
Java安全之Javassist动态编程文章插图
3 一些小想法按照我的理解来说就是可以去将类和字节码进行互相转换 。 那么按照这个思路来延申的话 , 我们可以做到什么呢?我首先想到的可能就是webshell的一些免杀 , 例如说Jsp的最常见的一些webshell , 都是采用Runtime , ProcessBuilder这两个类去进行构造 , 执行命令 。 按照WAF的惯性这些设备肯定是把这些常见的执行命令函数给拉入黑名单里面去 。 那么如果说可以转换成字节码的话呢?字节码肯定是不会被杀的 。 如果说这时候将Runtime这个类转换成字节码 , 内嵌在Jsp中 , 后面再使用Javassist来将字节码还原成类的话 , 如果转换的几个方法没被杀的话 , 是可以实现过WAF的 。 当然这些也只是我的一些臆想,因为Javassist并不是JDK中自带的 , 实现的话后面可以再研究一下 。 但是类加载器肯定是可以去加载字节码 , 然后实现执行命令的 。 这里只是抛砖引玉 , 更多的就不细说了 。 如果有更好的想法也可以提出来一起去交流 。