This post is intended to document the internal implementation of the shim engine in ntdll, not the SDB format or writing your own shims via SDB. Although, we will touch on writing your own shim engine and its (ab)uses in the future.
What are shims?
- Built-in windows api hooking mechanism
- Introduced in Windows XP
- Integrated natively into ntdll loader mechanisms
- Made to provide an official api to handle backwards compatibility with older/unsupported applications.
Shim engine initialization
Shim engine initialization code path
The initialization of the shim engine begins a few functions down the chain of process initialization (in relative to the entry into usermode process initialization). The shim engine initialization chain follows this path.
Entry into usermode code via LdrInitializeThunk, LdrpInitialize, _LdrpInitialize, LdrpInitializeProcess, (code relevent to the shim engine's initialization begins here), LdrpInitShimEngine, (main shim engine initialization routine) LdrpGetShimEngineInterface, LdrpLoadShimEngine
Initialization of the shim engine begins in LdrpInitializeProcess after initialziation of kernel32 and kernelbase. LdrpInitializeProcess will decide if it should initialize the shim engine based on of if the pShimData of the PEB is non-zero. If the loader has decided it’s going to load the shim engine, the PEB’s AppCompatInfo field is zeroed, and execution is passed down to the next function in the chain (LdrpInitShimEngine)
The true initialization of the shim engine begins here. LdrpInitShimEngine will begin to construct a path to the default shim engine (apphelp.dll) by using LdrpBuildSystem32FileName to concatenate the apphelp.dll string to the system32 path. We now initialize the dll path by use of LdrpInitializeDllPath, in preparation for usage of LdrpLoadDll.
As stated, we attempt to load the default shim engine by passing our newly constructed
$SYSTEMDIR$\apphelp.dll path to LdrpLoadDll. The following disassembly represents such.
After this, our dllpath is now freed by RtlReleasePath. If we were successful in our loading of apphelp, we will now preform some small cleanup (LdrpDereferenceModule), setup g_pShimEngineModule with a pointer to the newly loaded module’s image base, and make a call to LdrpGetShimEngineInterface.
This function is responsible for loading the shim engine’s exports which are used by ntdll.
Retrieval of the exported routines is done in a rather unoptimized manner, each export is retrieved using an inlined block of code which only differs by the routine output destination, and the routine name. The routine works by initializing a stack allocated UNICODE_STRING with the desired routine name using RtlInitAnsiString, and passing it on to LdrGetProcedureAddressEx. If any of the exports fail to be resolved by LdrGetProcedureAddressEx, the resolving will stop, and the shim engine will mark g_ShimsEnabled to false and unload the shim engine. The following image shows disassembly of the inlined block which is repeated for each exported routine name.
Presuming all of the desired shim engine exports are resolved successfully, it’s now time to cache off the routine addresses in their corresponding global points.
Each routine has a corresponding global variable following the format of
g_pfnSE_InitializeEngine. These global pointers are encrypted with a simple algorithm, which is actually just an inlined version of RtlEncodeSystemPointer. The algorithm functions as such:
( pointer ^ Cookie ) ROR8 ( Cookie & 0x3F ). The cookie being the Cookie of the KUSER_SHARED_DATA system-wide shared page.
If all is successful, the function will return back to its caller (LdrpInitShimEngine).
LdrpInitShimEngine (Part 2, post-LdrpGetShimEngineInterface)
Moving back into LdrpInitShimEngine, if our call to LdrpGetShimEngineInterface returns successfully, we will prepare to pass execution on to the first shim engine callback, g_pfnSE_InitializeEngine. The g_pfnSE_InitializeEngine value is decrypted using the inverse of the simple algorithm that I’ve detailed above. This callback serves to populate a list of shims which the shim engine has deemed needed for this process, if the callback succeeds, we pass the list onto LdrpLoadShimEngine.
This is the function which preforms the task of loading all shim DLLs in the list provided by the g_pfnSE_InitializeEngine callback.
As per normal when intending to use LdrpLoadDll, we setup a dllpath using LdrpInitializeDllPath. The list of shim DLLs is simply an array of variably-sized UNICODE_STRING objects, of which the MaximumLength of the current entry is used to advance the pointer to the next UNICODE_STRING object in the list, this code could be written as such,
current_shim_entry = (current_shim_entry + 2 * (unicode_shim_name.MaximumLength >> 1) );. We’ll begin iterating this until its end now. The current DLL in the iteration will be passed into LdrpLoadDll for an attempted load. What happens if LdrpLoadDll doesn’t return successfully depends on the loader debug options (LdrpDebugFlags).
LdrpDebugFlags & 3 will cause a debug log containing the shim DLL path, aswell as the return value of LdrpLoadDll to be emitted. The other flag,
LdrpDebugFlags & 10, will cause a debug break (INT3). If the loading of the shim DLL succeeds, the module is pinned using LdrpPinModule, and its DdagNode’s state is checked for the LdrModulesReadyToInit state. If the state matches, we will pass the module’s DdagNode onto LdrpInitializeNode to handle its dependencies. I wont go into the details of the Windows8+ DdagNode system, but here is a link to a small resource by Geoff Chappell. Following the successful load of the module, and a successfull call to LdrpInitializeNode, we decrypt and call the pfnSE_ShimDllLoaded shim engine callback, to notify it of a new shim being loaded. After iteration is complete, g_pfnSE_InstallBeforeInit will be decrypted and alerted. LdrpDllNotificationLock is now locked using RtlEnterCriticalSection, and we begin iterating loading shim DLLs to send them a SE_DllLoaded notification.
Following the loop, and notifications, the LdrpDllNotificationLock lock is released using RtlLeaveCritical section. The shim DLL dependencies are then initialized by use of LdrpInitializeShimDllDependencies. This concludes the initialization of the shim engine.
Shim engine callback implementation
The shim engine callbacks of ntdll mostly pertain to the actions of the windows loader, but there are some exceptions. The following is a list of shim engine callbacks in ntdll, and their uses.
This callback is called by LdrpAppCompatRedirect when the windows loader attempts to resolve the name/path of a DLL which is to be loaded, for example, by LdrpMapDllSearchPath or LdrpMapFullPath. The path which was resolved by normal means is then passed on to the shim engine, aswell as a pointer to a boolean stack variable. This variable can be written to by the callback to indicate that the DLL path has been changed by the callback, and should be used.
This callback is called when the windows loader wants to resolve a module export’s address, for example, by LdrGetProcedureAddress, LdrGetProcedureAddressEx, and many more. The higher level apis for resolving module export addresses (GetProcAddress, etc), are merely wrappers for the two functions I’ve specified. This can be used for a fluent EAT hooking system, integrated into the loader process. Other parts of ntdll do make use of this callback, even LdrpLoadWow64, this can be used to hook the following routines of wow64.dll (assuming we’re running under a wow64 process)
Aside from the already stated callers, the following routines will also call into LdrGetProcedureAddressForCaller, and in turn, SE_GetProcAddressForCaller.
The callback is given a pointer to the resolvee module’s PLDR_DATA_TABLE_ENTRY entry, and an output pointer for the routine address. If the output pointer is written, the loader will use the routine address set through the callback instead.
Not much to say here, simply a callback for the RtlExitUserProcess sub-function, LdrShutdownProcess.
Callback for any images which the windows loader is loading, called by LdrpSendPostSnapNotifications. Mechanisms are in place by LdrpSendPostSnapNotifications to prevent infinite recursion or erroneous behavior when calling the callback, as this callback would also be called for any shims which are loading.
The second part of this post will detail the implementation of a usermode rootkit by creating your own shim engine, and replacing the default apphelp shim engine. This could also be used as a means of persistence. Until the next time.
© 2018 dro