毕设-Fuzz-AFLGo源码阅读-5


AFLGo源码阅读

按照Main函数中的顺序

setup_signal_handlers

回到目录

初始化各种信号量

终止进程的、超时的等等

check_asan_opts()

回到目录

通过检查环境变量中的值来判断–检查ASAN设置

fix_up_sync()–不知道具体用途

回到目录

没理解

save_cmdline(argc, argv)

回到目录

orig_cmdline保存复制当前命令行

fix_up_banner(argv[optind])

回到目录

根据最后一个参数设置标头(banner)?

check_if_tty()

回到目录

检查是不是在终端运行

get_core_count()

回到目录

从系统文件中获取cpu核的相关信息

bind_to_free_cpu()

回到目录

把进程绑定在具体的内核上?

check_crash_handling()

回到目录

保证core dumps不会进入程序, 否则会增加将崩溃信息通过waitpid传递给fuzzer的延迟。

check_cpu_governor()

回到目录

要把CPU频率调节的算法(可能忽视fuzz产生的短进程)关了,以提高aflgo-fuzz的效率。

setup_post()

回到目录

不理解

setup_shm()

回到目录

配置共享内存和virgin_bits, 并且将共享内存的首地址赋值给trace_bits.

init_count_class16()

回到目录

之所以用左移是为了加快速度

最终初始化是下面这个样子。16位一个
0-0 …. 128-0 -256个元素 :0-1-2-4-8-16-32-64-128
⬇ 0-1 ….
0-2 ….
0-2 ….
0-4 ….
0-4 ….
………….
0-128…. 128-128 一共 65536个 16bit

setup_dirs_fds()

回到目录

  1. flock 了 out_dir_fd

  2. 创建了跟下面有关的目录

    • queue
    • crashes
    • hangs
      ….
  3. 还创建其他的fd(/dev/null&/dev/urandom)方便后续使用

read_testcases()

回到目录

input directory中读取所有的测试用例,检测测试用例的大小,以及是否已经完成了deterministic fuzzing阶段,然后添加到queue中。

初始化

  • queued_at_start Total number of initial inputs
  • last_path_time Time for most recent path (ms)

load_auto()

回到目录

加载自动生成的附加组件

pivot_inputs()

回到目录

  • 首先检查是不是之前跑过的
    • 如果是的话,看一下id是不是一致。
      • id一致, 要改变对应entry的depth
    • 如果不是,就起新名字 id:%06u,orig:%s
    • 然后就是重新命名文件,并且更改q->fname=nfn

C语言知识

strrchr和strchr类似,但是从右向左找字符c,找到字符c第一次出现的位置就返回,函数名中间多了一个字母r可以理解为Right-to-left。

load_extras()

回到目录

这个没看懂是干啥的。
跟这些有关,但是不知道具体在fuzz的过程中起到了什么作用

struct extra_data {
  u8* data;                           /* Dictionary token data            */
  u32 len;                            /* Dictionary token length          */
  u32 hit_cnt;                        /* Use count in the corpus          */
};

static struct extra_data* extras;     /* Extra tokens to fuzz with        */
static u32 extras_cnt;                /* Total number of tokens read      */

static struct extra_data* a_extras;   /* Automatically selected extras    */
static u32 a_extras_cnt;              /* Total number of tokens available */

find_timeout()

回到目录

只有在Resuming an older fuzzing job的情况下,才会使用。

从状态目录中读取文件名, 并把exec_timeout :后面的值复制给exec_tmout, 将timeout_given赋值为3.

detect_file_args()

回到目录

根据参数@@后面带的东西,更改文件名. 看的也不是很懂。

C语言知识

定义函数:char * getcwd(char * buf, size_t size);

函数说明:getcwd()会将当前的工作目录绝对路径复制到参数buf 所指的内存空间,参数size 为buf 的空间大小。

