1

I have an x86 windows application (32 bit) and I am importing a function with an x64 struct parameter definition.

The function in question is: ImageRvaToSection. When you use ImageRvaToSection, the function definition automatically has the struct necessary for your application (winnt.h); if your application is x86 (32) it uses PIMAGE_NT_HEADERS which expands to PIMAGE_NT_HEADERS32.

And of course, if your application is x64, it uses the same PIMAGE_NT_HEADERS but expands to PIMAGE_NT_HEADERS64.

I need to be able to call ImageRvaToSection with the 32 and 64 structure parameter inside my x86 application.

Basically what I did was:

typedef PIMAGE_SECTION_HEADER(WINAPI* pImageRvaToSection64) (PIMAGE_NT_HEADERS64, PVOID, ULONG);

pImageRvaToSection64 pointerImageRvaToSection64;

pointerImageRvaToSection64 = (pImageRvaToSection64)::GetProcAddress(::GetModuleHandle(L"Dbghelp.dll"),"ImageRvaToSection");

This works fine from my tests, I can use pointerImageRvaToSection64 with IMAGE_NT_HEADERS64 as a parameter called from inside a 32-bit application.

However, I have no idea how safe is this to do? I'm aware of the wow64ext, but I`m not sure if this applies to this scenario.

Please advise.

Al Francis
  • 333
  • 1
  • 12
Mecanik
  • 1,539
  • 1
  • 20
  • 50
  • 1
    You can't load a 64 bit DLL into a 32 bit application. The dbghelp.dll you're loading will be the 32 bit version. – Jonathan Potter Nov 17 '19 at 10:54
  • @JonathanPotter Yes I know this, but that's not the point... – Mecanik Nov 17 '19 at 11:05
  • The point is you can't magically call 64 bit code from 32 bit code just by casting. – Jonathan Potter Nov 17 '19 at 12:28
  • Why do you need to use `PIMAGE_NT_HEADERS64` in the x86 windows application (32 bit)? – Rita Han Nov 18 '19 at 09:30
  • @RitaHan-MSFT Because I read x64 applications section... ? And by the way before you say it does not work, it does... just test it :) – Mecanik Nov 18 '19 at 12:16
  • @NorbertBoros Have you noticed that the size of `PIMAGE_OPTIONAL_HEADER64` is 4 bytes on 32 bit application and 8 bytes on 64 bit application? Pointers can be truncated to 32-bit values. – Rita Han Nov 19 '19 at 02:10
  • @RitaHan-MSFT No I haven't noticed... this is why I asked the question. If you know more, post an answer explaining why/where/how... – Mecanik Nov 19 '19 at 08:38
  • 1
    It is not entirely implausible that the 32-bit version of dbghelp.dll can correctly handle a 64-bit image. There is a [dedicated documentation page](https://learn.microsoft.com/en-us/windows/win32/debug/updated-platform-support) about it, documenting the functions and structures that *needed* to be updated to handle 64-bit images. But of course inadequate to give you a warranty. Ideally an MSFT employee would talk to a team member to confirm your finding. – Hans Passant Nov 19 '19 at 09:44
  • @HansPassant Once again, thanks. So this is unlikely to work properly under all windows editions, I rather go with rewolf's wow64ext... what do you advise ? An MSFT member will never get in touch with me, neither help me. – Mecanik Nov 19 '19 at 09:48
  • @HansPassant Yes well... what can I say. Before I would deploy this to hundreds of users, on different Windows desktop editions I would need some certainty... to be honest from all my tests this works fine, why/where/how... that is the question. – Mecanik Nov 19 '19 at 09:55
  • @NorbertBoros What criteria are you using to determine that "it works" ? – Jonathan Potter Nov 20 '19 at 01:01
  • What;s the reason for doing all this? And how do you populate *PIMAGE\_NT\_HEADERS64* (and sub *struct*s) members? – CristiFati Nov 21 '19 at 17:50
  • @CristiFati The file is read with CreateFile -> MapViewOfFile, and I populate the structs accordingly. – Mecanik Nov 22 '19 at 06:55

3 Answers3

2

Original Answer
There are no remarks on MSDN about this, but I assume it should be universal given the PE format. Keep in mind that the Portable Executable Format (PE) is identical for both architectures until it reaches IMAGE_OPTIONAL_HEADER. The IMAGE_FIRST_SECTION(Nt) macro is WoW64 safe because of this. All you need to do is loop through ImageFileHeader.NumberOfSections and check if the passed VA is within the section VA bounds.

Update
I was on mobile when I answered, but I just went to go take a look at ImageRvaToSection on Windows 10. As expected, there are no architecture checks and it will work for both PE32 and PE64 files the same. It should be noted that the Base argument isn't even used, which is potentially due to backwards compatibility from a time when it was, in which case there may be issues across architectures on those older versions. Due to MSDN having no remarks about the architecture I can't technically guarantee it's safe for all previous versions of Windows. However, there's really no reason it shouldn't be, particularly because it's safe on W10 and there are no remarks stating this has changed.

Alternative
This is trivial to implement yourself in an architecture independent way, as I mentioned in my initial answer. The code below uses the same method as ImageRvaToSection reversed from my system and is guaranteed to work for both PE32 and PE64.

template<typename T>
PIMAGE_SECTION_HEADER ImageRvaToSectionEx(T NtHeaders, uint32_t Rva)
{
    auto nt = NtHeaders;
    auto num_sections = nt->FileHeader.NumberOfSections;
    auto cur_section = IMAGE_FIRST_SECTION(nt);

    for (auto i = 0; i < num_sections; ++i, ++cur_section)
    {
        auto sec_beg = cur_section->VirtualAddress;
        auto sec_end = (sec_beg + cur_section->Misc.VirtualSize);

        if (Rva >= sec_beg && Rva < sec_end)
            return cur_section;
    }

    return reinterpret_cast<PIMAGE_SECTION_HEADER>(nullptr);
}
Pickle Rick
  • 808
  • 3
  • 6
  • Thanks, so bottom line is... this is safe to do ? – Mecanik Nov 21 '19 at 14:31
  • I have updated my answer to provide more details and some example code as an alternative. – Pickle Rick Nov 22 '19 at 05:02
  • Thank you! "there are no architecture checks" this is what I suspected, but multiple pair of eyes are better than one :). You also provided a "manual" way of resolving RVA to section, so once again thanks. – Mecanik Nov 22 '19 at 06:52
