archiveJan 3, 2021
Windows SEH chain and TEB ExceptionList
How Windows builds the per-thread SEH singly-linked list on the stack, how FS:[0] and the TEB point at it, and how to walk the chain in WinDbg.
SEH
Exception handlers form a singly-linked list tied to each thread. Nodes live on the stack. The list head is a pointer at the start of the Thread Environment Block (TEB). To install a new handler, code pushes a new node, links it to the old head, then updates the TEB pointer so the new node is first.
Each node is an _EXCEPTION_REGISTRATION_RECORD: the handler address plus a pointer to the next node. The last node's "next" pointer is not NULL — it is 0xFFFFFFFF.
0:000> dt _EXCEPTION_REGISTRATION_RECORD
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : Ptr32 _EXCEPTION_DISPOSITION
0:005> dt _EXCEPTION_REGISTRATION_RECORD
combase!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : Ptr32 _EXCEPTION_DISPOSITIONThe TEB is also reachable through the FS segment starting at FS:[0], so you often see code like this:
mov eax, dword ptr fs:[00000000h] ; retrieve the head
push eax ; save the old head
lea eax, [ebp-10h]
mov dword ptr fs:[00000000h], eax ; set the new head
mov ecx, dword ptr [ebp-10h] ; get the old head (NEXT field of the current head)
mov dword ptr fs:[00000000h], ecx ; restore the old headCompilers usually register one global handler and track which region is executing with a global. Each thread has its own TEB, and the OS keeps the FS-selected segment pointed at the current thread's TEB.
To get the TEB address, read FS:[18h] (the TEB Self field).
0:003> !teb
TEB at 00b95000
ExceptionList: 032af770
StackBase: 032b0000
StackLimit: 032ac000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 00b95000
EnvironmentPointer: 00000000
ClientId: 0000351c . 00004360
RpcHandle: 00000000
Tls Storage: 00000000
PEB Address: 00b50000
LastErrorValue: 0
LastStatusValue: 0
Count Owned Locks: 0
HardErrorMode: 0Confirm FS points at that TEB:
0:003> dg fs
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0053 00b95000 00000fff Data RW Ac 3 Bg By P Nl 000004f3FS:[18h] holds the TEB address:
0:003> ?poi(fs:[18])
Evaluate expression: 12144640 = 00b95000ExceptionList is the first field of _NT_TIB:
0:003> dt nt!_NT_TIB ExceptionList
ntdll!_NT_TIB
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORDEach node is an _EXCEPTION_REGISTRATION_RECORD. Walk the whole list with !slist:
0:003> !slist $teb _EXCEPTION_REGISTRATION_RECORD
SLIST HEADER:
+0x000 Alignment : 32b0000032af770
+0x000 Next : 32af770
+0x004 Depth : 0
+0x000 Sequence : 0
SLIST CONTENTS:
032af770
+0x000 Next : 0x032af7dc _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x77759990 _EXCEPTION_DISPOSITION ntdll!_except_handler4+0
032af7dc
+0x000 Next : 0x032af7f4 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x77759990 _EXCEPTION_DISPOSITION ntdll!_except_handler4+0
032af7f4
+0x000 Next : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x7776734b _EXCEPTION_DISPOSITION ntdll!FinalExceptionHandlerPad27+0
ffffffff
+0x000 Next : ????
+0x004 Handler : ????
Can't read memory at ffffffff, error 0$teb is the TEB address. A shorter walk uses !exchain:
0:003> !exchain
032af770: ntdll!_except_handler4+0 (77759990)
CRT scope 0, filter: ntdll!DbgUiRemoteBreakin+3b (7778db7b)
func: ntdll!DbgUiRemoteBreakin+3f (7778db7f)
032af7dc: ntdll!_except_handler4+0 (77759990)
CRT scope 0, filter: ntdll!__RtlUserThreadStart+3d6c5 (77784c8a)
func: ntdll!__RtlUserThreadStart+3d75e (77784d23)
032af7f4: ntdll!FinalExceptionHandlerPad27+0 (7776734b)
Invalid exception stack at ffffffffYou can also dump a single record by address:
0:003> dt 032af770 _EXCEPTION_REGISTRATION_RECORD
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0x032af7dc _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x77759990 _EXCEPTION_DISPOSITION ntdll!_except_handler4+0