WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

Javaweb

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

PWN

CTF

heap

其它

关于博客

面试

杂谈

javassist入门

javassist是用来生成class字节码的类库

0x01 添加依赖

1
2
3
4
5
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>

0x02 生成字节码

下面来看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import javassist.*;

import java.io.FileOutputStream;

public class javassist_test {
public static void createClass() throws Exception{
//实例化一个ClassPool容器
ClassPool pool = ClassPool.getDefault();
//新建一个CtClass,类名为test
CtClass cc = pool.makeClass("test");
//添加父类
//这个父类必须存在
//这个父类是随便写的
cc.setSuperclass(pool.get("com.test.superclass"));
//添加接口
//同样的也必须存在,可以实现多个接口吗,以CtClass[]数组的方式
//这个aa是随手写的,如果没有是没办法实现的
cc.setInterfaces(new CtClass[]{pool.get("com.test.aa"),pool.get("java.io.Serializable")});

//写java代码
String cmd = "System.out.println(\"test\");";
//制作一个空的类初始化,插入到静态代码块内
//在初始化时就能调用
cc.makeClassInitializer().insertBefore(cmd);

//重新设置类名
String randomClassName = "EvilTest";
cc.setName(randomClassName);
//设置构造函数
//平时写java时不写无参构造函数在编译class的时候会默认加上,这里可能就直接没有了所以在实例化的时候可能报错
//这是无参构造函数
CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, cc);
//设置构造函数内代码
ctConstructor.setBody("{System.out.println(0);}");
//添加构造函数
cc.addConstructor(ctConstructor);

//设置类的属性,第一个参数为属性的类型,如果是基础的属性是这样写的CtClass.charType,第二个参数是属性的名称,最后一个参数是指定类
CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
//设置属性的修饰符,如果要加静态是这样写的Modifier.PUBLIC|Modifier.STATIC
//这里指定私有
param.setModifiers(Modifier.PRIVATE);
//添加属性,第二个参数给属性添加默认值
cc.addField(param, CtField.Initializer.constant(""));

//设置有参数的构造函数,指定参数类型
CtConstructor ctConstructor2 = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
//这里是要实现this.name=name,$0=this,$1就是接收到的参数
ctConstructor2.setBody("{$0.name = $1;}");
//添加构造函数
cc.addConstructor(ctConstructor2);

//设置一个名称为PrintName的函数,第一个参数为返回值,第二个参数是函数名,第三个参数是函数的参数,最后指定类
CtMethod ctMethod = new CtMethod(pool.get("java.lang.String"), "PrintName", new CtClass[]{}, cc);
//设置函数中的代码,因为前面有指定返回类型,不要忘记返回相同的类型
ctMethod.setBody("{String name = \"aaa\";System.out.println(name);return name;}");
//如果要设置函数的修饰符也是和属性一样的ctMethod.setModifiers(Modifier.PUBLIC);
//添加函数
cc.addMethod(ctMethod);

//将生成的类文件保存下来
cc.writeFile();
//加载该类
// Class c = cc.toClass();
//创建对象
// c.newInstance();
}


public static void main(String[] args) {
try {
createClass();
} catch (Exception e){
e.printStackTrace();
}
}
}

下面为生成的字节码文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import com.test.aa;
import com.test.superclass;
import java.io.Serializable;

public class EvilCat extends superclass implements aa, Serializable {
private String name = "";

static {
System.out.println("test");
}

public EvilCat() {
System.out.println(1);
}

public EvilCat(String var1) {
this.name = var1;
}

public String PrintName() {
String var1 = "aaa";
System.out.println(var1);
return var1;
}
}

当时还有一点想不明白

该怎么导入外部类

搜索了之后发现有2种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import javassist.*;

public class javassist_import {
public static void createClass() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("test");

pool.importPackage("java.util");

CtMethod ctMethod = new CtMethod(pool.get("java.util.HashMap"), "PrintHashMap", new CtClass[]{}, cc);
ctMethod.setBody("{HashMap hashMap = new HashMap();return hashMap;}");
cc.writeFile();

}


public static void main(String[] args) {
try {
createClass();
} catch (Exception e){
e.printStackTrace();
}
}
}

使用importPackage方法导入包,这样下面的类都可以直接使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import java.util.HashMap;

public class Cat {
public HashMap PrintHashMap() {
HashMap var1 = new HashMap();
return var1;
}

public Cat() {
}
}

这里还有一段小插曲,当时一直找不到改怎么写,就去问一位师傅,师傅去查了GPT说有这个方法

不过当时GPT的示例代码上面显示该方法在CtClass下,去查了官方文档发现没有这方法,然后就懵了

还以为GPT开始编造方法了

后面师傅说有些文章也是有写这方法,再去搜索后发现是ClassPool类的方法

当时继续问GPT还说不好意思说错了没有这个方法,然后给了第二种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import javassist.*;

public class javassist_import {
public static void createClass() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("test");


CtMethod ctMethod = new CtMethod(pool.get("java.util.HashMap"), "PrintHashMap", new CtClass[]{}, cc);
ctMethod.setBody("{java.util.HashMap hashMap = new java.util.HashMap();return hashMap;}");
cc.writeFile();

}


public static void main(String[] args) {
try {
createClass();
} catch (Exception e){
e.printStackTrace();
}
}
}

直接写全包名是相同的效果

最后是修改类

首先创建一个测试的类

1
2
3
4
5
6
7
8
9
10
11
package com.test;

public class test {

public test(){
System.out.println("0");
}
public static void main(String[] args) {
System.out.println("HelloWorld");
}
}

然后使用javassist修改测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import javassist.*;
import java.io.FileOutputStream;

public class javassist_test {

public static void resetClass() throws Exception{
ClassPool pool = ClassPool.getDefault();
//用get取到测试类
CtClass ctClass = pool.get("com.test.test");
//去构造函数
CtConstructor constructor = ctClass.getConstructors()[0];
//在构造函数前加上代码
constructor.insertBefore("System.out.print(\"211\");");
//在构造函数后加上代码
constructor.insertAfter("{System.out.print(\"000\");}");
//这里的大括号没什么关系
//设置静态代码块方法
ctClass.makeClassInitializer().setBody("{System.out.print(\"000\");}");
//字节码内容写到文件内
new FileOutputStream("2.class").write(ctClass.toBytecode());
}
public static void main(String[] args) {
try {
resetClass();
} catch (Exception e){
e.printStackTrace();
}
}
}

上面就是一些javassist的基础用法

修改类的方法还有一些,比如函数过大不可能重写,有办法通过匹配来指定的内容

这里没用到先不写了后面用到再写