windowsvisual-c++portable-executable

PE Loader with Relocation


I'm trying to learn PE format and how PE loaders work, I took this repository as an example, https://github.com/TheD1rkMtr/FilelessPELoader.

The FilelessPELoader doesn't support relocation. Right now if the PE loader can allocate executable memory at PE's ImageBase the PE file could be loaded and executed otherwise it will fail.

    preferAddr = (LPVOID)ntHeader->OptionalHeader.ImageBase;
...
    pImageBase = (BYTE*)VirtualAlloc(
        preferAddr,
        ntHeader->OptionalHeader.SizeOfImage,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE);

So I did my best to implement this feature myself but unfortunately I don't get the desired result. Here are my changes to PELoader() function,

void pe_loader(char* data, DWORD datasize) {

    masquerade_cmdline();

    DWORD chksum = 0;
    for (DWORD i = 0; i < datasize; i++) {
        chksum = data[i] * i + chksum / 3;
    };

    BYTE* pImageBase = NULL;
    LPVOID preferAddr = 0;
    DWORD OldProtect = 0;

    IMAGE_NT_HEADERS* ntHeader = (IMAGE_NT_HEADERS*)get_nt_header(data);
    if (!ntHeader) {
        exit(0);
    }

    IMAGE_DATA_DIRECTORY* relocDir = get_pe_directory(data, IMAGE_DIRECTORY_ENTRY_BASERELOC);
    preferAddr = (LPVOID)ntHeader->OptionalHeader.ImageBase;

    HMODULE dll = LoadLibraryA("ntdll.dll");
    NtUnmapViewOfSectionProc NtUnmapViewOfSection = (NtUnmapViewOfSectionProc)
        GetProcAddress(dll, "NtUnmapViewOfSection");
    NtUnmapViewOfSection((HANDLE)-1, (LPVOID)ntHeader->OptionalHeader.ImageBase);

    //pImageBase = (BYTE*)VirtualAlloc(
    //    preferAddr,
    //    ntHeader->OptionalHeader.SizeOfImage,
    //    MEM_COMMIT | MEM_RESERVE,
    //    PAGE_EXECUTE_READWRITE);
    //if (!pImageBase) {
    //    if (!relocDir) {
    //        exit(0);
    //    }
    //    else {
    //        pImageBase = (BYTE*)VirtualAlloc(
    //            NULL,
    //            ntHeader->OptionalHeader.SizeOfImage,
    //            MEM_COMMIT | MEM_RESERVE,
    //            PAGE_EXECUTE_READWRITE);
    //        if (!pImageBase)
    //        {
    //            exit(0);
    //        }
    //    }
    //}

    pImageBase = (BYTE*)VirtualAlloc(NULL,
        ntHeader->OptionalHeader.SizeOfImage,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE);
    if (!pImageBase)
    {
        exit(0);
    }

    // Copy the PE file in allocated memory
    ntHeader->OptionalHeader.ImageBase = (size_t)pImageBase;
    memcpy(pImageBase, data, ntHeader->OptionalHeader.SizeOfHeaders);

    LPVOID lpRelocHdr = NULL;
    IMAGE_SECTION_HEADER* SectionHeaderArr =
        (IMAGE_SECTION_HEADER*)(size_t(ntHeader) + sizeof(IMAGE_NT_HEADERS));

    for (int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++)
    {
        if (strcmp((char*)SectionHeaderArr[i].Name, ".reloc") == 0)
        {
            //lpImageRelocSection = (PIMAGE_SECTION_HEADER)SectionHeaderArr[i].VirtualAddress;
            lpRelocHdr = LPVOID(size_t(pImageBase) + SectionHeaderArr[i].VirtualAddress);
        }

        memcpy(LPVOID(size_t(pImageBase) + SectionHeaderArr[i].VirtualAddress),
            LPVOID(size_t(data) + SectionHeaderArr[i].PointerToRawData),
            SectionHeaderArr[i].SizeOfRawData);
    }


    printf("Executable memory : %p\n", pImageBase);
    printf("RW memory : %p\n", data);

    DWORD DeltaImageBase = (DWORD)pImageBase - ntHeader->OptionalHeader.ImageBase;
    IMAGE_BASE_RELOCATION* ImageBaseReloc = (IMAGE_BASE_RELOCATION*)lpRelocHdr;
    cout << hex << ImageBaseReloc << endl;
    
    while (ImageBaseReloc->VirtualAddress != 0)
    {
        DWORD RelocEntries = (ImageBaseReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
        
        cout << "Virtual Address : " << hex << ImageBaseReloc->VirtualAddress << endl;
        cout << "Relocation Entries : " << hex << RelocEntries << endl;
        cout << "Size of Relocation Header : " << hex << (ImageBaseReloc->SizeOfBlock) << endl;
        cout << hex << ImageBaseReloc << endl;

        WORD* ImageRelocEntry = (WORD*)((char*)ImageBaseReloc + sizeof(IMAGE_BASE_RELOCATION));
        for (int i = 0; i < RelocEntries; i++)
        {
            //type is the first 4 bits of the relocation word
            int type = ImageRelocEntry[i] >> 12;
            // offset is the last 12 bits
            int offset = ImageRelocEntry[i] & 0x0fff;
            cout << type << "\t" << offset << endl;
            if (type == 0)
                continue;

            DWORD* ChangeAddr = (DWORD*)(pImageBase + ImageBaseReloc->VirtualAddress + offset);
            *ChangeAddr = DeltaImageBase;
            //memcpy(ChangeAddr, &DeltaImageBase, sizeof(DWORD));
        }

        ImageBaseReloc = (IMAGE_BASE_RELOCATION*)(((char*)ImageBaseReloc + ImageBaseReloc->SizeOfBlock));
    }
    

    // Fix the PE Import addr table
    repair_iat(pImageBase);

    // AddressOfEntryPoint
    size_t ret_addr = (size_t)(pImageBase)+ntHeader->OptionalHeader.AddressOfEntryPoint;

    // Jumping to the EntryPoint of the loaded PE
    std::cout << "  - Jumping..." << std::endl;

    //
    EnumThreadWindows(0, (WNDENUMPROC)ret_addr, 0);

    return;
}

The original functions code before my modification could be found here

I would appreciate your help to tell me what I did wrong and how I can fix the code?

Also when parsing the relocation table and entries, I still need to repair the import address table as well right? Those are two separate things? I'm not too sure about that part either.

Thanks


Solution

  • I'm getting back to this question... while searching for some clue online I came across the following youtube video enter link description here. It helped me understand what I should do and what I was doing wrong.

    So the working code i ended up with is,

    void pe_loader(char* data, DWORD datasize) {
    
        masquerade_cmdline();
    
        DWORD chksum = 0;
        for (DWORD i = 0; i < datasize; i++) {
            chksum = data[i] * i + chksum / 3;
        };
    
        BYTE* pImageBase = NULL;
        LPVOID preferAddr = 0;
        DWORD OldProtect = 0;
    
        IMAGE_NT_HEADERS* ntHeader = (IMAGE_NT_HEADERS*)get_nt_header(data);
        if (!ntHeader) {
            exit(0);
        }
    
        IMAGE_DATA_DIRECTORY* relocDir = get_pe_directory(data, IMAGE_DIRECTORY_ENTRY_BASERELOC);
        preferAddr = (LPVOID)ntHeader->OptionalHeader.ImageBase;
    
    
        pImageBase = (BYTE*)VirtualAlloc(NULL,
            ntHeader->OptionalHeader.SizeOfImage,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_EXECUTE_READWRITE);
        if (!pImageBase)
        {
            exit(0);
        }
    
        // Copy the PE file in allocated memory
        memcpy(pImageBase, data, ntHeader->OptionalHeader.SizeOfHeaders);
    
        LPVOID lpRelocHdr = NULL;
        IMAGE_SECTION_HEADER* SectionHeaderArr =
            (IMAGE_SECTION_HEADER*)(size_t(ntHeader) + sizeof(IMAGE_NT_HEADERS));
    
        for (int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++)
        {
            if (strcmp((char*)SectionHeaderArr[i].Name, ".reloc") == 0)
            {
                //lpImageRelocSection = (PIMAGE_SECTION_HEADER)SectionHeaderArr[i].VirtualAddress;
                lpRelocHdr = LPVOID(size_t(pImageBase) + SectionHeaderArr[i].VirtualAddress);
            }
    
            memcpy(LPVOID(size_t(pImageBase) + SectionHeaderArr[i].VirtualAddress),
                LPVOID(size_t(data) + SectionHeaderArr[i].PointerToRawData),
                SectionHeaderArr[i].SizeOfRawData);
        }
    
    
        printf("Executable memory : %p\n", pImageBase);
        printf("RW memory : %p\n", data);
    
        DWORD DeltaImageBase = (DWORD)pImageBase - ntHeader->OptionalHeader.ImageBase;
        IMAGE_BASE_RELOCATION* ImageBaseReloc = (IMAGE_BASE_RELOCATION*)lpRelocHdr;
        cout << hex << ImageBaseReloc << endl;
        
        while (ImageBaseReloc->VirtualAddress != 0)
        {
            DWORD RelocEntries = (ImageBaseReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
            
            cout << "Virtual Address : " << hex << ImageBaseReloc->VirtualAddress << endl;
            cout << "Relocation Entries : " << hex << RelocEntries << endl;
            cout << "Size of Relocation Header : " << hex << (ImageBaseReloc->SizeOfBlock) << endl;
            cout << hex << ImageBaseReloc << endl;
    
            WORD* ImageRelocEntry = (WORD*)((char*)ImageBaseReloc + sizeof(IMAGE_BASE_RELOCATION));
            for (int i = 0; i < RelocEntries; i++)
            {
                //type is the first 4 bits of the relocation word
                int type = ImageRelocEntry[i] >> 12;
                // offset is the last 12 bits
                int offset = ImageRelocEntry[i] & 0x0fff;
                cout << type << "\t" << offset << endl;
                if (type == 0)
                    continue;
    
                DWORD* ChangeAddr = (DWORD*)(pImageBase + ImageBaseReloc->VirtualAddress + offset);
                *ChangeAddr += DeltaImageBase;
            }
    
            ImageBaseReloc = (IMAGE_BASE_RELOCATION*)(((char*)ImageBaseReloc + ImageBaseReloc->SizeOfBlock));
        }
        
    
        // Fix the PE Import addr table
        repair_iat(pImageBase);
    
        // AddressOfEntryPoint
        size_t ret_addr = (size_t)(pImageBase)+ntHeader->OptionalHeader.AddressOfEntryPoint;
    
        // Jumping to the EntryPoint of the loaded PE
        std::cout << "  - Jumping..." << std::endl;
    
        //
        EnumThreadWindows(0, (WNDENUMPROC)ret_addr, 0);
    
        return;
    }
    

    My initial code had more than one problem, the pointer arithmetic, setting

        ntHeader->OptionalHeader.ImageBase = (size_t)pImageBase;
    

    and at last the each entry on relocation table is pointing to an address based on the base address we need to add the delta to the current value.

    Thanks for the creator of that video.