This post will continue where the first one left off and explain the operations happening on the doubly linked list of exception handlers. To understand anything in this post, you should read the first one.
Finding the Link Relationships
Given the information from part one, there are two structures at work here: _LdrpVectorHandlerList, which is a non-exported named symbol, and _LdrpVectorHandlerEntry, which is the name given to the struct allocated in _RtlpAddVectoredHandler. Each of these structures has two pointers within them that get moved around.
771E3686 cmp dword ptr [ebp+8],0
771E368A je _RtlpAddVectoredHandler@12+13DF3h (771F7414h)
--------> Jump resolved below
----771F7414 mov eax,dword ptr [edi+4]
----771F7417 mov dword ptr [esi],edi
----771F7419 mov dword ptr [esi+4],eax
----771F741C mov dword ptr [eax],es
----771F741E mov dword ptr [edi+4],esi
----771F7421 jmp _RtlpAddVectoredHandler@12+7Bh (771E369Ch)
771E3690 mov eax,dword ptr [edi]
771E3692 mov dword ptr [esi],eax
771E3694 mov dword ptr [esi+4],edi
771E3697 mov dword ptr [eax+4],esi
771E369A mov dword ptr [edi],esi
The best way to find out what is happening is to dynamically trace adding exception handlers. For example, what goes on in the code when three exception handlers are added in series?Each one will be added to the head of the list, so that if an exception occurs then the call order will be VectoredHandler3 -> VectoredHandler2 -> VectoredHandler1 -> Unhandled exception. For the case of a handler being inserted at the head of the list, the following instructions will be executed:
771E3690 mov eax,dword ptr [edi]
771E3692 mov dword ptr [esi],eax
771E3694 mov dword ptr [esi+4],edi
771E3697 mov dword ptr [eax+4],esi
771E369A mov dword ptr [edi],esi
The easiest way to see what is going on is to make a table of the runs. Here let X, Y, Z be the different memory addresses of ESI. Let Base be the base address of _LdrpVectorHandlerList, relative to EAX and EDI. I’ve also reproduced the structures and the mappings of registers to fields below.
typedef struct _LdrpVectorHandlerEntry { _LdrpVectorHandlerEntry *pLink1; +0x0 [ESI] _LdrpVectorHandlerEntry *pLink2; +0x4 [ESI+0x4] DWORD dwAlwaysOne; +0x8 PVECTORED_EXCEPTION_HANDLER pVectoredHandler; +0xC } VECTORED_HANDLER_ENTRY, *PVECTORED_HANDLER_ENTRY; typedef struct _LdrpVectorHandlerList { SRWLOCK srwLock; +0x0 VECTORED_HANDLER_ENTRY *pLink1; +0x4 [EDI] VECTORED_HANDLER_ENTRY *pLink2; +0x8 } VECTORED_HANDLER_LIST, *PVECTORED_HANDLER_LIST; +0xC
First run
[X] | [X+4] | [*(Base+4)] | [Base] |
---|---|---|---|
0x77284728 | 0x77284728 | X | X |
Second run
[Y] | [Y+4] | [*(Base+4)] | [Base] |
---|---|---|---|
X | 0x77284728 | Y | Y |
Third run
[Z] | [Z+4] | [*(Base+4)] | [Base] |
---|---|---|---|
Y | 0x77284728 | Z | Z |
Looking at the results of these three adds, you can begin to see a relationship.
[X] = [ESI] Always holds the address of the previous handler
[X+4] = [ESI+0x4] Always holds the address of the base of the table
[*(Base+4)] = [EAX+0x4] Always holds the address of the new handler
[Base] = [EDI] Always holds the address of the new handler
Given that this operation is to insert at the head of the list, it is possible to draw some conclusions. Since [ESI] always contains the address of the previous topmost handler, it can be assumed to be a pointer to the next handler in the chain. [ESI+0x4] can be assumed to be a pointer to the previous handler in the chain, which in the case of inserting a head node, is set as the base of the exception list. Now the struct definition can be completed.
typedef struct _LdrpVectorHandlerEntry { _LdrpVectorHandlerEntry *pNext; _LdrpVectorHandlerEntry *pPrev; DWORD dwAlwaysOne; PVECTORED_EXCEPTION_HANDLER pVectoredHandler; } VECTORED_HANDLER_ENTRY, *PVECTORED_HANDLER_ENTRY; |
[EAX+0x4] is a bit more difficult to discern. EAX holds the value of the address of the second field in _LdrpVectorHandlerList. This is dereferenced and the second item in the dereferenced struct is set to the address of the new handler. What is happening here is that the pPrev field of the current topmost handler prior to inserting a new one is set to the address of the new handler, thus keeping the list chain intact. This may not seem obvious from looking at the assembly but is what is occurring when actually stepping through the instructions with a debugger. Lastly, EDI, which is the first member of _LdrpVectorHandlerList is set to hold the address of the new handler.
Now for the other case: inserting at the back of the vectored exception list. In that scenario, the following instructions will be executed:
771F7414 mov eax,dword ptr [edi+4] 771F7417 mov dword ptr [esi],edi 771F7419 mov dword ptr [esi+4],eax 771F741C mov dword ptr [eax],esi 771F741E mov dword ptr [edi+4],esi 771F7421 jmp _RtlpAddVectoredHandler@12+7Bh (771E369Ch) |
This is a slight variation on the first case. The best way to see what is going on is to step through the assembly code again. Here X, Y, and Z will map to [ESI] like last time. Here Base will be [EDI+0x4], the third member of _LdrpVectorHandlerList — unlike [EDI] in the previous segment, which was the second member. [Base+0x4] will be [EDI + 0x4].
First run
[X] | [X+4] | [Base] | [*(Base+4)] |
---|---|---|---|
0x77284728 | 0x77284728 | X | X |
Second run
[Y] | [Y+4] | [Base] | [*(Base+4)] |
---|---|---|---|
0x77284728 | X | Y | Y |
Third run
[Z] | [Z+4] | [Base] | [*(Base+4)] |
---|---|---|---|
0x77284728 | Y | Z | Z |
Again,
[X] = [ESI] Always holds the address of the base of the table
[X+4] = [ESI+0x4] Holds the address of the previous handler
[Base] = [EAX] Holds the address to the new handler
[*(Base +4)] = [EDI+0x4] Holds the address of the new handler
Here, the mappings that were established for [X] and [X+4] as pNext and pPrev still make sense. For a node inserted at the back of the exception list, pNext will point to the base of the table (end), and pPrev will point to the address of the previous handler. Here [Base] is the third member of _LdrpVectorHandlerList. Given what is known from the previous run and this one, it is possible to draw a conclusion that the two pointers in _LdrpVectorHandlerList are pointers to the first and last exception handlers. The definition of _LdrpVectorHandlerList can now be completed.
typedef struct _LdrpVectorHandlerList { SRWLOCK srwLock; VECTORED_HANDLER_ENTRY *pFirstHandler; VECTORED_HANDLER_ENTRY *pLastHandler; } VECTORED_HANDLER_LIST, *PVECTORED_HANDLER_LIST; |
That wraps up the implementation details of vectored exception handlers. The full C implementation will be provided in the next post. Follow on Twitter for more updates.