Reputation: 1049
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
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:
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)
DbgHelp.dll that you're loading will be 32bit as well (as seen in the below picture):
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):
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:
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.
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)
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:
Upvotes: 1
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
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