利用afl fuzz server
利用AFL Fuzz Server
Lolware在4月份写了一篇文章关于如何利用AFL fuzz Nginx。AFL本身针对处理文件类型的程序进行Fuzz,而针对处理网络协议类型的程序并不能很好的支持。在博客中Lolware说其借助preeny中的desock将socket重定向到stdin/stdout以解决AFL对网络协议类型程序不能处理的问题。但是根据blog中的内容我进行了反复实验却没能成功,主要问题发生在重定向之后nginx处理请求时延迟过长导致不能正常进行测试,并且对于WebServer这种服务程序在处理请求时需要考虑到大量关键字问题,也就是说如果在afl中添加预设字典效果会更好一些。
时间到了7月,Jonathan Foote写了一篇How to fuzz a server with American Fuzzy Lop,其中用到了AFL的Persistent mode和select的方式对Server程序进行Fuzz,其中的示例为Knot DNS,由于DNS默认采用了UDP,所以其直接调用了sendto对程序本身的socket进行feed。然而如果是TCP模式这种方法就有一定的问题,比如上述的Nginx。
这里我认为可以有一种变通的解决思路,该方法理论上能够解决AFL在Fuzz无状态网络协议处理程序的先天不足。
假设有如下demo:
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include<arpa/inet.h>
int main(void) {
int sockfd, clientfd;
socklen_t cliaddr_len;
struct sockaddr_in server_addr, client_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("Something wrong\n");
exit(1);
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(1024);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int br = bind(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr));
if (br == -1) {
perror("Something wrong\n");
exit(1);
}
if ((listen(sockfd, 20)) == -1) {
perror("Something wrong\n");
exit(1);
}
char buf[MAXLINE];
for (;;) {
clientfd = accept(sockfd, (struct sockaddr *) &client_addr,
&cliaddr_len);
printf("server get connection from %s.\n", inet_ntoa(
client_addr.sin_addr));
int readize = 0;
while ((readize = read(clientfd, buf, MAXLINE)) > 0) {
printf("Content:%.*s", readize,buf);
printf("Length:%d...\n", readize);
}
write(clientfd, buf, readize);
close(clientfd);
}
return EXIT_SUCCESS;
}
程序本身是一个简单的Server端的socket程序,其监听1024端口并接收数据,接收成功后将数据长度以及内容打印出来。
然而这样一个程序如果我们需要利用AFL对其进行Fuzz,则需要找到accept函数的调用部分并将其改为从本地文件读取内容。对于简单的socket程序我们可以这样做,然而对于复杂的大型程序来说,这显得不实际。
一种相对通用的方法是,首先定位accept,然后在accept之前创建一个线程,此线程所做的工作是从本地读取一个文件,并将其内容通过socket方式发送到原程序监听的端口上。我们将上述程序修改如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include<arpa/inet.h>
#include <pthread.h>
#include<sys/time.h>
const int MAXLINE = 1024;
void *thread(void *arg){
int sockfd,sock_dt;
struct sockaddr_in my_addr;
struct sockaddr_in dest_addr;
int destport =1024;
int n_send_len;
printf("thread is going to run and send sth to origin socket\n");
sleep(1);
sockfd=socket(AF_INET,SOCK_STREAM,0);
dest_addr.sin_family=AF_INET;
dest_addr.sin_port=htons(destport);
dest_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
memset(&dest_addr.sin_zero,0,8);
connect(sockfd,(struct sockaddr*)&dest_addr,sizeof(struct sockaddr));
n_send_len = send(sockfd,"Content sent from thread\n",strlen("Content sent from thread\n"),0);
printf("%d bytes sent\n",n_send_len);
close(sockfd);
return NULL;
}
int main(void) {
int sockfd, clientfd;
socklen_t cliaddr_len;
struct sockaddr_in server_addr, client_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("Something wrong\n");
exit(1);
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(1024);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int br = bind(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr));
if (br == -1) {
perror("Something wrong\n");
exit(1);
}
if ((listen(sockfd, 20)) == -1) {
perror("Something wrong\n");
exit(1);
}
char buf[MAXLINE];
for (;;) {
pthread_t th;
pthread_create(&th,NULL,thread,NULL);
clientfd = accept(sockfd, (struct sockaddr *) &client_addr,
&cliaddr_len);
sleep(1);
printf("server get connection from %s.\n", inet_ntoa(
client_addr.sin_addr));
int readize = 0;
while ((readize = read(clientfd, buf, MAXLINE)) > 0) {
printf("Content:%.*s", readize,buf);
printf("Length:%d...\n", readize);
}
write(clientfd, buf, readize);
close(clientfd);
}
return EXIT_SUCCESS;
}
可以看到我们在accept函数执行之前创建了线程,线程会主动发起连接请求并发送数据。 程序输出结果如下:
thread is going to run and send sth to origin socket
25 bytes sent
server get connection from 127.0.0.1.
Content:Content sent from thread
Length:25...
thread is going to run and send sth to origin socket
25 bytes sent
server get connection from 127.0.0.1.
Content:Content sent from thread
Length:25...
接下来我们以nginx为例来看下如何更改程序以便能够正常进行fuzz。在nginx中我们采用同样的思路,首先定位到main函数中并找到ngx_single_process_cycle函数的调用处,在上面的判断之前增加新线程。
//AFL Thread Start @Puzzor 2015.08.03
pthread_t th;
pthread_create(&th,NULL,thread,NULL);
//AFL Thread End
除此之外,我们需要Nginx接收文件参数并读取文件中的请求内容 由于nginx本身是一个守护进程,因此我们需要在每次接收完请求后将nginx关闭以进行下一次fuzz,于是线程处理函数这样:
void *thread(void *arg){
int sockfd;
struct sockaddr_in dest_addr;
int destport =8080;
sleep(0.5);
sockfd=socket(AF_INET,SOCK_STREAM,0);
dest_addr.sin_family=AF_INET;
dest_addr.sin_port=htons(destport);
dest_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
memset(&dest_addr.sin_zero,0,8);
connect(sockfd,(struct sockaddr*)&dest_addr,sizeof(struct sockaddr));
send(sockfd,"hello\n\n",strlen("hello\n\n"),0);
close(sockfd);
exit(0);
return NULL;
}
修改完成后我们就可以利用AFL对Nginx进行Fuzz了,但是还需要注意的是目前我们只能够单进程进行Fuzz,因为在nginx.conf中只配置了一个端口,如果进行并行Fuzz会出现不同的变异样本发送到同一Server端,造成潜在的异常。因此如果利用AFL进行并行Fuzz,我们需要为每一个进程指定不同的conf文件,这一点可以利用”-c”参数。除此之外,我们需要传递端口参数以便线程发送数据时发送到相应的端口上。ngx_get_options函数中添加代码如下:
//AFL Read File Option Start @Puzzor 2015.08.03
case 'r':
if (*p) {
AFL_File = (char *)p;
}
else{
AFL_File=argv[++i];
}
break;
//AFL Read File Option End
最后,目前只解决了免去对程序大量分析的工作,然而在优化上还可以继续深入,比如可以和persistent mode 相结合以提高单进程Fuzz效率。