-=( ---------------------------------------------------------------------- )=- -=( Natural Selection Issue #1 ---------------- Correct Exception Handling )=- -=( ---------------------------------------------------------------------- )=- -=( 0 : Contents --------------------------------------------------------- )=- 0 : Contents 1 : Exception Overview 2 : Parsing SEH Chains 3 : Inside The Handler 4 : Assembling The SEH 5 : Conclusion -=( 1 : Exception Overview ----------------------------------------------- )=- There have been two official articles about SEH, one by Matt Pietrek and one by Jeremey Gordon. Since then, others have interpreted both these works and created smaller articles in an effort to simplify them. SEH macros are also given in virus magazines so that you don't even need to know how SEH works. Here is a wakeup call. Most of those articles, and all of those macros, and every virus that uses SEH at the moment, is doing it completely WRONG. I am proud to say, that finally there is this article to set the record straight. SEH is a very simple system. Let's start at the beginning. ; EAX = -1 MOV EAX, [EAX] This memory access through a bad pointer which will generate a CPU exception which stores the current registers into a CONTEXT structure and calls Win32. The CONTEXT.ControlWord holds masks that tell which sets of registers within the CONTEXT are valid to access, as sometimes not all registers are saved. CONTEXT_i386 EQU 00010000h CONTEXT_i486 EQU 00010000h CONTEXT_CONTROL EQU CONTEXT_i386 OR 00000001h CONTEXT_INTEGER EQU CONTEXT_i386 OR 00000002h CONTEXT_SEGMENTS EQU CONTEXT_i386 OR 00000004h CONTEXT_FLOATING_POINT EQU CONTEXT_i386 OR 00000008h CONTEXT_DEBUG_REGISTERS EQU CONTEXT_i386 OR 00000010h CONTEXT_FULL EQU CONTEXT_CONTROL \ OR CONTEXT_INTEGER \ OR CONTEXT_SEGMENTS MAXIMUM_SUPPORTED_EXTENSION EQU 512 FLOATING_SAVE_AREA STRUCT ControlWord DW ? StatusWord DW ? TagWord DW ? ErrorOffset DW ? ErrorSelector DW ? DataOffset DW ? DataSelector DW ? registerArea DB SIZE_OF_80387_REGISTERS DUP (?) Cr0NpxState DW ? FLOATING_SAVE_AREA ENDS CONTEXT STRUCT ContextFlags DW ? iDr0 DW ? iDr1 DW ? iDr2 DW ? iDr3 DW ? iDr6 DW ? iDr7 DW ? FloatSave FLOATING_SAVE_AREA <> regGs DW ? regFs DW ? regEs DW ? regDs DW ? regEdi DW ? regEsi DW ? regEbx DW ? regEdx DW ? regEcx DW ? regEax DW ? regEbp DW ? regEip DW ? regCs DW ? regFlag DW ? regEsp DW ? regSs DW ? Extendedregisters DB MAXIMUM_SUPPORTED_EXTENSION DUP (?) CONTEXT ENDS -=( 2 : Parsing SEH Chains ----------------------------------------------- )=- Each executing Thread has a Thread Information Block starting at FS:[0] that stores the start of the SEH Chain in NT_TIB.ExceptionList. The SEH Chain is a series of EXCEPTION_REGISTRATION structures, each storing a pointer to the previous EXCEPTION_REGISTRATION structure, and a pointer to the Handler code to get executed when an Exception occurs. The last EXCEPTION_REGISTRATION in the chain will have a PreviousHandler set to -1, and an ExceptionHandler routine to show the Illegal Operation window and then closes the process. This is set up automatically by Win32 when it creates each process. NT_TIB STRUCT ExceptionList DD ? StackBase DD ? StackLimit DD ? SubSystemTib DD ? UNION FiberData DD ? Version DD ? ENDS ArbitraryUserPointer DD ? Self DD ? NT_TIB ENDS EXCEPTION_REGISTRATION STRUCT PreviousHandler DD ? ExceptionHandler DD ? EXCEPTION_REGISTRATION ENDS Win32 loops through each EXCEPTION_REGISTRATION calling the ExceptionHandler with a set of arguments. These give the ExceptionHandler the information it needs to attempt to fix the Exception. Note that the ExceptionHandler has a "C", so that the compiler will exit the PROC with a simple RET, instead of a RET 10H [which is an "STDCALL"]. Win32 likes to POP its own arguments off. ExceptionHandler PROC C, pExceptionRecord :DWORD, pEstablisherFrame :DWORD, pContextRecord :DWORD, pDispatcherContext:DWORD pExceptionRecord is a pointer to EXCEPTION_RECORD. pEstablisherFrame is a pointer to the EXCEPTION_REGISTRATION structure that Win32 is working with at the moment. pContextRecord is a pointer to a CONTEXT as saved earlier, and pDispatcherContext contains a value that isn't used on Intel CPUs. EXCEPTION_RECORD STRUCT ExceptionCode DW ? ExceptionFlags DW ? pExceptionRecord DW ? ExceptionAddress DW ? NumberParameters DW ? ExceptionInformation DW EXCEPTION_MAXIMUM_PARAMETERS DUP (?) EXCEPTION_RECORD ENDS ExceptionCode shows what sort of Exception occured. ExceptionFlags is what what your Handler is expected to do. pExceptionRecord is a pointer to yet another EXCEPTION_RECORD, and is used if this Handler causes an exception. ExceptionAddress is where the Exception occured. NumberParameters has the number of entries in ExceptionInformation. ExceptionInformation depends on the type of Exception. : ExceptionCode Structure = TTCRXXXXXXXXXXXXXXXXXXXXXXXXXXX TT = 00 = Success 01 = Information 10 = Warning 11 = Error C = 0 = Microsoft 1 = Application R = 0 = Reserved XXXXXXXXXXXXXXXXXXXXXXXXXXX = Exception Number : For a list of ExceptionCodes see NTSTATUS.H in the Microsoft Platform SDK : ExceptionFlags EXCEPTION_CONTINUABLE = 0 = Try to fix the problem EXCEPTION_NONCONTINUABLE = 1 = Clean up and exit only EXCEPTION_UNWINDING = 2 = Clean up and exit only : ExceptionInformation (When ExceptionCode is STATUS_ACCESS_VIOLATION) [0] = 0 = Read = 1 = Write [4] = Memory address being Read/Written If ExceptionFlags is EXCEPTION_CONTINUABLE, our Handler needs to return an ExceptionContinueExecution or ExceptionContinueSearch. If NONCONTINUABLE or UNWINDING, we may only return ExceptionContinueSearch. When Win32 receives an ExceptionContinueExecution, it will do some internal house keeping, then restore the registers from the CONTEXT. These include EIP, so execution continues from the original Exception Address, or from a different place if your Handler has changed this value. When Win32 receives an ExceptionContinueSearch, it will go to the Previous Handler from your EXCEPTION_REGISTRATION structure, and continue until it can continue with ExceptionContinueExecution, or it reaches its ExitProcess Handler, or it reaches an -1 PreviousHandler, marking the end of the Chain, at which time it generates another Exception and closes the Process. -=( 3 : Inside the Handler ----------------------------------------------- )=- Most viruses will point the Handler field directly to where execution should continue, however this causes two problems. Firstly, the SEH routines themselves are left in an unknown state. Lots of state switches and memory allocation has occured to get SEH stated, and the routines to undo all of this are never called. This alone is wrong. Secondly, if the Exception occured within a Win32 API, things get worse, as even more handles other critical internal variables are left hanging. What needs to happen in this case, is an Unwind, where each SEH above yours that didn't fix the problem gets to clean up before normal processing continues. So let's look at what a proper Handler needs to do to not cause problems. I shouldn't need to remind you, but don't modify any registers other than EAX, without restoring them on exit from your Handler. IF (EXCEPTION_RECORD.ExceptionFlags == EXCEPTION_CONTINUABLE) { DO_UNWINDS; FIXUP_CODE; RETURN ExceptionContinueExecution; } ELSE { DO_CLEANUP; RETURN ExceptionContinueSearch; } Making an Unwind occur involves a call to an RtlUnwind API in the Kernel32 DLL. It doesn't save EBX, ESI, or EDI registers, so save them beforehand. Here's the API. FARPROC RtlUnwind { PEXCEPTION_REGISTRATION pEndFrame, LPVOID ReturnEip, PEXCEPTION_RECORD pRecord, DWORD ReturnEax } pEndFrame is a pointer to the EXCEPTION_REGISTRATION where RtlUnwind stops processing. ReturnEip is where you would like RtlUnwind to return to once it has finished processing, although it isn't used in Win32 at the moment. pRecord is a pointer to the EXCEPTION_RECORD structure you'd like to pass to each SEH, which is modified so that EXCEPTION_UNWIND Flag is set. If you use a 0 here, an empty structure is created on the stack. ReturnEax is the value you'd like RtlUnwind to give in EAX once it has finished processing. RtlUnwind starts at FS:[0], and loops through until pEndFrame, calling each SEH with EXCEPTION_RECORD, telling it to Unwind. While it doesn't call the pEndFrame, it does change FS:[0] to point to the PreviousHandler from that structure once it has finished. Our FIXUP_CODE will need to change CONTEXT.regEip to point to where we want code to continue executing in our virus, usually shared code at the end of the crashed procedure, where handles are closed and values returned to the caller. We may also need to replace ESP and EBP with valid values, because they could be pointing anywhere, especially if we crashed in the middle of an API call. DO_CLEANUP is where handles and threads should be closed, as it's called by the system only when multiple exceptions within exceptions have occured, as indicated by the ExceptionFlags. -=( 4 : Assembling the SEH ----------------------------------------------- )=- Let's look at a situation that requires SEH. PROC { SEH_INSTALL; // Set up a local EXCEPTION_REGISTRATION structure CREATE_HANDLES; // Open files, allocate memory, etc SENSITIVE_CODE; // Code that may cause Exceptions, etc SEH_REMOVAL; // Remove EXCEPTION_REGISTRATION structure SAFE: // If there was an Exception, continue here, as an // RtlUnwind has removed the Handler already CLOSE_HANDLES; // Close files, deallocate memory, etc } EXCEPTION_REGISTRATION structures are typically created on the stack, like: PUSH OFFSET HANDLER // ExceptionHandler PUSH FS:[0] // PreviousHandler MOV FS:[0], ESP // Point SEH to us When our Handler is called, it is given the pEstablisherFrame argument which points to our EXCEPTION_REGISTRATION structure on the stack. We can expand EXCEPTION_REGISTRATION to store other things we want to pass to our Handler, such as what to set CONTEXT.regEip to, and important registers we might want to save, such as EBP [for stack frames or delta offsets]. These can now be accessed within the Handler as offsets to pEstablisherFrame. // SEH_INSTALL; PUSH EBP // A register we want to save PUSH OFFSET SAFE // Safe address to resume execution, regEip PUSH OFFSET HANDLER // ExceptionHandler PUSH FS:[0] // PreviousHandler MOV FS:[0], ESP // Point SEH to us CREATE_HANDLES; SENSITIVE_CODE; // SEH_REMOVAL; POP FS:[0] // PreviousHandler ADD ESP, 12 // ExceptionHandler + Safe + register // SAFE: CLOSE_HANDLES; Now, we only need to create our Handler routine. This one piece of code is used for all SEH we create, even nested SEH within each other. It sets the regEip and regEbp as stored in our EXCEPTION_REGISTRATION structure. It is able to remove itself from the SEH Chain by using RtlUnwind and sets regEsp to pEstablisherFrame + 16, as that's the top of the stack minus SEH data. HANDLER PROC C \ USES EBX ECX EDX ESI EDI, pExceptionRecord :DWORD, pEstablisherFrame :DWORD, pContextRecord :DWORD, pDispatcherContext:DWORD MOV EAX, [pExceptionRecord] CMP [EAX][EXCEPTION_RECORD.ExceptionFlags], EXCEPTION_CONTINUABLE JNE HANDLER_ABORT LEA EAX, @F INVOKE RtlUnwind, [pEstablisherFrame], EAX, [pExceptionRecord], \ [pEstablisherFrame] @@: MOV ESI, [pContextRecord] PUSH [EAX][8] POP [ESI][CONTEXT.regEip] PUSH [EAX][12] POP [ESI][CONTEXT.regEbp] ADD EAX, 16 MOV [ESI][CONTEXT.regEsp], EAX MOV EAX, ExceptionContinueExecution RET HANDLER_ABORT: MOV EAX, ExceptionContinueSearch RET HANDLER ENDP -=( 5 : Conclusion ------------------------------------------------------- )=- It's hard enough to debug every possible error in your virus, without having to check every memory reference through a pointer, so that it doesn't create an Exception. Thanks to SEH, this can all now be guaranteed under all Win32 variants, with just one Handler and an SEH_INSTALL/SEH_REMOVAL macro around important parts of your code. Give standard SEH code the finger. Use Correct Exception Handling instead. -=( ---------------------------------------------------------------------- )=- -=( Natural Selection Issue #1 --------------- (c) 2002 Feathered Serpents )=- -=( ---------------------------------------------------------------------- )=-