2

I'm tracking a function called CreateIoCompletionPort in dns.exe. The import tab in IDA shows that it's imported from api-ms-win-core-io-l1-1-0.dll. However, it appears in .rdata section in the .dll file. How to find the assembly code of this function?

.rdata:000000018000110B aCreateiocomple db 'CreateIoCompletionPort',0
.rdata:000000018000110B                                         ; DATA XREF: .rdata:off_1800010A4↑o
.rdata:0000000180001122 ; Exported entry   2. CreateIoCompletionPort
.rdata:0000000180001122                 public CreateIoCompletionPort
.rdata:0000000180001122 ; HANDLE __stdcall CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort, ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads)
.rdata:0000000180001122 CreateIoCompletionPort db 'kernel32.CreateIoCompletionPort',0
.rdata:0000000180001122                                         ; DATA XREF: .rdata:off_180001088↑o
.rdata:0000000180001142 aDeviceiocontro db 'DeviceIoControl',0  ; DATA XREF: .rdata:off_1800010A4↑o
.rdata:0000000180001152 ; Exported entry   3. DeviceIoControl
.rdata:0000000180001152                 public DeviceIoControl

2 Answers 2

4

Before I answer the question you asked, let me answer the question that you probably meant to ask: What are I/O completion ports on Windows or in general?

There is little point in attempting to chase (or reverse engineer) documented behavior. Of course if you want to look into the implementation of the function on the kernel side of things you may want to look at CreateIoCompletionPort() implemented in kernelbase.dll as a wrapper of NtCreateIoCompletion() and NtSetInformationFile() (and NtClose()). But even if you are interested in that part (which you'll find in ntoskrnl.exe) you could peek at the ReactOS to get an idea of how it may have been implemented on Windows (please note that ReactOS very often deviates from Windows, but to get a general idea it's great). Alternatively if you have access to the Windows Research Kernel (WRK) as student of an eligible institution you could have a look at the actual implementation of around a little under twenty years ago. Even better if you happen to have a source license to Windows, but given your question I assume you don't have that.


Actual answer to the question you asked

Generally I suggest you read up on API Sets first: here and here and here before you continue.

The summary is approximately that the API Sets are an attempt by Microsoft to categorize the classic Win32 API and move their implementations into more logical places all the while not breaking forward compatibility for previously built software. Looking at the implementation it also seems as if they are striving for versioning the APIs, probably to get rid of other more fragile mechanisms like AppCompat shims that aim for forward compatibility as well.

As an example: much of the implementation details of what used to be in kernel32.dll during the Windows NT/2000/XP/2003 era went into kernelbase.dll over the years.

After reading up on the details you could then use a tool like Dependencies (somewhat of a spiritual successor to Dependency Walker) Api Set Viewer or API Set Resolver to figure out the dependencies.

However, what you encountered here:

.rdata:0000000180001122 ; Exported entry   2. CreateIoCompletionPort
.rdata:0000000180001122                 public CreateIoCompletionPort
.rdata:0000000180001122 ; HANDLE __stdcall CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort, ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads)
.rdata:0000000180001122 CreateIoCompletionPort db 'kernel32.CreateIoCompletionPort',0
.rdata:0000000180001122                                         ; DATA XREF: .rdata:off_180001088↑o

Is called a function forwarder. It's been in use for ages on Windows and at least the ground rules are easy.

  1. the export points to a string
  2. the string contains a module (stem only, without extension) and a function name: e.g. <module>.<function-name> (or kernel32.CreateIoCompletionPort in your exact example)
  3. when such a function forwarder is imported/resolved, the module gets loaded (if no already loaded) and then the pointer inside the importing entity will point to an address within that module

If you are a developer and you target a modern Windows version such as Windows 10 your application will statically import functions from api-ms-win-core-io-l1-1-0.dll and similar DLLs (see first link at the top) instead of kernel32.dll and others previously. These contain at most stubs but usually function forwarders to the "canonical entity" for said function.

