A few days ago Nahuel and me took a look at a piece of shellcode that wasn’t working.

After performing a stack pivoting and successfully executing a ROP chain, the shellcode was supposed to setup an Structured Exception Handler in order to catch memory access errors when scanning the address space of the process. But for some unknown reason, the Exception Handler wasn’t being called when an exception was triggered.

The Test Case

This is a minimal test case to reproduce the issue:

1) allocate some memory for a new stack

2) make ESP point to this new memory region

3) setup a Structured Exception Handler (0x00401040 in this example)

4) generate an exception

00401000 6A 04 PUSH 4 ;PAGE_READWRITE 00401002 68 00300000 PUSH 3000 ;MEM_COMMIT|MEM_RESERVE 00401007 68 00001000 PUSH 100000 ;Size 0040100C 6A 00 PUSH 0 ;Address 0040100E E8 E5E10A00 CALL <JMP.&KERNEL32.VirtualAlloc> ;Call VirtualAlloc 00401013 09C0 OR EAX,EAX ;Was VirtualAlloc successful? 00401015 75 07 JNZ SHORT test.0040101E 00401017 6A 01 PUSH 1 00401019 E8 CADF0A00 CALL <JMP.&KERNEL32.ExitProcess> ;if VirtualAlloc failed, then exit 0040101E 8DA0 FCFF0F00 LEA ESP,DWORD PTR DS:[EAX+FFFFC] ;Make ESP point to the bottom (highest address) of the stack 00401024 68 40104000 PUSH test.00401040 ;Setup our EXCEPTION_REGISTRATION_RECORD: Address of the Exception Handler 00401029 6A FF PUSH -1 ;Setup our EXCEPTION_REGISTRATION_RECORD: last EXCEPTION_REGISTRATION_RECORD 0040102B 64:8925 00000000 MOV DWORD PTR FS:[0],ESP ;Set the current SEH chain 00401032 A3 00000000 MOV DWORD PTR DS:[0],EAX ;Generate an exception

If you run this code, you’ll notice that, after the access violation is generated on purpose at address 0x00401032, the execution won’t be transferred to the Exception Handler function at address 0x00401040. But why?

Digging into the exception dispatcher mechanism

To answer this question we need to look at the KiUserExceptionDispatcher function from the ntdll library:

.text:77F06FE8 ; __stdcall KiUserExceptionDispatcher(x, x) .text:77F06FE8 public _KiUserExceptionDispatcher@8 .text:77F06FE8 _KiUserExceptionDispatcher@8 proc near ; DATA XREF: .text:off_77EF61B8o .text:77F06FE8 .text:77F06FE8 var_C = dword ptr -0Ch .text:77F06FE8 var_8 = dword ptr -8 .text:77F06FE8 var_4 = dword ptr -4 .text:77F06FE8 arg_0 = dword ptr 4 .text:77F06FE8 .text:77F06FE8 cld .text:77F06FE9 mov ecx, [esp+arg_0] .text:77F06FED mov ebx, [esp+0] .text:77F06FF0 push ecx .text:77F06FF1 push ebx .text:77F06FF2 call _RtlDispatchException@8 ; RtlDispatchException(x,x)

KiUserExceptionDispatcher calls RtlDispatchException.

RtlDispatchException calls the Vectored Exception Handlers in the first place, and then it calls RtlpGetStackLimits in order to read the stack’s top address (highest memory address of the stack, stored at TEB + 4) and the stack’s bottom address (lowest memory address of the stack, stored at TEB + 8) from the TEB:

RtlDispatchException(x,x)+23 lea eax, [ebp+stack_top] RtlDispatchException(x,x)+26 push eax ; stack_top RtlDispatchException(x,x)+27 lea eax, [ebp+stack_bottom] RtlDispatchException(x,x)+2A push eax ; stack_bottom RtlDispatchException(x,x)+2B call _RtlpGetStackLimits@8 ; RtlpGetStackLimits(x,x)

RtlpGetStackLimits just reads those two fields from the TEB:

RtlpGetStackLimits(x,x)+6 mov eax, large fs:18h RtlpGetStackLimits(x,x)+C mov ecx, [ebp+stack_top] RtlpGetStackLimits(x,x)+F mov edx, [ebp+stack_bottom] RtlpGetStackLimits(x,x)+12 mov [ebp+teb], eax RtlpGetStackLimits(x,x)+15 mov eax, [ebp+teb] RtlpGetStackLimits(x,x)+18 mov eax, [eax+4] ;grab the stack's top address RtlpGetStackLimits(x,x)+1B mov [ecx], eax RtlpGetStackLimits(x,x)+1D mov eax, [ebp+teb] RtlpGetStackLimits(x,x)+20 mov eax, [eax+8] ;grab the stack's bottom address RtlpGetStackLimits(x,x)+23 mov [edx], eax

A few instructions later, the RtlDispatchException function checks if the address of the first (and only, in this case) EXCEPTION_REGISTRATION_RECORD (located in the new stack) is located between the stack’s bottom address and the stack’s top address as defined in the thread’s TEB:

;at this point, EBX == address of the first and only EXCEPTION_REGISTRATION_RECORD in the new stack RtlDispatchException(x,x)+80 cmp ebx, [ebp+stack_bottom] ; if addr of EXCEPTION_REGISTRATION_RECORD < stack_bottom, then exit RtlDispatchException(x,x)+83 jb loc_77F3A885 RtlDispatchException(x,x)+89 lea eax, [ebx+8] RtlDispatchException(x,x)+8C cmp eax, [ebp+stack_top] ; if addr of EXCEPTION_REGISTRATION_RECORD + 8 > stack_top, then exit RtlDispatchException(x,x)+8F ja loc_77F3A885

So, if the address of the first EXCEPTION_REGISTRATION_RECORD is not within the bounds of the thread’s stack as defined in the thread’s TEB, then RtlDispatchException will exit without calling RtlpExecuteHandlerForException, which ultimately should call our Exception Handler.

The End

These notes apply, at least, to the 32-bit versions of Windows Seven SP1 and Windows XP SP3. I haven’t checked other OS versions.

By the way, note that a similar check is performed by EMET 4.0 to detect stack pivoting, as mentioned in this Defcon 21 presentation.