libAFL速通Fuzzing101 (3)

本系列是学习《Fuzzing101 with LibAFL》系列博客(后文统称:原博客)的笔记分享,在学习介绍 LibAFL 用法的同时总结 Rust 知识点。
前置知识: fuzz基本概念、AFL基本使用
本篇要点

  • LibAFL
    • Forkserver模式: Sugar API
    • AFL tools:afl-cmin、optmin、afl-tmin、afl-cov
    • 字段固定
  • Rust
    • Builder
    • clap

Execise-3

source & corpus

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# tcpdump
wget https://github.com/the-tcpdump-group/tcpdump/archive/refs/tags/tcpdump-4.9.1.tar.gz
tar -xzvf tcpdump-4.9.1.tar.gz
mv tcpdump-tcpdump-4.9.1 tcpdump
rm tcpdump-4.9.1.tar.gz
# tcpdump的依赖pcap
wget https://github.com/the-tcpdump-group/libpcap/archive/refs/tags/libpcap-1.8.0.tar.gz
tar -xzvf libpcap-1.8.0.tar.gz
mv libpcap-libpcap-1.8.0/ libpcap
rm libpcap-1.8.0.tar.gz
# 用scapy生成corpus
## 原文使用poetry,直接pip也可
pip install scapy
python create-bootp.py
# 本篇没用到qemu,但依然需要这个依赖
apt-get install -y ninja-build

Makefile.toml

还是熟悉的配方,还是熟悉的cargo make build。还是不熟悉的bug。。。

 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
[tasks.build]
dependencies = [
    "build-cargo",
    "copy-project-to-build",
    "build-libpcap",
    "build-tcpdump",
]

[tasks.build-cargo]
command = "cargo"
args = ["build", "--release"]

[tasks.copy-project-to-build]
script = """
mkdir -p build/
cp ../target/release/exercise-3 build/
sudo setcap cap_sys_admin+epi build/exercise-3
"""

[tasks.build-libpcap]
env = { "CC" = "afl-clang-lto", "LLVM_CONFIG" = "llvm-config-15", "AFL_MAP_SIZE" = "86217", "AFL_USE_ASAN" = "1" }
cwd = "libpcap"
script = """
./configure --enable-shared=no --prefix="${CARGO_MAKE_WORKING_DIRECTORY}/../build/"
make
make install
"""

[tasks.build-tcpdump]
cwd = "tcpdump"
script = """
./configure --prefix="${CARGO_MAKE_WORKING_DIRECTORY}/../build/"
make
make install
sudo setcap cap_sys_admin+epi ../build/sbin/tcpdump
mkdir -p ../solutions
"""

[tasks.build-tcpdump.env]
"CC" = "afl-clang-lto"
"LLVM_CONFIG" = "llvm-config-15"
"AFL_USE_ASAN" = "1"
"AFL_MAP_SIZE" = "86217"
"CFLAGS" = "-I${CARGO_MAKE_WORKING_DIRECTORY}/../build/include/"
"LDFLAGS" = "-L${CARGO_MAKE_WORKING_DIRECTORY}/../build/lib/"
补充:Linux中的Capability机制

只有root和普通进程的权限管理不够灵活,普通进程要么什么都不能做,要么sudo什么都能做。于是将root特权分割成诸多能力Capability。

进程拥有三组能力集:

  • cap_effective:可用能力集
  • cap_inheritable:可继承能力集
  • cap_permitted:最大能力集

可执行文件也有三组能力集,与进程对应:

  • cap_effective:
  • cap_allowed:可继承的能力集
  • cap_forced:必须拥有才能执行的能力集

均可简记为eip。setcap是配置Capability的工具,其操作类似chmod。

Debug: build-libpcap

啥叫 -ldw
1
2
= note: /usr/bin/ld: cannot find -ldw: No such file or directory
        collect2: error: ld returned 1 exit status
  • -ldw代表libdw.so,apt安装即可
我afl-clang-lto呢
1
2
3
configure:2853: afl-clang-lto --version >&5
./configure: line 2855: afl-clang-lto: command not found
configure:2864: $? = 127
  • 让CC指向AFLpulsplus里面的afl-clang-lto。切记要绝对路径
你根本不在bpf/net
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ make  --debug
Reading makefiles...
Updating makefiles....
Updating goal targets....
 File 'all' does not exist.
   File 'libpcap.a' does not exist.
       Prerequisite 'grammar.c' is newer than target 'grammar.h'.
      Must remake target 'grammar.h'.
      Successfully remade target file 'grammar.h'.
     File 'bpf_filter.o' does not exist.
       File 'bpf_filter.c' does not exist.
         File 'bpf/net/bpf_filter.c' does not exist.
        Must remake target 'bpf/net/bpf_filter.c'.
