以下代码是自定义 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 回调,所以我找不到问题出在哪里。
有什么想法吗?
您的加载程序不完整。它只是加载 PE 映像,修复其 IAT 和重定位,然后执行其主入口点。但在入口点准备好执行之前还需要执行其他步骤。
在这种情况下,加载程序需要遍历 PE 的 TLS 数据目录,根据需要初始化其信息,以便应用程序代码可以使用它。 MSDN 上有记录: