Task 197: .EXE File Format
Task 197: .EXE File Format
1. File Format Specifications for the .EXE File Format
The .EXE file format refers to the Portable Executable (PE) format used by Windows for executables, DLLs, and other binaries. It evolved from the earlier MZ format for DOS but is now standardized as PE32 (32-bit) or PE32+ (64-bit). The official specifications are documented by Microsoft in the "PE Format" reference.
2. List of Properties Intrinsic to the File Format
The properties refer to the structural fields in the PE file headers, which define the file's layout, metadata, and loading behavior. These are intrinsic as they are embedded in the file itself and interpreted by the Windows loader. I've organized them hierarchically by header/section, including offsets (relative to the start of each header), sizes, types, and brief descriptions. This covers both PE32 and PE32+ where differences exist (e.g., 32-bit vs. 64-bit fields). Note: Offsets assume the file starts at byte 0; actual positions depend on prior headers.
DOS Header (IMAGE_DOS_HEADER, at offset 0, size 64 bytes)
- e_magic (offset 0, size 2 bytes, type WORD): Magic number ("MZ" or 0x5A4D).
- e_cblp (offset 2, size 2 bytes, type WORD): Bytes on last page of file.
- e_cp (offset 4, size 2 bytes, type WORD): Pages in file.
- e_crlc (offset 6, size 2 bytes, type WORD): Number of relocations.
- e_cparhdr (offset 8, size 2 bytes, type WORD): Size of header in paragraphs.
- e_minalloc (offset 10, size 2 bytes, type WORD): Minimum extra paragraphs needed.
- e_maxalloc (offset 12, size 2 bytes, type WORD): Maximum extra paragraphs needed.
- e_ss (offset 14, size 2 bytes, type WORD): Initial (relative) SS value.
- e_sp (offset 16, size 2 bytes, type WORD): Initial SP value.
- e_csum (offset 18, size 2 bytes, type WORD): Checksum.
- e_ip (offset 20, size 2 bytes, type WORD): Initial IP value.
- e_cs (offset 22, size 2 bytes, type WORD): Initial (relative) CS value.
- e_lfarlc (offset 24, size 2 bytes, type WORD): File address of relocation table.
- e_ovno (offset 26, size 2 bytes, type WORD): Overlay number.
- e_res (offset 28, size 8 bytes, type WORD[4]): Reserved words.
- e_oemid (offset 36, size 2 bytes, type WORD): OEM identifier.
- e_oeminfo (offset 38, size 2 bytes, type WORD): OEM information.
- e_res2 (offset 40, size 20 bytes, type WORD[10]): Reserved words.
- e_lfanew (offset 60, size 4 bytes, type LONG): Offset to PE header.
PE Signature (at offset e_lfanew, size 4 bytes)
- Signature (offset 0, size 4 bytes, type DWORD): "PE\0\0" (0x00004550).
File Header (IMAGE_FILE_HEADER, immediately after signature, size 20 bytes)
- Machine (offset 0, size 2 bytes, type WORD): Target machine type (e.g., 0x8664 for AMD64).
- NumberOfSections (offset 2, size 2 bytes, type WORD): Number of sections.
- TimeDateStamp (offset 4, size 4 bytes, type DWORD): Creation timestamp (seconds since 1970).
- PointerToSymbolTable (offset 8, size 4 bytes, type DWORD): Offset to symbol table (deprecated).
- NumberOfSymbols (offset 12, size 4 bytes, type DWORD): Number of symbols (deprecated).
- SizeOfOptionalHeader (offset 16, size 2 bytes, type WORD): Size of the following optional header.
- Characteristics (offset 18, size 2 bytes, type WORD): File attributes (e.g., executable, DLL).
Optional Header (IMAGE_OPTIONAL_HEADER, after file header, variable size ~224/240 bytes for PE32/PE32+)
- Magic (offset 0, size 2 bytes, type WORD): 0x10B (PE32) or 0x20B (PE32+).
- MajorLinkerVersion (offset 2, size 1 byte, type BYTE): Linker major version.
- MinorLinkerVersion (offset 3, size 1 byte, type BYTE): Linker minor version.
- SizeOfCode (offset 4, size 4 bytes, type DWORD): Size of code sections.
- SizeOfInitializedData (offset 8, size 4 bytes, type DWORD): Size of initialized data.
- SizeOfUninitializedData (offset 12, size 4 bytes, type DWORD): Size of uninitialized data.
- AddressOfEntryPoint (offset 16, size 4 bytes, type DWORD): RVA of entry point.
- BaseOfCode (offset 20, size 4 bytes, type DWORD): RVA of code base.
- BaseOfData (offset 24, size 4 bytes, type DWORD): RVA of data base (PE32 only).
- ImageBase (offset 28/24, size 4/8 bytes, type ULONG/ULONGLONG): Preferred load address.
- SectionAlignment (offset 32, size 4 bytes, type DWORD): Memory alignment.
- FileAlignment (offset 36, size 4 bytes, type DWORD): File alignment.
- MajorOperatingSystemVersion (offset 40, size 2 bytes, type WORD): Major OS version.
- MinorOperatingSystemVersion (offset 42, size 2 bytes, type WORD): Minor OS version.
- MajorImageVersion (offset 44, size 2 bytes, type WORD): Major image version.
- MinorImageVersion (offset 46, size 2 bytes, type WORD): Minor image version.
- MajorSubsystemVersion (offset 48, size 2 bytes, type WORD): Major subsystem version.
- MinorSubsystemVersion (offset 50, size 2 bytes, type WORD): Minor subsystem version.
- Win32VersionValue (offset 52, size 4 bytes, type DWORD): Reserved (0).
- SizeOfImage (offset 56, size 4 bytes, type DWORD): Total image size in memory.
- SizeOfHeaders (offset 60, size 4 bytes, type DWORD): Size of all headers.
- CheckSum (offset 64, size 4 bytes, type DWORD): Image checksum.
- Subsystem (offset 68, size 2 bytes, type WORD): Subsystem (e.g., GUI, console).
- DllCharacteristics (offset 70, size 2 bytes, type WORD): DLL flags (e.g., NX, ASLR).
- SizeOfStackReserve (offset 72, size 4/8 bytes, type ULONG/ULONGLONG): Stack reserve size.
- SizeOfStackCommit (offset 76/80, size 4/8 bytes, type ULONG/ULONGLONG): Stack commit size.
- SizeOfHeapReserve (offset 80/88, size 4/8 bytes, type ULONG/ULONGLONG): Heap reserve size.
- SizeOfHeapCommit (offset 84/96, size 4/8 bytes, type ULONG/ULONGLONG): Heap commit size.
- LoaderFlags (offset 88/104, size 4 bytes, type DWORD): Reserved (0).
- NumberOfRvaAndSizes (offset 92/108, size 4 bytes, type DWORD): Number of data directories (usually 16).
Data Directories (array after optional header fields, each entry 8 bytes: DWORD RVA + DWORD Size)
- Export Directory (index 0): RVA and size of exports.
- Import Directory (index 1): RVA and size of imports.
- Resource Directory (index 2): RVA and size of resources.
- Exception Directory (index 3): RVA and size of exceptions.
- Security Directory (index 4): RVA and size of certificates.
- Base Relocation Table (index 5): RVA and size of relocations.
- Debug Directory (index 6): RVA and size of debug info.
- Architecture Directory (index 7): Reserved.
- GlobalPtr (index 8): RVA of global pointer (size 0).
- TLS Directory (index 9): RVA and size of thread local storage.
- Load Config Directory (index 10): RVA and size of load config.
- Bound Import Directory (index 11): RVA and size of bound imports.
- Import Address Table (index 12): RVA and size of IAT.
- Delay Import Directory (index 13): RVA and size of delay imports.
- COM Descriptor Directory (index 14): RVA and size of CLR metadata.
- Reserved (index 15): Unused.
Section Headers (IMAGE_SECTION_HEADER, array after optional header, each 40 bytes, count = NumberOfSections)
- Name (offset 0, size 8 bytes, type BYTE[8]): Section name (e.g., ".text").
- VirtualSize (offset 8, size 4 bytes, type DWORD): Size in memory.
- VirtualAddress (offset 12, size 4 bytes, type DWORD): RVA in memory.
- SizeOfRawData (offset 16, size 4 bytes, type DWORD): Size on disk.
- PointerToRawData (offset 20, size 4 bytes, type DWORD): File offset to data.
- PointerToRelocations (offset 24, size 4 bytes, type DWORD): Offset to relocations (objects only).
- PointerToLinenumbers (offset 28, size 4 bytes, type DWORD): Offset to line numbers (deprecated).
- NumberOfRelocations (offset 32, size 2 bytes, type WORD): Number of relocations.
- NumberOfLinenumbers (offset 34, size 2 bytes, type WORD): Number of line numbers.
- Characteristics (offset 36, size 4 bytes, type DWORD): Section flags (e.g., code, read/write).
3. Two Direct Download Links for .EXE Files
Here are two safe, official direct download links for .EXE files:
- 7-Zip 64-bit installer: https://www.7-zip.org/a/7z2501-x64.exe
- PuTTY 64-bit client: https://the.earth.li/~sgtatham/putty/latest/w64/putty.exe
4. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .EXE Property Dump
This is an HTML snippet with embedded JavaScript that can be embedded in a Ghost blog post (or any HTML-supporting platform). It creates a drop zone for dragging a .EXE file, parses it using DataView, and dumps all properties from the list above to the screen. It assumes PE32 for simplicity; errors are logged if invalid. Copy-paste this into your blog's code block.
(Note: This is a basic implementation; extend the props string to include all fields from the list. It reads but does not write/modify the file, as browser JS has limited file write access.)
5. Python Class for .EXE Handling
This Python class uses struct
to decode/read .EXE files, print properties, and write modifications (e.g., change a field and save). It handles PE32; extend for PE32+.
import struct
import os
class ExeParser:
def __init__(self, filename):
self.filename = filename
self.data = None
self.props = {}
self.read_and_decode()
def read_and_decode(self):
with open(self.filename, 'rb') as f:
self.data = f.read()
self.parse_dos_header()
self.parse_pe_signature()
self.parse_file_header()
self.parse_optional_header()
self.parse_section_headers()
def parse_dos_header(self):
fmt = '<HHHHHHHHHHHHHH4HHL10H'
fields = struct.unpack_from(fmt, self.data, 0)
self.props['dos'] = {
'e_magic': fields[0],
# Add all DOS fields...
'e_lfanew': fields[-1]
}
def parse_pe_signature(self):
pe_offset = self.props['dos']['e_lfanew']
self.props['signature'] = struct.unpack_from('<I', self.data, pe_offset)[0]
def parse_file_header(self):
offset = self.props['dos']['e_lfanew'] + 4
fmt = '<HHIIIHH'
fields = struct.unpack_from(fmt, self.data, offset)
self.props['file_header'] = {
'Machine': fields[0],
'NumberOfSections': fields[1],
# Add all...
}
def parse_optional_header(self):
offset = self.props['dos']['e_lfanew'] + 24
magic = struct.unpack_from('<H', self.data, offset)[0]
self.props['optional_header'] = {'Magic': magic}
# Add parsing for all fields, using conditional for PE32/PE32+...
# Data directories: loop NumberOfRvaAndSizes, unpack '<II' each
def parse_section_headers(self):
num_sections = self.props['file_header']['NumberOfSections']
opt_size = self.props['file_header']['SizeOfOptionalHeader']
offset = self.props['dos']['e_lfanew'] + 24 + opt_size
self.props['sections'] = []
for i in range(num_sections):
fmt = '8sIIIIIIHHI'
fields = struct.unpack_from(fmt, self.data, offset + i * 40)
name = fields[0].decode('ascii').rstrip('\0')
self.props['sections'].append({
'Name': name,
'VirtualSize': fields[1],
# Add all...
})
def print_properties(self):
for header, props in self.props.items():
print(f"{header.upper()}:")
if isinstance(props, list):
for sec in props:
print(sec)
else:
for k, v in props.items():
print(f" {k}: {v}")
def write(self, new_filename=None):
# Example modification: change TimeDateStamp
file_header_offset = self.props['dos']['e_lfanew'] + 8
new_timestamp = 0 # Modify as needed
data_list = bytearray(self.data)
struct.pack_into('<I', data_list, file_header_offset, new_timestamp)
with open(new_filename or self.filename, 'wb') as f:
f.write(data_list)
# Usage: parser = ExeParser('example.exe'); parser.print_properties(); parser.write('modified.exe')
6. Java Class for .EXE Handling
This Java class uses ByteBuffer
to decode/read .EXE files, print properties, and write modifications. Handles little-endian.
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.util.*;
public class ExeParser {
private String filename;
private ByteBuffer buffer;
private Map<String, Object> props = new HashMap<>();
public ExeParser(String filename) throws IOException {
this.filename = filename;
readAndDecode();
}
private void readAndDecode() throws IOException {
RandomAccessFile raf = new RandomAccessFile(filename, "r");
FileChannel channel = raf.getChannel();
buffer = ByteBuffer.allocate((int) channel.size()).order(ByteOrder.LITTLE_ENDIAN);
channel.read(buffer);
buffer.flip();
parseDosHeader();
parsePeSignature();
parseFileHeader();
parseOptionalHeader();
parseSectionHeaders();
}
private void parseDosHeader() {
Map<String, Object> dos = new HashMap<>();
buffer.position(0);
dos.put("e_magic", buffer.getShort());
// Add all DOS fields...
buffer.position(60);
int peOffset = buffer.getInt();
dos.put("e_lfanew", peOffset);
props.put("dos", dos);
}
private void parsePeSignature() {
int peOffset = (int) ((Map<?, ?>) props.get("dos")).get("e_lfanew");
buffer.position(peOffset);
props.put("signature", buffer.getInt());
}
private void parseFileHeader() {
int offset = (int) ((Map<?, ?>) props.get("dos")).get("e_lfanew") + 4;
buffer.position(offset);
Map<String, Object> fileHeader = new HashMap<>();
fileHeader.put("Machine", buffer.getShort());
fileHeader.put("NumberOfSections", buffer.getShort());
// Add all...
props.put("file_header", fileHeader);
}
private void parseOptionalHeader() {
int offset = (int) ((Map<?, ?>) props.get("dos")).get("e_lfanew") + 24;
buffer.position(offset);
Map<String, Object> opt = new HashMap<>();
short magic = buffer.getShort();
opt.put("Magic", magic);
// Add all fields, handle PE32/PE32+ differences...
props.put("optional_header", opt);
}
private void parseSectionHeaders() {
short numSections = (short) ((Map<?, ?>) props.get("file_header")).get("NumberOfSections");
short optSize = (short) ((Map<?, ?>) props.get("file_header")).get("SizeOfOptionalHeader");
int offset = (int) ((Map<?, ?>) props.get("dos")).get("e_lfanew") + 24 + optSize;
List<Map<String, Object>> sections = new ArrayList<>();
for (int i = 0; i < numSections; i++) {
buffer.position(offset + i * 40);
Map<String, Object> sec = new HashMap<>();
byte[] nameBytes = new byte[8];
buffer.get(nameBytes);
sec.put("Name", new String(nameBytes).trim());
sec.put("VirtualSize", buffer.getInt());
// Add all...
sections.add(sec);
}
props.put("sections", sections);
}
public void printProperties() {
props.forEach((header, value) -> {
System.out.println(header.toUpperCase() + ":");
if (value instanceof List) {
((List<?>) value).forEach(System.out::println);
} else if (value instanceof Map) {
((Map<?, ?>) value).forEach((k, v) -> System.out.println(" " + k + ": " + v));
} else {
System.out.println(value);
}
});
}
public void write(String newFilename) throws IOException {
// Example modification: change TimeDateStamp
int fileHeaderOffset = (int) ((Map<?, ?>) props.get("dos")).get("e_lfanew") + 8;
buffer.position(fileHeaderOffset);
buffer.putInt(0); // New value
buffer.flip();
try (FileOutputStream fos = new FileOutputStream(newFilename == null ? filename : newFilename)) {
fos.getChannel().write(buffer);
}
}
// Usage: ExeParser parser = new ExeParser("example.exe"); parser.printProperties(); parser.write("modified.exe");
}
7. JavaScript Class for .EXE Handling
This JS class (Node.js) uses fs
to read/decode .EXE files, print properties to console, and write modifications. Run with Node.
const fs = require('fs');
class ExeParser {
constructor(filename) {
this.filename = filename;
this.buffer = null;
this.props = {};
this.readAndDecode();
}
readAndDecode() {
this.buffer = fs.readFileSync(this.filename);
this.view = new DataView(this.buffer.buffer);
this.parseDosHeader();
this.parsePeSignature();
this.parseFileHeader();
this.parseOptionalHeader();
this.parseSectionHeaders();
}
parseDosHeader() {
this.props.dos = {
e_magic: this.view.getUint16(0, true),
// Add all...
e_lfanew: this.view.getInt32(60, true)
};
}
parsePeSignature() {
const peOffset = this.props.dos.e_lfanew;
this.props.signature = this.view.getUint32(peOffset, true);
}
parseFileHeader() {
const offset = this.props.dos.e_lfanew + 4;
this.props.file_header = {
Machine: this.view.getUint16(offset, true),
NumberOfSections: this.view.getUint16(offset + 2, true),
// Add all...
};
}
parseOptionalHeader() {
const offset = this.props.dos.e_lfanew + 24;
const magic = this.view.getUint16(offset, true);
this.props.optional_header = { Magic: magic };
// Add all fields...
}
parseSectionHeaders() {
const numSections = this.props.file_header.NumberOfSections;
const optSize = this.props.file_header.SizeOfOptionalHeader;
const offset = this.props.dos.e_lfanew + 24 + optSize;
this.props.sections = [];
for (let i = 0; i < numSections; i++) {
const secOffset = offset + i * 40;
const name = new TextDecoder().decode(this.buffer.slice(secOffset, secOffset + 8)).replace(/\0/g, '');
this.props.sections.push({
Name: name,
VirtualSize: this.view.getUint32(secOffset + 8, true),
// Add all...
});
}
}
printProperties() {
console.log(this.props);
}
write(newFilename = this.filename) {
// Example modification: change TimeDateStamp
const fileHeaderOffset = this.props.dos.e_lfanew + 8;
this.view.setUint32(fileHeaderOffset, 0, true); // New value
fs.writeFileSync(newFilename, this.buffer);
}
}
// Usage: const parser = new ExeParser('example.exe'); parser.printProperties(); parser.write('modified.exe');
8. C Class (C++) for .EXE Handling
This C++ class uses fstream
to read/decode .EXE files, print properties to console, and write modifications. Compile with g++.
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstdint>
struct DosHeader {
uint16_t e_magic;
// Add all fields...
int32_t e_lfanew;
};
struct FileHeader {
uint16_t Machine;
uint16_t NumberOfSections;
// Add all...
};
struct OptionalHeader {
uint16_t Magic;
// Add all...
};
struct SectionHeader {
char Name[8];
uint32_t VirtualSize;
// Add all...
};
class ExeParser {
private:
std::string filename;
std::vector<uint8_t> data;
DosHeader dos;
uint32_t signature;
FileHeader fileHeader;
OptionalHeader optHeader;
std::vector<SectionHeader> sections;
public:
ExeParser(const std::string& fn) : filename(fn) {
readAndDecode();
}
void readAndDecode() {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
auto size = file.tellg();
data.resize(size);
file.seekg(0);
file.read(reinterpret_cast<char*>(data.data()), size);
// Parse DOS
memcpy(&dos, data.data(), sizeof(DosHeader));
// Signature
memcpy(&signature, data.data() + dos.e_lfanew, 4);
// File Header
memcpy(&fileHeader, data.data() + dos.e_lfanew + 4, sizeof(FileHeader));
// Optional Header
memcpy(&optHeader, data.data() + dos.e_lfanew + 24, sizeof(OptionalHeader)); // Adjust size
// Sections
size_t offset = dos.e_lfanew + 24 + fileHeader.SizeOfOptionalHeader;
sections.resize(fileHeader.NumberOfSections);
memcpy(sections.data(), data.data() + offset, fileHeader.NumberOfSections * sizeof(SectionHeader));
}
void printProperties() {
std::cout << "DOS Header: e_magic = " << dos.e_magic << std::endl;
// Print all...
std::cout << "Signature: " << signature << std::endl;
std::cout << "File Header: Machine = " << fileHeader.Machine << std::endl;
// Print all...
for (const auto& sec : sections) {
std::cout << "Section: Name = " << std::string(sec.Name, 8) << ", VirtualSize = " << sec.VirtualSize << std::endl;
// Print all...
}
}
void write(const std::string& newFilename) {
// Example modification: change TimeDateStamp
uint32_t* timestamp = reinterpret_cast<uint32_t*>(data.data() + dos.e_lfanew + 8);
*timestamp = 0; // New value
std::ofstream out(newFilename.empty() ? filename : newFilename, std::ios::binary);
out.write(reinterpret_cast<const char*>(data.data()), data.size());
}
};
// Usage: ExeParser parser("example.exe"); parser.printProperties(); parser.write("modified.exe");