1

Your code doesn't do what you think it does.

GetModuleHandle(L"Dbghelp.dll")

When this runs inside 32 bit process, there's no way this will get you the handle of 64-bit Dbghelp.dll. 64 bit DLLs can't be loaded into 32-bit processes.

GetProcAddress( ..., "ImageRvaToSection")

Because the DLL is 32-bit, this gets you the address of the 32-bit ImageRvaToSection function.

(pImageRvaToSection64)

This doesn't check anything (and it can't), nor create wrappers. You're getting a normal 32-bit version of the function, type casted into function pointer with different argument type.

This works fine from my tests

Well, the 32 and 64 bit structures are quite similar. The first field that differs is OptionalHeader.ImageBase. All fields before that one appear to have the same type, are placed at the same offset, across 32 and 64 bit versions.

Soonts
  • 20,079
  • 9
  • 57
  • 130
  • Thanks, I`m aware of the fact that I get the 32 bit dbghelp.dll, as well as the address. My concern is and it was, if it's safe to use it as I do... – Mecanik Nov 20 '19 at 10:38
  • @NorbertBoros Then please specify how you use it. The code you have there is equivalent to the normal ImageRvaToSection function, with the argument type casted from another pointer type. – Soonts Nov 20 '19 at 10:47
  • I am opening an x64 application/file, and reading the section from an x32 application. More or less like this: IMAGE_SECTION_HEADER* lpSectionHeader = pointerImageRvaToSection64(...); does this help ? – Mecanik Nov 20 '19 at 10:50
1

It's unclear why you took this approach (LoadLibrary / GetProcAddress), when you could easily use ImageRvaToSection (from DbgHelp.h). Some might see this as an XY Problem.

Recap:

  1. You're in the 32bit process, meaning that (by default) you can't cross the 32bit boundary: 4GiB (well, except for the wider types, but they aren't so relevant for now).
    That means that all pointers (e.g. PIMAGE_NT_HEADERS64, ...) will be 32bit (but I guess the address itself is not very important, what matters is the contents)

  2. DbgHelp.dll that you're loading will be 32bit as well (as seen in the below picture):

    enter image description here

  3. ImageRvaToSection belongs to the 32bit .dll (so does any other function), so it expects a [MS.Docs]: IMAGE_NT_HEADERS32 structure pointer as its 1st argument (leave the others aside for the moment), and more: no matter what you pass, it will treat is as such (PIMAGE_NT_HEADERS32), by no means it will become "64bit capable"

As a consequence, passing it a PIMAGE_NT_HEADERS64, technically yields Undefined Behavior.
I posted a brief description on the topic ([SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer)) (although it involves Python, still applies).

Going through some basic 64bit / 32bit differences: there are types that change sizes between the 2 (like pointers) and structures, e.g. containing the aforementioned types (winnt.h (part of Win SDK 10.0.18362.0) (VStudio Community 2019)) members.

main00.c:

#include <stdio.h>
#include <Windows.h>


void loadDbgHelp() {
    HMODULE mod = LoadLibraryW(L"DbgHelp.dll");
    if (mod == NULL) {
        printf("Error loading module: %d\n", GetLastError());
    } else {
        printf("Module loaded at: 0x%016X", (unsigned long long)mod);
    }
}


void printSizes() {
    printf("Sizes (bits)\n");
    printf(
        "  Types:\n"
        "    int: %d\n"
        "    DWORD: %d\n"
        "    ULONGLONG: %d\n"
        "    size_t: %d\n\n"
        , sizeof(int) * 8, sizeof(DWORD) * 8, sizeof(ULONGLONG) * 8, sizeof(size_t) * 8
    );
    printf(
        "  Pointers:\n"
        "    PIMAGE_SECTION_HEADER: %d\n"
        "    PIMAGE_NT_HEADERS: %d\n"
        "    void*: %d\n\n"
        , sizeof(PIMAGE_SECTION_HEADER) * 8, sizeof(PIMAGE_NT_HEADERS) * 8, sizeof(void*) * 8
    );
    printf(
        "  Structs:\n"
        "    IMAGE_SECTION_HEADER: % d\n"
        "    IMAGE_NT_HEADERS32: %d\n"
        "    IMAGE_NT_HEADERS64: %d\n"
        "    IMAGE_FILE_HEADER: %d\n"
        "    IMAGE_OPTIONAL_HEADER32: %d\n"
        "    IMAGE_OPTIONAL_HEADER64: %d\n"
        , sizeof(IMAGE_SECTION_HEADER) * 8, sizeof(IMAGE_NT_HEADERS32) * 8, sizeof(IMAGE_NT_HEADERS64) * 8
        , sizeof(IMAGE_FILE_HEADER) * 8, sizeof(IMAGE_OPTIONAL_HEADER32) * 8, sizeof(IMAGE_OPTIONAL_HEADER64) * 8
    );
}


int main()
{
    //loadDbgHelp();

    printSizes();

}

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q058898815\src]> sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***

[prompt]> dir /b
main00.c

[prompt]> "e:\Install\x86\Microsoft\Visual Studio Community\2019\VC\Auxiliary\Build\vcvarsall.bat" x64
**********************************************************************
** Visual Studio 2019 Developer Command Prompt v16.3.10
** Copyright (c) 2019 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'

[prompt]> :: Build for 64bit
[prompt]> cl /nologo /MD /W0 main00.c  /link /NOLOGO /OUT:main00_064.exe
main00.c

[prompt]> "e:\Install\x86\Microsoft\Visual Studio Community\2019\VC\Auxiliary\Build\vcvarsall.bat" x86
**********************************************************************
** Visual Studio 2019 Developer Command Prompt v16.3.10
** Copyright (c) 2019 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x86'

[prompt]> :: Build for 32bit
[prompt]> cl /nologo /MD /W0 main00.c  /link /NOLOGO /OUT:main00_032.exe
main00.c

[prompt]> dir /b *.exe
main00_032.exe
main00_064.exe

[prompt]>
[prompt]> :: Launch 32bit executable -------
[prompt]> main00_032.exe
Sizes (bits)
  Types:
    int: 32
    DWORD: 32
    ULONGLONG: 64
    size_t: 32

  Pointers:
    PIMAGE_SECTION_HEADER: 32
    PIMAGE_NT_HEADERS: 32
    void*: 32

  Structs:
    IMAGE_SECTION_HEADER:  320
    IMAGE_NT_HEADERS32: 1984
    IMAGE_NT_HEADERS64: 2112
    IMAGE_FILE_HEADER: 160
    IMAGE_OPTIONAL_HEADER32: 1792
    IMAGE_OPTIONAL_HEADER64: 1920

[prompt]>
[prompt]> :: Launch 64bit executable ------- compare to 32bit executable -------
[prompt]> main00_064.exe
Sizes (bits)
  Types:
    int: 32
    DWORD: 32
    ULONGLONG: 64
    size_t: 64

  Pointers:
    PIMAGE_SECTION_HEADER: 64
    PIMAGE_NT_HEADERS: 64
    void*: 64

  Structs:
    IMAGE_SECTION_HEADER:  320
    IMAGE_NT_HEADERS32: 1984
    IMAGE_NT_HEADERS64: 2112
    IMAGE_FILE_HEADER: 160
    IMAGE_OPTIONAL_HEADER32: 1792
    IMAGE_OPTIONAL_HEADER64: 1920

Coming back: technically you have Undefined Behavior (as I already stated), but let's see if you really are (on the 32bit process):

  • Normally, the PMAGE_NT_HEADERS32 pointer comes from [MS.Docs]: ImageNtHeader function. If you don't do anything to it (before passing it to ImageRvaToSection), you're fine. But, concerning your goal, your actions (e.g. specifying PIMAGE_NT_HEADERS64 as the 1st argument) are pretty useless ("as futile as resistance against being assimilated by Borg" :) ). The fact that you specified the 64bit struct, doesn't change the memory layout, it just gives you a different (and fake) view over it (in theory, of anything that comes after the 1st part that differ)
  • But, if on the other hand, you're changing (assuming no monkey-business) member values, then there might be problems. And those problems come from type sizes differences. In this case, only the IMAGE_OPTIONAL_HEADER* (which comes last in IMAGE_NT_HEADERS*) can pose problems. I was preparing a piece of code for this scenario, but as you said you;re not modifying the members, I won't do it anymore

main01.c:

#include <stdio.h>
#include <Windows.h>
#include <DbgHelp.h>

#define IMAGES_PATH "e:/Work/Dev/StackOverflow/q058898815/src/"


BOOL handleImage(const char* path) {
    printf("\nHandling: [%s]\n", path);
    HANDLE hFile = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("CreateFileW failed: 0x%08X\n", GetLastError());
        return 0;
    }
    LARGE_INTEGER fileSize;
    if (!GetFileSizeEx(hFile, &fileSize))
    {
        printf("GetFileSizeEx failed: 0x%08X\n", GetLastError());
        CloseHandle(hFile);
        return 0;
    }
    HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
    if (hMap == NULL)
    {
        printf("CreateFileMapping failed: 0x%08X\n", GetLastError());
        CloseHandle(hFile);
        return 0;
    }
    LPVOID view = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
    if (view == NULL)
    {
        printf("MapViewOfFile failed: 0x%08X\n", GetLastError());
        CloseHandle(hMap);
        CloseHandle(hFile);
        return 0;
    }

    PIMAGE_NT_HEADERS pNtHeaders = ImageNtHeader(view);
    if (pNtHeaders == NULL)
    {
        printf("ImageNtHeader failed: 0x%08X\n", GetLastError());
        CloseHandle(hMap);
        CloseHandle(hFile);
        return 0;
    }
    IMAGE_NT_HEADERS ntHeaders = *pNtHeaders;

    printf("IMAGE_NT_HEADERS info:\n  FileHeader.Machine: 0x%04X\n  FileHeader.NumberOfSections: %d\n"
           "  FileHeader.SizeOfOptionalHeader: %d\n  OptionalHeader.Magic: 0x%04X\n"
           , ntHeaders.FileHeader.Machine, ntHeaders.FileHeader.NumberOfSections
           , ntHeaders.FileHeader.SizeOfOptionalHeader, ntHeaders.OptionalHeader.Magic);

    PIMAGE_SECTION_HEADER pSectionHeaders = ImageRvaToSection(pNtHeaders, NULL, 0x1000);
    if (pSectionHeaders == NULL)
    {
        printf("ImageRvaToSection failed: 0x%08X\n", GetLastError());
    } else {
        printf("IMAGE_SECTION_HEADER info:\n  Misc.PhysicalAddress: %d\n  Misc.VirtualSize: %d\n  VirtualAddress: %d\n  SizeOfRawData:%d\n"
               , pSectionHeaders->Misc.PhysicalAddress, pSectionHeaders->Misc.VirtualSize, pSectionHeaders->VirtualAddress, pSectionHeaders->SizeOfRawData);
    }
    if (!UnmapViewOfFile(view))
    {
        printf("UnmapViewOfFile failed: 0x%08X\n", GetLastError());
    }

    CloseHandle(hMap);
    CloseHandle(hFile);
    return 1;
}


int main() {
    SYSTEM_INFO systemInfo;
    GetSystemInfo(&systemInfo);
    printf("Processor architecture: 0x%04X\n", systemInfo.wProcessorArchitecture);

    handleImage(IMAGES_PATH "main00_032.exe");
    handleImage(IMAGES_PATH "main00_064.exe");

    printf("\nDone.\n");
    return 0;
}

Output:

[prompt]> cl /nologo /MD /W0 main01.c  /link /NOLOGO DbgHelp.lib /OUT:main01_032.exe
main01.c

[prompt]> main01_032.exe
Processor architecture: 0x0000

Handling: [e:/Work/Dev/StackOverflow/q058898815/src/main00_032.exe]
IMAGE_NT_HEADERS info:
  FileHeader.Machine: 0x014C
  FileHeader.NumberOfSections: 4
  FileHeader.SizeOfOptionalHeader: 224
  OptionalHeader.Magic: 0x010B
IMAGE_SECTION_HEADER info:
  Misc.PhysicalAddress: 3550
  Misc.VirtualSize: 3550
  VirtualAddress: 4096
  SizeOfRawData:3584

Handling: [e:/Work/Dev/StackOverflow/q058898815/src/main00_064.exe]
IMAGE_NT_HEADERS info:
  FileHeader.Machine: 0x8664
  FileHeader.NumberOfSections: 5
  FileHeader.SizeOfOptionalHeader: 240
  OptionalHeader.Magic: 0x020B
IMAGE_SECTION_HEADER info:
  Misc.PhysicalAddress: 3272
  Misc.VirtualSize: 3272
  VirtualAddress: 4096
  SizeOfRawData:3584

Done.

Notes:

  1. As probably noticed, the output values for the 2 executables match the ones from the documentation (e.g. [MS.Docs]: IMAGE_OPTIONAL_HEADER32 structure structure). Also [MS.Docs]: IMAGE_FILE_HEADER structure contains a remark (emphasis is mine) that my be of interest:

    Members

    Machine

    The architecture type of the computer. An image file can only be run on the specified computer or a system that emulates the specified computer.

  2. ImageRvaToSection succeeded in both cases (thanks @PickleRick for pointing my mistake out). On the other hand, check IMAGE_NT_HEADERS.FileHeader.SizeOfOptionalHeader which matches the values from previous program (divided by 8)

  3. If for some 64bit image, (ULONGLONG) members from IMAGE_OPTIONAL_HEADER structure, will (in reality) be larger than 0xFFFFFFFF, they will be truncated to DWORDs, and that's where Undefined Behavior would probably show its teeth (when you'd try to access (directly or indirectly) those members)

Conclusions:

  • It all depends on what you're actually trying to achieve
  • You are fine (according to previous note #2., IMAGE_OPTIONAL_HEADER (64bit and 32bit) appear to be handled)
  • If possible, I'd build my app for 64bit. As time goes by, 32bit architecture will become obsolete (in fact there are several Lnx distributions that support it for backward compatibility only, and they discourage new applications that target it)
CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • Thank you, but your final thoughts are ? – Mecanik Nov 23 '19 at 21:32
  • In terms of undefined behavior, as long as it's done correctly you don't need anything from ``IMAGE_OPTIONAL_HEADER``. First section is ``&IMAGE_NT_HEADER.OptionalHeader + IMAGE_FILE_HEADER.SizeOfOptionalHeader`` and each ``IMAGE_SECTION_HEADER`` is the same size on both ``x86`` and ``x64``. I also verified it's implemented this way on ``dbghelp.ImageRvaToSection`` in Windows 10. I'm not sure why your call would have failed unless you passed an invalid rva. Also note, as I said in my answer, the ``Base`` argument isn't even used so you can just pass ``nullptr``. – Pickle Rick Nov 24 '19 at 03:25
  • Ah, just looked at your code, had only read your comments before that. The reason it's failing is because you're passing 0 as the rva, which is of course where the headers are stored. Try passing 0x1000 instead, which is pretty generically where the first section is, and it should work. – Pickle Rick Nov 24 '19 at 03:32
  • @PickleRick: thank you for pointing out the *rva* hint, it did the trick! – CristiFati Nov 24 '19 at 12:23