注:
1、在调用此函数时,buf 所指的内存空间要足够大。若工作目录绝对路径的字符串长度超过参数size 大小,则返回NULL,errno 的值则为ERANGE。
2、倘若参数buf 为NULL,getcwd()会依参数size 的大小自动配置内存(使用malloc()),如果参数size 也为0,则getcwd()会依工作目录绝对路径的字符串程度来决定所配置的内存大小,进程可以在使用完次字符串后利用free()来释放此空间。

setup_stdio_file()

回到目录

如果没有用-f指定输出文件的话, 那就用默认的.cur_input创建

check_binary()

回到目录

具体代码没看。。

检查目标二进制文件是否存在,以及它是否是shell脚本。确保可以进行afl的插桩。

get_qemu_argv()

回到目录

不知道干啥的

perform_dry_run()

回到目录

简单的把所有的测试用例都提前运行一遍,确保程序像预期的那样运行。如果不是的话,会有一些相应的提示。

calibrate_case()

测试一个entry,看看是不是有覆盖率、新的路径的添加等等变量是否正常工作啥的。

这里会运行run_target来计算distance, 这是对已经加入队列的entry而言的。

关于entry属性里面的var_behavior的理解: 因为在calibrate的阶段中,是没有发生变异的,那么如果测试用例在经过不同次数的执行后,产生了不一样的path。那么就把这个entry标记为variable这个属性并没有影响到后续的其他步骤。根据注释,应该只是简单的标注,方便能找到吧。

count_bytes()

数一下有多少个字节不为零, 8位代表一个path, 不同的命中次数可能会导致8位中不同位置的bit置1

update_bitmap_score()

当某个entry触发了新的path, 我们要与之前的同样触发这个path的”最优”的entry进行一个比较。看看到底谁更优秀。

所谓的top_rated[] 就是 a minimal set of paths that trigger all the bits seen in the bitmap so far.

minimize_bits()

trace_bits压缩为一个占用空间更小的数组。1位代表一个path现在。所以刚好是分配了MAP_SIZE>>3的空间。

cull_queue()

回到目录

top_rated[i] 代表的就是发现路径序号为i的最优entry(fav_factor最小的) 而且关键的是top_rated[i] 指针指向的是queue中的特定的entry。所以在将top_rated[i]->favored = 1 时,原来queue中的entryfavored也同样被设置为1

值得注意的是,并不是说top_rated[]中所有的entry都是favored的。当且仅当你发现的path是你之前entry都没有发现过的情况下,这个entry才会被设置为favored

我觉得这里有个值得深思的地方,程序这样设计的话,test_case的顺序会影响到其是否会被设置为favored. 这种随机性会不会对框架整体的性能产生一定的影响。

mark_as_redundant

把对应的entry标记为redundant,其间还会创建一些目录,至于什么作用没看懂。

show_init_stats()

回到目录

显示统计数据 Total calibration cycles\max_bits\min_bits\exec_us\len等等

根据平均运行时间重新设置一个timeout_given

find_start_position()

回到目录

当要恢复程序进程的时候,从fuzzer_stats目录的文件的文件名中读取相应的位置。

write_stats_file()

回到目录

把用到的基本状态信息都写入到状态文件中,这些变量都会在终端页面显示中用到。

save_auto()

回到目录

自动保存生成的extras,这个跟token有关系,但没看懂token到底有什么作用。

fuzz_one && while循环

回到目录

