fuzz实战之honggfuzz

Author Avatar
hac425 6月 03, 2018
  • 在其它设备中阅读本文章

Honggfuzz实战

前言

本文介绍 libfuzzerafl 联合增强版 honggfuzz .同时介绍利用 honggfuzzfuzz 网络应用服务。

介绍

honggfuzz 也是 google 开发的一款 fuzz . 其设计思路 和 libfuzzerafl 类似 ,感觉就是 libfuzzer + afl 的 增强版。

编译

1
2
3
git clone https://github.com/google/honggfuzz.git
cd honggfuzz
make

honggfuzz 的使用文档在

1
https://github.com/google/honggfuzz/tree/master/docs

对几条命令做个解释

1
honggfuzz -f input_dir -z -s -- /usr/bin/djpeg

-f : 指定初始样本集目录

-z : 使用编译时的指令插桩信息来 为 样本变异做回馈, 默认选项

-s : 表示目标程序从 stdin 获取输入,即样本数据通过 stdin 喂给程序

1
honggfuzz -f input_dir -- /usr/bin/djpeg ___FILE___

___FILE___ : 类似于 afl@@ , 表示程序通过文件获取输入,fuzz 过程会被替换为相应的样本文件名

1
honggfuzz -f input_dir -P -- /usr/bin/djpeg_persistent_mode

-P : 表示使用 persistent 模式

简单示例(libFuzzer模式)

这里用 libfuzzer-workshop 里面的 libxml2 来进行 fuzz 测试

1
https://github.com/Dor1s/libfuzzer-workshop/tree/master/lessons/08

下载那个 libxml2.tgz 然后解压,用 hfuzz-clang 编译

1
2
3
4
5
6
7
8
tar xvf libxml2.tgz
cd libxml2/
CC=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang CXX=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang++
export CC=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang
export CXX=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang++
./autogen.sh
CC=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang CXX=/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang++ ./configure
make -j4

/home/haclh/vmdk_kernel/honggfuzz/honggfuzz 所在的目录

然后会生成 libxml2/.libs/libxml2.a

看看 fuzzer 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifdef __cplusplus
extern "C" {
#endif
#include <inttypes.h>
#include "libxml/parser.h"
#include <stdlib.h>
#include <libhfuzz/libhfuzz.h>
FILE* null_file = NULL;
int LLVMFuzzerInitialize(int* argc, char*** argv)
{
null_file = fopen("/dev/null", "w");
return 0;
}
int LLVMFuzzerTestOneInput(const uint8_t* buf, size_t len)
{
xmlDocPtr p = xmlReadMemory((const char*)buf, len, "http://www.google.com", "UTF-8", XML_PARSE_RECOVER | XML_PARSE_NONET);
if (!p) {
return 0;
}
xmlDocFormatDump(null_file, p, 1);
xmlFreeDoc(p);
return 0;
}
#ifdef __cplusplus
}
#endif

libfuzzer 的代码基本一致,用 hfuzz-clang 编译(类似于 libfuzzer , 需要和 libhfuzz.a 链接)。

1
/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang persistent-xml2.c -Ilibxml2/include libxml2/.libs/libxml2.a /usr/lib/x86_64-linux-gnu/liblzma.a /home/haclh/vmdk_kernel/honggfuzz/libhfuzz/libhfuzz.a -lz -o persistent-xml2

然后运行 fuzz

1
/home/haclh/vmdk_kernel/honggfuzz/honggfuzz -W out -f ~/vmdk_kernel/libfuzzer-workshop-master/lessons/08/corpus2/ -- ./persistent-xml2

-W : 指定输出目录

-f : 指定 初始样本集目录

paste image

Persistent Fuzzing

honggfuzz 还支持 persistent 模式 , 而且他这种模式实现的比 afl 要 更加容易使用。直接上 demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <inttypes.h>
extern HF_ITER(uint8_t** buf, size_t* len);
void test(char* buf){
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();
}
}
}
}
}
int main(void) {
for (;;) {
size_t len;
uint8_t *buf;
HF_ITER(&buf, &len);
test(buf);
}
return 0;
}

代码和普通的应用差不多, 需要注意的就是 HF_ITER程序可以通过这个宏 来获取 honggfuzz 生成的样本数据,这就非常方便了,我们可以把它插入对数据处理的逻辑, 然后循环这段逻辑,就可以不断的进行 fuzz 而不用重新起进程。

对应到上面的代码就是, 一个死循环,循环里面使用 HF_ITER 获取数据,然后把数据喂给我们要测试的逻辑 (这里是 test 函数), 在 test 函数内部 如果满足条件,会触发 abort 模拟一个crash, 让 honggfuzz 捕获到。

编译

1
/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang test.c /home/haclh/vmdk_kernel/honggfuzz/libhfuzz/libhfuzz.a -o test

然后运行

