# CVE-2017-9048 ## Description CVE: https://www.cve.org/CVERecord?id=CVE-2017-9048 # Compile ## Download ```shellsession git clone https://github.com/GNOME/libxml2.git && cd libxml2 git checkout v2.9.4 ``` ## Build 由于这个漏洞发生在 `valid.c` 的 `xmlSnprintfElementContent` 中,所以我们可以禁用 一些无关的东西来提高 fuzzing 效率。 ```shellsession ./autogen.sh AFL_USE_ASAN=1 CC=afl-clang-lto CXX=afl-clang-lto++ ./configure --prefix="$(realpath ../libxml2-fuzz-asan)" --disable-shared --without-debug --without-ftp --without-http --without-legacy --without-python make clean && AFL_USE_ASAN=1 make -j`nproc` && AFL_USE_ASAN=1 make install ``` 每次耗时最长的就是「如何编译」……
看了一圈,报错原因全都是因为 `invalid token at start of a preprocessor expressio n`,而这个错误的产生又是因为 `.in` 文件中 `@@` 占位多了一个空格,比如 `@WITH_PUS H @`,把那个空格去掉即可。 如果使用 `grug-far.nvim`,那可以用 `@([^@s]+)s+@` 搜索,`@$1@` 替换:
之后再次编译就没问题了。 说实话这个插桩数量确实有点吓人了,光插桩就用了十三分钟……所以一会儿我们必须多线程 fuzz 才行。
为了检查插桩是否成功,可以使用 `ASAN_OPTIONS=help=1 ../libxml2-fuzz-asan/bin/xml lint`,或者查看符号:`nm ../libxml2-fuzz-asan/bin/xmllint | rg -i asan`。 由于 ASAN 会占用大量内存,并造成 2x - 10x 的减速,所以建议是只开一个 ASAN 线程用 来找错,剩下线程都用普通插桩的版本来快速探路提高吞吐量。 更多信息可参考 [Notes for using ASAN with afl-fuzz](https://aflplus.plus/docs/no tes_for_asan/) 。 再编译一个普通版: ```shellsession CC=afl-clang-lto CXX=afl-clang-lto++ ./configure --prefix="$(realpath ../libxml2-fuzz-lite)" --disable-shared --without-debug --without-ftp --without-http --without-legacy --without-python make clean && make -j`nproc` && make install ``` # Samples 由于随机编译很难凑出一个正确的 xml tag, 所以我们可以给 fuzzer 加上字典,增加它撞 到正确格式的概率。AFL++ 提供了一些常用字典:
至于 corpus, libxml2 自己的 test 目录下就有一些,然后我又自己写了两个 `<!DOCTYPE a []>` 和 `<a b="c">d</a>`。 # Fuzzing `xmllint` 有很多选项,理想情况是尽可能都组合上用一遍,以便让它能探索到更多路径。
我写了个自动起多线程 fuzz 的脚本,并在参数池中随便放了几个参数: ```bash #!/bin/bash MASTER_BIN="../libxml2-fuzz-asan/bin/xmllint" SLAVE_BIN="../libxml2-fuzz-lite/bin/xmllint" INPUT_CORPUS="corpus" OUTPUT_DIR="outs" SHM_BASE="/dev/shm/fuzz" # Arguments for the Master instance MASTER_ARGS="--debug --valid" # Argument pool for Slave instances SLAVE_ARGS_POOL=( "--memory --oldxml10" "--postvalid" ) # --- Dictionary Support --- DICT_PATH="./dict/xml.dict" DICT_OPT="" if [ -d "$DICT_PATH" ] || [ -f "$DICT_PATH" ]; then DICT_OPT="-x $DICT_PATH" fi # --- Environment Check --- if [ ! -f "$MASTER_BIN" ] || [ ! -f "$SLAVE_BIN" ]; then echo "[-] Error: Fuzzing binaries not found. Check your paths." exit 1 fi TOTAL_THREADS=${1:-4} if [ "$TOTAL_THREADS" -lt 1 ]; then echo "Usage: $0 [total_threads]" exit 1 fi # --- Resume Logic --- if [ -d "$OUTPUT_DIR/master" ]; then echo "[*] Existing output detected. Resuming fuzzing session..." INPUT_OPT="-i -" else echo "[*] First run. Using input corpus: $INPUT_CORPUS" mkdir -p "$OUTPUT_DIR" INPUT_OPT="-i $INPUT_CORPUS" fi mkdir -p "$SHM_BASE" # --- Launch Master (ASAN) --- echo "[+] Launching Master (ASAN) | Args: $MASTER_ARGS @@" mkdir -p "$SHM_BASE/master" AFL_TMPDIR="$SHM_BASE/master" afl-fuzz $INPUT_OPT -o "$OUTPUT_DIR" -m none $DICT_OPT -M master -- "$MASTER_BIN" $MASTER_ARGS @@ >"$OUTPUT_DIR/master.log" 2>&1 & # Brief sleep to let Master initialize sleep 2 # --- Launch Slaves (Non-ASAN) --- NUM_VARIANTS=${#SLAVE_ARGS_POOL[@]} for i in $(seq 1 $((TOTAL_THREADS - 1))); do SLAVE_NAME="slave_$i" ARG_INDEX=$(((i - 1) % NUM_VARIANTS)) CURRENT_ARGS=${SLAVE_ARGS_POOL[$ARG_INDEX]} echo "[+] Launching $SLAVE_NAME | Args: $CURRENT_ARGS @@" mkdir -p "$SHM_BASE/$SLAVE_NAME" AFL_TMPDIR="$SHM_BASE/$SLAVE_NAME" afl-fuzz $INPUT_OPT -o "$OUTPUT_DIR" -m none $DICT_OPT -S "$SLAVE_NAME" -- "$SLAVE_BIN" $CURRENT_ARGS @@ >/dev/null 2>&1 & done echo "------------------------------------------------------" echo "[!] Successfully started $TOTAL_THREADS instances with @@ input mode." echo "[!] Check status: afl-whatsup $OUTPUT_DIR" echo "[!] Stop all: pkill afl-fuzz" echo "------------------------------------------------------" ``` 搞明白怎么多线程 fuzz, 这个 chall 最关键的部分就算是完成了,至于跑出来的 crashes 中有没有我们期望的那个,要跑多久,已经意义不大了。所以,拜拜。 ~说着,滚床上干了一个小时论文调研,下来一看发现已经有不少 crashes 了。~
哎呀这个 ASAN 又帅又好用,好喜欢 _uwu_