接下来是循环中的函数

  • 首先在进入循环之前, 要先cull_queue, 把favor的entry标记出来

  • 判断queue_cur是否为空

    • 如果为空的话,说明是第一次进入循环。进行必要的初始化。
  • 然后就是fuzz_one

    • 判断在当下的队列中,是否含有 favored\non-fuzzedentry,如果有那么会以99%的概率跳过那些已经被fuzz过或者不是favoredentry.
    • 如果没有上面所说的那种类型的entry 会以75%跳过not fuzzed ,以95%跳过fuzzed的entry
    • 然后将test case中的内容映射到内存中,这样文件中的位置直接就有对应的内存地址,对文件的读写可以直接用指针来做而不需要read\write函数。
    • 如果最初的calibration阶段失败了, 那现在要重新来一遍。
    • trimming阶段,这个阶段的作用,没看懂。 不明白为什么这个函数会调用run_taget
    • 计算entry分数
    • 看看是否要跳过deterministic变异阶段
      • 如果skip_deterministic设置为1、或者entry fuzzed或者entry->passed_det设置为1)
      • 如果执行路径校验将其置于该主实例的范围之外,则跳过确定性模糊处理。
    • 按照以下阶段进行变异
      • simple bitflip
      • arithmetic
      • interst
      • dictionary
      • havoc
      • splice

        当然在这些变异阶段中, 大多都是每变异一次就进行common_fuzz_stuff。 还有很多为了保证程序效率(比如: 当变异出现的结果在之前的变异阶段已经被运行过的时候可以跳过、当对于某个字节的变异没有出现效果,那在以后的变异阶段就不会变异该字节了-相当于认为该字节对于提高程序效果没有太大的意义)
        还有common_fuzz_stuff阶段产生出来的新的变异enrty会根据save_if_interetring函数来决定是否加入到队列中。加入队列的方式是尾插法!只不过把刚刚添加进队列的entry看作是queue_top

    循环结束后,回对sync_fuzzer进行一个操作,这个可以后面再看。

calculate_score()

计算得分,跟得分有关的因素

  • exec_usavg_execc_us的大小关系, exec_us相对越小, 得分肯定就越高
  • bitmap_size(发现的路径数) 和 avg_bitmap_size大小关系, bitmap_size相对越高, 得分越高
  • handicap 某个testcase可能是在程序运行的末尾才发现, 然后被添加到队列中。而这个时候,队列中前面的entry很有可能已经运行了很多cycle. 所以,这部分后来添加到队列中entry得分更高。
  • depth 原文 under the assumption that fuzzing deeper test cases is more likely to reveal stuff that can’t be discovered with traditional fuzzers. depth的值越大,得分也就越高。也就是说, 一些变异的entry较大可能会是后面才添加进来的。所以假设越往后添加进来的越高。这个要跟上面的handicap相区别,depth反映的是队列中的entry数量, handicap是整体队列变异的cycle
  • cooling_schedule 基于距离的模拟退火算法, 距离越近的随着时间的推移, power_factor会越来越高. 相对应的得分也就越高. perf_score *= power_factor

具体的得分,跟确定性变异阶段的时间没有关系,得分越高,随机性变异阶段的时间也就越长。

common_fuzz_stuff()

把经过变异修改的文件重新写入testcase, 然后在进行run_target()。接着运行save_if_interesting()判断是否对变异的testcase进行统计或者其他操作

run_target()

  • 第一种情况: 独自运行exec, 等待子进程结束
  • 第二种情况: 通过管道和forkserver通信,forkserver fork出一个子进程进行fuzz,将子进程的状态写入通道。 父进程再通过通道中的信息, 对程序状态进行返回。当然在子进程进行fuzz的过程中 trace_bits会发生更新
C 知识
进程状态
  • WIFSIGNALED(status)为非0 表明进程异常终止 用来判断crash
  • WIFSTOPPED(status)为非0 表明进程处于暂停状态 用来判断fork server是否正常进行, 此时因为fork server是处于循环当中,所以对应的状态是处于暂停。
  • WTERMSIG(status) 获取程序退出的信号(比如:SIGKILL)
setitimer

关于这个,详细的内容网上都有。 但是没搞清楚这个时间定时到底是阻塞的还是非阻塞的。??

save_if_interesting

看一下当前的testcase是否触发了新的路径, 如果触发了新的路径,需要把这个testcase添加到当前的队列里面。并且要在queue中以("%s/queue/id:%06u,%llu,%s", out_dir, queued_paths, get_cur_time() - start_time ,describe_op(hnb))这样的形式命名。

根据run_target()的返回值,处理timeout、crash、error的情况

