如何使⽤AFL进⾏⼀次完整的fuzz过程
第⼀次写,不知从何⼊⼿,就把最近在研究的AFL的⼀些⽂章翻译先发出来吧。。。。
原⽂地址foxglovesecurity/2016/03/15/fuzzing-workflows-a-fuzz-job-from-start-to-finish/
近年来,随着越来越多像AFL这种易⽤的⼯具的出现,降低了门槛,给了初学者以希望,使得很多⼈都开始对fuzzing产⽣了兴趣。许多⽹站都对AFL的功能进⾏了简单的介绍,eg:如何对给定的软件进⾏fuzzing,但是没有介绍过当决定结束的时候要做什么。
在本⽂中,将会进⾏⼀个完整的fuzz过程。⾸先,到⼀个合适的软件来进⾏fuzzing看起来并不容易,但是你可以根据⼀定的规则
⼀定的规则来使得你能更容易地开始fuzz。⼀旦选好了软件,最好地fuzz⽅式是什么?选择哪⼀个测试⽤例来作为seed?我们怎么知道做得怎么样,我们遗漏了⽬标程序中的哪些代码路径?
我们希望能够覆盖这⼀切,对如何有效地和有效地从头到尾完成⼀个完整的Fuzz⼯作流程提供⼀个全⽅⾯的观点。在这⾥,以AFL为例。
⼀、What should I fuzz? Finding the right software
AFL对于C/C++应⽤效果最好,所以这是⼀条我们软件的规则。以下还有⼀些问题:
1.有没有范例代码可⽤?
这个项⽬具有的功能太多,为了fuzz这些功能能不能被修剪。如果⼀个项⽬有⼀个简单的(bare-bones)范例代码,这会使得我们的fuzz过程变得简单。
2.能不能⾃⼰编译软件?(是否开源,能得到源码)
当你能⾃⼰编译源码时,AFL最有效。尽管它⽀持QEMU的userland模式来进⾏⿊盒的⼆进制插桩,但是这会⼤⼤影响AFL的效果。
在理想环境下,应该能够使⽤afl-clang-fast or afl-clang-fast++来编译软件。
3.Are there easily available and unique testcases available?有没有可⽤的unique测试⽤例
我们可能会fuzzing⼀个⽂件格式(通过修改,也可以fuzz⽹络应⽤),并且拥有⼀些unique、interesting的测试⽤例来作为seed,使我们有⼀个好的开端。如果项⽬有⽂件类型的测试⽤例来进⾏单元测试(或者保持具有已知bug的⽂件来进⾏回归测试),这将是巨⼤的成功。
这些基本问题会帮你在以后的过程中节省很多时间,避免很多问题,如果你刚刚开始的话。
The yaml-cpp project
github是能帮你快速解决上述问题的地⽅。在github上你能很容易地到⽤C/C++的写的项⽬。例如,搜索C++和200star将会显⽰yaml-cpp这个项⽬。⼤致从这个项⽬中看⼀下之前的三个问题,并看看能多简单地开始fuzzing。
1.Can I compile it myself?
CMake 是⼀个跨平台的,开源的构建系统】这看起来⾮常棒,因为我们可以⾃⼰选择yaml-cpp使⽤cmake作为build system。【CMake
yaml-cpp使⽤cmake作为build system。
⽤什么编译器,afl-clang-fast++将会Just Work。在yaml-cpp的中有⼀个有趣的记录,它默认构建⼀个静态库,这对我们来说是完美的,因为我们想给AFL⼀个静态编译、插桩的⼆进制⽂件来进⾏fuzz。
2.Is there example code readily available?
在项⽬的根⽬录下的util⽂件夹中,有⼀些⼩的cpp⽂件,它们是展⽰yaml-cpp库某些特性的简单(  bar
e-bones )⼯具。特别有趣的是parse.cpp⽂件。这个⽂件是完美的,因为它已经写好了从stdin接收数据的功能,我们可以很容易地改写来使⽤AFL的持久模式(persistent mode),这将会有显著的速度提升。
3.Are there easily available and unique/interesting testcases available?
在项⽬的根⽬录下的test⽂件夹中是⼀个specexamples.h⽂件,⾥⾯有⼤量的unique和interesting的YAML的测试⽤例,每⼀个都执⾏yaml库中的⼀段特定代码。这对fuzzer⽤来作为种⼦⽣成测试⽤例是⾮常好的。
⼆、Starting the fuzz job
这⾥不会将AFL的安装配置,因为有很多⽂章已经介绍过了。我们假设这些已经完成,afl-clang-fast和afl-clang-fast++也已经配置完成。{export CC=afl-clang;export CXX=afl-clang++;可以使⽤env来查看环境变量}afl-g++⼯作起来没什么问题,但是afl-
clang-fast++肯定是⾸选
clang-fast++肯定是⾸选。下⾯开始获取yaml-cpp的代码,并使⽤AFL来构建它。
# git clone # cd yaml-cpp
# mkdir build
# cd build
# cmake -DCMAKE_CXX_COMPILER=afl-clang-fast++ ..
# make
我们成功构建之后,可以对源码进⾏⼀些修改,来让AFL能更快。从根⽬录的/util/parse.cpp中,我们可以使⽤AFL的trick来更新main()函数for持久模式。
int main(int argc, char** argv) {
Params p = ParseArgs(argc, argv);
if (argc > 1) {
std::ifstream fin;
fin.open(argv[1]);
parse(fin);
} else {
parse(std::cin);
}
return 0;
}
对于main()函数,我们可以修改其else语句,包含⼀个while循环和⼀个特殊的AFL函数__AFL_LOOP(),该函数通过⼀些memory 在进程中执⾏⼆进制fuzzing,(  as opposed to )⽽不是新开⼀个进程给每⼀个我们想要fuzz的测试⽤例。修wizardry使得AFL在进程中
改如下:
if (argc > 1) {
std::ifstream fin;
fin.open(argv[1]);
cmake如何使用
parse(fin);
} else {
while (__AFL_LOOP(1000)) {
parse(std::cin);
}
}
在else语句中新的while循环中,我们传递1000给__AFL_LOOP()函数。这告诉AFL在⼀个进程中fuzz1000个test
注意,在else语句中新的while循环中,我们传递1000给__AFL_LOOP()函数。这告诉AFL在⼀个进程中fuzz1000个test cases,然后在新开⼀个进程执⾏相同的⼯作。你可以增加执⾏次数通过指定⼀个数字,以内存使⽤(内存泄漏)为代价;这是⾼度可调cases,然后在新开⼀个进程执⾏相同的⼯作。
节的,基于你fuzzing的应⽤。添加这种类型的代码以启动持久模式也不总是很容易的。⼀些应⽤由于启
动时产⽣的资源或其他因素,可能没有⽀持添加while循环的架构。due to resources spawned during start up or other factors.
‘make’来重新⽣成parse.cpp。
重新编译。返回根⽬录下的build⽬录,输⼊‘make’
1、Test i ng t h e bi nary
随着⼆进制程序被编译,我们可以使⽤AFL的afl-showmap⼯具来测试它。afl-showmap⼯具将会运⾏⼀个给定的插桩的⼆进制程序(通过stdin接收的任意输⼊通过stdin传递给 插桩好的⼆进制程序 ),并打印在程序执⾏期间它看到的反馈的报告。
# afl-showmap -o /dev/null -- ~/parse < <(echo hi)
afl-showmap 2.03b by <lcamtuf@google>
[*] Executing '~/parse'...
-- Program output begins --
hi
-- Program output ends --
[+] Captured 1787 tuples in '/dev/null'.
#
通过将输⼊更改为应该执⾏新代码路径的内容,您应该可以看到在报告结束时报告的元组数量增加或减少。
# afl-showmap -o /dev/null -- ~/parse < <(echo hi: blah)
afl-showmap 2.03b by <lcamtuf@google>
[*] Executing '~/parse'...
-- Program output begins --
hi: blah
-- Program output ends --
[+] Captured 2268 tuples in '/dev/null'.
#
可以看到,发送⼀个简单的YAML key(hi)只表⽰1787个元组的反馈,但是具有value的YAML key (hi:blah)表⽰2268个元组的反馈。 我们应该很好地去使⽤插桩的⼆进制程序,现在我们需要测试⽤例来⽣成我们fuzzing需要的testcases。
2、S eedi ng w i t h h i gh quali t y t est cases
你给fuzzer的初始testcase是⼀个⾮常重要的⽅⾯,关系到⼀次fuzz运⾏是否能到⼀些好的crashes。像前⾯所说的,test⽬录下的specexamples.h⽂件有很好的test cases来开始,但是它们可以更好。对于这项⼯作,我从头⽂件中⼿动复制这些范例并粘贴到要使⽤的
就是将yaml中 test⽬录下的specexamples.h ⽂件中的那些例test case,以节省读者的时间,链接在这,这是我是⽤的原始种⼦。(就是将yaml中
⼦,分成⼀个⼀个的test case⽤于fuzzing )
AFL带有两个⼯具确保:
测试语料库中的⽂件尽可能有效地唯⼀
每个测试⽂件尽可能⾼效地表⽰其唯⼀的代码路径
minimizing。
这两个⼯具是afl-cmin和afl-tmin,它们执⾏最⼩化minimizing
afl-cmin⼯具需要⼀个给定的包含可能的(potential)test case的⽂件夹,然后运⾏每⼀个并将收到的反馈与所有其他的test case 进⾏对⽐,到最有效地表⽰最unique的代码路径的最好的test case。最好的test case被保存到⼀个新的⽬录。
afl-tmin⼯具只⽤于⼀个指定的⽂件。当我们进⾏fuzzing时,我们不想浪费CPU来处理⼀些相对于test case表⽰代码路径来说⽆⽤的bit或byte。为了使每⼀个test case达到表⽰与原始测试⽤例相同的代码路径所需的最⼩值, afl-tmin遍历test case的实际字节,逐步删除很⼩的数据块,直到删除任意字节都会影响到代码路径表⽰。这很啰嗦,但是对于有效地fuzzing来说,这都是很重要的步骤,也是需要理解的重要概念。下⾯看⼀个例⼦。
‘2’这个⽂件来开始操作:
在作者创建的项⽬中,从specexamples.h⽂件中得到这些原始test case中,选择‘2’
# afl-tmin -i 2 -o 2.min -- ~/parse
afl-tmin 2.03b by <lcamtuf@google>
[+] Read 80 bytes from '2'.
[*] Performing dry run (mem limit = 50 MB, timeout = 1000 ms)...
[+] Program terminates normally, minimizing in instrumented mode.
[*] Stage #0: One-time
[+] Block normalization complete, 36 bytes replaced.
[*] --- Pass #1 ---
[*] Stage #1: Removing blocks
Block length = 8, remaining size = 80
Block length = 4, remaining size = 80
Block length = 2, remaining size = 76
Block length = 1, remaining size = 76
[+] Block removal complete, 6 bytes deleted.
[*] Stage #2: Minimizing symbols (22 code points)...
[+] Symbol minimization finished, 17 symbols (21 bytes) replaced.
[*] Stage #3:
[+] Character minimization done, 2 bytes replaced.
[*] --- Pass #2 ---
[*] Stage #1: Removing blocks
Block length = 4, remaining size = 74
Block length = 2, remaining size = 74
Block length = 1, remaining size = 74
[+] Block removal complete, 0 bytes deleted.
File size reduced by : 7.50% (to 74 bytes)
Characters simplified : 79.73%
Number of execs done : 221
Fruitless execs : path=189 crash=0 hang=0
[*] Writing output to '2.min'...
[+] We're done here. Have a nice day!
# cat 2
hr: 65 # Home runs
avg: 0.278 # Batting average
rbi: 147 # Runs Batted In
# cat 2.min
00: 00 #00000
000: 00000 #0000000000000000
000: 000 #000000000000000
#
这是⼀个很好的例⼦,说明了AFL的强⼤。AFL不知道YAML是什么,但是它能有效地清除所有不是⽤来表⽰键值对的特殊YAML字符的所有字符。它通过确定改变这些特定字符将会改变从插桩⼆进制程序得到的反馈,来保留它们。它也从原始⽂件中删除4个不影响代码路径表⽰的字节,节省CPU的浪费。
为了快速最⼩化启动测试语料库,我通常使⽤快速for循环将每个⽂件最⼩化为⼀个具有.min特殊⽂件扩展名的新⽂件。
# for i in *; do afl-tmin -i $i -o $i.min -- ~/parse; done;
# mkdir ~/testcases && cp *.min ~/testcases
这个for循环将会遍历该⽬录下的每⼀个⽂件,并使⽤afl-tmin来使它达到最⼩化为⼀个名字相同,多了.min扩展名的⽂件。这样我可以把*.min复制到我⽤来作为AFL的种⼦的⽂件夹。
3、S t art i ng t h e fuz z ers
这⼀部分是⼤多数fuzzing演⽰的结束,但是我这只是⼀个开始。现在我们有⼀个⾼质量的测试⽤例集来作为AFL的种⼦,我们就可以开始了。Optionally,我们也可以利⽤the dictionary token functionality来⽣成AFL的种⼦,with⽤YAML特殊字符来增加⼀点potency能⼒,但这个作为⼀个练习留给读者。
AFL有两种类型的fuzzing策略,⼀种是确定性的,⼀种是随机的、混乱的。当开始afl-fuzz实例afl-fuzz实例时,你可以指定fuzz实例fuzz实例要遵循策略的类型。⼀般来说,你只需要⼀个确定性的(主)fuzzer,但是你可以有很多随机(从)fuzzer。如果你之前使⽤过AFL,并且不知道这是在说什么,那你之前可能只运⾏了⼀个afl-fuzz实例afl-fuzz实例。如果没有指定fuzzing策略,afl-fuzz实例afl-fuzz实例将会在每个策略间来回切换。
# screen afl-fuzz -i testcases/ -o syncdir/ -M fuzzer1 -- ./parse # screen afl-fuzz -i testcases/ -o syncdir/ -S fuzzer2 -- ./parse
⾸先,请注意我们如何在screen(linux命令)screen(linux命令)会话中启动每个实例。这允许我们连接和断开连接到运⾏fuzzer的screen会话,所以我们不会意外关闭运⾏afl-fuzz实例的终端! 还要注意在每个相应的命令中使⽤的参数-M和-S。通过传递-M fuzzer1参数给afl-fuzz,我告诉它是⼀个master fuzzer(使⽤确定性策略),并且fuzz实例fuzz实例的名称是fuzzer1。另⼀⽅⾯,传递给第⼆个命令的-S fuzzer2参数,说明要使⽤随机,混乱的策略运⾏实例,名称为fuzzer2。 这两个模糊器将相互⼯作,当新的代码路径被到时,来回传递新的测试⽤例。
三、When to stop and prune 何时停⽌和删除减少
⼀旦模糊器运⾏相对较长的时间(我喜欢等到⾄少Master Fuzzer已经完成它的第⼀个周期,这时从fuzzer实例从fuzzer实例通常完成了许多周期),我们不应该停⽌⼯作并开始看崩溃。在fuzzing过程中,AFL创建⼀个包含新测试⽤例的巨⼤的语料库,其中可能仍然存在在fuzzing过程中,AFL创建⼀个包含新测试⽤例的巨⼤的语料库,其中可能仍然存在bugs
bugs。我们应该尽量最⼩化这个新的语料库,然后重新设置fuzzer的种⼦,让它们继续运⾏。这是⼀个其他介绍⽂章没有提过的过程,因为它是⽆聊,乏味,可能需要很长时间,但它是⾼效fuzzing的关键。耐⼼和勤奋是美德。耐⼼和勤奋是美德。
⼀旦yaml-cpp的parse程序的master fuzzer完成了它的第⼀个周期(我花了⼤约10个⼩时,平均可能需
要24个⼩时),我们可以继续并停⽌我们的afl-fuzz实例。我们需要合并和最⼩化每个实例的队列queue,并重新启动fuzzing。当使⽤多个fuzzing实例运⾏时,AFL 将 在根⽬录的syncdir⽬录⾥,根据传给afl-fuzz的参数(fuzzer的名称fuzzer的名称),为每个fuzzer 维护⼀个独⽴的、同步⽬录。 每个单独的fuzzer syncdir⽬录都包含⼀个队列queue⽬录,其中包含AFL能够⽣成的所有导致新的代码路径被检测出来的测试⽤例。
我们需要合并每个fuzz实例的队列⽬录,但是因为其中会有很多重叠,需要最⼩化这个新的测试数据集。
# cd ~/syncdir # ls fuzzer1 fuzzer2# mkdir queue_all # cp fuzzer*/queue/* queue_all/# afl-cmin -i queue_all/ -o queue_cmin -- ~/parse corpus minimization tool for afl-fuzz by <lcamtuf@google>
[*] Testing the [+] OK, 884 tuples recorded.[*] Obtaining traces for input files in 'queue_all/'...Processing file [*] Sorting trace sets (this may take a while)...[+] Found 34373 unique tuples across 1159 files.[*] Finding best candidates for Processing file [*] Sorting candidate list (be patient)...[*] Processing candidates and writing Processing tuple [+] Narrowed down to 859 files, saved in 'queue_cmin'.

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。