This series of blog posts is about the new dynamic link libraries (DLLs) layout in Windows 6.x operating systems, where functions are now exported by new modules but the real implementation is located elsewhere. Static analysis tools might have problems dealing with this DLL restructuring. This blog post is aimed at presenting what is this new scheme, how it is implemented and how it is possible to leverage it so it can be used by static analysis tools.
Windows 7 introduced a profound reorganization of dynamic link libraries (DLLs). This restructuring effort is related with what is known as “Minwin” (Minimal Windows kernel) [WINNO]. Minwin is a complete reorganization of the Windows core components call graph so that lower level APIs never call higher level APIs. Consequently each API layer can evolve separately.
This core components reorganization is undocumented except for some hints on the MSDN website [MSFT1]. It should be important to note that it is by no means important to programmers as it doesn't change the visible behavior of the Windows operating system.
DLLs now forward their calls into Minwin DLLs, for example:
- Kernel32.dll → KernelBase.dll
- Advapi32.dll → KernelBase.dll
The problem is that, with this re-factoring, a single DLL might contain multiple logical sets of APIs. For example KernelBase will now contain different logical sets like registry handling, process and thread handling, memory handling, etc.
To elude this problem Microsoft separated the API architecture from the API implementation by using “Virtual DLLs”. The mapping between virtual DLLs and implementation DLLs is made by a single DLL named ApiSetSchema.dll. We use the name “ApiSetSchema” to refer to this particular mechanism:
A Kernel view of ApiSetSchema mechanism
Note: The following analysis has been made on Windows 8 Consumer Preview 32-bit. The same process roughly applies to Windows 7.
The whole ApiSetSchema mechanism is based on a single file: "ApiSetSchema.dll" located in the \SystemRoot\system32 directory.
The ApiSetSchema mechanism is activated very early in the boot process. The ApiSetSchema.dll file is loaded at boot time by winload.exe during winload!OslpLoadAllModules and winload!OsLoadImage alongside with very important images like the Windows kernel and the hardware abstraction layer (HAL).
Once the file is loaded, a view of a piece of the ApiSetSchema.dll image is created at the system startup during the Phase 1 of the kernel initialization with the help of the nt!MiInitializeApiSets function:
3: kd> kp ChildEBP RetAddr 80d96af8 82325a95 nt!MiInitializeApiSets 80d96b4c 8234631e nt!MiInitSystem+0x20d 80d96d68 8218972c nt!Phase1InitializationDiscard+0xd41 80d96d74 820ae080 nt!Phase1Initialization+0xe 80d96db0 81f756d1 nt!PspSystemThreadStartup+0x4a 00000000 00000000 nt!KiThreadStartup+0x19
We'll now see what the nt!MiInitializeApiSets function does. First the function gets a shared access on nt!PsLoadedModuleList which is a list of loaded kernel modules. This is done by using nt!PsLoadedModuleResource which controls the access to the list of loaded modules:
push 1 ; Wait push offset _PsLoadedModuleResource ; Resource call _ExAcquireResourceSharedLite@8 ; acquire access mov esi, _PsLoadedModuleList ; list of loaded modules xor ebx, ebx cmp esi, offset _PsLoadedModuleList
The code then builds a _UNICODE_STRING pointing to the "ApiSetSchema.dll" string:
push 20h pop eax mov [ebp+ApiSetSchemaUString.Length], ax push 22h pop eax mov [ebp+ApiSetSchemaUString.MaximumLength], ax mov [ebp+ApiSetSchemaUString.Buffer], offset aApisetschema_d ; "ApiSetSchema.dll"
The code then searches the “ApiSetSchema.dll” in the _PsLoadedModuleList list of modules:
SearchApiSetSchemaDLL: push 1 ; CaseInSensitive lea eax, [esi+_LDR_DATA_TABLE_ENTRY.BaseDllName] push eax ; String2 lea eax, [ebp+ApiSetSchemaUString] mov edi, esi push eax ; String1 mov [ebp+LdrDataTableEntry], edi call _RtlEqualUnicodeString@12 ; RtlEqualUnicodeString(x,x,x) test al, al jnz short .stringequal mov esi, [esi+_LDR_DATA_TABLE_ENTRY.InLoadOrderLinks.Flink] cmp esi, offset _PsLoadedModuleList jnz short SearchApiSetSchemaDLL
As shown in the above code, each entry in the doubly linked list of nt!PsLoadedModuleList can be seen as a _LDR_DATA_TABLE_ENTRY structure. The above code just checks the equality between the string pointed by _LDR_DATA_TABLE_ENTRY.BaseDllName and the local variable ApiSetSchemaUString:
3: kd> dt @esi _LDR_DATA_TABLE_ENTRY nt!_LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x83e37338 - 0x83e37440 ] +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0xffffffff - 0xffffffff ] +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ] +0x010 InProgressLinks : _LIST_ENTRY [ 0x0 - 0x0 ] +0x018 DllBase : 0x80400000 Void +0x01c EntryPoint : 0x80400000 Void +0x020 SizeOfImage : 0xc000 +0x024 FullDllName : _UNICODE_STRING "SystemRootsystem32ApiSetSchema.dll" +0x02c BaseDllName : _UNICODE_STRING "ApiSetSchema.dll"
Once the _LDR_DATA_TABLE_ENTRY corresponding to the "ApiSetSchema.dll" DLL file is found the code releases the access on the module list:
loc_923D0E: mov eax, large fs:124h ; _KTHREAD push eax push offset _PsLoadedModuleResource call ExpReleaseResourceForThreadLite ; release list access
Given the _LDR_DATA_TABLE_ENTRY structure that describes the "ApiSetSchema.dll" module, the code gets its image base and start parsing the in-memory file as a Portable Executable (PE) file.
For this purpose, it obtains the _IMAGE_NT_HEADERS structure and starts parsing each of the _IMAGE_SECTION_HEADERS that describes each of the sections that lie in the PE file, searching for a section named “.apiset”:
mov eax, [edi+_LDR_DATA_TABLE_ENTRY.DllBase] ; base of ApiSetSchema.dll push ebx push [edi+_LDR_DATA_TABLE_ENTRY.SizeOfImage] lea esi, [ebp+NtImageHeader] xor ecx, ecx call near ptr off_42AF30+1CCFh ; nt!RtlImageNtHeaderEx test eax, eax js loc_923E21 mov ebx, [ebp+NtImageHeader] movzx eax, [ebx+IMAGE_NT_HEADERS32.FileHeader.SizeOfOptionalHeader] lea esi, [ebx+IMAGE_NT_HEADERS32.OptionalHeader] ; IMAGE_NT_HEADERS32.OptionalHeader.Magic add esi, eax ; esi = _IMAGE_SECTION_HEADER* xor edx, edx xor eax, eax mov ecx, edx cmp ax, [ebx+IMAGE_NT_HEADERS32.FileHeader.NumberOfSections] jnb short loc_923DA1 mov edi, eax ; edi = eax = Number of sections loc_923D80: push 8 ; Length push offset a_apiset ; ".apiset" push esi ; IMAGE_SECTION_HEADER.Name : current section name call _RtlCompareMemory@12 cmp eax, 8 jnz loc_9331E2 ; if not found, check next section
The “.apiset” section is the first one in the DLL file:
A kernel section object is then created and mapped onto the system memory, using the ".apiset" section attributes:
push edx push edx push 8000000h push 4 mov [ebp+SectionVirtualSize], eax lea eax, [ebp+SectionVirtualSize] push eax push edx push 0F001Fh lea eax, [ebp+Section] push eax mov [ebp+var_24], edx call _MmCreateSection@32 ;Create section object test eax, eax js short loc_923E21 lea eax, [ebp+ViewSize] push eax ; ViewSize xor ebx, ebx lea eax, [ebp+MappedBase] mov [ebp+ViewSize], ebx mov ebx, [ebp+Section] push eax ; MappedBase push ebx ; Section call _MmMapViewInSystemSpace@12 ; Map view into system space [kernel] mov [ebp+Section], eax
The content of the “.apiset” section is then copied onto the newly available system memory. The kernel section object and the memory view of the section are saved onto global kernel variables. The system image of “apisetschema.dll” is unloaded as it is not needed anymore:
mov eax, [esi+IMAGE_SECTION_HEADER.VirtualAddress] push [esi+IMAGE_SECTION_HEADER.Misc.VirtualSize] ; size_t add eax, [edi+_LDR_DATA_TABLE_ENTRY.DllBase] mov esi, [ebp+MappedBase] push eax ; source: content of the .apiset “flat” file section push esi ; destination: kernel memory [pertaining to the kernel section object] call _memcpy add esp, 0Ch push edi call _MmUnloadSystemImage@4 ; “apisetschema.dll” can be removed mov _MiApiSetSection, ebx ; section object mov _MiApiSetSchema, esi ; kernel view of the “.apiset” file section
Below is an overview of the memory view (nt!MiApiSetSchema) and the section object (nt!MiApiSetSection):
0: kd> dp nt!MiApiSetSchema L1 8141bfcc 801c0000 0: kd> dd 801c0000 801c0000 00000002 00000134 00004bb4 00000036 801c0010 00004bec 00004c00 00000048 00004c48 801c0020 00004c5c 00000052 00004cb0 00004cc4 801c0030 0000003e 00004d04 00004d18 00000034 801c0040 00004d4c 00004d60 00000034 00004d94 801c0050 00004da8 0000003c 00004de4 00004df8 801c0060 0000003c 00004e34 00004e48 0000003e 801c0070 00004e88 00004e9c 00000038 00004ed4 0: kd> dp nt!MiApiSetSection L1 8141bfe0 8240c518 0: kd> !object 8240c518 Object: 8240c518 Type: (836b3c88) Section ObjectHeader: 8240c500 (new version) HandleCount: 0 PointerCount: 1
Note that we’ll explain later what the content of the memory view is.
From now on, the section and its view are hold by the system.
Once the system has booted and as soon as a process is started, the nt!MmMapApiSetView function is called:
1: kd> kb ChildEBP RetAddr Args to Child a617b428 81edb06d 86b121c0 000002aa 00000000 nt!MmMapApiSetView a617b45c 81f3ed39 8691d040 86b121c0 a617b5b0 nt!PspSetupUserProcessAddressSpace+0xf5 a617b5f4 81ed29bf 8691d040 840c1001 00000000 nt!PspAllocateProcess+0x90f a617bd20 81db999c 011de5bc 011de56c 02000000 nt!NtCreateUserProcess+0x3df a617bd20 77b25ee4 011de5bc 011de56c 02000000 nt!KiFastCallEntry+0x12c
This function is responsible for setting the _PEB.ApiSetMap field as seen below:
mov esi, ecx ; esi = ecx = EPROCESS* xor edi, edi mov [ebp+BaseAddress], edi ; BaseAddress is NULL : system choose the right place in process address space mov [ebp+var_30], edi mov [ebp+var_2C], edi mov [ebp+var_20], edi push 2 push 400000h push 1 lea eax, [ebp+var_20] push eax lea eax, [ebp+var_30] push eax push edi push edi lea eax, [ebp+BaseAddress] push eax push esi ; Process push _MiApiSetSection call _MmMapViewOfSection@40 ; Map the section into process address space mov ecx, eax test ecx, ecx js short loc_6747FC mov edx, [esi+_EPROCESS.Peb] mov [ebp+ms_exc.disabled], edi mov eax, [ebp+BaseAddress] mov [edx+38h], eax ; set _PEB.ApiSetMap
The last line of code above (at 0x006747F0) sets a view of the ApiSetMap in the PEB structure. As a reminder the PEB (Process Environment Block) is an internal Windows structure (there's only one PEB for a process) that is accessible by the process itself and its threads. This structure, which keeps many internals stuffs belonging to the process, lies in user-land and can be easily accessed without doing a system call (syscall).
To summarize what we've seen so far:
- Windows uses a mechanism for redirecting virtual DLLs to logical (implementation) DLLs.
- At start up, Winload.exe loads the “apisetschema.dll” file.
- During Kernel initialization Phase 1, the “.apiset” file section from the DLL is mapped onto system kernel memory: A kernel object section and a view of this section are created.
- When a process is started, a view of the section is created into the process address and is available through the _PEB.ApisetMap field: the content of the “.apiset” file section from the “apisetschema.dll” file is therefore available into the process user space.
In the next posts we'll see how the from what the ApiSetMap view is composed and how it is interpreted from user-land and kernel-land and how it can be used from static tools.
If you would like to learn more about our security audits and explore how we can help you, get in touch with us!