; Catching wow64 exceptions in 32bit SEH - world first ;] ; Copyleft (c) Omega Red 2007 ; fasm source ; 32-bit executable format PE GUI entry start include '%fasminc%\win32a.inc' include '%fasminc%\macro\proc32.inc' ;------------------------------------------------ include 'context.inc' include 'context64.inc' ;------------------------------------------------ use32 section 'code' code readable executable ; values from 64bit ntdll - hardcoded for now ;P KiUED64 = 0x77ef31b0 ; ntdll!KiUserExceptionDispatcher NtSCT64 = 0x77ef1970 ; ntdll!NtSetContextThread test64 = 0x78b83c8d ; wow64cpu!GetCurrentProcessorNumber start: ; install SEH push dword seh_handler push dword [fs:0] mov dword [fs:0], esp ; unprotect 64bit ntdll ;] invoke vprotect, KiUED64, 0x10, PAGE_EXECUTE_READWRITE, dupa ; detour it to our proc ;] mov byte [KiUED64], 0xe9 ; jmp mov dword [KiUED64+1], (hookproc-KiUED64-5) ; relative offset ; messagebox push 0 push caption3 push caption3 push 0 ; ^ msgbox parameters push continue ; return address ; cause exception which will set up hardware memory breakpoint on return address ud2 nop ; EXPEXTED RESULT: break on return from msgbox (which will read return address) jmp [msgbox] ; call messagebox continue: nop ; install BPX on 64it RtlRandom cli ; call 32bit function, which will thunk into 64bit version and cause breakpoint in 64bit code call [test32] ; disable BP ud2 ; delete SEH pop dword [fs:0] add esp, 4 invoke msgbox, 0, caption4, caption4, 0 invoke exit, 0 ;------------------------------------------------------------------------- ; 31 29 27 25 23 21 19 17 15 13 12 9 8 7 6 5 4 3 2 1 0 ; +---------------+---------------+---------------+---------------+ ; |LEN|R/W|LEN|R/W|LEN|R/W|LEN|R/W| |G| |G|L|G|L|G|L|G|L|G|L| ; | | | | | | | | |0 0|D|0 0 0| | | | | | | | | | | DR7 ; | 3 | 3 | 2 | 2 | 1 | 1 | 0 | 0 | | | |E|E|3|3|2|2|1|1|0|0| ; +-------------------------------+---+-+-----------------+-+-+-+-+ ;------------------------------------------------------------------------- proc seh_handler ExceptionRecord, EstablisherFrame, ContextRecord, DispatcherContext push ebx esi edi ; save win32 callback registers mov edx, [ExceptionRecord] mov esi, [ContextRecord] mov eax, [edx+EXCEPTION_RECORD.ExceptionCode] cmp eax, STATUS_ILLEGAL_INSTRUCTION jne @f ; install BP add [esi+CONTEXT.Eip], 2 inc dword [count] cmp dword [count], 2 jne seh_install ; delete BP mov [esi+CONTEXT.Dr0], 0 and [esi+CONTEXT.Dr7], not 0x000f0001 invoke msgbox, 0, caption7, caption7, 0 jmp seh_end seh_install: mov eax, [esi+CONTEXT.Esp] ; [esp] = return address for MessageBox call mov [esi+CONTEXT.Dr0], eax ; address of breakpoint or [esi+CONTEXT.Dr7], 0x000f0001 ; enable local BPMD (4B) on DR0 invoke msgbox, 0, caption1, caption1, 0 jmp seh_end @@: cmp eax, STATUS_PRIVILEGED_INSTRUCTION jne @f ; install BPX add [esi+CONTEXT.Eip], 1 mov [esi+CONTEXT.Dr1], test64 ; address of breakpoint or [esi+CONTEXT.Dr7], 0x00000004 ; enable local BPX on DR1 invoke msgbox, 0, caption6, caption6, 0 jmp seh_end @@: ; breakpoint - display DRs cinvoke printf, buf, format1, eax, [edx+EXCEPTION_RECORD.ExceptionAddress], [esi+CONTEXT.Dr0], [esi+CONTEXT.Dr6], [esi+CONTEXT.Dr7] invoke msgbox, 0, buf, caption5, 0 test [esi+CONTEXT.Dr6], 2 ; bp1? je @f and [esi+CONTEXT.Dr7], not 4 ; disable bp1 if hit @@: and [esi+CONTEXT.Dr6], not 0x0000000f ; clear status bits ; or [esi+CONTEXT.EFlags], 1 shl 16 ; set resume flag - ONLY WORKS ON 64BIT seh_end: xor eax, eax ; continue execution pop edi esi ebx ret ; proc macro takes care of stack balance endp ;------------------------------------------------ ; ntdll!KiUserExceptionDispatcher hook ; 64-bit code here! ;] use64 hookproc: cmp dword [rsp+0x4d0], 0x4000001e ; wow64 single step? jne hook_end ; We've got debug exception caused by 64bit code. ; Now we need to disable HW breakpoints - if we don't do that, ; further wow64 exception processing (which will touch our stack) ; will cause another nested debug break which would be fatal. ; Also, we CAN'T use SEH to clear DRs for obvious reasons ; (more nested exceptions, and wow64 exc. won't be passed to 32bit SEH). ; save regs mov [_rcx], rcx mov [_rdx], rdx mov [_r8], r8 mov [_r9], r9 ; use NtSetContextThread to disable Dr7 mov qword [ctx64+CONTEXT64.Dr7], 0 mov rcx, -2 ; current thread mov rdx, ctx64 call NtSCT64 ; after this debug registers should be clear, we can continue mov rcx, [_rcx] mov rdx, [_rdx] mov r8, [_r8] mov r9, [_r9] hook_end: mov rax, qword [0x0000000077fa6720] ; first line from original KiUserExceptionDispatcher jmp KiUED64 +7 ;ntdll!KiUserExceptionDispatch: ;00000000`77ef31b0 488b0569350b00 mov rax,qword ptr [ntdll!Wow64PrepareForException (00000000`77fa6720)] ;00000000`77ef31b7 4885c0 test rax,rax ;00000000`77ef31ba 740f je ntdll!KiUserExceptionDispatcher+0x1b (00000000`77ef31cb) ;00000000`77ef31bc 488bcc mov rcx,rsp ;00000000`77ef31bf 4881c1d0040000 add rcx,4D0h ; EXCEPTION_RECORD ;00000000`77ef31c6 488bd4 mov rdx,rsp ; CONTEXT ;00000000`77ef31c9 ffd0 call rax {wow64!Wow64PrepareForException (00000000`6b0299e0)} ;------------------------------------------------ use32 section 'data' data readable writable count dd 0 exc_rec EXCEPTION_RECORD 0xc000001d,0,0,0,0 format1 db 'Code: %-8x, Address: %08x, DR0: %08x, DR1: %08x, DR6: %08x, DR7: %08x',0 caption1 db 'BPM0 installed',0 caption3 db 'Test',0 caption4 db 'END',0 caption5 db 'Exception',0 caption6 db 'BPX1 installed',0 caption7 db 'BPM0 deleted',0 buf db 100 dup (0) dupa dd 0 ; 64bit hook data align 64 ctx64 CONTEXT64 0,0,0,0,0,0,CONTEXT_DEBUG_REGISTERS64 _rcx dq 0 _rdx dq 0 _r8 dq 0 _r9 dq 0 ;------------------------------------------------ data import library\ kernel, 'kernel32.dll',\ ntdll, 'ntdll.dll',\ user, 'user32.dll' import kernel,\ exit, 'ExitProcess',\ vprotect,'VirtualProtect' import ntdll,\ test32,'NtGetCurrentProcessorNumber' import user,\ msgbox, 'MessageBoxA',\ printf, 'wsprintfA' end data ;------------------------------------------------