← Back to Writeups
CUSTOMMediumReversing

ums (RE4_ums)

XESXOR9/2/20264 min read
#reversing#custom#medium#deterministic_pie_base#red_herring_elimination#ret2win#return_address_overwrite#stack_buffer_overflow

ums (RE4_ums)

Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: deterministic_pie_base, red_herring_elimination, ret2win, return_address_overwrite, stack_buffer_overflow

Summary

Walkthrough of the CUSTOM Reversing ums (RE4_ums). Task: 32-bit x86 PIE ELF User Management System menu app where get_flag enforces deliberately unsatisfiable record checks (id==2, age==25, name, passport). Solution: ignore the RE red herring — get_fl

Description

Presenting the mighty User Management System. Can you pwn it and access the flag vault?

A 32-bit x86 PIE ELF ums.elf (NOT stripped) implementing a menu-driven User Management System:

1 = List users
2 = Add user
3 = Delete user
4 = Get flag
5 = Exit

The flag is printed by obfuscated_print_the_flag after get_flag passes its checks. Goal: make the program print the flag.

Analysis

Data layout

USERS is an array of 120-byte (0x78) records: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

OffsetFieldType
+0x00idint
+0x04passportint64
+0x0cname[52]char
+0x40ageint
+0x50password[40]char

Pre-loaded records:

  • id 1 — "Gianni Reversace", password qwerty1212
  • id 2 — "Tanya Pwnina", age 8

The next-id counter starts at 3, so any user added through the menu gets id >= 3.

The intended check is a red herring (unsatisfiable)

get_flag (sub @ 0xca3) prompts for an id and a password, finds the record with that id, and requires all of: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

  • id == 2
  • password matches
  • age == 25
  • id == age/10 (⇒ id must be 2)
  • name == "Flag_Holder"
  • passport with (p/100000) == 2*(p%100000)

The only id == 2 record is Tanya (wrong fields, unknown password). Added users always get id >= 3, so a forged id == 2 record cannot be created through the menu. The "intended RE puzzle" (forge id==2 via add/delete logic) is deliberately impossible.

Eliminated dead-ends (documented for future readers)

  • delete_user's rep movsd copies 0x24 dwords = 144 = the full record stride, so it is a clean shift (no exploitable 24-byte overrun).
  • forward-only scanf("%s") overflows in add/list paths can only write a record at index == USER_COUNT, just outside the get_flag scan range.
  • next_id / USER_COUNT corruption paths are all blocked by the %s no-null-byte + tail-only-add constraints. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The real vulnerability

get_flag reads the password with scanf("%s") into a stack buffer at [ebp-0x5c] with no length bound and no stack canary (__stack_chk_fail is absent from the binary).

Distance from the buffer to the saved return address:

0x5c + 4 = 96 bytes

Overwriting the saved EIP with the address of obfuscated_print_the_flag redirects execution straight into the flag printer, bypassing all the checks. The scanf("%s") NUL terminator lands at ebp+8 (past the 4-byte return slot), so the overwrite is clean. This is a classic ret2win stack buffer overflow — and the challenge title literally says "pwn it". ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Solution

Deterministic PIE base under qemu-user

The PIE base is deterministic under the Docker i386/ubuntu:focal qemu-user setup: 0x00400000 (verified via QEMU_LOG=page across runs). So the win function address is fixed:

0x00400000 + 0x10b5 (obfuscated_print_the_flag) = 0x004010b5

Recovering buffered output

Output is pipe-buffered and the routine SIGSEGVs after printing (discarding the buffer). Fix by forcing unbuffered output with stdbuf -o0.

Exploit

stdin sequence: menu 4 (Get flag), identify as id 2 (value ignored by the overwrite), then the password = 96 bytes padding + little-endian 0x004010b5, then 5 (Exit): ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

bash
python3 -c "import sys; sys.stdout.buffer.write(b'4\n2\n'+b'A'*96+(0x4010b5).to_bytes(4,'little')+b'\n5\n')" > exploit_ret.txt

docker run --rm -i --platform linux/386 -v "$PWD":/w -w /w i386/ubuntu:focal \
  sh -c 'stdbuf -o0 ./ums.elf < exploit_ret.txt'

Output ends with:

flag: REDACTED

Submitted via POST flag; both the bare REDACTED and flag{...} forms were accepted.

Note for ARM macOS: running a 32-bit x86 ELF requires Docker i386/ubuntu:focal (slow qemu emulation — run in background, and kill hung containers if Docker reports "no space left on device"). ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Key Takeaways / Lessons

  • Mastering deterministic_pie_base, red_herring_elimination, ret2win, return_address_overwrite, stack_buffer_overflow was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-06-15 • CUSTOM • ums (RE4_ums) • Difficulty: Medium