make: *** No rule to make target 'bpf/net/bpf_filter.c', needed by 'bpf_filter.c'.  Stop.

好熟悉的bug,👴模糊的记得之前捣鼓别的fuzzer的时候好像也遇到过,但是👴清楚的记得当时我没解决。

于是,👴开始排查是否有依赖缺失,sudo apt-get install libpcap-dev,没用。总不能是 WSL 内核就少点东西吧,我都WSL2了。

又回头看了看源码,发现 libpcap 的 Github 上有一份bpf_filter.c,而作者给的fuzzing-101-solution里面没有。👴直接上libpacp仓库复制,没用。

再看看 fuzzing-101 仓库,他用的是 libpacp1.80 版本,👴去翻仓库的标签,果然1.8版本有bpf/net/bpf_filter.c。破案了。👴直接进行一个issue的提。

后面一步没报错。偶剋~

1
2
3
4
5
6
7
8
$ ./build/sbin/tcpdump --h
tcpdump version 4.9.1
libpcap version 1.8.0
OpenSSL 3.0.2 15 Mar 2022
Compiled with AddressSanitizer/CLang.
$ ./build/sbin/tcpdump -r corpus/bootp-testcase.pcap
reading from file corpus/bootp-testcase.pcap, link-type IPV4 (Raw IPv4)
15:11:03.147545 IP localhost.bootps > 127.1.1.1.bootpc: BOOTP/DHCP, Request from 00:00:00:00:00:00 (oui Ethernet), length 236

fuzzer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
    let parsed_opts = parser::parse_args();
    let cores = Cores::from_cmdline(&parsed_opts.cores).expect("Failed to parse cores");

    ForkserverBytesCoverageSugar::<86217>::builder()
        .input_dirs(&[parsed_opts.input])
        .output_dir(parsed_opts.output)
        .cores(&cores)
        .program(parsed_opts.target)
        .debug_output(parsed_opts.debug)
        .arguments(&parsed_opts.args)
        .build()
        .run()
}

Sugar API to simplify the life of the naive user of LibAFL

LibAFL贴心的为我这种 naive user 准备了一条龙服务,提供了 Sugar API 将前两篇的样板代码进一步简化。好的,fuzzer写完了。

Rust 基础之 Builder

parser.rs

 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
#[derive(Parser, Debug)]
pub struct FuzzerOptions {

    #[clap(short, long, default_value = "solutions")]
    pub output: PathBuf,

    #[clap(short, long, default_value = "corpus", multiple_values = true)]
    pub input: PathBuf,

    #[clap(short, long)]
    pub cores: String,

    #[clap(short, long, required = true, takes_value = true)]
    pub target: String,

    #[clap(short, long)]
    pub debug: bool,

    #[clap(
        short,
        long,
        allow_hyphen_values = true,
        multiple_values = true,
        takes_value = true
    )]
    pub args: Vec<String>,
}

pub fn parse_args() -> FuzzerOptions {
    FuzzerOptions::parse()
}

这里使用了 clap 库来解析命令行参数,转换为数据结构FuzzerOptions

Rust 基础之 注解

跑🏃‍

直接跑是很dumb的,迟迟跑不出结果。这里先抄个答案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 $ ./build/sbin/tcpdump -r corpus/bootp_asan.pcap -v                                                 [2:27:14]
reading from file corpus/bootp_asan.pcap, link-type EN10MB (Ethernet)
08:00:00.000000 IP (tos 0x0, ttl 252, id 40207, offset 0, flags [+, DF, rsvd], proto UDP (17), length 60951, bad cksum ff (->8336)!)
=================================================================
==23730==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6060000000b4 at pc 0x56339a3e4010 bp 0x7fffaf70f850 sp 0x7fffaf70f848
READ of size 2 at 0x6060000000b4 thread T0
    #0 0x56339a3e400f in bootp_print /home/czy/fuzzing-101-solutions/exercise-3/tcpdump/./print-bootp.c:325:2
    #1 0x56339a46960a in ip_print_demux /home/czy/fuzzing-101-solutions/exercise-3/tcpdump/./print-ip.c:387:3
    #2 0x56339a47008b in ip_print /home/czy/fuzzing-101-solutions/exercise-3/tcpdump/./print-ip.c:658:3
    #3 0x56339a4207c2 in ethertype_print /home/czy/fuzzing-101-solutions/exercise-3/tcpdump/./print-ether.c:333:10
    #4 0x56339a41e731 in ether_print /home/czy/fuzzing-101-solutions/exercise-3/tcpdump/./print-ether.c:236:7
    #5 0x56339a36bff1 in pretty_print_packet /home/czy/fuzzing-101-solutions/exercise-3/tcpdump/./print.c:339:18
    #6 0x56339a36bff1 in print_packet /home/czy/fuzzing-101-solutions/exercise-3/tcpdump/./tcpdump.c:2506:2
    #7 0x56339a74458a in pcap_offline_read /home/czy/fuzzing-101-solutions/exercise-3/libpcap/./savefile.c:507:4
    #8 0x56339a362050 in pcap_loop /home/czy/fuzzing-101-solutions/exercise-3/libpcap/./pcap.c:875:8
    #9 0x56339a362050 in main /home/czy/fuzzing-101-solutions/exercise-3/tcpdump/./tcpdump.c:2009:12
  