1
2
3
mkdir in
echo 111 > in/1
/home/haclh/vmdk_kernel/honggfuzz/honggfuzz -P -f in/ -W out -- ./test

-P : 用于开启 persistent 模式

马上就能看的 crash 了。
paste image

然后会在 out 目录生成触发 crash 的文件

1
2
22:28 haclh@ubuntu:persistent $ xxd out/SIGABRT.PC.7ffff6efa428.STACK.17326c6e5e.CODE.-6.ADDR.\(nil\).INSTR.cmp____\$0xfffffffffffff000\,%rax.fuzz
00000000: 666f 6f21 foo!

内容满足触发 abort 的条件

Honggfuzz NetDriver

介绍

这个是 honggfuzz 自带的一个库 用于 fuzz socket 类程序

1
https://github.com/google/honggfuzz/tree/master/libhfnetdriver

用它 fuzz socket 程序很方便, 只要把 main 函数 改成 HFND_FUZZING_ENTRY_FUNCTION (: 不确定是不是所有的都这样,具体还是得看代码 ,以后再研究研究

1
2
3
4
5
int main(int argc, char *argv[]){
......................
......................
......................
}

改成

1
2
3
4
5
HFND_FUZZING_ENTRY_FUNCTION(int argc, char *argv[]){
......................
......................
......................
}

然后用 hfuzz-clang 编译, 同时用 libhfnetdriver.a 链接即可。

demo

不知道为啥 libmodbustcp server 用这种方式跑不起来,这里用一个 有漏洞的 socket 程序作为例子,试试这个功能。

vuln.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include <crypt.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
/* Do nothing with first message */
void handleData0(char *data, int len) {
printf("Auth success\n");
}
/* Second message is stack based buffer overflow */
void handleData1(char *data, int len) {
char buff[8];
bzero(buff, 8);
memcpy(buff, data, len);
printf("Handledata1: %s\n", buff);
}
/* Third message is heap overflow */
void handleData2(char *data, int len) {
char *buff = malloc(8);
bzero(buff, 8);
memcpy(buff, data, len);
printf("Handledata2: %s\n", buff);
free(buff);
}
void handleData3(char *data, int len) {
printf("Meh: %i\n", len);
}
void handleData4(char *data, int len) {
printf("Blah: %i\n", len);
}
void doprocessing(int sock) {
char data[1024];
int n = 0;
int len = 0;
while (1) {
bzero(data, sizeof(data));
len = read(sock, data, 1024);
if (len == 0 || len <= 1) {
return;
}
printf("Received data with len: %i on state: %i\n", len, n);
switch (data[0]) {
case 'A':
handleData0(data, len);
write(sock, "ok", 2);
break;
case 'B':
handleData1(data, len);
write(sock, "ok", 2);
break;
case 'C':
handleData2(data, len);
write(sock, "ok", 2);
break;
case 'D':
handleData3(data, len);
write(sock, "ok", 2);
break;
case 'E':
handleData4(data, len);
write(sock, "ok", 2);
break;
default:
return;
}
n++;
}
}
HFND_FUZZING_ENTRY_FUNCTION(int argc, char *argv[]) {
int sockfd, newsockfd, portno, clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n, pid;
if (argc == 2) {
portno = atoi(argv[1]);
} else {
portno = 5001;
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
int reuse = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const char *)&reuse, sizeof(reuse)) < 0)
perror("setsockopt(SO_REUSEPORT) failed");
bzero((char *)&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
printf("Listening on port: %i\n", portno);
/* Now bind the host address using bind() call.*/
if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR on binding");
exit(1);
}
listen(sockfd, 5);
clilen = sizeof(cli_addr);
while (1) {
newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
if (newsockfd < 0) {
perror("ERROR on accept");
exit(1);
}
printf("New client connected\n");
doprocessing(newsockfd);
printf("Closing...\n");
shutdown(newsockfd, 2);
close(newsockfd);
}
}

就是一个简单的 socket 程序,其中有两个漏洞 一个栈溢出 一个堆溢出, 同时把 int main 改成了 HFND_FUZZING_ENTRY_FUNCTION

代码修改自: https://github.com/google/honggfuzz/blob/master/socketfuzzer/vulnserver_cov.c

编译

1
/home/haclh/vmdk_kernel/honggfuzz/hfuzz_cc/hfuzz-clang vuln.c /home/haclh/vmdk_kernel/honggfuzz/libhfnetdriver/libhfnetdriver.a -o vuln

关键是要和 libhfnetdriver.a 链接

运行

1
2
3
4
mkdir in
echo A > in/1
echo B > in/2
_HF_TCP_PORT=5001 /home/haclh/vmdk_kernel/honggfuzz/honggfuzz -f in -- ./vuln

前面三条命令用于生成两个简单样本文件

最后一条命令用于启动 fuzzer .

_HF_TCP_PORT 指定 server 监听的端口, fuzzer 会和这个端口建立 tcp 连接,然后发送数据。

