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