简体   繁体   中英

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.

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

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.

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 (eg 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):

    在此处输入图像描述

  3. ImageRvaToSection belongs to the 32bit .dll (so does any other function), so it expects a [MS.Docs]: IMAGE_NT_HEADERS 32 structure pointer as its 1 st 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, eg 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 (eg specifying PIMAGE_NT_HEADERS64 as the 1 st 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 1 st 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 (eg [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 DWORD s, 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)

The key point is that the pointer is 32 bits on x86 application but 64 bits on x64 application. When you access pointer belongs to a 64-bit application in a 32-bit application, the pointer will be truncated from 64 bits to 32 bits. It loses its significant 32 bits data.

You get correct result from your test possibly due to that the system can limit the application memory allocation occurs below the 2 GB limit, so that if the application truncates a pointer, no significant data is lost (upper 32 bits are all zeros). But when this limitation is removed the application may result in failure without any warning. Use it carefully by yourself when it is required.

More helpful references: Rules for Using Pointers , Virtual Address Space

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM