AFL使用指南

栏目: 编程工具 · 发布时间: 5年前

内容简介:二进制分析方面主要利用技术包括:动态分析(Dynamic Analysis)、静态分析(Static Analysis)、符号化执行(Symbolic Execution)、Constraint Solving、资讯流追踪技术(Data Flow Tracking)以及自动化测试(Fuzz Testing)本指南使用的环境是 kali linux 2019.1目标程序

二进制分析方面主要利用技术包括:动态分析(Dynamic Analysis)、静态分析(Static Analysis)、符号化执行(Symbolic Execution)、Constraint Solving、资讯流追踪技术(Data Flow Tracking)以及自动化测试(Fuzz Testing)

本指南使用的环境是 kali linux 2019.1

0x01 AFL的基本使用

1. 使用afl-gcc

1.1 使用AFL插桩程序

目标程序

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>

int vuln(char *str)
{
    int len = strlen(str);
    if(str[0] == 'A' && len == 66)
    {
        raise(SIGSEGV);
        //如果输入的字符串的首字符为A并且长度为66,则异常退出
    }
    else if(str[0] == 'F' && len == 6)
    {
        raise(SIGSEGV);
        //如果输入的字符串的首字符为F并且长度为6,则异常退出
    }
    else
    {
        printf("it is good!\n");
    }
    return 0;
}

int main(int argc, char *argv[])
{
    char buf[100]={0};
    gets(buf);//存在栈溢出漏洞
    printf(buf);//存在格式化字符串漏洞
    vuln(buf);

    return 0;
}

使用afl-gcc进行插桩编译

afl-gcc -g -o ./zerotest/vuln ./zerotest/vuln.c

PS:

如果目标程序中有Makefile,那么分两种情况:

  1. 程序是用autoconf构建,那么此时只需要执行如下即可
./configure CC="afl-gcc" CXX="afl-g++"

此外,还可以执行如下语句设置LD_LIBRARY_PATH让程序加载经过AFL插桩的.so文件,进行静态构建而不是动态链接

./configure --disable-shared CC="afl-gcc" CXX="afl-g++"
  1. 程序不是用autoconf构建,那么直接修改Makefile文件中的编译器为 afl-gcc/g++

为了后期更好的分析crash,在此处可以开启Address Sanitizer(ASAN)这个内存检测工具,此 工具 可以更好的检测出缓存区溢出、UAF 等内存漏洞,开启方法如下:

AFL_USE_ASAN=1 ./configure CC=afl-gcc CXX=afl-g++ LD=afl-gcc--disable-shared
AFL_USE_ASAN=1 make

不使用 AFL 编译插桩时,可使用以下方式开启 Address Sanitizer。

./configure CC=gcc CXX=g++ CFLAGS="-g -fsanitize=address"
make

1.2 开始fuzz

fuzz的语法一般情况是两种:

  1. 直接从stdin读取输入的目标程序
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program […params…]
  1. 从文件读取输入的目标程序,@@就是占位符,表示输入替换的位置
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@

此处我采用第一种方式

afl-fuzz -m 300 -i ./zerotest/fuzz_in -o ./zerotest/fuzz_out ./zerotest/vuln -f

PS: 常见参数的含义如下

  • -f参数表示:testcase的内容会作为afl_test的stdin
  • -m参数表示分配的内存空间
  • -i 指定测试样本的路径
  • -o 指定输出结果的路径
  • /dev/null 使错误信息不输出到屏幕
  • -t:设置程序运行超时值,单位为 ms
  • -M:运行主(Master) Fuzzer
  • -S:运行从属(Slave) Fuzzer

1.3 fuzz的结果

AFL使用指南

从界面上主要注意以下几点:

  1. last new path 如果报错那么要及时修正命令行参数,不然继续fuzz也是徒劳(因为路径是不会改变的);
  2. cycles done 如果变绿就说明后面及时继续fuzz,出现crash的几率也很低了,可以选择在这个时候停止
  3. uniq crashes 代表的是crash的数量

1.4 crash分析

PS: xxd命令的作用就是将一个文件以十六进制的形式显示出来

AFL使用指南

可以看到已经得到的几个crash文件,那么分析的话只需要将其作为之前vuln文件的输入,使用gdb调试分析就可以得到详细结果了,但是在这之前可以使用xxd看一下其中数据的内容做一个初步的判断。

