自定义 PE 加载器中的线程本地存储

问题描述 投票:0回答:1

以下代码是自定义 PE 加载程序,用于修复 IAT 并应用重定位(如果需要):

#include <stdio.h>
#include <windows.h>
#include <iostream>
#pragma warning(disable : 4996)
#define getNtHdr(buf) ((IMAGE_NT_HEADERS *)((size_t)buf + ((IMAGE_DOS_HEADER *)buf)->e_lfanew))
#define getSectionArr(buf) ((IMAGE_SECTION_HEADER *)((size_t)buf + ((IMAGE_DOS_HEADER *)buf)->e_lfanew + sizeof(IMAGE_NT_HEADERS)))

bool readBinFile(const char fileName[], char** bufPtr, size_t& length)
{
    if (FILE* fp = fopen(fileName, "rb"))
    {
        fseek(fp, 0, SEEK_END);
        length = ftell(fp);
        *bufPtr = (char*)malloc(length + 1);
        fseek(fp, 0, SEEK_SET);
        fread(*bufPtr, sizeof(char), length, fp);
        return true;
    }
    else
        return false;
}

void fixIat(char* peImage)
{
    auto dir_ImportTable = getNtHdr(peImage)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    auto impModuleList = (IMAGE_IMPORT_DESCRIPTOR*)&peImage[dir_ImportTable.VirtualAddress];
    for (HMODULE currMod; impModuleList->Name; impModuleList++)
    {
        printf("\timport module : %s\n", &peImage[impModuleList->Name]);
        currMod = LoadLibraryA(&peImage[impModuleList->Name]);

        auto arr_callVia = (IMAGE_THUNK_DATA*)&peImage[impModuleList->FirstThunk];
        for (int count = 0; arr_callVia->u1.Function; count++, arr_callVia++)
        {
            auto curr_impApi = (PIMAGE_IMPORT_BY_NAME)&peImage[arr_callVia->u1.Function];
            arr_callVia->u1.Function = (size_t)GetProcAddress(currMod, (char*)curr_impApi->Name);
            if (count < 5)
                printf("\t\t- fix imp_%s\n", curr_impApi->Name);
        }
    }
}

#define RELOC_32BIT_FIELD 0x03
#define RELOC_64BIT_FIELD 0x0A
typedef struct BASE_RELOCATION_ENTRY
{
    WORD Offset : 12;
    WORD Type : 4;
} entry;

void fixReloc(char* peImage)
{
    auto dir_RelocTable = getNtHdr(peImage)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    auto relocHdrBase = &peImage[dir_RelocTable.VirtualAddress];
    for (UINT hdrOffset = 0; hdrOffset < dir_RelocTable.Size;)
    {
        auto relocHdr = (IMAGE_BASE_RELOCATION*)&relocHdrBase[hdrOffset];
        entry* entryList = (entry*)((size_t)relocHdr + sizeof(*relocHdr));
        for (size_t i = 0; i < (relocHdr->SizeOfBlock - sizeof(*relocHdr)) / sizeof(entry); i++)
        {
            size_t rva_Where2Patch = relocHdr->VirtualAddress + entryList[i].Offset;
            if (entryList[i].Type == RELOC_32BIT_FIELD)
            {
                *(UINT32*)&peImage[rva_Where2Patch] -= (size_t)getNtHdr(peImage)->OptionalHeader.ImageBase;
                *(UINT32*)&peImage[rva_Where2Patch] += (size_t)peImage;
            }
            else if (entryList[i].Type == RELOC_64BIT_FIELD)
            {
                *(UINT64*)&peImage[rva_Where2Patch] -= (size_t)getNtHdr(peImage)->OptionalHeader.ImageBase;
                *(UINT64*)&peImage[rva_Where2Patch] += (size_t)peImage;
            }
        }
        hdrOffset += relocHdr->SizeOfBlock;
    }
}

void peLoader(char* exeData)
{
    auto imgBaseAt = (void*)getNtHdr(exeData)->OptionalHeader.ImageBase;
    auto imgSize = getNtHdr(exeData)->OptionalHeader.SizeOfImage;
    bool relocOk = !!getNtHdr(exeData)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;

    char* peImage = (char*)VirtualAlloc(relocOk ? 0 : imgBaseAt, imgSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (peImage)
    {
        printf("[v] exe file mapped @ %p\n", peImage);
        memcpy(peImage, exeData, getNtHdr(exeData)->OptionalHeader.SizeOfHeaders);
        for (int i = 0; i < getNtHdr(exeData)->FileHeader.NumberOfSections; i++)
        {
            auto curr_section = getSectionArr(exeData)[i];
            memcpy(
                &peImage[curr_section.VirtualAddress],
                &exeData[curr_section.PointerToRawData],
                curr_section.SizeOfRawData);
        }
        printf("[v] file mapping ok\n");

        fixIat(peImage);
        printf("[v] fix iat.\n");



        fixReloc(peImage);
        printf("[v] apply reloc.\n");

        auto addrOfEntry = getNtHdr(exeData)->OptionalHeader.AddressOfEntryPoint;
        printf("[v] invoke entry @ %p ...\n", &peImage[addrOfEntry]);
        ((void (*)()) & peImage[addrOfEntry])();
    
}
    else
        printf("[x] alloc memory for exe @ %p failure.\n", imgBaseAt);
    
}

int main(int argc, char** argv)
{
    char* exeBuf;
    size_t exeSize;
    if (argc != 2)
        puts("usage: ./peLoader [path/to/exe]");
    else if (readBinFile(argv[1], &exeBuf, exeSize))
        peLoader(exeBuf);
    else
        puts("[!] exe file not found.");
    return 0;
}

问题是,当我使用以下使用线程本地存储的程序对其进行测试时,初始线程本地变量在使用此 PE 加载器加载时似乎未初始化:

#include "windows.h"
#include <stdio.h>
#include <thread>
#include <iostream>
#pragma warning(disable : 4996)
int _declspec(thread) a = 999;




#define getNtHdr(buf) ((IMAGE_NT_HEADERS *)((size_t)buf + ((IMAGE_DOS_HEADER *)buf)->e_lfanew))
#define getSectionArr(buf) ((IMAGE_SECTION_HEADER *)((size_t)buf + ((IMAGE_DOS_HEADER *)buf)->e_lfanew + sizeof(IMAGE_NT_HEADERS)))
void thread_func() {
    std::cout << a << std::endl;
    a = 12;
        
    std::cout << a << std::endl;
    
}

int main()
{
    a = 8;
    std::cout << a << std::endl;
    
    
    std::thread v(thread_func);
    v.join();
    
}

正常输出为:

8
999
12

当我通过PE加载器执行它时,输出是:

8
0
12

我认为问题在于,线程局部

a
变量在我分配
main()
therad_func()
中的值之前并未初始化。

如您所见,没有应用 TLS 回调,所以我找不到问题出在哪里。

有什么想法吗?

c++ winapi
1个回答
0
投票

您的加载程序不完整。它只是加载 PE 映像,修复其 IAT 和重定位,然后执行其主入口点。但在入口点准备好执行之前还需要执行其他步骤。

在这种情况下,加载程序需要遍历 PE 的 TLS 数据目录,根据需要初始化其信息,以便应用程序代码可以使用它。 MSDN 上有记录:

PE 格式:.tls 部分

© www.soinside.com 2019 - 2024. All rights reserved.