SEH on Windows, call stack traceback is gone
Asked Answered
H

1

5

I am reading this article about the SEH on Windows. and here is the source code of myseh.cpp

I debugged myseh.cpp. I set 2 breakpoints at printf("Hello from an exception handler\n"); at line:24 and DWORD handler = (DWORD)_except_handler; at line: 36 respectively.

Then I ran it and it broke at line:36. I saw the stack trace as follows.

enter image description here As going, AccessViolationException occurred because of mov [eax], 1 Then it broke at line:24. I saw the stack trace as follows. enter image description here

The same thread but the frame of main was gone! Instead of _except_handle. And ESP jumped from 0018f6c8 to 0018ef34;it's a big gap between 0018f6c8 and 0018ef34 After Exception handled.

I know that _except_handle must be run at user mode rather than kernel mode. After _except_handle returned, the thread turned to ring0 and then windows kernel modified CONTEXT EAX to &scratch & and then returned to ring3 . Thus thread ran continually.

I am curious about the mechanism of windows dealing with exception: WHY the frame calling main was gone?

WHY the ESP jumped from 0018f6c8 to 0018ef34?(I mean a big pitch), Do those ESP address belong to same thread's stack??? Did the kernel play some tricks on ESP in ring3??? If so, WHY did it choose the address of 0018ef34 as handler callback's frame? Many thanks!

Hovercraft answered 7/1, 2017 at 9:28 Comment(4)
Yes same thread's stack. kernel copy the CONTEXT and EXCEPTION_RECORD to the user thread stack. off course bellow Esp of exception - for this already Esp serious decremented. and then KiUserExceptionDispatcher callback is called from kernel with pointer to this copied CONTEXT and EXCEPTION_RECORD records. finally your _except_handler called. but if you look for ContextRecord->Esp you can note that it will be exactly same as Esp in main(). for real handlers implementation look \VC\crt\src\i386\chandler4.c and \VC\crt\src\amd64\chandler.cExtrusive
Yeah, I know that CONTEXT must be a snapshot of hardware when exception occurs. so, ContextRecord->Esp==Esp in main().In fact, we ought to blame on debugger's default setting which loses stack trace. Btw,I know the modifiability of *ContextRecord, that's the mean by which kernel recover context of hardware as you wish before back to ring3. but WHY is the 2nd param ExceptionRecord of type* rather than const *?Hovercraft
ExceptionRecord really not const. the ExceptionFlags is modified during exception handling. we walk by stack two time ! first we looking for __try/__except blocks, until some not return EXCEPTION_EXECUTE_HANDLER then _except_handlerX called RtlUnwindEx which set EXCEPTION_UNWIND flag in ExceptionFlags and again walk stack for __try/__finally handlers. look for IS_DISPATCHING(Flag) and IS_UNWINDING(Flag) macro in winnt.h and wdm.h - also study chandler.c note if (IS_DISPATCHING(ExceptionRecord->ExceptionFlags)) switchExtrusive
also IS_TARGET_UNWIND used in x64. simply _except_handler from myseh.cpp is very primitive. real handlers which support __try/__except and __try/__finally subcalls is for complex and need modify (RtlUnwindEx) ExceptionFlags how minimum. also nested ExceptionRecord tooExtrusive
S
7

You are using the default debugger settings, not good enough to see all the details. They were chosen to help you focus on your own code and get the debug session started as quickly as possible.

The [External Code] block tells you that there are parts of the stack frame that do not belong to code that you have written. They don't, they belong to the operating system. Use Tools > Options > Debugging > General and untick the "Enable Just My Code" option.

The [Frames below might be incorrect...] warning tells you that the debugger doesn't have accurate PDBs to correctly walk the stack. Use Tools > Options > Debugging > Symbols and tick the "Microsoft Symbol Servers" option and choose a cache location. The debugger will now download the PDBs you need to debug through the operating system DLLs. Might take a while, it is only done once.

You can reason out the big ESP change, the CONTEXT structure is quite large and takes up space on the stack.

After these changes you ought to now see something resembling:

ConsoleApplication1942.exe!_except_handler(_EXCEPTION_RECORD * ExceptionRecord, void * EstablisherFrame, _CONTEXT * ContextRecord, void * DispatcherContext) Line 22    C++
ntdll.dll!ExecuteHandler2@20()  Unknown
ntdll.dll!ExecuteHandler@20()   Unknown
ntdll.dll!_KiUserExceptionDispatcher@8()    Unknown
ConsoleApplication1942.exe!main() Line 46   C++
ConsoleApplication1942.exe!invoke_main() Line 64    C++
ConsoleApplication1942.exe!__scrt_common_main_seh() Line 255    C++
ConsoleApplication1942.exe!__scrt_common_main() Line 300    C++
ConsoleApplication1942.exe!mainCRTStartup() Line 17 C++
kernel32.dll!@BaseThreadInitThunk@12()  Unknown
ntdll.dll!__RtlUserThreadStart()    Unknown
ntdll.dll!__RtlUserThreadStart@8()  Unknown

Recorded on Win10 version 1607 and VS2015 Update 2. This isn't the correct way to write SEH handlers, find a better example in this post.

Steal answered 7/1, 2017 at 13:9 Comment(3)
Thanks.Yes, I know that the example is just aimed to tell its most fundamental concepts, not practical.Hovercraft
Could you please elaborate on why this is not a correct reason to write SEH handlers? The reason why I ask is because I want to implement a little piece of code that will just put new handler at the beginning of SEH linked list, cause exception and then remove the handler from the list. Something along these lines: void foo() { _EXCEPTION_REGISTRATION_RECORD newHandlerStruct; newHandlerStruct.Handler = (PEXCEPTION_ROUTINE) theHandlerFunc; newHandlerStruct.Next = (_EXCEPTION_REGISTRATION_RECORD*)__readfsdword(0x0); __writefsdword(0x0, (DWORD64)&newHandlerStruct); __debugbreak(); }Brabant
Pietrek's code is ancient, dates from the days before a 64-bit OS was common and the Internet relentlessly attacked Windows programs. Highly exploitable, a basic way to turn data into executable code. Microsoft had to come up with countermeasures, SafeSEH is the most basic one. it is your compiler's job to generate the appropriate code.Steal

© 2022 - 2024 — McMap. All rights reserved.