1

I have this PELoader project which basically loads a PE into memory. and it's working fine with x86 PEs but when tried to make it work with x64s I got stuck.

What I've tried is:

  • I replaced some of the data types with the x64 equivalent such as IMAGE_NT_HEADERS* with IMAGE_NT_HEADERS64*
  • Even though it automatically uses the right one when you switch the architecture. but didn't make any difference.
  • I also replaced this casting (DWORD) with reinterpret_cast<DWORD_PTR> and the exception changed. I guess it was necessary but not enough.
  • I tried making data types bigger. and it solved some but I still have bad reallocation.

Update 2

I just found out that the problem is in the reallocation. (check the code)

I've fixed an Error before I discovered this and it was in the data type that stores the base address from the optional header.

it was stored in a DWORD but I've changed it to char* and problem solved.

so I think the Error is in the casting or some data types of some values.

Here is the relocation Code:

/** Handle relocations **/
    
    //this is how much we shifted the ImageBase
    DWORD_PTR delta_VA_reloc = (reinterpret_cast<DWORD_PTR>(ImageBase)) - p_NT_HDR->OptionalHeader.ImageBase;
    
    // if there is a relocation table, and we actually shitfted the ImageBase
    if (data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) {
        printf("\n[*] The allocated address is not the prefered address, started relocating\n");
        //calculate the relocation table address
        IMAGE_BASE_RELOCATION* p_reloc = (IMAGE_BASE_RELOCATION*)(ImageBase + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
        
        //once again, a null terminated array
        while (p_reloc->VirtualAddress != 0) {
            // how many relocations in this block
            // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each)
            //std::cout << sizeof(WORD) << "\n"; sizeof word is 2
            DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

            // the first relocation element in the block, right after the header (using pointer arithmetic again)
            WORD* reloc = (WORD*)(p_reloc + 1);
            for (int i = 0; i < size; ++i) {
                
                //type is the first 4 bits of the relocation word
                int type = reloc[i] >> 12;

                // offset is the last 12 bits
                unsigned long long int offset = reloc[i] & 0x0fff;
                //printf("--------------- %#llx\n", offset);

                //this is the address we are going to change
                DWORD* change_addr = (DWORD*)(ImageBase + p_reloc->VirtualAddress + offset);

                // there is only one type used that needs to make a change
                // When you relocate you should look if the flag HIGHT_LOW is active for PE32 and DIR64 for PE32+
                switch (type) {
                case IMAGE_REL_BASED_HIGHLOW://for x86
                    *change_addr += delta_VA_reloc;
                    break;
                case IMAGE_REL_BASED_DIR64://for x64
                    *change_addr += delta_VA_reloc;
                    break;
                default:
                    break;
                }
            }

            // switch to the next relocation block, based on the size
            p_reloc = (IMAGE_BASE_RELOCATION*)((reinterpret_cast<DWORD_PTR>(p_reloc)) + p_reloc->SizeOfBlock);
        }
    }
  • Casting a pointer to a `DWORD` is not safe in x64 (technically on any platform!) you should be using `INTPTR` or `UINTPTR` or `std::intptr_t` or `std::uintptr_t` if you need it in an "integer" format. – Mgetz Feb 16 '22 at 14:46
  • should i use ``reinterpret_cast`` – zerofee gaming Feb 16 '22 at 14:49
  • You should use `std::intptr_t` or `std::uintptr_t` those are the only ones guaranteed to be safe for a round trip in the standard. `DWORD` in any form isn't safe because it's a 32bit type. – Mgetz Feb 16 '22 at 14:51
  • I've used ```reinterpret_cast``` and the first error is gone but I have this new exception in another line where I change the sections prevleges – zerofee gaming Feb 16 '22 at 15:02
  • I just found this : "All the platform-dependent types that changed with the transition from 32-bit to 64-bit end with _PTR (DWORD_PTR will be 32-bit on 32-bit Windows and 64-bit on 64-bit Windows)." on this post: https://stackoverflow.com/questions/39419/how-large-is-a-dword-with-32-and-64-bit-code – zerofee gaming Feb 16 '22 at 15:04
  • Just an FYI you'll run into DEP issues on x64 if you use anything but `PAGE_EXECUTE_READ` for executable sections AFAIK. They can't be both Write and execute at the same time – Mgetz Feb 16 '22 at 15:07
  • I was wrong the exception didn't specify any line as the trigger for the exception it was the line the cursor was on before clicking on debug. – zerofee gaming Feb 16 '22 at 15:08
  • @Mgetz ok but that's not what's triggering the exception right? I'll keep that in mind. – zerofee gaming Feb 16 '22 at 15:11
  • At this point I'm guessing it's probably an alignment assumptions issue, but I don't know enough about the PE header changes in x64 to be 100% sure. My guess is it's either `DWORD` or potentially `QWORD` aligned. – Mgetz Feb 16 '22 at 15:17
  • It's also worth noting that `lookup_addr &= 0x0000ffff; //setting the high part to 0` is almost certainly wrong as you're ANDing with a 32bit value. – Mgetz Feb 16 '22 at 15:18
  • when using breakpoints the exception didn't accrue until this line is executed: ```((void (*)(void)) start_address)();``` – zerofee gaming Feb 16 '22 at 15:21
  • @Mgetz I set the high part to 0 for the ```GetProcAddress()``` it needs to know that it's getting an ordinal instead of a name. Is there another way of doing it? – zerofee gaming Feb 16 '22 at 15:23
  • Well that doesn't... it sets the upper part of the lower 32bits to zero. – Mgetz Feb 16 '22 at 15:25
  • I've noticed that the program takes a lot of time to throw the exception which I think because the exception is thrown from the mapped PE, not from the PELoader. Is this possible? – zerofee gaming Feb 16 '22 at 15:26
  • debug the call into the mapped PE is the only advice I can make – Mgetz Feb 16 '22 at 15:28
  • @Mgetz ah ok makes sense for 32bit it's enough to have the upper part of 32bit zeroed but in 64 I need to set the high 32bit of dword to 0. but how do I do that? – zerofee gaming Feb 16 '22 at 15:29
  • @Mgetz how do I debug the call into the mapped PE? do I need a binary debugger? – zerofee gaming Feb 16 '22 at 15:30
  • @Mgetz I foundthis : "" You have some difference in the import table when you look for name or ordinal in import functions bit is masked as 0x80000000 for PE32, 0x8000000000000000 for PE32+ The magic number for the image pe format is 0x10b for PE32 and 0x20b for PE32+ The field BaseOfData in optional header is on PE32 but not in PE32+ When you relocate you should look if the flag HIGHT_LOW is active for PE32 and DIR64 for PE32+ "" in this post: https://stackoverflow.com/questions/47310152/is-difference-structure-pe32-bit-and-pe-64-bit – zerofee gaming Feb 16 '22 at 16:41
  • does it change the way of reading the import table etc? – zerofee gaming Feb 16 '22 at 16:42
  • I've changed the code according to a post I found that talks about the difference between x64 PE and x86 PE – zerofee gaming Feb 16 '22 at 17:04

1 Answers1

0

I found the problem:

I don't know exactly but when using DWORD* in this line:

DWORD* change_addr = (DWORD*)(ImageBase + p_reloc->VirtualAddress + offset);

and I printed the chnage_addr, in x86 it prints the whole 6 bytes that represents the address.

but in x64 it only prints 4 bytes. I think in windows DWORD* is smaller in x64.

So the solution I found is using long data types

unsigned long long int* change_addr = (unsigned long long int*)(ImageBase + p_reloc->VirtualAddress + offset);