The API Set Schema

Windows 7 brings a significant reorganisation of the lower levels of the Win32 subsystem. Long-familiar ADVAPI32 functions are moved to KERNEL32 and from both to a new DLL named KERNELBASE. Other ADVAPI32 functions are moved to a new DLL named SECHOST. Very many executables in Windows 7 import functions from new DLLs that have unusually long names such as “API-MS-Win-Core-LocalRegistry-L1-1-0.dll”. This importing is done by ADVAPI32 and KERNEL32, by DLLs for general support such as MFC42, MSVCRT and OLE32, by many services, and by all sorts of other executables all through the lower levels of Windows out as far as SHELL32 and SHLWAPI.

There is not much official documentation of this. The original Software Development Kit (SDK) for Windows 7 makes just the one mention of KERNELBASE, in a brief page about New Low-Level Binaries. If not much more ever is documented about it, then in one sense there should not be much surprise. After all, higher-level executables distributed with Windows continue to import as before from such DLLs as KERNEL32, and since the SDK has no import libraries for the new DLLs, the intention is surely that programs written outside Microsoft, and probably also most that are written inside Microsoft, will know nothing of the new DLLs and should be unaffected. The new DLLs with the long names are anyway just stubs in which all exported functions are implemented no more than needed for hard-coded failure. Moreover, these failing implementations have not all received great care: see for instance that CreateFileW in API-MS-Win-Core-File-L1-1-0.dll returns a hard-coded NULL (0) instead of INVALID_HANDLE_VALUE (-1).

In another sense, the lack of documentation may astonish, depending on what one expects to be told about the Windows architecture in order to assess its security or robustness. These new DLLs are part of a small but significant embellishment of how NTDLL resolves imports when loading user-mode modules. It turns out that all imports from any DLL whose (case-insensitive) module name starts with API- are subject to a new form of redirection. Since very many Windows executables import from such modules and especially since KERNEL32 and ADVAPI32 do so for the initial handling of several hundred of the most commonly used Windows API functions, software that can interfere with this new redirection could be very powerful in terms of modifying behaviour throughout Windows for relatively little effort.

This new redirection is managed by NTDLL as a preferred alternative to isolation through activation contexts. Whether the imports from any particular API- module are redirected depends entirely on the contents of a new file, named ApiSetSchema.dll in the System32 directory. Although ApiSetSchema.dll is a DLL, it is wanted only for data. The whole file is mapped into system space by the NT kernel during phase 1 of system initialisation. From there, the wanted data is mapped into the user-mode space of each newly initialised process and a pointer to this data is placed in a new member, named ApiSetMap at offset 0x38, of the process’s semi-documented PEB structure. The kernel recognises the data only as the whole contents of a section that is named “.apiset” and is aligned to 64KB (i.e., whose VirtualAddress member in the IMAGE_SECTION_HEADER has the low 16 bits clear). The kernel has nothing to do with interpreting these contents: it just provides them for NTDLL to interpret. Conversely, NTDLL knows nothing of where the contents came from. To NTDLL, whenever it is to resolve an import to one module from another, whatever is at the address given by ApiSetMap is accepted as a map, in an assumed format (described below), for importing from somewhere else instead.

Data Format

The map begins with an 8-byte header which is followed immediately by an array of 12-byte entries which each describe one API Set module:

Offset Size Description
0x00 dword ignored, but perhaps intended as a version number
0x04 dword number of API Set modules
0x08 unsized array of entries for API Set modules

The entries are assumed to be already sorted in case-insensitive alphabetical order. Each API Set module is named without the API- prefix and without a file extension:

Offset Size Description
0x00 dword offset from start of map to name of API Set module
0x04 word size, in bytes, of name of API Set module
0x08 dword offset from start of map to description of host modules

Names are in Unicode and are not null-terminated. If the module to be imported from is an API Set module as found in the schema, then the import is redirected to some host module. The list of possible hosts is assumed to contain at least one to use by default. However, there may be exceptions, which are selected according to the name of the importing module. Each host is described by a 0x10-byte structure. The possible hosts are described by a structure that begins with a count and a description of the default host, optionally followed by descriptions of the exceptional hosts. Entries for the exceptional hosts are assumed to be already sorted in case-insensitive alphabetical order of the importing module.

