Mecanik
Mecanik

Reputation: 1049

C/C++ Importing WinAPI function with x64 structure parameter inside x86 application

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.

Upvotes: 1

Views: 1183

Answers (3)

CristiFati
CristiFati

Reputation: 41137

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)

Upvotes: 1

Pickle Rick
Pickle Rick

Reputation: 808

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);
}

Upvotes: 2

Soonts
Soonts

Reputation: 21956

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.

Upvotes: 1

Related Questions