Runtime DLL name resolution: ApiSetSchema - Part II

In the previous blog post we have seen how the ApiSetSchema was set up during boot time by the system. In this post we’ll see what the structure of the ApiSetSchema is and finally in the next blog post we’ll see how it is used in user-land and kernel-land.

The structures showed here are inferred from the disassembly of a precise function in the Windows Loader which will be detailed later, in the next blog post. In fact the function code can be hard to understand if the structures and their relationships are not explained before seeing the code, thus we have chosen to first explain the structures.

In the previous post we discussed how the content of the .apiset section of the apisetschema.dll file (we call this content “ApiSetSchema”) was mapped onto each process address space during the process creation. This time we’ll see exactly what the content of this section is.

The ApiSetSchema is made of multiple undocumented structures. We start with the heading structure which is as follow:

typedef struct _APISETMAP{
    DWORD Version;                    // dummy name (this field is never used)
    DWORD NumberOfHosts;              // number of DLLHOSTDESCRIPTOR structures following.
    DLLHOSTDESCRIPTOR descriptors[1]; // array of DLLHOSTDESCRIPTOR structures.
} APISETMAP, *PAPISETMAP;

Below is an excerpt from the content of the section. The first DWORD (4 bytes value), has a value of 2 (the file must be read in little endianness). This field is probably a version number although this field seems to be never directly used.

The following field is the NumberOfHosts field, which is set to 0x134 in our example. This field indicates how much structures lie in the ApiSetSchema:

Following those two fields is an array of DLLHOSTDESCRIPTOR structure (in our example there are 0x134 structures):

typedef struct _DLLHOSTDESCRIPTOR{
    DWORD OffsetDllString;
    DWORD StringLength;
    DWORD OffsetDllRedirector; // offset to DLLREDIRECTOR
} DLLHOSTDESCRIPTOR, *PDLLHOSTDESCRIPTOR;

Thus, the first structure in the DLLHOSTDESCRIPTOR array is as follow:

Hence, the field values are:

  • OffsetDllString: 0x4BB4
  • StringLength: 0x36
  • OffsetDllRedirector: 0x4BEC

If we go to the offset 0x4BB4 (offset are from the very beginning of the ApiSetSchema) we find an UTF-16 string with a length (indicated by the "StringLength" field) of 0x36 bytes:

In our case the string reads: “ms-win-advapi32-auth-l1-1”. Note that the StringLength field indicates the precise length of the string without NULL bytes. As a side note it should be mentioned that the DLL names in the ApiSetSchema structures doesn’t start with “API-“ while the files present on the file system do.We'll see the reason in the next post.

Therefore, the name pointed by the OffsetDllString field is the name of the virtual DLL that gets redirected to a logical/real DLL which owns the implementation of all the function exported by this virtual DLL.

Note that the DLLHOSTDESCRIPTOR array is an alphabetically sorted array where the DLLHOSTDESCRIPTOR.OffsetDllString is the sorting key.

To get the name of the logical DLL we have to follow the OffsetDllRedirector field, which is at offset 0x4BEC in our example.

At this offset we have a DLLREDIRECTOR structure:

typedef struct _DLLREDIRECTOR{
    DWORD NumberOfRedirections; // Number of REDIRECTION structs.
    REDIRECTION Redirection[1]; // array of REDIRECTION structures
} DLLREDIRECTOR, *PDLLREDIRECTOR;

The DLLREDIRECTOR structure – at offset 0x4BEC – is as follow in our example:

The NumberOfRedirections field is set to 1, thus it’s followed by one REDIRECTION structure:

typedef struct _REDIRECTION{
    DWORD OffsetRedirection1;
    USHORT RedirectionLength1;
    USHORT _pad1;
    DWORD OffsetRedirection2;
    USHORT RedirectionLength2;
    USHORT _pad2;
}REDIRECTION, *PREDIRECTION;

Note: The padding fields are not necessary if the structure is packed by 4 (as with the "#pragma pack" directive).

As shown above, each REDIRECTION structure can have at most two sub-redirections. If the NumberOfRedirections is set to 1, the first sub-redirection (OffsetRedirection1 and RedirectionLength1) will always have its RedirectionLength field set to 0 thus the OffsetRedirection field is not important and only the second redirection is meaningful:

  • OffsetRedirection1: 0x4c00
  • RedirectionLength1: 0x0
  • OffsetRedirection2: 0xfd0
  • RedirectionLength2: 0x18

If we go at offset 0xFD0 we find a string of length 0x18:

The string reads “advapi32.dll”. Note that once again the string is an UTF-16 string without any ending NULL bytes.

Finally, this means that the Virtual DLL named “api-ms-win-advapi32-auth-l1-1-0.dll” maps to the logical DLL named “advapi32.dll”.

In some cases the first redirection is not NULL which means that another redirection will take place, as in the following example (script output):

[*] [32] Virtual DLL [structure offset: 0x188]
        OffsetDllString: 0x10f4 ; StringLength: 0x34 ; OffsetDllRedirector: 0x1128
        Virtual DLL Name: ms-win-core-appinit-l1-1-0
        Number of redirections: 2
        OffsetRedirection1: 0x114c ; RedirectionLength1: 0x0 ; OffsetRedirection2: 0xecc ; RedirectionLength2: 0x18
            Redirection -> String1:  ; String2: kernel32.dll
        OffsetRedirection1: 0xecc ; RedirectionLength1: 0x18 ; OffsetRedirection2: 0x10d8 ; RedirectionLength2: 0x1c
            Redirection -> String1: kernel32.dll ; String2: kernelbase.dll

This means that the api-ms-win-core-appinit-l1-1-0.dll DLL file is redirected to kernel32.dll as indicated by the first redirection, but, with the help of the second redirection, it is shown that kernel32.dll is also redirected to kernelbase.dll.

You'll find below an archive with some Python 3 scripts and their outputs:

scripts

  • ApiSetMapLoad.py: Extracts the content of the .apisetmap section (from apisetschema.dll) to a file.
  • ApiSetMapParse.py: Parses the content of the .apisetmap section.
  • SearchFiles.py: Search for all Virtual DLL files on a system.
  • out_parsingapiset.txt: An output example of ApiSetMapParse.py executed on Windows 8 Consumer Preview 32-bit.
  • win7_64_names.txt: output from SearchFiles.py on Windows 7 SP1 64-bit.
  • win8_32_CP_names.txt: output from SearchFiles.py on Windows 8 Consumer Preview 32-bit.

On the next blog post we'll see how these structures are used by the Windows' loader and the kernel.

Comments