PEs & DLLs
Portable Executable (PE) Format
Portable Executable (PE) is the file format for executables on Windows.
Not only .exe
files are PE files, dynamic link libraries (.dll
), Kernel modules (.srv
), Control panel applications (.cpl
) and many others are also PE files.
Executables (e.g. EXEs, DLLs) are often refered as "Images".
PE Structure
- DOS Header (IMAGE_DOS_HEADER)
The first part of a PE (Portable Executable) file is called the DOS (Disk Operating System) header, which starts with the signature "MZ" (0x4D 0x5A) to confirm it's a valid PE file. The DOS header structure includes two key elements:
e_magic
: A 2-byte field always set to 0x5A4D
(or "MZ
"), confirming the DOS header.
e_lfanew
: A 4-byte field that stores the offset to the start of the NT Header, which is where the actual PE file information begins. This offset is consistently located at an offset of 0x3C
from the start of the DOS header.
To retrieve the DOS Header:
- DOS Stub
DOS stub which an error message that prints "This program cannot be run in DOS mode" in case the program is loaded in DOS mode or "Disk Operating Mode". It is worth noting that the error message can be changed by the programmer at compile time.
This is not a PE header, but it's good to be aware of it.
- NT Header (IMAGE_NT_HEADERS)
The NT Header (IMAGE_NT_HEADERS) in a PE (Portable Executable) file is a crucial part that contains two other headers: FileHeader and OptionalHeader, providing extensive details about the file. It's identified by the "PE" signature, represented as 0x50450000
, with "PE" padded by two null bytes. You can access the NT Header using the e_lfanew member within the DOS Header.
There are two versions of the NT Header based on the machine's architecture:
32-bit Version (IMAGE_NT_HEADERS32):
64-bit Version (IMAGE_NT_HEADERS64):
The primary difference between these versions is the structure of the OptionalHeader, which can be IMAGE_OPTIONAL_HEADER32
for 32-bit or IMAGE_OPTIONAL_HEADER64
for 64-bit architectures.
To retrieve the NT Header:
- File Header (IMAGE_FILE_HEADER)
The File Header (IMAGE_FILE_HEADER) in a PE file, accessible from the NT Header, includes information about the file's characteristics.
The most important struct members are:
NumberOfSections
- The number of sections in the PE file (discussed later).Characteristics
- Flags that specify certain attributes about the executable file, such as whether it is a dynamic-link library (DLL) or a console application.SizeOfOptionalHeader
- The size of the following optional header
To retrieve the File Header:
- Optional Header (IMAGE_OPTIONAL_HEADER)
The Optional Header it's essential for the execution of the PE file. It is referred to as optional because some file types do not have it.
The optional header contains a ton of information that can be used. Below are some of
the struct members that are commonly used:
Magic
- Describes the state of the image file (32 or 64-bit image)MajorOperatingSystemVersion
- The major version number of the required operatingsystem (e.g. 11, 10)
MinorOperatingSystemVersion
- The minor version number of the required operating system (e.g. 1511, 1507, 1607)SizeOfCode
- The size of the .text section (Discussed later)AddressOfEntryPoint
- Offset to the entry point of the file (Typically the main function)BaseOfCode
- Offset to the start of the .text sectionSizeOfImage
- The size of the image file in bytesImageBase
- It specifies the preferred address at which the application is to be loaded into memory when it is executed. However, due to Window's memory protection mechanisms like ASLR, it's rare to see an image mapped to its preferred address because the Windows PE Loader maps the file to a different address. This random allocation will cause issues in the implementation of red teaming techniques because some addresses that are considered constant were changed. The Windows PE loader will then go through PE relocation to fix these addresses.DataDirectory
- One of the most important members in the optional header. This is an array ofIMAGE_DATA_DIRECTORY
, which contains the directories in a PE file.
To retrieve the Optional Header:
- Data Directory
The Data Directory can be accessed from the optional's header last member.
A specific data directory can be accessed the following index:
Export Directory: A PE's export directory is a data structure that contains information about functions and variables that are exported from the executable. It contains the addresses of the exported functions and variables, which can be used by other executable files to access the functions and data. The export directory is generally found in DLLs that export functions (e.g. kernel32.dll
exporting CreateFileA
).
Import Address Table: The import address table is a data structure in a PE that contains information about the addresses of functions imported from other executable files. The addresses are used to access the functions and data in the other executables (e.g. Application.exe importing CreateFileA
from kernel32.dll
).
To access the data directory:
- PE Sections
PE sections contain the code and data used to create an executable program. Each PE section is given a unique name and typically contains executable code, data, or resource information. There is no constant number of PE sections because different compilers can add, remove or merge sections depending on the configuration.
The following PE sections are the most important ones and exist in almost every PE.
.text
- Contains the executable code which is the written code..data
- Contains initialized data which are variables initialized in the code..rdata
- Contains read-only data. These are constant variables prefixed with const ..idata
- Contains the import tables. These are tables of information related to the functions called using the code. This is used by the Windows PE Loader to determine which DLL files to load to the process, along with what functions are being used from each DLL..reloc
- Contains information on how to fix up memory addresses so that the program can be loaded into memory without any errors..rsrc
- Used to store resources such as icons and bitmaps
Each PE section has an IMAGE_SECTION_HEADER data structure that contains valuable information about it. These structures are saved under the NT headers in a PE file and are stacked above each other where each structure represents a section.
To access the sections:
Dynamic-Link Library (DLL)
Both .exe
and .dll
file types are considered portable executable formats but there are differences between the two.
DLLs are shared libraries of executable functions or data that can be used by multiple applications simultaneously. They are used to export functions to be used by a process.
Unlike EXE files, DLL files cannot execute code on their own. Instead, DLL libraries need to be invoked by other programs to execute the code.
- System-Wide DLL Base Address
The Windows OS uses a system-wide DLL base address to load some DLLs at the same base address in the virtual address space of all processes on a given machine to optimize memory usage and improve system performance.
- Reasons why DLLs are very often used in Windows
1. Modularization of Code - Instead of having one massive executable that contains the entire functionality, the code is divided into several independent libraries with each library being focused on specific functionality.
2. Code Reuse - DLLs promote code reuse since a library can be invoked by multiple processes.
3. Efficient Memory Usage - When several processes need the same DLL, they can save memory by sharing that DLL instead of loading it into the process's memory.
- DLL Entry Point
DLLs can optionally specify an entry point function that executes code when a certain task occurs such as when a process loads the DLL library.
Sample DLL Code:
- Exporting a Function
Exporting a function in a Dynamic Link Library (DLL) allows other programs or processes to use that function. To export a function, you declare it with the extern and __declspec(dllexport) keywords.
By using extern and __declspec(dllexport)
, you indicate that HelloWorld
is intended to be used by other programs, making it accessible to applications that use this DLL.
Dynamic Linking
It's possible to use the LoadLibrary, GetModuleHandle and GetProcAddress WinAPIs to import a function from a DLL.
This is a method of loading and linking code (DLLs) at runtime rather than linking them at compile time using the linker and import address table.
- Loading a DLL
Calling a common function will force the Windows OS to load the DLL exporting the function into the calling process's memory address space. This loads the dll automatically by the OS when the process started and not by the code.
If the application doesn't have the dll we want (maybe one created by us) loaded into memory, it would require the usage of the LoadLibrary:
- Retrieving a DLL's Handle
If the dll is already loaded into the application's memory, one can retrieve its handle via the GetModuleHandle WinAPI function without leveraging the LoadLibrary function:
- Retrieving a Function's Address
Once the DLL is loaded into memory and the handle is retrieved, the next step is to retrieve the function's address. This is done withGetProcAddress:
- Invoking The Function
- Dynamic Linking Example
The code assumes that user32.dll
, the DLL that exports that function, isn't loaded into memory.
*The above MessageBoxAFunctionPointer
data type could be represented as fnMessageBoxA
- Rundll32.exe
Rundll32.exe can be used to run an exported function of a DLL file:
rundll32.exe <dllname>, <function exported to run>
For example, User32.dll exports the function LockWorkStation which locks the machine. To run the function, use the following command:
rundll32.exe user32.dll,LockWorkStation
- DLL Creation in VS
Launch Visual studio and create a new project.
Select the Dynamic-Link Library (DLL) option.
The provided DLL template comes with framework.h , pch.h and pch.cpp which are known as Precompiled Headers. These are files used to make the project compilation faster for large projects. To delete them, highlight them and delete. After deleting the precompiled headers, the compiler's default settings must be changed to confirm that precompiled headers should not be used in the project. Go to C/C++ > Advanced Tab, Change the 'Precompiled Header' option to 'Not Using Precompiled Headers' and press 'Apply'.
Last updated