分别看一下这几个crash的信息

  1. 可以看到应该是满足了开头是F且字符串长度为6的异常退出情况
    AFL使用指南
  2. 看这个数据情况可能是栈溢出
    AFL使用指南
  3. 栈溢出
    AFL使用指南
  4. 符合首字符为A且栈溢出
    AFL使用指南
  5. 格式化字符串?可能
    AFL使用指南
  6. 符合首字符为A且字符串长度为66的异常退出情况
    AFL使用指南

主要参考:

《初探Fuzz-AFL》

1.5 语料库蒸馏(Corpus Distillation)

一般来说在进行fuzz之前构建一份有效的语料库是十分有必要的,这将作为程序开始时的种子。

语料库的信息来源主要如下:

  • 使用项目自身提供的测试用例
  • 目标程序bug提交页面
  • 使用格式转换器,用从现有的文件格式生成一些不容易找到的文件格式:
  • afl源码的testcases目录下提供了一些测试用例
  • 其他开源的语料库

收集完后可以使用afl提供的工具来对语料库进行进一步的处理:

  1. afl-cmin: 移除执行相同代码的输入文件

    afl-cmin的核心思想是: 尝试找到与语料库全集具有相同覆盖范围的最小子集。

    它一般的两种执行模式是:

afl-cmin -i input_dir -o output_dir -- /path/to/tested/program [params]
afl-cmin -i input_dir -o output_dir -- /path/to/tested/program [params] @@
  1. afl-tmin: 减小单个输入文件的大小

    它有两种工作模式: instrumented mode和crash mode。默认的工作方式是instrumented mode

# instrumented mode
afl-tmin -i input_file -o output_file -- /path/to/tested/program [params] @@
# crash mode 将会剔除导致crash的文件
afl-tmin -x -i input_file -o output_file -- /path/to/tested/program [params] @@

由于只能针对单个目标进行使用,因此使用如下 shell 脚本进行批量处理

for i in *; do afl-tmin -i $i -o tmin-$i -- ~/path/to/tested/program [params] @@; done;

或者修改如下的 Python 脚本进行预处理

import os
import sys
import shutil
def cmin():
    command = ' -m 300 -t 5000 ./utilities/magick convert @@ /dev/null' 
    os.system('afl-cmin -i seeds/tmin -o seeds/cmin ' + command)
def tmin():
    command = ' -m 300 -t 5000 ./utilities/magick convert @@ /dev/null' 
    seed_list = os.listdir('seeds/all_format')
    for seed in seed_list:
        in_file = os.path.join('seeds/all_format', seed)
        out_file = os.path.join('seeds/tmin', seed)
        if os.path.getsize(in_file) > 1024*1:
            if os.path.getsize(in_file) < 1024*3 and not seed.endswith('.txt'):
                os.system('afl-tmin -i ' + in_file + ' -o ' + out_file + command)
                print('afl-tmin -i ' + in_file + ' -o ' + out_file + command)
            else:
                pass
        elif os.path.getsize(in_file) > 0:
            shutil.copyfile(in_file,out_file)
        else:
            pass
def convert(origin_seeds):
    seed_list = os.listdir(origin_seeds)
    for seed in seed_list:
        seed_in = os.path.join(origin_seeds, seed)
        file_name = (os.path.splitext(seed)[0])
        coder_list = os.listdir('coders')
        for cfile in coder_list:
            if cfile.endswith('.c'):
                extern = cfile[:cfile.find('.c')]
                seed_out = 'seeds/all_format/' + file_name + '.' + extern
                os.system('utilities/magick convert ' + seed_in + ' ' + seed_out)
if __name__ == '__main__':
    if len(sys.argv) < 2:
        print 'Usage: ' + sys.argv[0] + ' origin_seeds_dir'
    else:
        origin_seeds_dir = sys.argv[1]
        try:
            os.mkdir('seeds')
            seeds_path = os.path.join(os.path.abspath('.'),'seeds')
            os.mkdir(os.path.join(seeds_path,'all_format'))
            os.mkdir(os.path.join(seeds_path,'cmin'))
            os.mkdir(os.path.join(seeds_path,'tmin'))
        except:
            print 'make dir fail!'
        convert(origin_seeds_dir)
        tmin()
        cmin()

预处理脚本来自: 《使用 AFL 进行模糊测试》

2. LLVM Mode模式

2.1 启用llvm

LLVM Mode模式编译程序可以获得更快的Fuzzing速度,因此针对大型项目可以考虑启用。

下载必要的安装包

