前言
眼看着网络交互的程序越来越多,我们的afl-fuzz却不能对此进行fuzz,太可惜了!本文尝试对net-snmp进行源码修改,使其通过stdin/stdout完成相应功能,让afl-fuzz重获用武之地(..•˘_˘•..),顺便对fuzz出的crash进行简单分析
net-snmp的编译安装
CXX=afl-clang-fast++ CC=afl-clang-fast ./configure --prefix=/root/0524/mortysnmp
AFL_USE_ASAN=1 make -j4
make install
安装完成后,我们可以开启snmpd服务,并监听1024端口
./snmpd -f 127.0.0.1:1024
如下命令对snmpd服务接收udp包的情况进行测试
echo 'hellotest' > /dev/udp/127.0.0.1/1024
动静态分析溯源程序流程
snmpd服务在接收到请求后对snmp协议进行解析处理,通过查阅相关资料,找到相关解析函数,位置如下 我们可以使用gdb attach到程序中,在_snmp_parse中打下断点,然后发送udp请求,再对程序进行函数回溯,如下
snmpd.c: main(int argc, char *argv[])
↓
snmpd.c:receive();
↓
snmp_api.c:snmp_read2(&readfds);
↓
snmp_api.c:snmp_sess_read2((void *) slp, fdset);
↓
snmp_api.c:rc = _sess_read(sessp, fdset);
↓
snmp_api.c:rc = _sess_process_packet(sessp, sp, isp, transport, opaque,olength, rxbuf, length);
↓
snmp_api.c:ret = snmp_parse(sessp, sp, pdu, packetptr, length);
↓
snmp_api.c:rc = _snmp_parse(sessp, pss, pdu, data, length);
请求包内容的验证
确定了程序的流程之后,我们再对请求包的内容进行验证.在_sess_process_packet处打下断点,跟到程序中,对packetptr中的内容进行验证,查看packetptr指针指向的值 同时进行网络抓包,对比data的值与packetptr的内容 由上图可以看出,packetptr指向的内容正是我们发送的数据
修改udp通信为stdin/stdout的一种思路
通常的fuzz程序对于网络交互并没有很好的支持,这就需要我们将网络交互转变为stdin/stdout,这篇文章给我们提供了很好的思路(https://puzzor.github.io/%E5%88%A9%E7%94%A8AFL-Fuzz-Server).
通过修改程序源码,在端口监听之后,添加发送socket请求的代码,并通过stdin控制socket请求的内容,从而实现网络交互到stdin/stdout的转换.
干就完了
第一步我们要做的就是定位开启监听时的代码位置.首先attach到程序上,我们看到程序运行init_master_agent后开启了监听
我们在init_master_agent
代码下方添加发送socket内容的代码
int mortys = 0;
int n = 0;
int reuse = 1;
struct sockaddr_in srv;
char buf[256] = {0};
bzero(&srv, sizeof(srv));
srv.sin_family = PF_INET;
srv.sin_addr.s_addr = inet_addr("127.0.0.1");
srv.sin_port = htons(1024);
/*创建 UDP 套节字*/
s = socket(AF_INET, SOCK_DGRAM, 0);
memset(buf, 0, 256);
/*读取用户输入到buf中*/
fgets(buf, 256, stdin);
/*通过套节字 s 向服务器发送数据*/
if ((n = sendto(mortys, buf, strlen(buf), 0, (struct sockaddr *) &srv, sizeof(struct sockaddr))) < 0)
{
perror("sendto");
return -1;
}else{
printf("send to len %d:%s\n", n, buf);
}
这段代码在程序开启监听后发送udp请求,保证程序在没有其他网络交互的情况下,运行到snmp_parse,实现snmp的解析,而对请求包内容的控制,在下一步进行.
第二步在snmp解析处修改请求包内容,在_sess_read函数中替换如下内容 这段代码修改了请求包的内容为我们用户的输入,实现网络请求至用户输入输出的转变
最后一步,分别在receive函数和_snmp_parse函数中添加exit(0),使程序能够在snmp解析后及时断开 至此程序修改大功告成!!!
再重新编译
make clean
AFL_USE_ASAN=1 make -j4
make install
stdin功能验证
这时我们无需网络发包,只需要stdin即可控制我们的输入流,且snmp解析完成后程序可即时断开,且输入特定请求包,程序在解析snmp时crash,摆脱了对网络请求的依赖
crash分析(CVE-2018-18066)
CVE-2018-18066是Net-SNMP 5.8之前版本错误的snmp解析方式导致的DoS.这里我们对此进行简要分析.首先阅读源码,我们从这里开始对程序进行跟随,其中packetptr指向请求包,length为包的长度
一路跟随直至如下函数开始正式解析,其中data指向请求包
到这里解析后的内容通过pdu对象传入snmp_oid_compare函数中
当运行到if (*(name1) != *(name2)) {
这里时
r14寄存器为零,执行mov r15,QWORD PTR [r14]
造成空指针异常
参考
http://dumpco.re/blog/net-snmp-5.7.3-remote-dos