The first line can't be changed how you have it. I think you're misunderstanding the layout of the data structures or what your code is doing.
module_base points to the base of the module and assuming it's of PE format, that's where the PE header should be. For backwards compatibility reasons, Microsoft kept the old DOS header as the root data structure. Up there is maintained the tiniest of DOS programs that just prints something like "This program cannot run in DOS". As an aside, you could technically craft your own PE header and include a fully functioning DOS program along side your modern PE program.
The DOS header is defined as IMAGE_DOS_HEADER in winnt.h as so:
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
Anyway, since DOS is gone for the most part the only useful field up there is a field called e_lfanew
(not elfnew). It's an offset to the new NT PE header, hence the name. The comment 'File address of new exe header' is how we know it's an offset. Without going into too much detail, the header doesn't really know where in memory it's going to get loaded. But it does know it's at the top (aka address 0) of the file. So a 'file address' is the distance from the top of the module to where ever you're attempting to go.
The DOS header on its own does not have a field called OptionalHeader
therefor you can't module_base->OptionalHeader
. OptionalHeader
is a field in the new NT Header that e_lfanew
points to.
Again in winnt.h the NT header is defined as
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
So to get to the NT header you need to do something like PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)(module_base + ((PIMAGE_DOS_HEADER)module_base)->e_lfanew);
Reading between the lines it looks like where you're actually trying to go is the IMAGE_DIRECTORY_ENTRY_EXPORT (defined as the 0 index into the DataDirectory, also in winnt.h). So once you have your nt_headers pointer, you can do PIMAGE_EXPORT_DIRECTORY export_dir = (PIMAGE_EXPORT_DIRECTORY)(module_base + nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
All of these data structures are defined in winnt.h but Microsoft doesn't like to document them much online. They kind of talk about the PE format here but it stays general enough to require a lot of reading between the lines. There are plenty of others who have documented parts of this header, but I hesitate to link any without taking the time to thoroughly read through because they're often just out of date enough to be confusing.
Comment to let me know if you need any more clarification.