5

今日、Windows Portable Executable ファイル構造の解析をいじっていると、非常に奇妙な問題に遭遇しました。具体的には、エクスポート テーブルで。

DLL 内のエクスポートされた関数の関数アドレスを解決しようとすると、スタック オーバーフローが発生しました (これが最も適切な QA ボードのように思えました)。

既存のメソッドGetProcAddressを呼び出すのではなく、手動で解析を行う独自のバージョンを作成しました。GetProcAddress既存の方法を使用するように言わないでください。GetProcAddressそれは私の現在の状況には適しておらず、これから何かを学びたいと思っています。

私が遭遇するほとんどの状況で、私のバージョンは見事に機能し、問題は発生しませんでした。API-MS-Win-Core-ProcessThreads-L1-1-0.dllただし、この関数は(の再帰的解析の一部として)という名前の DLL に対してテストされてKernel32.dllおり、これが StackOverflow が発生したときです。

からエクスポートされた次の関数に絞り込みましたAPI-MS-Win-Core-ProcessThreads-L1-1-0.dll

CreateRemoteThreadEx

さて、このエクスポートされた関数は、実際には転送されたエクスポートです。通常、これは心配ありません。転送されたエクスポートを処理するように関数を作成しました。ただし、この関数はに転送されます

api-ms-win-core-processthreads-l1-1-0.CreateRemoteThreadEx

ここで問題を見ている人はいますか?コードをステップ実行すると、GetProcAddress関数は を呼び出しLoadLibraryapi-ms-win-core-processthreads-l1-1-0再帰的に lookup を試みますCreateRemoteThreadEx。ただし、次の反復では、CreateRemoteThreadEx関数は再び転送されます...

api-ms-win-core-processthreads-l1-1-0.CreateRemoteThreadEx

そして、StackOverflow が始まります。もう少し調査した結果、呼び出しの結果がわかりました

LoadLibraryA("api-ms-win-core-processthreads-l1-1-0");

と同じ結果を返します

LoadLibraryA("kernel32.dll");

私は困惑しています。

これが私の現在のコードです:

#include <Windows.h>

#define MKPTR(p1,p2) ((DWORD_PTR)(p1) + (DWORD_PTR)(p2))

INT LookupExport(IMAGE_DOS_HEADER* pDosHd, DWORD* pNames, DWORD nNames, LPCSTR lpProcName)
{
    // Do a binary search on the name pointer table
    INT start = 0, 
        index = -1,
        middle = -1, 
        end = nNames - 1,
        cmp = 0;

    CHAR *pName;

    while (start <= end && index == -1)
    {
        middle = (start + end) >> 1;
        pName = (CHAR*)MKPTR(pDosHd, pNames[middle]);

        if ((cmp = strcmp(pName, lpProcName)) == 0)
            index = middle; // found
        else if (cmp < 0)
            start = middle + 1;
        else
            end = middle;
    }

    return index;
}

FARPROC InternalGetProcAddress(HMODULE hModule, LPCSTR lpProcName)
{
    BOOL ordinalSearch = HIWORD(lpProcName) == 0;
    WORD ordinal = 0;
    IMAGE_DOS_HEADER *pDosHd = (IMAGE_DOS_HEADER*)hModule;

    if (pDosHd->e_magic != IMAGE_DOS_SIGNATURE)
        return NULL;

    IMAGE_NT_HEADERS *pNtHd = (IMAGE_NT_HEADERS*)MKPTR(pDosHd, pDosHd->e_lfanew);
    if (pNtHd->Signature != IMAGE_NT_SIGNATURE)
        return NULL;

    IMAGE_DATA_DIRECTORY directory = pNtHd->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    if (directory.Size == 0 || directory.VirtualAddress == 0)
        return NULL;

    IMAGE_EXPORT_DIRECTORY *pExports = (IMAGE_EXPORT_DIRECTORY*)MKPTR(pDosHd, directory.VirtualAddress);
    if (!ordinalSearch)
    {
        INT index = LookupExport(pDosHd, (DWORD*)MKPTR(pDosHd, pExports->AddressOfNames), pExports->NumberOfNames, lpProcName);
        if (index == -1)
            return NULL;
        ordinal = ((WORD*)MKPTR(pDosHd, pExports->AddressOfNameOrdinals))[index];
    }
    else
    {
        ordinal = LOWORD(lpProcName);
    }

    INT delta = pExports->Base - 1;
    DWORD dwAddress = ((DWORD*)MKPTR(pDosHd, pExports->AddressOfFunctions))[ordinal - delta];
    // Check whether forwarded:
    if (dwAddress >= directory.VirtualAddress && dwAddress < (directory.VirtualAddress + directory.Size))
    {
        CHAR pForward[256];
        strcpy(pForward, (CHAR*)MKPTR(pDosHd, dwAddress));
        CHAR *pFunction = strchr(pForward, '.');
        if (pFunction == NULL)
            return NULL;

        // break into seperate parts and recurse
        *pFunction++ = 0;
        return InternalGetProcAddress(LoadLibraryA(pForward), pFunction);
    }

    return (FARPROC)MKPTR(hModule, dwAddress);
}