Taking your example CreateIoCompletionPort() whose function forwarder in api-ms-win-core-io-l1-1-0.dll points to the export of the same name from kernel32.dll we see:

.text:000000018001D950 ; HANDLE __stdcall CreateIoCompletionPortStub(HANDLE FileHandle, HANDLE ExistingCompletionPort, ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads)
.text:000000018001D950                 public CreateIoCompletionPortStub
.text:000000018001D950 CreateIoCompletionPortStub proc near    ; DATA XREF: .rdata:0000000180083838↓o
.text:000000018001D950                                         ; .rdata:off_1800990A8↓o
.text:000000018001D950                 jmp     cs:__imp_CreateIoCompletionPort
.text:000000018001D950 CreateIoCompletionPortStub endp

... whereas cs:__imp_CreateIoCompletionPort is:

.idata:0000000180081458 ; HANDLE (__stdcall *CreateIoCompletionPort)(HANDLE FileHandle, HANDLE ExistingCompletionPort, ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads)
.idata:0000000180081458                 extrn __imp_CreateIoCompletionPort:qword

... imported from ... *drum roll* api-ms-win-core-io-l1-1-0.dll. Oh my, we've just gone full circle, haven't we? But the innards of the API Set implementation and the PE loader (from ntdll.dll) help to resolve this. You can read more about API Set resolution here (also mind the references section!), by the author of the aforementioned Dependencies tool. If we use the second mentioned tool, Api Set Viewer, we can see that the functionality from api-ms-win-core-io-l1-1-0.dll is hosted by kernel32.dll and kernelbase.dll respectively:

Api Set Viewer

CreateIoCompletionPort() in particular is hosted by kernelbase.dll. And peeking at its implementation there we can see it wraps the NtCreateIoCompletion(), NtSetInformationFile() and NtClose() NT native APIs about which you can learn in part in the Windows Driver Kit, the few pieces Microsoft divulges via winternl.h in the Windows SDK and in books like Nebbett's "Windows NT/2000 Native API Reference" and similar works of the same era as well as the ReactOS source (mentioned before), the phnt project by Process Hacker and sites like undocumented.ntinternals.net. If you want to dig further into the internals of the API Set Map you could have a first look at the PEB layout (offsets 0x38 and 0x68 respectively) and the article from the author of Dependencies.

And next time you'll know what a function fowarder is when you run across one of these odd "string references" in the export table of a PE file.

1
+100

i wrote this to dissect the file apisetschema.dll some time back found in windows 10 system32 folder

this code is based on Geoff Chappells studies on ApiSetSchema

code in apisetres.cpp

