A Brief Dive Into x86 Windows Structured Exception Handling Implementation
This post has been ported from my old self-hosted blog. The original post was published on 22/12/2021.
When it comes to handling exceptions in programming, we are all familiar with try/catch
and its other variant syntax sugars. For example, below is a divide by zero error raised as an exception in Python.
PS C:\Users\tahai\code\blog> python
Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> d = 9 // 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>>
The exception name is called ZeroDivisionError
. In this case we didn't catch the error but, the system did it for us.
We can improve a bit our code like the following:
>>> try:
... d = 9 // 0
... except Exception as e:
... print(f"Exception raised: {e}")
...
Exception raised: integer division or modulo by zero
>>>
Nice! We caught an exception; however, this is a generic exception as the name suggests: Exception
. The exception was not specific. Since we are dealing with divisions, we can specify that the program should raise an exception if a division by zero occurs, but the program should continue running.
To resolve this, we can use ZeroDivisonError
exception instead of Exception broad class right?
>>> try:
... d = 9 // 0
... except ZeroDivisionError as e:
... print(f"Zero Deivions Error spotted: {e}")
...
Zero Divison Error spotted: integer division or modulo by zero
>>>
This works. We isolated the case of zero division. However, what happens if another type of error is raised within the same try/except block? Let's assume that an error occurs just before the zero division error.
>>> try:
... pint("foobar")
... d = 9 // 0
... except ZeroDivisionError as e:
... print(f"Exception raised: {e}")
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: name 'pint' is not defined
>>>
Well, it goes unhandled by our program, and the system still catches it, but the program crashes. In this case, a NameError was raised because we misspelled the print() function. To illustrate the idea that the system can encounter nested exceptions and that exceptions are stored in a list, we are going to raise an exception that the system cannot handle.
>>> try:
... d = 9 // 0
... except Exception(e):
... print("Exception raised!")
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
NameError: name 'e' is not defined
Notice this message down below:
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
NameError: name 'e' is not defined
Great! This demonstrates that the system can handle nested exceptions. Now, let's delve into some definitions. So far, we have only encountered software exceptions, as the name suggests. But there is also a counterpart known as hardware exceptions, which are initiated by the CPU. For instance, this can happen when a program attempts to read from an invalid memory address.
Now that we are familiar with the types of exceptions, it's important to note that they operate at the thread level. This means that we can find information about these exceptions in the Thread Environment Block (TEB) structure of Windows.
To further explore this, let's open Notepad (x86 version) within Windbg and set a breakpoint at the entry point. This will allow us to examine the structure.
0:000> bp $exentry
0:000> g
ModLoad: 77090000 770b5000 C:\WINDOWS\SysWOW64\IMM32.DLL
Breakpoint 0 hit
eax=0012830c ebx=00204000 ecx=00941860 edx=00005000 esi=00941860 edi=00941860
eip=00941860 esp=0047fd64 ebp=0047fd70 iopl=0 nv up ei pl zr na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000247
notepad!wWinMainCRTStartup:
00941860 e85d080000 call notepad!__security_init_cookie (009420c2)
Using dt
we can examine TEB
structure in Windbg.
0:000> dt _TEB
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
+0x040 Win32ThreadInfo : Ptr32 Void
+0x044 User32Reserved : [26] Uint4B
+0x0ac UserReserved : [5] Uint4B
+0x0c0 WOW32Reserved : Ptr32 Void
+0x0c4 CurrentLocale : Uint4B
+0x0c8 FpSoftwareStatusRegister : Uint4B
+0x0cc ReservedForDebuggerInstrumentation : [16] Ptr32 Void
+0x10c SystemReserved1 : [26] Ptr32 Void
+0x174 PlaceholderCompatibilityMode : Char
+0x175 PlaceholderHydrationAlwaysExplicit : UChar
+0x176 PlaceholderReserved : [10] Char
+0x180 ProxiedProcessId : Uint4B
+0x184 _ActivationStack : _ACTIVATION_CONTEXT_STACK
+0x19c WorkingOnBehalfTicket : [8] UChar
+0x1a4 ExceptionCode : Int4B
+0x1a8 ActivationContextStackPointer : Ptr32 _ACTIVATION_CONTEXT_STACK
+0x1ac InstrumentationCallbackSp : Uint4B
...
The field of particular interest to us is the first one. At offset 0x00, we have the NtTib, which is a structure of type NTTIB. We can use the 'dt' command once again to examine the contents of this structure.
0:000> dt _NT_TIB
ntdll!_NT_TIB
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : Ptr32 Void
+0x008 StackLimit : Ptr32 Void
+0x00c SubSystemTib : Ptr32 Void
+0x010 FiberData : Ptr32 Void
+0x010 Version : Uint4B
+0x014 ArbitraryUserPointer : Ptr32 Void
+0x018 Self : Ptr32 _NT_TIB
ExceptionList is the first element of the structure, that's the one we are looking for, the type of the structure is _EXCEPTION_REGISTRATION_RECORD
.
0:000> dt _EXCEPTION_REGISTRATION_RECORD
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : Ptr32 _EXCEPTION_DISPOSITION
This is a singly linked list of _EXCEPTION_REGISTRATION_RECORD structures, where the first member (Next) points to the next _EXCEPTION_REGISTRATION_RECORD struct. The end of the list is indicated by the value -1 in hexadecimal.
The second member (Handler) is a pointer to the exception callback function named _exception_handler, which returns an _EXCEPTION_DISPOSITION enum.
0:000> dt _EXCEPTION_DISPOSITION
ntdll!_EXCEPTION_DISPOSITION
ExceptionContinueExecution = 0n0
ExceptionContinueSearch = 0n1
ExceptionNestedException = 0n2
ExceptionCollidedUnwind = 0n3
However, when examining the file headers and searching for the string '_excpect_handler', we found several matches. These matches indicate functions that serve as exception handlers.
Note that on the machine I am using, both MinGW and Visual C++ are installed. For our discussion, we will focus on the Visual C++ implementation.
The declaration of the _EXCEPTION_DISPOSITION enum can be found at line 18.
...
18 // Exception disposition return values
19 typedef enum _EXCEPTION_DISPOSITION
20 {
21 ExceptionContinueExecution,
22 ExceptionContinueSearch,
23 ExceptionNestedException,
24 ExceptionCollidedUnwind
25 } EXCEPTION_DISPOSITION;
...
If we scroll down to line 29, we come across the function decorator _except_handler.
...
29 // SEH handler
30 #ifdef _M_IX86
31
32 struct _EXCEPTION_RECORD;
33 struct _CONTEXT;
34
35 EXCEPTION_DISPOSITION __cdecl _except_handler(
36 _In_ struct _EXCEPTION_RECORD* _ExceptionRecord,
37 _In_ void* _EstablisherFrame,
38 _Inout_ struct _CONTEXT* _ContextRecord,
39 _Inout_ void* _DispatcherContext
40 );
41
42 #elif defined _M_X64 || defined _M_ARM || defined _M_ARM64
43 #ifndef _M_CEE_PURE
44
45 struct _EXCEPTION_RECORD;
46 struct _CONTEXT;
47 struct _DISPATCHER_CONTEXT;
48
49 _VCRTIMP EXCEPTION_DISPOSITION __C_specific_handler(
50 _In_ struct _EXCEPTION_RECORD* ExceptionRecord,
51 _In_ void* EstablisherFrame,
52 _Inout_ struct _CONTEXT* ContextRecord,
53 _Inout_ struct _DISPATCHER_CONTEXT* DispatcherContext
54 );
55
56 #endif
57 #endif
...
Given that we are currently working with x86 Assembly, our focus will be on the x86 implementation.
...
30 #ifdef _M_IX86
31
32 struct _EXCEPTION_RECORD;
33 struct _CONTEXT;
34
35 EXCEPTION_DISPOSITION __cdecl _except_handler(
36 _In_ struct _EXCEPTION_RECORD* _ExceptionRecord,
37 _In_ void* _EstablisherFrame,
38 _Inout_ struct _CONTEXT* _ContextRecord,
39 _Inout_ void* _DispatcherContext
40 );
41
42 #elif defined _M_X64 || defined _M_ARM || defined _M_ARM64
...
The function takes four parameters, and for our purposes, we are particularly interested in the _Establisher_Frame and ContextRecord.
The _Establisher_Frame parameter points to the Exception_Registration_Record structure, while the _ContextRecord parameter refers to a Context structure. This Context structure is of great interest as it contains assembly registers such as EIP and others.
0:000> dt _CONTEXT
ntdll!_CONTEXT
+0x000 ContextFlags : Uint4B
+0x004 Dr0 : Uint4B
+0x008 Dr1 : Uint4B
+0x00c Dr2 : Uint4B
+0x010 Dr3 : Uint4B
+0x014 Dr6 : Uint4B
+0x018 Dr7 : Uint4B
+0x01c FloatSave : _FLOATING_SAVE_AREA
+0x08c SegGs : Uint4B
+0x090 SegFs : Uint4B
+0x094 SegEs : Uint4B
+0x098 SegDs : Uint4B
+0x09c Edi : Uint4B
+0x0a0 Esi : Uint4B
+0x0a4 Ebx : Uint4B
+0x0a8 Edx : Uint4B
+0x0ac Ecx : Uint4B
+0x0b0 Eax : Uint4B
+0x0b4 Ebp : Uint4B
+0x0b8 Eip : Uint4B
+0x0bc SegCs : Uint4B
+0x0c0 EFlags : Uint4B
+0x0c4 Esp : Uint4B
+0x0c8 SegSs : Uint4B
+0x0cc ExtendedRegisters : [512] UChar
Now that we have gained a basic understanding of SEH implementation, let's proceed to list the exceptions of the current process using the exchain command/extension in Windbg.
0:000> !exchain
0047fdbc: ntdll!_except_handler4+0 (7714ad40)
CRT scope 0, filter: ntdll!__RtlUserThreadStart+3cdb8 (77174827)
func: ntdll!__RtlUserThreadStart+3ce51 (771748c0)
0047fdd4: ntdll!FinalExceptionHandlerPad9+0 (77158a39)
Invalid exception stack at ffffffff
Take note of the Invalid exception stack at fffffff, as it signifies the end of the ExceptionList. From the command output, we can determine that the ExceptionList starts at memory address 0x0047fdbc, with its associated Handler function located at address 0x7714ad40. Additionally, the next EXCEPTIONREGISTRATION_RECORD structure begins at 0x0047fdd4, and its corresponding Handle function callback resides at 0x77158a39.
0047fdbc: ntdll!_except_handler4+0 (7714ad40)
0047fdd4: ntdll!FinalExceptionHandlerPad9+0 (77158a39)
To confirm this, we can manually traverse the ExceptionList by following the memory addresses provided by the previous command. However, before we proceed with that, let's confirm the ExceptionList using the 'teb' extension.
0:000> !teb
TEB at 00207000
ExceptionList: 0047fdbc
StackBase: 00480000
StackLimit: 0046f000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 00207000
EnvironmentPointer: 00000000
ClientId: 00002254 . 0000385c
RpcHandle: 00000000
Tls Storage: 00654fe8
PEB Address: 00204000
LastErrorValue: 126
LastStatusValue: c0000135
Count Owned Locks: 0
HardErrorMode: 0
The address of the ExceptionList matches the one obtained from the !exchain command. Now, let's delve deeper and proceed with our investigation.
0:000> dt _nt_tib 0047fdbc
ntdll!_NT_TIB
+0x000 ExceptionList : 0x0047fdd4 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : 0x7714ad40 Void
+0x008 StackLimit : 0x89b0af72 Void
+0x00c SubSystemTib : (null)
+0x010 FiberData : 0x0047fddc Void
+0x010 Version : 0x47fddc
+0x014 ArbitraryUserPointer : 0x77137a6e Void
+0x018 Self : 0xffffffff _NT_TIB
We are aware that the ExceptionList is a singly linked list of _EXCEPTION_REGISTRATION_RECORD structures. Next, we will take the pointer of the structure and examine it using the 'dt' command.
0:000> dt _EXCEPTION_REGISTRATION_RECORD 0x0047fdd4
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x77158a39 _EXCEPTION_DISPOSITION ntdll!FinalExceptionHandlerPad9+0
Excellent! The 'next' member points to 0xffffffff, indicating the end of the list, as seen in the output of the !exchain command.
0:000> !exchain
0047fdbc: ntdll!_except_handler4+0 (7714ad40)
CRT scope 0, filter: ntdll!__RtlUserThreadStart+3cdb8 (77174827)
func: ntdll!__RtlUserThreadStart+3ce51 (771748c0)
0047fdd4: ntdll!FinalExceptionHandlerPad9+0 (77158a39)
Invalid exception stack at ffffffff
Also, observe that the Handler pointer address matches. This observation provides us with a deeper understanding of how SEH operates at a high level.
Moving forward, we will write a small assembly program in FASM. This program will print the addresses of the ExceptionList, StackBase, and StackLimit. This exercise serves both as an enjoyable activity and an opportunity to reinforce our understanding of the subject.
...
18 start:
19 xor edx,edx
20 xor ebx,ebx
21 xor ecx,ecx
22
23 mov ecx,[fs:ecx] ; ecx holds ExceptionList
24 push ecx ; save it for later
25 PrintPointer ecx,exception_address_string
26
27
28 ;mov ebx,[ecx] ; ebx now holds Next (execption_registration_record) pointer
29 pop ebx ; fetch ExceptionList address from stack.
30 mov ebx, [ebx] ; Dereference the pointer (_Exception_Registration_Record struct) Next.
31 PrintPointer ebx,exception_record ; ebx contains the value of Next.
32 mov ebx,[ebx+0x4] ; ebx now ecx _exception_hander pointer (Handler)
33 PrintPointer ebx,exception_handler
34
35 xor ecx,ecx
36 mov ecx,[fs:0x4 ] ; ecx now holds StackBase
37 PrintPointer ecx,stackbase_string
38 mov ecx,[fs:0x8 ] ; ecx now holds StackLimit
39 PrintPointer ecx,stacklimit_string
40
41 jmp finish
42 finish:
43 invoke TerminateProcess, 0xffffffff,0x00
...
The code is concise. It begins by nullifying the edx, ebx, and ecx registers. Then, it copies the address of the ExceptionList to the ecx register by accessing the FS segment register at offset 0x00.
xor edx,edx
xor ebx,ebx
xor ecx,ecx
mov ecx,[fs:ecx] ; ecx holds ExceptionList
push ecx ; save it for later
PrintPointer ecx,exception_address_string
Afterwards, it pushes the value of ecx onto the stack to save it for later use. Following that, it simply prints the address of the ExceptionList using a custom macro called PrintPointer.
macro PrintPointer reg,string
{
xor eax,eax
mov dword eax,string
cinvoke printf,eax,reg
}
The macro is straightforward. It begins by zeroing out the eax register. Next, it copies a DWORD value from the specified string variable into the eax register. Finally, it calls the printfs function with the reg parameter.
The reg variable holds the pointer to the ExceptionList. To proceed further, please compile the provided code at the bottom of the page and open it with Windbg.
0:000> bp $exentry
*** Unable to resolve unqualified symbol in Bp expression '$extentry'.
0:000> g
Breakpoint 1 hit
eax=000dffcc ebx=00308000 ecx=00401000 edx=00401000 esi=00401000 edi=00401000
eip=00401000 esp=000dff74 ebp=000dff80 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
poc+0x1000:
00401000 31d2 xor edx,edx
0:000> u @eip Lf
poc+0x1000:
00401000 31d2 xor edx,edx
00401002 31db xor ebx,ebx
00401004 31c9 xor ecx,ecx
00401006 648b09 mov ecx,dword ptr fs:[ecx]
00401009 51 push ecx
0040100a 31c0 xor eax,eax
0040100c b800204000 mov eax,offset poc+0x2000 (00402000)
00401011 51 push ecx
00401012 50 push eax
00401013 ff1560304000 call dword ptr [poc+0x3060 (00403060)]
00401019 83c408 add esp,8
0040101c 5b pop ebx
0040101d 8b1b mov ebx,dword ptr [ebx]
0040101f 31c0 xor eax,eax
00401021 b813204000 mov eax,offset poc+0x2013 (00402013)
We set a breakpoint at 0040100a, specifically after the second 'xor eax, eax' instruction, so that we can examine the value of ecx, which holds the pointer to the ExceptionList.
0:000> bp 0040100a
0:000> g
Breakpoint 2 hit
eax=000dffcc ebx=00000000 ecx=000dffcc edx=00000000 esi=00401000 edi=00401000
eip=0040100a esp=000dff70 ebp=000dff80 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
poc+0x100a:
0040100a 31c0 xor eax,eax
0:000> r @ecx
ecx=000dffcc
0:000> !teb
TEB at 0030b000
ExceptionList: 000dffcc
StackBase: 000e0000
StackLimit: 000dd000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 0030b000
EnvironmentPointer: 00000000
ClientId: 00003ac8 . 00003078
RpcHandle: 00000000
Tls Storage: 00156038
PEB Address: 00308000
LastErrorValue: 187
LastStatusValue: c00700bb
Count Owned Locks: 0
HardErrorMode: 0
Excellent! We have confirmed that rcx indeed holds the ExceptionList pointer. Next, we will set a new breakpoint at 0040101f.
0:000> u @eip La
poc+0x100a:
0040100a 31c0 xor eax,eax
0040100c b800204000 mov eax,offset poc+0x2000 (00402000)
00401011 51 push ecx
00401012 50 push eax
00401013 ff1560304000 call dword ptr [poc+0x3060 (00403060)]
00401019 83c408 add esp,8
0040101c 5b pop ebx
0040101d 8b1b mov ebx,dword ptr [ebx]
0040101f 31c0 xor eax,eax
00401021 b813204000 mov eax,offset poc+0x2013 (00402013)
0:000> bl
0 e Disable Clear u 0001 (0001) ($extentry)
1 e Disable Clear 00401000 0001 (0001) 0:**** poc+0x1000
2 e Disable Clear 0040100a 0001 (0001) 0:**** poc+0x100a
0:000> bp 0040101f
0:000> g
Breakpoint 3 hit
eax=00000018 ebx=000dffe4 ecx=cbf993fe edx=00000000 esi=00401000 edi=00401000
eip=0040101f esp=000dff74 ebp=000dff80 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
poc+0x101f:
0040101f 31c0 xor eax,eax
At this stage, ebx should contain the pointer to the exceptionregistration_record structure.
0:000> r
eax=00000018 ebx=000dffe4 ecx=cbf993fe edx=00000000 esi=00401000 edi=00401000
eip=0040101f esp=000dff74 ebp=000dff80 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
poc+0x101f:
0040101f 31c0 xor eax,eax
0:000> r @ebx
ebx=000dffe4
0:000> dt ExceptionList 000dffcc
Symbol ExceptionList not found.
0:000> dt _nt_tib 000dffcc
ntdll!_NT_TIB
+0x000 ExceptionList : 0x000dffe4 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : 0x7714ad40 Void
+0x008 StackLimit : 0x708455ec Void
+0x00c SubSystemTib : (null)
+0x010 FiberData : 0x000dffec Void
+0x010 Version : 0xdffec
+0x014 ArbitraryUserPointer : 0x77137a6e Void
+0x018 Self : 0xffffffff _NT_TIB
0:000> dt _EXCEPTION_REGISTRATION_RECORD @ebx
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x77158a69 _EXCEPTION_DISPOSITION ntdll!FinalExceptionHandlerPad57+0
Confirmed! Now that we have a better understanding, let's expedite our investigation by setting a breakpoint at the end of the program, just before it terminates (0x00401080).
0:000> u @eip L20
poc+0x101f:
0040101f 31c0 xor eax,eax
00401021 b813204000 mov eax,offset poc+0x2013 (00402013)
00401026 53 push ebx
00401027 50 push eax
00401028 ff1560304000 call dword ptr [poc+0x3060 (00403060)]
0040102e 83c408 add esp,8
00401031 8b5b04 mov ebx,dword ptr [ebx+4]
00401034 31c0 xor eax,eax
00401036 b829204000 mov eax,offset poc+0x2029 (00402029)
0040103b 53 push ebx
0040103c 50 push eax
0040103d ff1560304000 call dword ptr [poc+0x3060 (00403060)]
00401043 83c408 add esp,8
00401046 31c9 xor ecx,ecx
00401048 648b0d04000000 mov ecx,dword ptr fs:[4]
0040104f 31c0 xor eax,eax
00401051 b840204000 mov eax,offset poc+0x2040 (00402040)
00401056 51 push ecx
00401057 50 push eax
00401058 ff1560304000 call dword ptr [poc+0x3060 (00403060)]
0040105e 83c408 add esp,8
00401061 648b0d08000000 mov ecx,dword ptr fs:[8]
00401068 31c0 xor eax,eax
0040106a b84f204000 mov eax,offset poc+0x204f (0040204f)
0040106f 51 push ecx
00401070 50 push eax
00401071 ff1560304000 call dword ptr [poc+0x3060 (00403060)]
00401077 83c408 add esp,8
0040107a eb00 jmp poc+0x107c (0040107c)
0040107c 6a00 push 0
0040107e 6aff push 0FFFFFFFFh
00401080 ff157c304000 call dword ptr [poc+0x307c (0040307c)]
After executing the program, we hit the breakpoint, and everything aligns as expected.
0:000> g
Breakpoint 4 hit
eax=00000015 ebx=77158a69 ecx=cbf993fa edx=00000000 esi=00401000 edi=00401000
eip=00401080 esp=000dff6c ebp=000dff80 iopl=0 nv up ei pl nz ac pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000216
poc+0x1080:
*** Unable to resolve unqualified symbol in Bp expression '$extentry'.
00401080 ff157c304000 call dword ptr [poc+0x307c (0040307c)] ds:002b:0040307c={KERNEL32!TerminateProcessStub (76029910)}
0:000> !exchain
000dffcc: ntdll!_except_handler4+0 (7714ad40)
CRT scope 0, filter: ntdll!__RtlUserThreadStart+3cdb8 (77174827)
func: ntdll!__RtlUserThreadStart+3ce51 (771748c0)
000dffe4: ntdll!FinalExceptionHandlerPad57+0 (77158a69)
Invalid exception stack at ffffffff
0:000> !teb
TEB at 0030b000
ExceptionList: 000dffcc
StackBase: 000e0000
StackLimit: 000dd000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 0030b000
EnvironmentPointer: 00000000
ClientId: 00003ac8 . 00003078
RpcHandle: 00000000
Tls Storage: 00156038
PEB Address: 00308000
LastErrorValue: 187
LastStatusValue: c00700bb
Count Owned Locks: 0
HardErrorMode: 0
We can compare the output of the debugger with the output of our program, as shown in the screenshot below.
You can find the complete source code of the POC here:
gist.github.com/tahadraidia/e95d104ba54b20f..
References: