Developing PE file packer step-by-step. Step 10. Overall architecture

Previous step is here.

I will do nothing with the code at this step, just explain architecture of the packer in easy to understand form, or, more precisely, of the file packed with it. I do this to help you understand how the packed file is organized without studying all the steps in detail. Possibly I should have started with this, but now it's too late.

So, imagine that we have a DLL file with following directories:
imports
exports
resources (including version information)
relocations
load configuration
TLS with callbacks

In short, just everything. How will all this be placed in the packed file?

This picture shows the structure of file after packing. It always has two sections. The first one contains packed data of an original file, structure with original file information required by the unpacker, and resources (not all of them, only icons, manifest and version information), if original file has them. The second section contains the unpacker body configured to unpack the current file. TLS directories (data and callbacks), relocations for the unpacker and TLS, original export table and load configuration directory will be also located there optionally.

That's all, not too complicated!

4 thoughts on “Developing PE file packer step-by-step. Step 10. Overall architecture”

  1. Hey d_x,
    I have a question a little bit of topic, ( I write here because I cannot find any other way)
    So I have this Pe loader:
    The problem I come up with is When I try to load an exe that uses this function GetModuleHandle(NULL) the ImageBase address I get is the ImageBase of the Pe loader's exe itself and not the char* peImage = (char*)VirtualAlloc(relocOk ? 0 : imgBaseAt, imgSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); which is the address in which the program is loaded.Any ideas on how I can modify that PE loader so when a program that uses GetModuleHandle(NULL) the return value is the address in which the program is loaded? Thanks in advance.

    #include
    #include
    #include
    #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 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 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 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)
    {
    std::cout << GetModuleHandle(NULL) << std::endl;
    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;
    }

  2. hi,

    wrote a PE loader and used RtlAddFunctionTable() to add a dynamic function table to handle exceptions raised by the loaded PE.

    I tested my PE loader with the following app, which is using SEH to handle a thrown exception:

    #include

    #include

    int main() {

    __try {

    int age = 16;

    if (age < 18) {

    RaiseException(0xDEAD, 0, 0, nullptr); // You can use a custom exception code

    }

    std::cout << "Access granted - you are old enough.";

    }

    __except (EXCEPTION_EXECUTE_HANDLER) {

    std::cout << "Access denied - You must be at least 18 years old.\n";

    }

    return 0;

    }

    My loader was able to execute the app just fine.

    Then, I tested the same code using C++ standard exceptions:

    #include

    #include

    #include

    using namespace std;

    int main() {

    try {

    int age = 16;

    if (age >= 18) {

    cout << "Access granted - you are old enough.";

    }

    else {

    throw (age);

    }

    }

    catch (int myNum) {

    cout << "Access denied - You must be at least 18 years old.\n";

    cout << "Age is: " << myNum;

    }

    return 0;

    }

    My app exploded during its execution.

    I though when using C++ standard exceptions, Visual Studio turns them into SEH code? I also tested it with every value in the Enable C++ Exceptions field in Visual Studio Settings.

    What am I missing?

Leave a Reply

Your email address will not be published. Required fields are marked *