どんな洞察も大歓迎です。

4

1 に答える 1

2

@sergmatのアドバイスに従った後、APIセットのドキュメントを調べました(興味のある人はここにあります)。GetProcAddress コードを変更して、Api Set テーブルの単純なルックアップを実行しました。

#include <Windows.h>

#define MKPTR(p1,p2) ((DWORD_PTR)(p1) + (DWORD_PTR)(p2))

typedef struct _stripped_peb32 {
    BYTE    unused1[0x038];
    PVOID   ApiSet;
    BYTE    unused2[0x1AC];
} PEB32;

typedef struct _stripped_peb64 {
    BYTE    unused1[0x068];
    PVOID   ApiSet;
    BYTE    unused2[0x23C];
} PEB64;

typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID       Reserved1;
    LPVOID      PebBaseAddress;
    PVOID       Reserved2[2];
    ULONG_PTR   UniqueProcessId;
    PVOID       Reserved3;
} PROCESS_BASIC_INFORMATION;

typedef struct _api_set_host {
    DWORD           ImportModuleName;
    WORD            ImportModuleNameLength;
    DWORD           HostModuleName;
    WORD            HostModuleNameLength;
} API_SET_HOST;

typedef struct _api_set_host_descriptor {
    DWORD           NumberOfHosts;
    API_SET_HOST    Hosts[1];
} API_SET_HOST_DESCRIPTOR;

typedef struct _api_set_entry {
    DWORD           Name;
    WORD            NameLength;
    DWORD           HostDescriptor;
} API_SET_ENTRY;

typedef struct _api_set_header {
    DWORD           unknown1;
    DWORD           NumberOfEntries;
    API_SET_ENTRY   Entries[1];
} API_SET_HEADER;

typedef NTSTATUS (__stdcall *fnNtQueryInformationProcess)(HANDLE ProcessHandle, DWORD ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength);

API_SET_HEADER *GetApiSetHeader()
{
    fnNtQueryInformationProcess NtQueryInformationProcess = (fnNtQueryInformationProcess)GetProcAddress(LoadLibraryW(L"ntdll.dll"), "NtQueryInformationProcess");
    if (!NtQueryInformationProcess)
        return NULL;

    PROCESS_BASIC_INFORMATION info;
    if (NtQueryInformationProcess(GetCurrentProcess(), 0, &info, sizeof(info), NULL) != S_OK)
        return NULL;

#if defined(_WIN32)
    return (API_SET_HEADER*)(((PEB32*)info.PebBaseAddress)->ApiSet);
#elif defined(_WIN64)
    return (API_SET_HEADER*)(((PEB64*)info.PebBaseAddress)->ApiSet);
#else
    return NULL; // unsupported architecture
#endif
}

HMODULE ResolveImportMap(LPCSTR lpModuleName)
{
    API_SET_HEADER *pHeader = GetApiSetHeader();
    if (pHeader == NULL)
        return NULL;
    API_SET_ENTRY *pEntry = pHeader->Entries;
    API_SET_HOST_DESCRIPTOR* pDescriptor;
    wchar_t module[128];

    // First, normalize the LPCSTR, the API Set table doesn't have the API- prefix
    if (strnicmp("api-", lpModuleName, 4) == 0)
        lpModuleName += 4;

    // Next convert the LPCSTR to a unicode string for comparison and remove the extension (if found)
    mbstowcs(module, lpModuleName, sizeof(module) / sizeof(wchar_t));
    wchar_t *dot = wcsrchr(module, L'.');
    if (dot) *dot = L'\0';

    // Begin the lookup:
    // todo: implement a case-insensitive binary search, not much to be gained for the effort IMO as there's
    //          only 35 entries in the current version of Windows 7, but the option is there for performance nuts.
    for(unsigned long i = 0; i < pHeader->NumberOfEntries; ++i, ++pEntry)
    {
        // Check the top-level host map
        if (wcsnicmp(module, (const wchar_t*)MKPTR(pHeader, pEntry->Name), pEntry->NameLength) == 0)
        {
            pDescriptor = (API_SET_HOST_DESCRIPTOR*)MKPTR(pHeader, pEntry->HostDescriptor);
            // iterate backwards through the hosts to find the most important one (I think this is naive)
            for(unsigned long j = pDescriptor->NumberOfHosts; j > 0; --j)
            {
                if (pDescriptor->Hosts[j - 1].HostModuleNameLength)
                {
                    memcpy(module, (const void*)MKPTR(pHeader, pDescriptor->Hosts[j - 1].HostModuleName), pDescriptor->Hosts[j - 1].HostModuleNameLength);
                    module[pDescriptor->Hosts[j - 1].HostModuleNameLength / sizeof(wchar_t)] = L'\0';
                    return GetModuleHandleW(module); // All the modules should already be loaded, so use GetModuleHandle rather than LoadLibrary
                }
            }
        }
    }

    return NULL;
}

INT LookupExport(IMAGE_DOS_HEADER* pDosHd, DWORD* pNames, DWORD nNames, LPCSTR lpProcName)
{
    // Do a binary search on the name pointer table
    INT start = 0, 
        index = -1,
        middle = -1, 
        end = nNames - 1,
        cmp = 0;

    CHAR *pName;

    while (start <= end && index == -1)
    {
        middle = (start + end) >> 1;
        pName = (CHAR*)MKPTR(pDosHd, pNames[middle]);

        if ((cmp = strcmp(pName, lpProcName)) == 0)
            index = middle; 
        else if (cmp < 0)
            start = middle + 1;
        else
            end = middle;
    }

    return index;
}

FARPROC InternalGetProcAddress(HMODULE hModule, LPCSTR lpProcName)
{
    if (hModule == NULL)
        return NULL;

    BOOL ordinalSearch = HIWORD(lpProcName) == 0;
    WORD ordinal = 0;
    IMAGE_DOS_HEADER *pDosHd = (IMAGE_DOS_HEADER*)hModule;

    if (pDosHd->e_magic != IMAGE_DOS_SIGNATURE)
        return NULL;

    IMAGE_NT_HEADERS *pNtHd = (IMAGE_NT_HEADERS*)MKPTR(pDosHd, pDosHd->e_lfanew);
    if (pNtHd->Signature != IMAGE_NT_SIGNATURE)
        return NULL;

    IMAGE_DATA_DIRECTORY directory = pNtHd->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    if (directory.Size == 0 || directory.VirtualAddress == 0)
        return NULL;

    IMAGE_EXPORT_DIRECTORY *pExports = (IMAGE_EXPORT_DIRECTORY*)MKPTR(pDosHd, directory.VirtualAddress);
    if (!ordinalSearch)
    {
        INT index = LookupExport(pDosHd, (DWORD*)MKPTR(pDosHd, pExports->AddressOfNames), pExports->NumberOfNames, lpProcName);
        if (index == -1)
            return NULL;
        ordinal = ((WORD*)MKPTR(pDosHd, pExports->AddressOfNameOrdinals))[index];
    }
    else
    {
        ordinal = LOWORD(lpProcName);
    }

    INT ordbase = pExports->Base - 1;
    DWORD dwAddress = ((DWORD*)MKPTR(pDosHd, pExports->AddressOfFunctions))[ordinal - ordbase];
    // Check whether forwarded:
    if (dwAddress >= directory.VirtualAddress && dwAddress < (directory.VirtualAddress + directory.Size))
    {
        CHAR pForward[256];
        strcpy(pForward, (CHAR*)MKPTR(pDosHd, dwAddress));
        CHAR *pFunction = strchr(pForward, '.');
        if (pFunction == NULL)
            return NULL;

        // break into seperate parts and recurse
        *pFunction++ = 0;
        // check if ordinal-forwarded
        if (*pFunction == '#')
            pFunction = (PSTR)(unsigned short)(atoi(++pFunction));

        HMODULE hDestination = LoadLibraryA(pForward);

        // detect an infinite loop, the forward declaration requests the same module handle with 
        // the same function lookup, this could be an Api Set (Windows7+)
        if (hDestination == hModule && (ordinalSearch ? LOWORD(pFunction) == LOWORD(lpProcName) : strcmp(pFunction, lpProcName) == 0))
            hDestination = ResolveImportMap(pForward); // ResolveImportMap will return NULL if not an API Set and so avoid infinite recursion

        return InternalGetProcAddress(hDestination, pFunction);
    }

    return (FARPROC)MKPTR(hModule, dwAddress);
}
于 2013-02-08T09:39:42.850 に答える