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:

ApiSetSchema

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:

Sections table

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!