A Brief Dive Into x86 Windows Structured Exception Handling Implementation

A Brief Dive Into x86 Windows Structured Exception Handling Implementation

Play this article

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:

Did you find this article valuable?

Support Taha Draidia by becoming a sponsor. Any amount is appreciated!