write_bitmap()

回到目录

把当前共享内存中的bitmap写到文件中去

write_stats_file()

回到目录

更新状态文件中的数据

stop_fuzzing:

回到目录

程序的终止是需要用户自己按下ctrl+c 循环不会自己退出

对占有的内存空间进行释放, 退出程序

AFLgo命令行启动参数

回到目录

参数 含义
i 输入目录
o 输出目录
M master sync ID
S master sync ID
f 目标文件
x 字典目录
t 超时时间设定
m 内存限制
d 是否跳过确定性变异阶段
B 加载bitmap
C Crash模式
T banner
Q QEMU模式
z 模拟退火算法选定
c 退火算法的运行时间
Required parameters:

  -i dir        - input directory with test cases
  -o dir        - output directory for fuzzer findings

Directed fuzzing specific settings:

  -z schedule   - temperature-based power schedules
                  {exp, log, lin, quad} (Default: exp)
  -c min        - time from start when SA enters exploitation
                  in secs (s), mins (m), hrs (h), or days (d)

Execution control settings:

  -f file       - location read by the fuzzed program (stdin)
  -t msec       - timeout for each run (auto-scaled, 50-1000 ms)
  -m megs       - memory limit for child process (50 MB)
  -Q            - use binary-only instrumentation (QEMU mode)

Fuzzing behavior settings:

  -d            - quick & dirty mode (skips deterministic steps)
  -n            - fuzz without instrumentation (dumb mode)
  -x dir        - optional fuzzer dictionary (see README)

Other stuff:

  -T text       - text banner to show on the screen
  -M / -S id    - distributed mode (see parallel_fuzzing.txt)
  -C            - crash exploration mode (the peruvian rabbit thing)

Linux命令

回到目录

  • export

    为shell变量或函数设置导出属性。它们会成为环境变量, 可以在脚本中访问它们,尤其是脚本中调用的子进程需要时。

  • echo

    echo命令 用于在shell中打印shell变量的值,或者直接输出指定的字符串。linux的echo命令,在shell编程中极为常用, 在终端下打印变量value的时候也是常常用到的,因此有必要了解下echo的用法echo命令的功能是在显示器上显示一段文字,一般起到一个提示的作用。

  • mkdir

    创建目录

  • cat

    连接多个文件并打印到标准输出

  • cut

    cut命令用来显示行中的指定部分,删除文件中指定字段。说明:该命令有两项功能,其一是用来显示文件的内容,它依次读取由参数 file 所指 明的文件,将它们的内容输出到标准输出上;其二是连接两个或多个文件,如cut fl f2 > f3将把文件 fl 和 f2 的内容合并起来,然后通过输出重定向符“>”的作用,将它们放入文件 f3 中。

  • rev

    将文件内容以字符为单位反序输出—也就是每行的字符都到过来

  • cp

    将源文件或目录复制到目标文件或目录中

  • pushd&&popd

    倒可以简单地把这个命令理解为切换/再换回来目录的命令。

  • chmod

    用来变更文件或目录的权限

  • mv

    mv命令 用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中。source表示源文件或目录,target表示目标文件或目录。如果将一个文件移到一个已经存在的目标文件中,则目标文件的内容将被覆盖

参考

回到目录

  1. http://rk700.github.io/2018/01/04/afl-mutations/
  2. https://rk700.github.io/2017/12/28/afl-internals/#%E5%88%86%E6%94%AF%E4%BF%A1%E6%81%AF%E7%9A%84%E5%88%86%E6%9E%90
  3. https://paper.seebug.org/496/#_2
  4. https://bbs.pediy.com/thread-265936.htm#msg_header_h1_2
  5. https://paper.seebug.org/1732/#afl-afl-asc
  6. https://www.anquanke.com/post/id/250540#h2-5
  7. https://linux.cmsblogs.cn/ —-查询linux命令的网站

文章作者: 美食家李老叭
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 美食家李老叭 !
评论
  目录