#include "apisetdefs.h"
int main(void)
{
    FILE *infile = NULL;
    errno_t err = fopen_s(&infile, "c:\\windows\\system32\\apisetschema.dll", "rb");
    if (err == 0 && infile != NULL)
    {
        fseek(infile, 0, SEEK_SET);
        size_t siz = fread_s(peheadbuf, BUSIZ, 1, 0x400, infile);
        if (siz == 0x400)
        {
            PIMAGE_DOS_HEADER dhead = (PIMAGE_DOS_HEADER)&peheadbuf;
            PIMAGE_NT_HEADERS64 nthead = (PIMAGE_NT_HEADERS64)(peheadbuf + dhead->e_lfanew);
            PIMAGE_SECTION_HEADER Section = IMAGE_FIRST_SECTION(nthead);
            for (WORD i = 0; i < nthead->FileHeader.NumberOfSections; i++)
            {
                if ((memcmp(Section->Name, ".apiset", 8)) == 0)
                {
                    fseek(infile, Section->PointerToRawData, SEEK_SET);
                    siz = fread_s(peheadbuf, BUSIZ, 1, sizeof(APISET_SCHEMA_HEADER_V6), infile);
                    if (siz == sizeof(APISET_SCHEMA_HEADER_V6))
                    {
                        apisethead = (PAPISET_SCHEMA_HEADER_V6)&peheadbuf;
                        printf("Version Number = %x\n", apisethead->VersionNumber);
                        printf("MapSize = %x\n", apisethead->Mapsize);
                        printf("isSealed = %x\n", apisethead->isSealed);
                        printf("Number of Apisets = %x\n", apisethead->NumAPISets);
                        printf("Offset to NameSpace Entries = %x\n", apisethead->OffsetNameSpaceEntries);
                        printf("Offset To hash Entries = %x\n", apisethead->OffsetHashEntries);
                        printf("Hash Multiplier = %x\n", apisethead->HashMultiplier);
                    }
                    DWORD rawdataaddr = Section->PointerToRawData;
                    DWORD mapoff = rawdataaddr + apisethead->OffsetNameSpaceEntries;
                    for (unsigned int j = 0; j < apisethead->NumAPISets; j++)
                    {
                        DWORD apisetname = (mapoff + (j * sizeof(API_SET_NAMESPACE_ENTRY)));
                        printf("%x\t", apisetname);
                        fseek(infile, apisetname, SEEK_SET);
                        siz = fread_s(apisetnsebuf, BUSIZ, 1, sizeof(API_SET_NAMESPACE_ENTRY), infile);
                        apinsentry = (PAPI_SET_NAMESPACE_ENTRY)(apisetnsebuf);
                        printf("%x\t", apinsentry->OffsetApiSetName);
                        DWORD apivdllname = ((rawdataaddr) + (apinsentry->OffsetApiSetName));
                        fseek(infile, apivdllname, SEEK_SET);
                        memset(wbuf,0,sizeof(wbuf));
                        siz = fread_s(wbuf, WBUSIZ , 1, apinsentry->SizeApiSetName, infile);
                        printf("%-60.*S\t", (apinsentry->SizeApiSetName), wbuf);
                        printf("%x\t", apinsentry->OffsetValueEntries); 
                        DWORD apildllname = ((rawdataaddr) + (apinsentry->OffsetValueEntries) );
                        printf("%x\t", apildllname);
                        fseek(infile, apildllname, SEEK_SET);
                        siz = fread_s(apisetvalentbuf, BUSIZ, 1, sizeof(API_SET_VALUE_ENTRY), infile);
                        apivalentry = (PAPI_SET_VALUE_ENTRY)(apisetvalentbuf);
                        printf("%x\t" , apivalentry->ValueOffset);
                        DWORD apivalentname = ((rawdataaddr) + (apivalentry->ValueOffset));
                        fseek(infile,apivalentname,SEEK_SET);
                        memset(wbuf,0,sizeof(wbuf));
                        siz = fread_s(wbuf, WBUSIZ , 1, apivalentry->ValueLength, infile);
                        printf("%-60.*S\n", (apivalentry->ValueLength)/2, wbuf);

                    }
                };
                Section++;
            }
        }
    }
}

header file apisetres.h

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#define BUSIZ 0x500
#define WBUSIZ BUSIZ / 2

unsigned char peheadbuf[BUSIZ];
unsigned char apisetnsebuf[BUSIZ];
unsigned char apisetvalentbuf[BUSIZ];
wchar_t wbuf[BUSIZ];

typedef struct _APISET_SCHEMA_HEADER_V6
{
    DWORD VersionNumber;
    DWORD Mapsize;
    DWORD isSealed;
    DWORD NumAPISets;
    DWORD OffsetNameSpaceEntries;
    DWORD OffsetHashEntries;
    DWORD HashMultiplier;
} APISET_SCHEMA_HEADER_V6, *PAPISET_SCHEMA_HEADER_V6;