秒出 crash

paste image

参考

FUZZING TCP SERVERS

实战

本节以 mongoose 为例 实战一波

1
https://github.com/cesanta/mongoose

这是一个用于 嵌入式网络服务的库, 实现了很多 IOT 中用到的协议。

本节以 http 为例进行 fuzz

程序的源代码内有很多的示例 , 其中 examples/simplest_web_server 目录里面是一个很简单的 http server

把代码中的 int main 改成 HFND_FUZZING_ENTRY_FUNCTION

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Copyright (c) 2015 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
static const char *s_http_port = "8000";
static struct mg_serve_http_opts s_http_server_opts;
static void ev_handler(struct mg_connection *nc, int ev, void *p) {
if (ev == MG_EV_HTTP_REQUEST) {
mg_serve_http(nc, (struct http_message *) p, s_http_server_opts);
}
}
HFND_FUZZING_ENTRY_FUNCTION(void) {
struct mg_mgr mgr;
struct mg_connection *nc;
mg_mgr_init(&mgr, NULL);
printf("Starting web server on port %s\n", s_http_port);
nc = mg_bind(&mgr, s_http_port, ev_handler);
if (nc == NULL) {
printf("Failed to create listener\n");
return 1;
}
// Set up HTTP server parameters
mg_set_protocol_http_websocket(nc);
s_http_server_opts.document_root = "."; // Serve current directory
s_http_server_opts.enable_directory_listing = "yes";
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}

然后用 hfuzz-clang 编译, 注意要和 libhfnetdriver.a 链接

1
2
export HONGGFUZZ_HOME=/home/haclh/vmdk_kernel/honggfuzz
$HONGGFUZZ_HOME/hfuzz_cc/hfuzz-clang simplest_web_server.c ../../mongoose.c -o simplest_web_server -I../.. -DMG_DISABLE_DAV_AUTH -DMG_ENABLE_FAKE_DAVLOCK $HONGGFUZZ_HOME/libhfnetdriver/libhfnetdriver.a -pthread

然后开始 fuzz

1
_HF_TCP_PORT=8000 /home/haclh/vmdk_kernel/honggfuzz/honggfuzz -W oout -f corpus_http1/ -w httpd.wordlist -- ./simplest_web_server

其中用到的样本集 和 字典文件来自

1
https://github.com/google/honggfuzz/tree/master/examples/apache-httpd

paste image

发现了 crash ,下面定位触发漏洞代码, 使用 -fsanitize=address 编译,可以在 触发漏洞时停下来。

1
clang -fsanitize=address simplest_web_server.c ../../mongoose.c -o simplest_web_server -g -W -Wall -Werror -I../.. -Wno-unused-function -DMG_DISABLE_DAV_AUTH -DMG_ENABLE_FAKE_DAVLOCK -pthread

然后触发漏洞的样本发送给服务器,就会 crash

1
2
3
4
5
6
7
8
01:19 haclh@ubuntu:simplest_web_server $ ./simplest_web_server
Starting web server on port 8000
=================================================================
==16867==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x619000000980 at pc 0x0000004e653d bp 0x7fffb8bab790 sp 0x7fffb8baaf40
READ of size 876 at 0x619000000980 thread T0
#0 0x4e653c in __asan_memcpy /home/haclh/vmdk_kernel/libfuzzer-workshop-master/src/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:23
#1 0x53b9d6 in mbuf_insert /tmp/t/mongoose-6.11/examples/simplest_web_server/../../mongoose.c:1477:24
#2 0x53bafc in mbuf_append /tmp/t/mongoose-6.11/examples/simplest_web_server/../../mongoose.c:1490:10

然后可以定位到 mongoose.c8925

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
8923 if (mg_start_process(opts->cgi_interpreter, prog, blk.buf, blk.vars, dir,
8924 fds[1]) != 0) {
8925 size_t n = nc->recv_mbuf.len - (hm->message.len - hm->body.len);
8926 struct mg_connection *cgi_nc =
8927 mg_add_sock(nc->mgr, fds[0], mg_cgi_ev_handler MG_UD_ARG(nc));
8928 struct mg_http_proto_data *cgi_pd = mg_http_get_proto_data(nc);
8929 cgi_pd->cgi.cgi_nc = cgi_nc;
8930 #if !MG_ENABLE_CALLBACK_USERDATA
8931 cgi_pd->cgi.cgi_nc->user_data = nc;
8932 #endif
8933 nc->flags |= MG_F_HTTP_CGI_PARSE_HEADERS;
8934 /* Push POST data to the CGI */
8935 if (n > 0 && n < nc->recv_mbuf.len) {
8936 mg_send(cgi_pd->cgi.cgi_nc, hm->body.p, n);
8937 }

本站文章均原创, 转载注明来源
本文链接:http://blog.hac425.top/2018/06/03/fuzz_with_honggfuzz.html