September 28, 2026
Cookie NILE CTF
Hi, In this article, I will explain how I solved the “Cookie” PWN challenge in the Nile CTF competition. Let’s get started.

By Mr_Karkr
4 min read
First, I performed reconnaissance for the challenge.
└─$ file cookie cookie: ELF 64-bit LSB executable, x86–64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86–64.so.2, BuildID[sha1]=3af5a6a88cda972c610008b024520eb511222b1e, for GNU/Linux 4.4.0, not stripped
The binary is a 64-bit ELF executable and is not stripped.
└─$ checksec ./cookie [*] '/home/karkr/Desktop/player_cookie/cookie' Arch: amd64–64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
This means that stack canary protection is enabled and the stack is non-executable, but PIE protection is disabled; consequently, addresses are static.
Upon reverse-engineering the program, I discovered a function named win that calls the system function our goal is to reach this function. There is also a function named vuln — the vulnerable function — so let's take a look at it.
The function declares two stack buffers a 64-byte buffer local_68 and a 24-byte buffer acStack_28. At line 12, read attempts to read 25 bytes into the 24-byte acStack_28 buffer, resulting in a one-byte stack overflow.
The input is then passed to puts because read does not append a null terminator, puts may also read beyond the buffer if the input is not null-terminated. Later, line 16 introduces a larger stack-based buffer overflow by reading up to 200 bytes into the 64-byte local_68 buffer.
We have now clearly identified the vulnerability, but how do we steal the canary ?
If we send 25 bytes, we'll see it print the canary values.
But wait wait how and why did this happen ??!
To understand this, we first need to look at how the local variables are arranged on the stack.
The relevant part of the stack looks like this:
Higher addresses
┌─────────────────────────┐
│ Stack Canary │
├─────────────────────────┤
│ acStack_28[24] │
└─────────────────────────┘
Lower addressesHigher addresses
┌─────────────────────────┐
│ Stack Canary │
├─────────────────────────┤
│ acStack_28[24] │
└─────────────────────────┘
Lower addressesOn linux, the stack canary conventionally has a null byte as its least-significant byte.
Normally, after the 24-byte acStack_28 buffer, memory looks conceptually like:
AAAAAAAAAAAAAAAAAAAAAAAA 00 ?? ?? ?? ?? ?? ?? ??
└────── 24 bytes ───────┘ └────── canary ───────┘AAAAAAAAAAAAAAAAAAAAAAAA 00 ?? ?? ?? ?? ?? ?? ??
└────── 24 bytes ───────┘ └────── canary ───────┘Let's take a look at our program.
After submit 23 'A':
So, when we input 25, a buffer overflow occurs; the extra byte overwrites the canary — specifically the null byte — and this is caused by that line:
read(0, acStack_28, 0x19);read(0, acStack_28, 0x19);This causes a one-byte overflow.
Now, it remains for us to understand how the puts function works.
The puts function continues reading the text until it encounters the null byte; once found, it prints the text.
To summarize: First, knowing that entering 25 bytes causes a one-byte overflow, we input 25 bytes. Second, this overflowing byte spills over into the canary value, overwriting the null byte. Third, the puts function executes; unable to find the null byte, it continues printing the canary bytes until it finally reaches a null byte. Ultimately, we successfully obtain the canary value.
Now, we need to write a script using pwntools that takes the canary values and appends the null byte to them.
I've already written a script that does this: it takes the canary value, prints it after appending the null byte, and unpacks it, since it was stored in little-endian format.
Now we can exploit the second buffer overflow and utilize that canary value; all that remains is to determine the offset and the address of the win function — which are the easiest parts.
To determine the offset, we can use a cyclic approach.
We now know that the distance from the start of our buffer to the start of the return address is 104.
If the distance from the start of the buffer to the start of the canary is 88, it is because the base address value ("rbp") is located after the canary and before the return address.
Now everything is ready, and we have obtained the address of the win function from Ghidra, using nm, or via any other method.
The final script will look like this:
We succeeded in obtaining the shell
After speaking with the author @ziad686
We got the flag: NileCTF{FrEe!C00kI_DiDY0uReT2Win!}
Finally, thanks to the author.
And Thank you for reading.