typedef struct _API_SET_NAMESPACE_ENTRY
{
    DWORD isSealed;
    DWORD OffsetApiSetName;
    DWORD SizeApiSetName;
    DWORD SizeApiSetNameNoHyphen;
    DWORD OffsetValueEntries;
    DWORD NumHosts;

} API_SET_NAMESPACE_ENTRY, *PAPI_SET_NAMESPACE_ENTRY;

typedef struct _API_SET_VALUE_ENTRY
{
    DWORD Flags;
    DWORD NameOffset;
    DWORD NameLen;
    DWORD ValueOffset;
    DWORD ValueLength;

} API_SET_VALUE_ENTRY, *PAPI_SET_VALUE_ENTRY;

PAPISET_SCHEMA_HEADER_V6 apisethead = NULL;
PAPI_SET_NAMESPACE_ENTRY apinsentry = NULL;
PAPI_SET_VALUE_ENTRY apivalentry = NULL;

compiled and linked in win 10 x64 vc 2019 community as x64 binary with

cl /Zi /W4 /analyze /Ehsc /Od apisetres.cpp /link /release

executed to print both virtual dll as well as logical dll names

:\>apisetres.exe  > output.txt

:\>head -n 15 output.txt
Version Number = 6
MapSize = 1c6b4
isSealed = 0
Number of Apisets = 37a
Offset to NameSpace Entries = 1c
Offset To hash Entries = 1aae4
Hash Multiplier = 1f
61c     538c    api-ms-onecoreuap-print-render-l1-1-0                           53d8    59d8    53ec    printrenderapihost.dll
634     5418    api-ms-win-appmodel-identity-l1-2-0                             5460    5a60    5474    kernel.appcore.dll
64c     5498    api-ms-win-appmodel-runtime-internal-l1-1-7                     54f0    5af0    5474    kernel.appcore.dll
664     5504    api-ms-win-appmodel-runtime-l1-1-3                              5548    5b48    5474    kernel.appcore.dll
67c     555c    api-ms-win-appmodel-state-l1-1-2                                559c    5b9c    5474    kernel.appcore.dll
694     55b0    api-ms-win-appmodel-state-l1-2-0                                55f0    5bf0    5474    kernel.appcore.dll
6ac     5604    api-ms-win-appmodel-unlock-l1-1-0                               5648    5c48    5474    kernel.appcore.dll
6c4     565c    api-ms-win-base-bootconfig-l1-1-0                               56a0    5ca0    56b4    advapi32.dll

the hash entries ripped and pasted to a file named hashdump
sorted the hashes index wise instead of default name wise
independnatly confirmed first 4 names by hashing them using hash algo described in the study with a python script as below

#index wise sorting of hashkeys from apisetschema.dll raw data 
import numpy as np
np.set_printoptions(formatter={'all':lambda x: format(x , '08X')})
a = np.fromfile('hashdump',np.dtype([('hash', '<u4'), ('index', '<u4')]))
b = sorted(a,key = lambda x: x[1])
c = list(filter(None,b))
print( c[0],c[1],c[2],c[3] )

#hashing actual virtual dll names excluding last hyphen 

apn = [
"api-ms-onecoreuap-print-render-l1-1",
"api-ms-win-appmodel-identity-l1-2",
"api-ms-win-appmodel-runtime-internal-l1-1",
"api-ms-win-appmodel-runtime-l1-1"
]

for j in range(0,len(apn),1):
    hashfactor = 0x1f
    hashkey = 0
    for i in apn[j]:
        hashkey = (hashkey * hashfactor + ord(i)) & 0xffffffff
    print("(%08X, %08x)" % (hashkey,j),end =' ')

actual hashes index wise from apisetschema.dll versus algorithmic hashing of names from output.txt

:\>apisethash.py
(BFEC7B66, 00000000) (1079FB19, 00000001) (59E37344, 00000002) (3655E8BE, 00000003)
(BFEC7B66, 00000000) (1079FB19, 00000001) (59E37344, 00000002) (3655E8BE, 00000003)
:\> 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.