Offset Size Description
0x00 dword number of hosts
0x04 unsized array of entries for hosts, default host first, then exceptional hosts

The 0x10-byte structure that describes any one host provides two case-insensitive names, one for the importing module that selects this host and one for the host.

Offset Size Description
0x00 dword offset from start of map to name of importing module, in Unicode
0x04 word size, in bytes, of name of importing module
0x08 dword offset from start of map to name of host module, in Unicode
0x0C word size, in bytes, of name of host module

Both names are in Unicode and are not null-terminated. For a default host, the members that describe the importing module are irrelevant.

The Windows 7 Schema

The ApiSetSchema.dll supplied with Windows 7 (both 32-bit and 64-bit, and with Windows Server 2008 R2, including the Core editions) defines 35 API Sets:

API Set Default Host Exceptional Mappings (Importer → Host)
API-MS-Win-Core-Console-L1-1-0 kernel32.dll  
API-MS-Win-Core-DateTime-L1-1-0 kernel32.dll  
API-MS-Win-Core-Debug-L1-1-0 kernelbase.dll  
API-MS-Win-Core-DelayLoad-L1-1-0 kernel32.dll  
API-MS-Win-Core-ErrorHandling-L1-1-0 kernel32.dll kernel32.dll → kernelbase.dll
API-MS-Win-Core-Fibers-L1-1-0 kernelbase.dll  
API-MS-Win-Core-File-L1-1-0 kernel32.dll kernel32.dll → kernelbase.dll
API-MS-Win-Core-Handle-L1-1-0 kernel32.dll kernel32.dll → kernelbase.dll
API-MS-Win-Core-Heap-L1-1-0 kernelbase.dll  
API-MS-Win-Core-Interlocked-L1-1-0 kernelbase.dll  
API-MS-Win-Core-IO-L1-1-0 kernel32.dll kernel32.dll → kernelbase.dll
API-MS-Win-Core-LibraryLoader-L1-1-0 kernelbase.dll  
API-MS-Win-Core-Localization-L1-1-0 kernelbase.dll  
API-MS-Win-Core-LocalRegistry-L1-1-0 kernel32.dll  
API-MS-Win-Core-Memory-L1-1-0 kernelbase.dll  
API-MS-Win-Core-Misc-L1-1-0 kernelbase.dll  
API-MS-Win-Core-NamedPipe-L1-1-0 kernelbase.dll  
API-MS-Win-Core-ProcessEnvironment-L1-1-0 kernelbase.dll  
API-MS-Win-Core-ProcessThreads-L1-1-0 kernel32.dll kernel32.dll → kernelbase.dll
API-MS-Win-Core-Profile-L1-1-0 kernelbase.dll  
API-MS-Win-Core-RtlSupport-L1-1-0 ntdll.dll  
API-MS-Win-Core-String-L1-1-0 kernelbase.dll  
API-MS-Win-Core-Synch-L1-1-0 kernel32.dll kernel32.dll → kernelbase.dll
API-MS-Win-Core-SysInfo-L1-1-0 kernelbase.dll  
API-MS-Win-Core-ThreadPool-L1-1-0 kernelbase.dll  
API-MS-Win-Core-UMS-L1-1-0 kernel32.dll  
API-MS-Win-Core-Util-L1-1-0 kernel32.dll kernel32.dll → kernelbase.dll
API-MS-Win-Core-XState-L1-1-0 ntdll.dll  
API-MS-Win-Security-Base-L1-1-0 kernelbase.dll  
API-MS-Win-Security-LSALookup-L1-1-0 sechost.dll  
API-MS-Win-Security-SDDL-L1-1-0 sechost.dll  
API-MS-Win-Service-Core-L1-1-0 sechost.dll  
API-MS-Win-Service-Management-L1-1-0 sechost.dll  
API-MS-Win-Service-Management-L2-1-0 sechost.dll  
API-MS-Win-Service-winsvc-L1-1-0 sechost.dll