Previous Windows PE Internals Writeups
- Creating a Windows Project in Visual Studio
- Getting a Handle to a Dynamically Linked Library
- Validating the MZ Signature
Previously
Previously, we learnt about how do we validate the MZ signature, which tells us that the file is very likely, a Portable Executable (PE) file. or a MS DOS Executable.
Let's Begin
Now, lets proceed to navigate to another structure within the PE called the NT_HEADERS aka PE HEADERS.
The NT_HEADERS consist of 3 main subsections.
- NT Signature aka PE Signature
- File Header aka PE Header
- Optional Header
Today we will validate the NT Signature.
Continuing with our previous article, let's relook at the PE image above and the definition of the IMAGE_DOS_HEADER.
typedef struct _IMAGE_DOS_HEADER
{
WORD e_magic;
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew; //Use this to jump to PIMAGE_NT_HEADER
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
We can see that there is a property e_lfanew. It contains a Relative Virtual Address (RVA) whose offset leads us to the PIMAGE_NT_HEADER.
e_lfanew simply means the long file address of the new executable header , kinda saying the IMAGE_DOS_HEADER is old as its for MS-DOS.
An RVA is basically an offset(in bytes) from the Base Address. Meaning you would only compute the destination address by taking the Base Address, which is our start of PE Address, and add it to the RVA to get the destination address.
Let's now compute our PIMAGE_NT_HEADER by adding PIMAGE_DOS_HEADER to the RVA in e_lfanew.
The code below shows how to do that.
PIMAGE_NT_HEADERS imageNtHeaders = (PIMAGE_NT_HEADERS)((unsigned char*)imageDosHeader + imageDosHeader->e_lfanew);
Let's break it down.
(unsigned char*)imageDosHeader
As imageDosHeader is of a type pointer to IMAGE_DOS_HEADER, we cant do pointer arithmetic byte-wise. Meaning we want to shift the pointer byte by byte as the RVA is the offset in bytes.
Hence we casted it to a (unsigned char*) which allows the pointer to move byte by byte as a char is 1 byte.
(unsigned char*)imageDosHeader + imageDosHeader->e_lfanew
Next we moved the pointer by imageDosHeader->e_lfanew number of bytes.
PIMAGE_NT_HEADERS imageNtHeaders = (PIMAGE_NT_HEADERS)((unsigned char*)imageDosHeader + imageDosHeader->e_lfanew);
Then we re-casted it back to PIMAGE_NT_HEADERS which is basically a macro for pointer to IMAGE_NT_HEADERS aka IMAGE_NT_HEADERS*.
Now we can easily get our signature from PIMAGE_NT_HEADERS and compare it with the macro IMAGE_NT_SIGNATURE.
if (imageNtHeaders->Signature == IMAGE_NT_SIGNATURE)
{
//omitted
}
IMAGE_NT_SIGNATURE is a macro found in winnt.h which defines the PE signature to be PE\0\0 which ends with 2 null bytes.
//From winnt.h
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
Hence overall, our code would look like the following.
#include <Windows.h>
int WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
HMODULE peBase = GetModuleHandleA("user32.dll");
if (peBase == NULL)
{
MessageBoxA(0, "Can't load user32.dll", "Error", MB_OK | MB_ICONERROR);
return 1;
}
PIMAGE_DOS_HEADER imageDosHeader = (PIMAGE_DOS_HEADER)peBase;
if (imageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
MessageBoxA(0, "user32.dll has the wrong Image Dos Signature!", "Error", MB_OK | MB_ICONERROR);
return 1;
}
PIMAGE_NT_HEADERS imageNtHeaders = (PIMAGE_NT_HEADERS)((unsigned char*)imageDosHeader + imageDosHeader->e_lfanew);
if (imageNtHeaders->Signature != IMAGE_NT_SIGNATURE)
{
MessageBoxA(0, "user32.dll has the wrong PE Signature!", "Error", MB_OK | MB_ICONERROR);
return 1;
}
MessageBoxA(0, "user32.dll has the right PE Signature!", "Success", MB_OK | MB_ICONINFORMATION);
return 0;
}
Anti-Virus Bleeping...
I did not expect my antivirus to catch up very quickly that what I am trying to do might be suspicious. Many malwares have similar type of function calls to perform DLL Injection. We might discuss that in the future.
I have temporarily disabled it.
In this exercise, I learnt how to navigate from the DOS HEADER to the PE HEADER via the e_lfanew property. Following that, I successfully verfied the PE Header. Interestingly, we also saw my Antivirus going off and suspecting my code to be malicious.



Top comments (0)