wget http://releases.llvm.org/8.0.0/llvm-8.0.0.src.tar.xz
wget http://releases.llvm.org/8.0.0/compiler-rt-8.0.0.src.tar.xz
wget http://releases.llvm.org/8.0.0/clang-tools-extra-8.0.0.src.tar.xz
wget http://releases.llvm.org/8.0.0/cfe-8.0.0.src.tar.xz

解压缩

xz -d ./*
tar xvf cfe-8.0.0.src.tar
tar xvf clang-tools-extra-8.0.0.src.tar
tar xvf llvm-8.0.0.src.tar
tar xvf compiler-rt-8.0.0.src.tar

源码合并

mv cfe-8.0.0.src clang
mv clang llvm-8.0.0.src/tools
mv clang-tools-extra-8.0.0.src extra
mv extra llvm-8.0.0.src/tools/clang
mv compiler-rt-8.0.0.src compiler-rt
mv compiler-rt llvm-8.0.0.src/projects

编译安装

mkdir build-8.0
cmake ../llvm-8.0.0.src/
cmake --build .
cmake --build . --target install
cmake -DCMAKE_INSTALL_PREFIX=/tmp/llvm -P cmake_install.cmake

上面的编译安装对硬件配置和硬盘的空间要求比较高,所以你可以直接使用源进行安装,比如:

apt install llvm clang

编译安装afl的llvm模块

(我的使用的是kali linux 2019.1进行编译的,clang版本过高会失败,使用clang++也会失败,所以最终发现下面方法可行)

cd afl/llvm_mode
export CXX=/usr/bin/g++
export CC=/usr/bin/clang-6.0
make

因为clang没有办法使用 update-alternatives ,因此我直接修改软连接

ln -s /usr/bin/clang-6.0 /usr/bin/clang
ln -sb /usr/bin/clang++-6.0 /usr/bin/clang++

之后就可以正常使用afl-clang-fast了

AFL使用指南

其实以上均太费劲,还有更简单的方法,kali linux的源中包含了afl,所以可以直接apt进行安装,装好之后afl-clang-fast也就有了

apt install afl

2.2 使用LLVM Mode模式进行fuzz

编译插桩

root@kali-z ~/Desktop/fuzz/afl$ ./afl-clang-fast -g -o ./zerotest/vuln-fast ./zerotest/vuln.c

之后重复上面的方式进行fuzz即可,接下来展示一个使用此模式fuzz php内核代码的例子。

1. 下载目标代码

wget https://github.com/php/php-src/archive/php-7.2.11.tar.gz && tar xf php-7.2.11.tar.gz

2. 进行编译插桩

cd php-src-php-7.2.11
./buildconf --force
CC=afl-clang-fast CXX=afl-clang-fast++ ./configure
AFL_USE_ASAN=1 make

PS: 如果报错缺失libconv,则在 Makefile 中的 EXTRA_LIBS = 添加 -liconv

3. 进行源代码的修改

未修改之前 sapi/cli/php_cli.c

AFL使用指南

修改之后 sapi/cli/php_cli.c

AFL使用指南

修改完之后执行如下进行rebuild

AFL_USE_ASAN=1 make

PS: 之所以进行这样的修改,是因为我们使用 php -r 来eval php string,因此定位到 sapi/cli/php_cli.c 进行代码的修改离开提升后期fuzz的效率。

4. 构造一个输入点

我们想在fuzz的时候从stdin进行数据的输入,因此构造如下输入点

unserialize(file_get_contents(“php://stdin”));

5. 根据上述的构造点构造输入数据

此处账户要考虑构造不同类类型的输入数据,构造如下

mkdir serialized_data && cd serialized_data
../sapi/cli/php -r 'echo serialize("a");' > string
../sapi/cli/php -r 'echo serialize(1);' > number
../sapi/cli/php -r 'echo serialize([1,2]);' > array_of_num
../sapi/cli/php -r 'echo serialize(["1","2"]);' > array_of_str
../sapi/cli/php -r 'echo serialize([["1","2"],["3","4"],[1,2]]);' > array_of_array
echo 'O:6:"zeroyu":1:{s:4:"test";O:7:"npusec2":1:{s:5:"test2";s:10:"phpinfo();";}}' > class

6. 开始fuzz

为了从地址清理(ASAN)中获得有用的结果,有必要设置一个环境变量,以便 PHP 禁用其自定义内存分配器,从而使内存安全问题对ASAN可见。

USE_ZEND_ALLOC=0 screen -S zeroyu

使用screen可以随时进入查看fuzz的结果

screen -r zeroyu

使用如下命令开始fuzz

cd..
afl-fuzz -i serialized_data -o basic_fuzz -m none -- ./sapi/cli/php -r 'unserialize(file_get_contents("php://stdin"));'

AFL使用指南

7. 分析crash

用是使用如下bash脚本来寻找可能是bug的crash,因为有些是良性的crash,是由于ASAN无法分配足够的内存。这是因为ASAN需要额外的内存来跟踪所有分配,而精心编制的序列化对象可能会触发大内存分配。

for FILE in $(ls id*); do cat $FILE | ../../sapi/cli/php -r "unserialize(file_get_contents('php://stdin'));" 2>&1 | grep -E "SUMMARY|ERROR" | grep -v "LargeMmap" && echo $FILE; done

参考: 《Fuzzing PHP for Fun and Profit》

3. 黑盒测试

参考: 《AFL漏洞挖掘技术漫谈(一):用AFL开始你的第一次Fuzzing》

4. 并行测试

4.1 单系统并行

查看系统核心数

cat /proc/cpuinfo| grep "cpu cores"| uniq

afl-fuzz并行Fuzzing,一般的做法是通过-M参数指定一个主Fuzzer(Master Fuzzer)、通过-S参数指定多个从Fuzzer(Slave Fuzzer)。

$ screen afl-fuzz -i testcases/ -o sync_dir/ -M fuzzer1 -- ./program
$ screen afl-fuzz -i testcases/ -o sync_dir/ -S fuzzer2 -- ./program
$ screen afl-fuzz -i testcases/ -o sync_dir/ -S fuzzer3 -- ./program

PS: -o指定的是一个同步目录,并行测试中,所有的Fuzzer将相互协作,在找到新的代码路径时,相互传递新的测试用例。所以不用担心重复的问题

两个辅助工具:

afl-whatsup
afl-gotcpu

4.2 多系统并行

压缩每个fuzzer实例目录中queue下的文件,通过如下SSH脚本同步分发到其他机器上解压。

#!/bin/sh

# authorized_keys的方式进行ssh认证
# 所有要同步的主机
FUZZ_HOSTS='172.21.5.101 172.21.5.102'
# SSH user
FUZZ_USER=root
# 同步目录
SYNC_DIR='/root/syncdir'
# 同步间隔时间
SYNC_INTERVAL=$((30 * 60))

if [ "$AFL_ALLOW_TMP" = "" ]; then
  if [ "$PWD" = "/tmp" -o "$PWD" = "/var/tmp" ]; then
    echo "[-] Error: do not use shared /tmp or /var/tmp directories with this script." 1>&2
    exit 1
  fi
fi

rm -rf .sync_tmp 2>/dev/null
mkdir .sync_tmp || exit 1

while :; do

  # 打包所有机器上的数据
  for host in $FUZZ_HOSTS; do
    echo "[*] Retrieving data from${host}..."
    ssh -o 'passwordauthentication no' ${FUZZ_USER}@${host} \
      "cd '$SYNC_DIR' && tar -czf - SESSION*" >".sync_tmp/${host}.tgz"
  done

  # 分发数据

  for dst_host in $FUZZ_HOSTS; do
    echo "[*] Distributing data to${dst_host}..."
    for src_host in $FUZZ_HOSTS; do
      test "$src_host" = "$dst_host" && continue
      echo " Sending fuzzer data from${src_host}..."
      ssh -o 'passwordauthentication no' ${FUZZ_USER}@$dst_host \
        "cd '$SYNC_DIR' && tar -xkzf - &>/dev/null" <".sync_tmp/${src_host}.tgz"
    done
  done

  echo "[+] Done. Sleeping for$SYNC_INTERVALseconds (Ctrl-C to quit)."
  sleep $SYNC_INTERVAL
  
done

0x02 Fuzz结果分析和代码覆盖率

1. 工作状态

afl-fuzz永远不会停止,所以何时停止测试很多时候就是依靠afl-fuzz提供的状态来决定的。具体的几种方式如下所示:

cycles done
afl-whatsup
afl-stat
afl-whatsup
afl-plot
pythia

2. fuzz结束判断

  • 状态窗口中”cycles done”字段颜色变为绿色该字段的颜色可以作为何时停止测试的参考;
  • 距上一次发现新路径(或者崩溃)已经过去很长时间了,至于具体多少时间还是需要自己把握;
  • 目标程序的代码几乎被测试用例完全覆盖,这种情况好像很少见;
  • pythia提供的各种数据中,path covera达到99或者correctness的值达到1e-08(含义: 从上次发现path/uniq crash到下一次发现之间大约需要1亿次执行)

3. 输出结果说明

queue:存放所有具有独特执行路径的测试用例。
crashes:导致目标接收致命signal而崩溃的独特测试用例。
crashes/README.txt:保存了目标执行这些crash文件的命令行参数。
hangs:导致目标超时的独特测试用例。
fuzzer_stats:afl-fuzz的运行状态。
plot_data:用于afl-plot绘图。

4. 对crash结果的简单分析和分类

  1. crash exploration mode

    afl-fuzz的一种运行模式,也称为peruvian rabbit mode,用于确定bug的可利用性,其输入的是crash的信息,之后使用 -C 启用这种模式,afl会自动探索并创造与之相关的crash来帮助你进行分析,比如判断能够控制某块内存地址的长度。

afl-fuzz -m none -C -i ./fuzz_out/crashes -o ./peruvian-were-rabbit_out -- ./vuln -f
  1. triage_crashes.sh

    AFL源码的experimental目录中有一个名为triage_crashes.sh的脚本,可以帮助我们触发收集到的crashes。

直接使用脚本跟参数的话,我们可以看到相关crash情况的寄存器等信息,但是如果只是大致分类的话,可以使用如下命令

/root/Desktop/fuzz/afl/experimental/crash_triage/triage_crashes.sh ./fuzz_out ./vuln 2>&1 | grep SIGNAL

效果如下,11代表了SIGSEGV信号,有可能是因为缓冲区溢出导致进程引用了无效的内存

AFL使用指南
  1. crashwalk

    优点:可以显示更为详细的信息

    项目地址: https://github.com/bnagy/crashwalk

# 手动模式
~/go/bin/cwtriage -root ./fuzz_out/crashes -match id -- ./vuln -f
# afl自动化模式
~/go/bin/cwtriage -root ./fuzz_out/crashes -afl

AFL使用指南

  1. afl-collect

项目地址: https://github.com/rc0r/afl-utils

afl-collect基于exploitable来检查crashes的可利用性。它可以自动删除无效的crash样本、删除重复样本以及自动化样本分类。

afl-collect -j 8 -d crashes.db -e gdb_script ./fuzz_out ./fuzz_in --  ./vuln --target-opts

效果如下

AFL使用指南

5. 代码覆盖率

afl-cov的使用说明如下:

首先使用gcov重新编译源码

gcc -fprofile-arcs -ftest-coverage vuln.c -o vuln_cov

如果遇到需要make进行编译的文件,执行如下:

$ make clean
$ ./configure --prefix=/root/tiff-4.0.10/build-cov CC="gcc" CXX="g++" CFLAGS="-fprofile-arcs -ftest-coverage" --disable-shared
$ make
$ make install

之后使用afl-cov来计算覆盖率

afl-cov -d ./fuzz_out --live --enable-branch-coverage -c . -e "cat AFL_FILE | ./vuln_cov AFL_FILE"

同时进行对插桩过的 vuln 的fuzz

afl-fuzz -i ./fuzz_in -o ./fuzz_out ./vuln -f

最终效果如下

AFL使用指南

生成的报告会保存在 /path/to/afl-fuzz-output/cov/web/lcov-web-final 路径下。


以上所述就是小编给大家介绍的《AFL使用指南》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

第二曲线:跨越“S型曲线”的二次增长

第二曲线:跨越“S型曲线”的二次增长

[英]查尔斯·汉迪(Charles Handy) / 苗青 / 机械工业出版社 / 2017-6 / 49.00

S型曲线是每个组织和企业在预测未来时一定会参考的工具,一切事物的发展都逃不开S型曲线(“第一曲线”)。 然而,从公司组织、企业治理、市场的变化,到个人职业发展、社会人际关系以及未来的教育与社会价值,多维度地探讨这个世界需要重新以不同的角度来思考问题,不能够总是停留在“第一曲线”的世界。 如果组织和企业能在第一曲线到达巅峰之前,找到带领企业二次腾飞的“第二曲线”,并且第二曲线必须在第一曲......一起来看看 《第二曲线:跨越“S型曲线”的二次增长》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

html转js在线工具
html转js在线工具

html转js在线工具