Log in

View Full Version : Non-continuable exception trick


OpenRCE_omega_red
March 15th, 2008, 21:55
I haven't seen it before in public but it's possible I'm not the first one who researched this subject. I implemented similar code about year ago in my "ever unfinished" crackme, but since I doubt I'll finish the crackme, here it goes.

The idea revolves about non-continuable exceptions, that is exceptions with EXCEPTION_NONCONTINUABLE flag set in exception record. Normally, if your SEH procedure gets such an exception, you're basically screwed: you can't return 'continue execution' status, and your process is going to be mercilessly killed. If you try to continue, you will get STATUS_NONCONTINUABLE_EXCEPTION thrown by Windows exception dispatcher - there is no way out. Or is there?

What if we patch or hook windows exception dispatcher (in our process only) and just clear the noncontinuable bit if it's present before dispatching the exception down to SEH? It turns out that it works as expected - we can now escape and continue even after originally non-continuable exception. Furthermore, debuggers seem to not really like it. Olly simply refuses to continue even if we clear the noncontinuable flag (but olly can't even properly handle hardware BPs set in the code so who cares . Windbg fares a bit better, but still falls in an infinite loop (maybe more experienced users could overcome that). IDA seems to not handle the "rethrow" of division by zero exception at the end properly (but I hardly use IDA's debugger, so others may have more luck). Also, it doesn't properly run on WINE I heard, but more tests would be nice.

Anyway, it's quite fun code, maybe it will be useful to someone. Below is the FASM source, and here is the source+exe:
http://omeg.pl/code/noncontinuable_exc.zip

Code:
;------------------------------------------------
; Invalid Disposition exception trick
; or making noncontinuable exception continuable
; Copyleft (c) Omega Red 2007, 2008
; fasm source
;------------------------------------------------
; 32-bit executable
format PE GUI
entry start

include '%fasminc%\win32a.inc'
include '%fasminc%\macro\proc32.inc'
;------------------------------------------------
struct EXCEPTION_POINTERS
ExceptionRecord dd ? ; ptr
ContextRecord dd ? ; ptr
ends

struct EXCEPTION_RECORD
ExceptionCode dd ?
ExceptionFlags dd ?
NestedExceptionRecord dd ?
ExceptionAddress dd ?
NumberParameters dd ?
ExceptionInformation dd 15 dup (?)
ends

SIZE_OF_80387_REGISTERS = 80
MAXIMUM_SUPPORTED_EXTENSION = 512

struct FLOATING_SAVE_AREA
ControlWord dd ?
StatusWord dd ?
TagWord dd ?
ErrorOffset dd ?
ErrorSelector dd ?
DataOffset dd ?
DataSelector dd ?
RegisterArea db SIZE_OF_80387_REGISTERS dup (?)
Cr0NpxState dd ?
ends

struct CONTEXT
ContextFlags dd ?
Dr0 dd ?
Dr1 dd ?
Dr2 dd ?
Dr3 dd ?
Dr6 dd ?
Dr7 dd ?
FloatSave FLOATING_SAVE_AREA
SegGs dd ?
SegFs dd ?
SegEs dd ?
SegDs dd ?
Edi dd ?
Esi dd ?
Ebx dd ?
Edx dd ?
Ecx dd ?
Eax dd ?
Ebp dd ?
Eip dd ?
SegCs dd ?
EFlags dd ?
Esp dd ?
SegSs dd ?
ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup (?)
ends
;------------------------------------------------
section 'code' code readable executable

start:
; int3
; install UEF
invoke SetUnhandledExceptionFilter, UnhandledExceptionFilter
; install SEH
push dword seh_handler
push dword [fs:0]
mov dword [fs:0], esp

invoke GetModuleHandle, _ntdll
invoke GetProcAddress, eax, _rre
; cause #UD exception which will set up hardware BPX on RtlRaiseException
ud2
nop

; cause divide error -> invalid disposition exception (normally noncontinuable )
xor eax,eax
div eax

_end:
; delete SEH
pop dword [fs:0]
add esp, 4

invoke msgbox, 0, t9, t9, 0
invoke exit, 0
;------------------------------------------------
proc seh_handler ExceptionRecord, EstablisherFrame, ContextRecord, DispatcherContext
push ebx esi edi ; save win32 callback registers

mov ebx, [ExceptionRecord]
mov esi, [ContextRecord]
mov eax, [ebx+EXCEPTION_RECORD.ExceptionCode]

cmp eax, STATUS_ILLEGAL_INSTRUCTION ; c000001d
jne seh_ss

invoke msgbox, 0, t2, t1, 0
; #UD: let's install BPX on RtlRaiseException
push [esi+CONTEXT.Eax] ; address here
pop [esi+CONTEXT.Dr0]
mov [esi+CONTEXT.Dr7], 0x00000101 ; enable local BPX on DR0
add [esi+CONTEXT.Eip], 2 ; skip UD2
jmp seh_end

seh_ss:
cmp eax, STATUS_SINGLE_STEP ; 80000004 - BPM or single step
jne seh_dz
invoke msgbox, 0, t3, t1, 0

; RtlRaiseException hook bpx ;]
; check if exception is noncontinuable - if it is, make it continuable
mov eax, [esi+CONTEXT.Esp] ; get esp on RtlRaiseException entry
mov eax, [eax+4] ; get parameter - ptr to EXCEPTION_RECORD
test [eax+EXCEPTION_RECORD.ExceptionFlags], EXCEPTION_NONCONTINUABLE
jz seh_ss2 ; continuable exception, ignore
; clear noncontinuable flag
and [eax+EXCEPTION_RECORD.ExceptionFlags], not EXCEPTION_NONCONTINUABLE
seh_ss2:
mov [esi+CONTEXT.Dr7], 0 ; disable bpx so we don't loop forever
jmp seh_end

