AFL基于代码插桩来生成测试用例,这样生成的样本就比较的好,而且针对 linux
做了许多性能优化使得速度也非常快。
AFL(American Fuzzy Lop)是由安全研究员Michał Zalewski开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具,它通过记录输入样本的代码覆盖率,从而调整输入样本以提高覆盖率,增加发现漏洞的概率。其工作流程大致如下:
①从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage);
②选择一些输入文件,作为初始测试集加入输入队列(queue);
③将队列中的文件按一定的策略进行“突变”;
④如果经过变异文件更新了覆盖范围,则将其保留添加到队列中;
⑤上述过程会一直循环进行,期间触发了crash的文件会被记录下来。
目录
AFL状态窗口代表意义
Fuzz网络程序
构建Modbus TCP Server
利用 preeny库
编译 modbus server
获取样本数据
使用获取的样本再次fuzz
总结
AFL Persistent Mode
总结
使用 afl
的常规步骤
- 如果有源码,用
afl-gcc
或者afl-clang-fast
编译源码,afl
会利用这些工具在编译期间对代码进行插桩,为后面的测试提供代码覆盖率,测试样本的变异则会基于代码覆盖率进行。无源码的话可以使用qemu
进行插桩 - 搜集好 初始样本集,如果必要的话使用
afl-cmin
把样本集进行精简。 - 然后用
afl-fuzz
开始fuzz
afl-cmin -i in/ -o out/ /path/to/program
in/
是初始样本集目录
out/
是 精简后的样本集存放的目录
afl-fuzz -i in/ -o out/ /path/to/program
in/
是初始样本集目录
out/
用于保存fuzz
过程中的一些文件
afl-fuzz
默认是往 stdin
中写测试数据,它同时支持从文件喂 测试数据给目标程序,只要把设置文件的参数修改为 @@
, fuzz
过程中 afl-fuzz
会把它替换成 文件名。
比如 ./a
这个程序的 第二个参数是要处理的文件的名称, 那么相应的 afl-fuzz 的命令就是
afl-fuzz -i in/ -o out/ ./a arg1 @@
AFL状态窗口代表意义
① Process timing:Fuzzer运行时长、以及距离最近发现的路径、崩溃和挂起经过了多长时间。
② Overall results:Fuzzer当前状态的概述。
③ Cycle progress:我们输入队列的距离。
④ Map coverage:目标二进制文件中的插桩代码所观察到覆盖范围的细节。
⑤ Stage progress:Fuzzer现在正在执行的文件变异策略、执行次数和执行速度。
⑥ Findings in depth:有关我们找到的执行路径,异常和挂起数量的信息。
⑦ Fuzzing strategy yields:关于突变策略产生的最新行为和结果的详细信息。
⑧ Path geometry:有关Fuzzer找到的执行路径的信息。
⑨ CPU load:CPU利用率
因为afl-fuzz永远不会停止,所以何时停止测试很多时候就是依靠afl-fuzz提供的状态来决定的。
Fuzz 网络程序
这里以 libmodbus
这个库为目标进行 fuzz 。
构建 Modbus TCP Server
库的官网地址如下
http://libmodbus.org/documentation/
这是一个用于 modbus
通讯的库, 通过这个库可以很方便的实现 modbus
服务器 和 客户端。这里以 modbus tcp
的服务端作为 fuzz
的对象。
首先在官网下载好源码
http://libmodbus.org/releases/libmodbus-3.1.4.tar.gz
源码目录下的 tests
目录里面有一些示例程序, 其中 tests/bandwidth-server-one.c
就实现了一个 modbus tcp server
精简得到
#include
#include
#include
#include
#include
int main(int argc, char *argv[]){
int s = -1;
modbus_t *ctx = NULL;
modbus_mapping_t *mb_mapping = NULL;
int rc;
int use_backend;
ctx = modbus_new_tcp("127.0.0.1", 1502);
s = modbus_tcp_listen(ctx, 1);
modbus_tcp_accept(ctx, &s);
mb_mapping = modbus_mapping_new(MODBUS_MAX_READ_BITS, 0,
MODBUS_MAX_READ_REGISTERS, 0);
if (mb_mapping == NULL) {
modbus_free(ctx);
return -1;
}
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
memset(query, 0, MODBUS_TCP_MAX_ADU_LENGTH);
rc = modbus_receive(ctx, query); // 获取客户端的请求数据
if (rc > 0) {
modbus_reply(ctx, query, rc, mb_mapping); // 处理并响应之
}
modbus_mapping_free(mb_mapping);
if (s != -1) {
close(s);
}
/* For RTU, skipped by TCP (no TCP connect) */
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
代码逻辑简单理一下
modbus_new_tcp
初始化modbus_t
结构体modbus_tcp_accept
和modbus_tcp_listen
就是调用socket
监听端口modbus_mapping_new
初始化一个缓冲区,用于模拟寄存器信息- 然后
modbus_receive
接收客户端的请求和输入 - 获取输入后就 通过
modbus_reply
处理 请求,以及构造响应数据包, 同时返回响应 - 然后就是释放掉分配的一些内存
利用 preeny 库辅助
afl
默认只能 fuzz
通过 stdin
和 文件 获取输入的程序, 要 fuzz
网络相关的程序,需要使用一个库
https://github.com/zardus/preeny
这个库利用 LD_PRELOAD
机制,重写了 很多库函数, 其中 desock.c 这个文件负责重写 socket
相关的函数,其实现的功能就是当应用从 socket
获取输入时,其实是从 stdin
获取输入。
首先下载编译下
git clone https://github.com/zardus/preeny.git
cd preeny/
sudo apt-get install libseccomp-dev
make
然后会在 x86_64-linux-gnu
目录下生成编译好的 lib
。
写个测试脚本,测试一下 (根据 tests
目录里面的 sock.c
改造)
#include
#include
#include
#include
int main(){
int s = socket(AF_INET, SOCK_STREAM, 0);
char buf[1024]={0};
char send_msg[] = "hello, send by send() :\n";
send(s, send_msg, strlen(send_msg), 0);
recv(s, buf, 1024, 0);
printf("recv from recv() : %s\n", buf);
}
编译运行
编译运行
gcc sock_test.c -o sock_test
LD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" ./sock_test
╭─birdpwn@ubuntu ~/vuln
╰─$ LD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" ./sock_test
hello, send by send() :
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
recv from recv() : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
往 socket
调用 send
, 成功往 stdout
输出了 字符串。
从 stdin
输入 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
,可以看到成功写入 buf
里面
所以我们就可以利用 preeny
来 fuzz modbus tcp server
了
编译 modbus server
首先使用 afl-gcc
编译 libmodbus
,对 libmodbus
插桩。
cd libmodbus-master/
CC=afl-gcc CXX=afl-g++ ./configure --enable-static
make -j4
--enable-static:用于生成静态库
然后在 src/.libs
下就可以看到编译好的库
─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4
╰─$ ls src/.libs/
libmodbus.a libmodbus.la libmodbus.lai libmodbus.so libmodbus.so.5 libmodbus.so.5.1.0 modbus-data.o modbus.o modbus-rtu.o modbus-tcp.o
libmodbus.a 就是编译好的静态库
然后使用我们修改过的 bandwidth-server-one.c
编译 和 fuzz
cd tests/
vim bandwidth-server-one.c
afl-gcc bandwidth-server-one.c -I../src ../src/.libs/libmodbus.a -o server
mkdir in
echo 11111 > in/1
LD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" afl-fuzz -i in -o out ./server
这里 直接用 echo
生成了一个 测试文件,如果直接用这个去测的话会发现速度非常的慢。
─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests
╰─$ afl-gcc bandwidth-server-one.c -I../src ../src/.libs/libmodbus.a -o server
afl-cc 2.52b by
afl-as 2.52b by
[+] Instrumented 24 locations (64-bit, non-hardened mode, ratio 100%).
╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests
╰─$ mkdir in
╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests
╰─$ echo 11111 > in/1
╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests
╰─$ LD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" afl-fuzz -i in -o out ./server
afl-fuzz 2.52b by
[+] You have 1 CPU core and 7 runnable tasks (utilization: 700%).
[*] Checking core_pattern...
[*] Setting up output directories...
[*] Scanning 'in'...
[+] No auto-generated dictionary tokens to reuse.
[*] Creating hard links for all input files...
[*] Validating target binary...
[*] Attempting dry run with 'id:000000,orig:1'...
[*] Spinning up the fork server...
[+] All right - fork server is up.
len = 6, map size = 108, exec speed = 501074 us
[+] All test cases processed.
[!] WARNING: The target binary is pretty slow! See /usr/local/share/doc/afl/perf_tips.txt.
[+] Here are some useful stats:
Test case count : 1 favored, 0 variable, 1 total
Bitmap range : 108 to 108 bits (average: 108.00 bits)
Exec timing : 501k to 501k us (average: 501k us)
[*] No -t option specified, so I'll use exec timeout of 1000 ms.
[+] All set and ready to roll!
这里 直接用 echo
生成了一个 测试文件,如果直接用这个去测的话会发现速度非常的慢。
获取样本数据
一组好的样本数据对 fuzzer
的影响还是非常大的,一般我们可以去网上搜索样本,比如图片,视频文件等。对于我们这次的目标 libmodbus
, 它自带了很多的测试程序,我们可以利用这些测试程序测试,然后用 tcpdump
抓包, 最后在把其中的请求数据保存下来,作为测试样本集。
首先使用 random-test-server 在 127.0.0.1:1502 起一个 modbus tcp 服务
╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests
╰─$ ./random-test-server
然后开启 tcpdump , 保存数据包到 ~/modbus.pcap
─birdpwn@ubuntu ~
╰─$sudo tcpdump -i lo -w ~/modbus.pcap
[sudo] password for birdpwn:
tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
最后使用 random-test-client
随机发送各种 modbus
请求到 127.0.0.1:1502
╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests
╰─$ ./random-test-client
Connecting to 127.0.0.1:1502
[00][01][00][00][00][06][FF][05][00][00][FF][00]
Waiting for a confirmation...
<00><01><00><00><00><06><FF><05><00><00><FF><00>
[00][02][00][00][00][06][FF][01][00][00][00][01]
Waiting for a confirmation...
然后写一个脚本把 ~/modbus.pcap
中 由客户端发送的数据包 (也就是目的地为 127.0.0.1:1502
的数据包) 的内容提取出来,每个数据包内容保存为一个单独的文件。
sudo apt-get install python-scapy
from scapy.all import *
save_path = "/tmp/seeds/"
uuid = 0
if not os.path.exists(save_path):
os.system("mkdir %s" %(save_path))
def save_to_file(data):
global uuid
with open("{}{}".format(save_path, uuid), "w") as fp:
fp.write(str(data))
uuid += 1
print "write test file: {}".format(uuid)
modbus_session = ''
pg = rdpcap("modbus.pcap")
session = pg.sessions()
for k in session.keys():
if k.endswith("127.0.0.1:1502"):
modbus_session = session[k]
for s in modbus_session:
payload = s[TCP].payload
if len(payload) > 4:
save_to_file(payload)
print "Total: %d tests" %(uuid)
生成测试用例:
─birdpwn@ubuntu /tmp/seeds
╰─$ ls
0 120 143 166 189 210 233 256 279 300 323 346 369 391 413 436 459 481 503 526 549 571 594 616 639 661 684 706 729 751 774 797 819 841 864 887 909 931 954 977
1 121 144 167 19 211 234 257 28 301 324 347 37 392 414 437 46 482 504 527 55 572 595 617 64 662 685 707 73 752 775 798 82 842 865 888 91 932 955 978
10 122 145 168 190 212 235 258 280 302 325 348 370 393 415 438 460 483 505 528 550 573 596 618 640 663 686 708 730 753 776 799 820 843 866 889 910 933 956 979
100 123 146 169 191 213 236 259 281 303 326 349 371 394 416 439 461 484 506 529 551 574 597 619 641 664 687 709 731 754 777 8 821 844 867 89 911 934 957 98
101 124 147 17 192 214 237 26 282 304 327 35 372 395 417 44 462 485 507 53 552 575 598 62 642 665 688 71 732 755 778 80 822 845 868 890 912 935 958 980
102 125 148 170 193 215 238 260 283 305 328 350 373 396 418 440 463 486 508 530 553 576 599 620 643 666 689 710 733 756 779 800 823 846 869 891 913 936 959 981
103 126 149 171 194 216 239 261 284 306 329 351 374 397 419 441 464 487 509 531 554 577 6 621 644 667 69 711 734 757 78 801 824 847 87 892 914 937 96 982
104 127 15 172 195 217 24 262 285 307 33 352 375 398 42 442 465 488 51 532 555 578 60 622 645 668 690 712 735 758 780 802 825 848 870 893 915 938 960 983
105 128 150 173 196 218 240 263 286 308 330 353 376 399 420 443 466 489 510 533 556 579 600 623 646 669 691 713 736 759 781 803 826 849 871 894 916 939 961 984
106 129 151 174 197 219 241 264 287 309 331 354 377 4 421 444 467 49 511 534 557 58 601 624 647 67 692 714 737 76 782 804 827 85 872 895 917 94 962 985
107 13 152 175 198 22 242 265 288 31 332 355 378 40 422 445 468 490 512 535 558 580 602 625 648 670 693 715 738 760 783 805 828 850 873 896 918 940 963 986
108 130 153 176 199 220 243 266 289 310 333 356 379 400 423 446 469 491 513 536 559 581 603 626 649 671 694 716 739 761 784 806 829 851 874 897 919 941 964 987
109 131 154 177 2 221 244 267 29 311 334 357 38 401 424 447 47 492 514 537 56 582 604 627 65 672 695 717 74 762 785 807 83 852 875 898 92 942 965 988
11 132 155 178 20 222 245 268 290 312 335 358 380 402 425 448 470 493 515 538 560 583 605 628 650 673 696 718 740 763 786 808 830 853 876 899 920 943 966 989
110 133 156 179 200 223 246 269 291 313 336 359 381 403 426 449 471 494 516 539 561 584 606 629 651 674 697 719 741 764 787 809 831 854 877 9 921 944 967 99
111 134 157 18 201 224 247 27 292 314 337 36 382 404 427 45 472 495 517 54 562 585 607 63 652 675 698 72 742 765 788 81 832 855 878 90 922 945 968
112 135 158 180 202 225 248 270 293 315 338 360 383 405 428 450 473 496 518 540 563 586 608 630 653 676 699 720 743 766 789 810 833 856 879 900 923 946 969
113 136 159 181 203 226 249 271 294 316 339 361 384 406 429 451 474 497 519 541 564 587 609 631 654 677 7 721 744 767 79 811 834 857 88 901 924 947 97
114 137 16 182 204 227 25 272 295 317 34 362 385 407 43 452 475 498 52 542 565 588 61 632 655 678 70 722 745 768 790 812 835 858 880 902 925 948 970
115 138 160 183 205 228 250 273 296 318 340 363 386 408 430 453 476 499 520 543 566 589 610 633 656 679 700 723 746 769 791 813 836 859 881 903 926 949 971
116 139 161 184 206 229 251 274 297 319 341 364 387 409 431 454 477 5 521 544 567 59 611 634 657 68 701 724 747 77 792 814 837 86 882 904 927 95 972
117 14 162 185 207 23 252 275 298 32 342 365 388 41 432 455 478 50 522 545 568 590 612 635 658 680 702 725 748 770 793 815 838 860 883 905 928 950 973
118 140 163 186 208 230 253 276 299 320 343 366 389 410 433 456 479 500 523 546 569 591 613 636 659 681 703 726 749 771 794 816 839 861 884 906 929 951 974
119 141 164 187 209 231 254 277 3 321 344 367 39 411 434 457 48 501 524 547 57 592 614 637 66 682 704 727 75 772 795 817 84 862 885 907 93 952 975
12 142 165 188 21 232 255 278 30 322 345 368 390 412 435 458 480 502 525 548 570 593 615 638 660 683 705 728 750 773 796 818 840 863 886 908 930 953 976
使用获取的样本再次 fuzz
然后以生成的样本集作为初始样本集进行 fuzz
LD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" afl-fuzz -i /tmp/seeds -o out ./server
刚开始一直报错
在一顿查找原因后,是配置有问题
╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests
╰─$ LD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" afl-fuzz -t 100+ -i seeds -o out ./server export AFL_NO_FORKSRV=1 1 ↵
afl-fuzz 2.52b by
[+] You have 1 CPU core and 7 runnable tasks (utilization: 700%).
[*] Checking core_pattern...
总结
afl + preeny
来 fuzz
网络应用 速度还行, 关键的还是要找到好的样本,从程序自带的测试用例中抓取也是一个不错的思路。
AFL Persistent Mode
在介绍一个 在 fuzz
一些网络程序时可能用到的特性, AFL
的 persistent
模式。
persistent
模式就是在程序的某个代码位置不断喂生成的变异数据进行 fuzz
, 而不用每次喂数据都得重新 fork
一个程序。
要使用这个特性,首先得编译 llvm_mode
cd afl-2.52b/
cd llvm_mode/
make
cd ..
sudo make install
此时就会有 afl-clang-fast
和 afl-clang-fast++
两个命令, 要使用这个模式,就要用这两个命令来编译目标应用。
面还是用 afl
自带的 测试文件 experimental/persistent_demo/persistent_demo.c
来看看。
#include
#include
#include
#include
#include
int main(int argc, char** argv) {
char buf[100];
while (__AFL_LOOP(1000)) {
memset(buf, 0, 100);
read(0, buf, 100);
if (buf[0] == 'f') {
printf("one\n");
if (buf[1] == 'o') {
printf("two\n");
if (buf[2] == 'o') {
printf("three\n");
if (buf[3] == '!') {
printf("four\n");
abort();
}
}
}
}
} // end of while (__AFL_LOOP(1000))
return 0;
}
最关键的 就是 AFL_LOOP(1000)
这个宏, 其中的参数指定循环的次数。
每一次循环 afl 都会生成 测试数据,然后喂到 stdin
, 这样 fuzzer
就可以在 AFL_LOOP
宏 包围的内部,通过 read(0,buf, size)
来获取测试数据,然后喂给目标程序的数据处理的代码,这样可以减少 fork
等操作的开销。
对应到上面的程序,就是 afl
会在
while (__AFL_LOOP(1000)) {
........
........
}
里面 fuzz 1000
次,即生成 1000
次测试数据, 然后会 return 0
. 程序结束,然后 afl
会重新起一个程序。继续这样的 fuzz
.
被 while (__AFL_LOOP(1000))
包围的代码,就是不断的 从 stdin
获取测试数据,然后进入下面的 if
判断逻辑。
编译 然后用 afl-fuzz
它
afl-clang-fast persistent_demo.c -o persistent_demo
afl-fuzz -i in/ -o out/ ./persistent_demo
总结
如果使用 afl
来 fuzz
网络应用,有两种方式
- 利用
preeny
把从socket
获取数据,转变为 从stdin
获取数据 - 利用
afl
的persistent
模式