答案是管用的,但是直接丢进 Libafl 却没有crash。

优化

目前的fuzzer原作者跑了一晚上啥也没跑出来,由此引入一些优化方法。

语料库压缩:optmin

optmin 是 afl-tmin 的优化版。将语料库中触发重复路径的部分最小化,有助于fuzzer减少重复尝试。这里的语料库在/solutions/queue,也就是AFL执行过若干轮后的。

1
2
3
4
5
6
7
# build
cd AFLplusplus/utils/optimin
./build_optimin.sh
mv optimin ../../../exercise-3
# run
cp -r solutions/queue/* queue_for_cmin
AFL_MAP_SIZE=86217 ASAN_OPTIONS=abort_on_error=1 ./optimin -f -i queue_for_cmin -o cminnified ./build/sbin/tcpdump -vr @@

样例压缩:afl-tmin

另一方面,tmin对单个输入进行压缩,减小文件大小也有助于提高效率。

1
2
3
4
5
6
7
# build
cd AFLplusplus/
make afl-tmin
mv afl-tmin ../exercise-3
# run
cp -r solutions/queue/* queue_for_cmin
AFL_MAP_SIZE=86217 ASAN_OPTIONS=abort_on_error=1 ./optimin -f -i queue_for_cmin -o cminnified ./build/sbin/tcpdump -vr @@

覆盖率观察:afl-cov

然而这种常规优化还是没能有突破,原作者决定观察一下那些代码还没有被覆盖。安装覆盖率工具afl-cov相对比较繁琐:

 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
export HOME=/fuzzing-101-solutions/exercise-3/
cd $home
# 安装afl-cov
sudo apt install lcov
git clone https://github.com/vanhauser-thc/afl-cov.git
# 移动源代码
mkdir exercise-3-gcov exercise-3-gcov/build
cp -r exercise-3/libpcap exercise-3-gcov
cp -r exercise-3/tcpdump exercise-3-gcov
cp -r exercise-3/solutions exercise-3-gcov
# 清理lock文件
cd exercise-3-gcov/solutions/queue
find * -empty -delete
# lcov 需要文件名仅为6位
## 原博客用的python脚本,👴直接请教 new bing
for i in *; do
  mv -v "$i" "${i: -6}"
done
# 编译带 cov 的 libpcap
cd $home/exercise-3-gcov/libpcap
make clean
/opt/afl-cov/afl-cov-build.sh -c ./configure --prefix=$(pwd)/../build; make
make install
# 编译带 cov 的 tcpdump
cd $home/exercise-3-gcov/tcpdump
make clean 
CFLAGS=-I$(pwd)/../build/include/ LDFLAGS=-L$(pwd)/../build/lib/ /opt/afl-cov/afl-cov-build.sh -c ./configure --prefix=$(pwd)/../build ; make
make install
sudo setcap cap_sys_admin+epi ../build/sbin/tcpdump
# 执行
/opt/afl-cov/afl-cov.sh -c solutions "./build/sbin/tcpdump -vr @@"

观察到根本运行不到目标CVE所在的文件。

固定输入

最后原作者决定改 Libafl 源码,在变异之前往里塞固定了的 BOOTY 头。但是👴的 LibAFL 源代码和原博客中有些微区别,虽然版本号一样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
///libafl/src/executors/forkserver.rs:438
if self.executor.uses_shmem_testcase() {
    let shmem = unsafe { self.executor.shmem_mut().as_mut().unwrap_unchecked() };
    let target_bytes = input.target_bytes();
    let size = target_bytes.as_slice().len();
    let size_in_bytes = size.to_ne_bytes();
    // The first four bytes tells the size of the shmem.
    shmem.as_mut_slice()[..4].copy_from_slice(&size_in_bytes[..4]);
    shmem.as_mut_slice()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + size)]
        .copy_from_slice(target_bytes.as_slice());
} else {
    self.executor
        .input_file_mut()
        .write_buf(input.target_bytes().as_slice())?;
}

不过还是找到了对应的位置,向write_buf()插入固定的头部即可。

👴觉得这样不够优雅,于是寻思能否进行一个类重写。

【To be continue】

Built with Hugo
Theme Stack designed by Jimmy