seh_dz:
cmp eax, STATUS_INTEGER_DIVIDE_BY_ZERO
jne seh_id
invoke msgbox, 0, t4, t1, 0
; divide error, let's trigger noncontinuable condition
mov eax, 0xdeadc0de ; invalid disposition
jmp seh_end2

seh_id: ; invalid disposition handler (which should be made continuable by our hook
cmp eax, STATUS_INVALID_DISPOSITION ; c0000026
jz @f
; unknown exception, commit suicide ;]
invoke msgbox, 0, t8, t1, 0
mov eax, 1 ; continue search (= effectively kill process)
jmp seh_end2

@@: ; fix the condition that leaded to noncontinuable exception - divide error in this case
invoke msgbox, 0, t5, t1, 0
mov eax, [ebx+EXCEPTION_RECORD.NestedExceptionRecord]
mov eax, [eax-0x0c] ; context pointer - ONLY IF EXCEPTION PARAMETERS ARE DIRECTLY BEFORE SEH FRAME!
mov [eax+CONTEXT.Eax], 0xdeadbeef ; correct div
; this exception is now continuable so we can go ahead

seh_end:
xor eax, eax ; status: continue execution
seh_end2:
pop edi esi ebx
ret ; proc macro takes care of stack balance
endp
;------------------------------------------------
UnhandledExceptionFilter:
push esi edi ebx

ExceptionPointers equ esp+0x10 ; EXCEPTION_POINTERS

mov eax, [ExceptionPointers]
mov ebx, [eax+EXCEPTION_POINTERS.ExceptionRecord] ; exception info
mov esi, [eax+EXCEPTION_POINTERS.ContextRecord] ; CPU state when exception occured
mov eax, [ebx+EXCEPTION_RECORD.ExceptionCode] ; code

cmp eax, STATUS_INTEGER_DIVIDE_BY_ZERO
jne @f
; nothing for now - seh fixes context 2 frames back *o*
; well, we NEED to handle it here - I still don't know exactly why ZwRaiseException ignores SEH after nested exception and throws us here
; seems like some unwind is going on, since DRs are reverted to the state at previous (first) DIV error
; (that's actually good, because we don't need to reenable bpx manually)
invoke msgbox, 0, t7, t6, 0

jmp uef_end
;------------
@@:
; unknown exception
invoke msgbox, 0, t8, t6, 0
;------------
uef_end:
mov eax, -1 ; continue

pop ebx edi esi
ret 4
;------------------------------------------------
section 'data' data readable writable

_ntdll db 'ntdll.dll',0
_rre db 'RtlRaiseException',0
t1 db 'SEH',0
t2 db 'STATUS_ILLEGAL_INSTRUCTION',10,'Installing RtlRaiseException hook',0
t3 db 'STATUS_SINGLE_STEP',10,'RtlRaiseException called',0
t4 db 'STATUS_INTEGER_DIVIDE_BY_ZERO',10,'Returning invalid disposition',0
t5 db 'STATUS_INVALID_DISPOSITION',10,'Fixing div and continuing',0
t6 db 'UEF',0
t7 db 'STATUS_INTEGER_DIVIDE_BY_ZERO',0
t8 db 'Unknown exception',0
t9 db 'Exiting properly, all is ok',0
;------------------------------------------------
data import

library user, 'user32.dll',\
kernel, 'kernel32.dll'

import user,\
msgbox, 'MessageBoxA'

import kernel,\
GetProcAddress, 'GetProcAddress',\
GetModuleHandle, 'GetModuleHandleA',\
SetUnhandledExceptionFilter, 'SetUnhandledExceptionFilter',\
exit, 'ExitProcess'
end data
;------------------------------------------------


https://www.openrce.org/blog/view/1085/Non-continuable_exception_trick

NeOXOeN
March 16th, 2008, 00:14
damn good work