← Back to Projects

Malware Analysis — Part 5: Static Analysis Part 1 — File Identification and PE Structure

A deep dive into static analysis — examining malware without executing it. Covers the Windows PE file format in detail, what headers and sections reveal, entropy analysis, packer detection, and reading import tables like a story.

W
WARN

This series covers the analysis of malicious software in controlled, isolated environments. All techniques described are for educational and defensive security purposes only. The author accepts no responsibility for damage to hardware, software, data, or systems resulting from following this guide.

Previous: Part 4 — Safe Sample Acquisition, Triage, and Permissions


What Is Static Analysis?

Static analysis is the examination of a file's contents without executing it. You are reading the file — its structure, its code, its data — and drawing conclusions about its behavior from what you observe.

This might sound limiting. How can you know what a program does without running it? The answer is that programs are written in code, and code reveals intent. The Windows APIs a program imports tell you what it's capable of doing. The strings embedded in its binary often include C2 server addresses, file paths, registry keys, and error messages. The structure of its sections tells you whether it's been packed to hide its contents.

Static analysis has several important advantages over dynamic analysis:

Safety first. You cannot accidentally trigger malware behavior while reading a file. Static analysis is always safe to perform, even on samples you haven't yet characterized.

Works on broken samples. Dynamic analysis requires a sample to execute successfully. Static analysis works on incomplete samples, samples designed for a different OS, samples that have been corrupted or modified, and samples that won't run in a VM.

Reveals design, not just behavior. Dynamic analysis shows you what the malware does in a specific environment at a specific moment. Static analysis reveals the code itself — including code paths that might never execute in your test environment (backup C2 servers, time-triggered behaviors, conditional logic based on system configuration).

The limitation: Modern malware almost always uses packing, encryption, or obfuscation to defeat static analysis. When you open a packed sample in a disassembler, you see the unpacker stub — not the actual malicious code, which is encrypted on disk. This is why Part 7 (dynamic analysis) and Part 10 (reverse engineering) follow this part — each technique complements the others.


The Windows PE File Format

The Portable Executable (PE) format is the file format used by all Windows executable files — .exe, .dll, .sys, .scr, and others. Understanding PE structure is fundamental to malware analysis because the structure tells you an enormous amount about what a file does before you've run a single tool.

Think of a PE file like a book. The table of contents (the headers) tells you where everything is. The chapters (the sections) contain the actual content. Some chapters are code, some are data, some are resources like images and strings.

The PE File Layout

┌─────────────────────────────────────────┐
│           DOS Header (64 bytes)          │  ← "MZ" magic bytes
├─────────────────────────────────────────┤
│           DOS Stub Program              │  ← "This program cannot be
├─────────────────────────────────────────┤     run in DOS mode" message
│           PE Signature ("PE\0\0")       │  ← Marks start of PE format
├─────────────────────────────────────────┤
│           COFF File Header (20 bytes)   │  ← Machine type, timestamps,
├─────────────────────────────────────────┤     number of sections
│           Optional Header               │  ← Entry point, image base,
├─────────────────────────────────────────┤     import/export directory
│           Section Table                 │  ← Table of contents for sections
├─────────────────────────────────────────┤
│           .text (code section)          │  ← Machine code instructions
├─────────────────────────────────────────┤
│           .data (initialized data)      │  ← Global variables, constants
├─────────────────────────────────────────┤
│           .rdata (read-only data)       │  ← Strings, import/export tables
├─────────────────────────────────────────┤
│           .rsrc (resources)             │  ← Icons, dialogs, embedded files
├─────────────────────────────────────────┤
│           Other sections...             │  ← .bss, .tls, .reloc, etc.
└─────────────────────────────────────────┘

The DOS Header and MZ Magic Bytes

Every PE file begins with the same two bytes: 4D 5A in hexadecimal, which is MZ in ASCII. This is called the magic number — a signature that identifies the file type. "MZ" are the initials of Mark Zbikowski, one of the original MS-DOS developers.

When you open a file in a hex editor and the first two bytes are MZ, you're looking at a Windows executable regardless of its extension. A file named document.pdf with MZ at the start is actually an executable masquerading as a PDF.

The DOS header also contains a critical field: e_lfanew, which is a pointer (offset from the start of the file) to where the PE signature begins. Follow this pointer and you find 50 45 00 00 — the bytes "PE" followed by two null bytes — which marks the start of the actual PE structure.

The COFF File Header

The COFF (Common Object File Format) header immediately follows the PE signature and contains:

Machine type: What CPU architecture this code was compiled for.

  • 0x014C = x86 (32-bit Intel/AMD)
  • 0x8664 = x86-64 (64-bit Intel/AMD)
  • 0xAA64 = ARM64

Number of sections: How many sections the file contains. Most normal programs have 5–8 sections. Packed malware often has very few (2–3) with unusual names.

Time/Date Stamp: When the executable was compiled, as a Unix timestamp. This is easily faked by malware authors — don't trust it as evidence of when the malware was created. A timestamp of 1970-01-01 (Unix epoch zero) or a date far in the future is a red flag suggesting it was deliberately manipulated.

Characteristics: Flags describing the file type.

  • 0x0002 = Executable image
  • 0x2000 = DLL
  • 0x0020 = Large address aware (can use >2GB of memory space)

The Optional Header

Despite its name, the Optional Header is required in PE executables. It contains critical information for the Windows loader:

AddressOfEntryPoint: The address where execution begins — where the first instruction of the program is located. For normal programs this points to some initialization code. For packed malware this points to the unpacker stub — the first code that runs, which decrypts the real malicious payload.

ImageBase: The preferred load address — where the program wants to be loaded in memory. Traditionally 0x00400000 for 32-bit executables and 0x0000000140000000 for 64-bit. Modern Windows uses ASLR (Address Space Layout Randomization) to randomize this, but the ImageBase field tells you what the developer intended.

SectionAlignment and FileAlignment: How sections are aligned in memory and on disk. Unusual values can indicate manual PE construction (common in malware that builds its own PE structure) or specific packing techniques.

DataDirectory: An array of 16 entries pointing to important data structures within the file:

  • Entry 0: Export Table (functions this file exports — important for DLLs)
  • Entry 1: Import Table (functions this file imports — key for analysis)
  • Entry 2: Resource Table (embedded resources)
  • Entry 5: Base Relocation Table
  • Entry 12: Import Address Table (IAT)

The Import Table — Reading Malware's Intentions

The Import Table is arguably the single most valuable part of a PE file for initial static analysis. It lists every Windows API function the program calls by name. Because Windows API function names are descriptive (CreateFile, WriteProcessMemory, RegSetValueEx), the import table essentially describes what the program does before you've read a single line of code.

How Imports Work

When Windows loads a program, it reads the import table and finds entries like:

kernel32.dll:
  - CreateFileA
  - WriteFile
  - ReadFile
  - CloseHandle

ws2_32.dll:
  - WSAStartup
  - connect
  - send
  - recv

Windows then locates these functions in the listed DLLs and fills in the Import Address Table (IAT) with the actual memory addresses. When the program calls CreateFile, it's actually calling the address that was filled into the IAT at load time.

Reading the Import Table Like a Story

Here's how to read imports to understand what a program does.

Kernel32.dll imports — Core Windows operations:

ImportWhat it suggests
CreateFileA/WCreates or opens files — file manipulation
WriteFileWrites data to files — file dropping, log writing
DeleteFileA/WDeletes files — cleanup, self-deletion
CopyFileA/WCopies files — spreading to new locations
FindFirstFileA/WSearches for files — filesystem enumeration
CreateProcessA/WCreates new processes — spawning child processes, executing payloads
OpenProcessOpens a handle to another process — prerequisite for process injection
VirtualAllocExAllocates memory in another process — process injection
WriteProcessMemoryWrites to another process's memory — process injection
CreateRemoteThreadCreates a thread in another process — completing process injection
LoadLibraryA/WLoads a DLL — dynamic loading, evasion
GetProcAddressResolves a function by name at runtime — API hiding
CreateMutexA/WCreates a mutex — single-instance check, a classic persistence/coordination technique
GetTempPathA/WGets the temp directory path — where malware often drops files
CreateServiceA/WCreates a Windows service — persistence mechanism
RegOpenKeyExA/WOpens a registry key — reading configuration or persistence
RegSetValueExA/WSets a registry value — writing persistence, changing configuration

WS2_32.dll imports — Network operations:

ImportWhat it suggests
WSAStartupInitializes network operations — any network communication
socketCreates a network socket
connectConnects to a remote host — outbound C2 connection
sendSends data — exfiltration or C2 communication
recvReceives data — receiving commands from C2
gethostbynameDNS resolution — resolving C2 domain
bind / listen / acceptListening for connections — backdoor server

Advapi32.dll imports — Security and registry:

ImportWhat it suggests
RegCreateKeyExA/WCreates registry keys — persistence
AdjustTokenPrivilegesModifies process privileges — privilege escalation
OpenSCManagerA/WOpens Service Control Manager — service manipulation
LookupPrivilegeValueA/WLooks up privilege constants — privilege escalation
CryptEncrypt / CryptDecryptCryptographic operations — data encryption/decryption

A classic process injection import set: If you see this combination, you're almost certainly looking at process injection malware:

OpenProcess
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread

This is the standard four-function pattern for injecting code into another process — it appears in almost every RAT, banking trojan, and advanced malware sample.

Import Hiding — Why Packed Samples Have Few Imports

A packed sample often shows almost no imports — just the handful needed to unpack itself:

kernel32.dll:
  - LoadLibraryA
  - GetProcAddress
  - VirtualAlloc

With just these three functions, the unpacker can:

  1. Allocate new memory (VirtualAlloc)
  2. Decrypt the real payload into that memory
  3. Load any DLL it needs (LoadLibraryA)
  4. Find any function it needs by name (GetProcAddress)

The real import table is hidden inside the encrypted payload. This is why packed samples require unpacking before import analysis is useful.


Section Analysis

PE sections contain the actual content of the executable. Understanding what each section is, what it should contain, and what anomalies look like is a key analysis skill.

Standard Sections

SectionNormal ContentsCharacteristics
.textExecutable code — machine instructionsExecute + Read permissions, low entropy
.dataInitialized global variablesRead + Write permissions, low entropy
.rdataRead-only data — strings, constants, import/export tablesRead-only, low entropy
.bssUninitialized data (zero-filled at load time)Read + Write, no disk space
.rsrcResources — icons, dialogs, version info, embedded filesRead-only, variable entropy
.relocBase relocation tableRead-only
.tlsThread Local Storage dataVariable

Section Anomalies in Malware

Unusual section names: Legitimate compilers produce sections with standard names. Malware sometimes uses sections with no name, random names, or names suggesting packing tools (.UPX0, .packed, etc.).

Unusual permissions: A section that is both writable and executable is highly suspicious. In a normal program, code sections are executable but not writable, and data sections are writable but not executable. A writable+executable section suggests self-modifying code — common in packers and shellcode.

Section size discrepancies: Each section has a Virtual Size (how much space it takes in memory) and a Raw Size (how much space it takes on disk). For a packed section, the Virtual Size is much larger than the Raw Size — because the packed data expands when decompressed in memory.


Entropy Analysis

Entropy is a measure of randomness or unpredictability in data. Shannon entropy is measured from 0 to 8 bits per byte:

Entropy RangeWhat It Suggests
0.0 – 1.0Highly repetitive data (lots of zero bytes)
3.5 – 5.0Normal compiled code — mix of opcodes and data
5.0 – 7.0Compressed or encoded data, text, natural language
7.0 – 8.0Encrypted or well-compressed data — very random-looking

High entropy (7.0+) in a PE section is a strong indicator of packing or encryption. When malware authors pack a binary, the encrypted payload section has entropy close to 8.0 because encrypted data looks indistinguishable from random bytes.

A normal Windows executable looks like this in PEStudio:

.text  (code):  entropy ~5.5–6.5
.rdata (data):  entropy ~3.5–5.0
.data:          entropy ~2.0–4.0

A packed malware sample might look like:

.text:          entropy ~7.8  ← Suspicious
.data:          entropy ~7.9  ← Suspicious

Two sections near maximum entropy means most of the file is encrypted — you'll need to unpack it before meaningful static analysis.


Tools for PE Analysis

PEStudio — Your First Tool

PEStudio (winitor.com) is a free static analysis tool specifically designed for PE files. It's the first tool most analysts open with a new sample because it provides a comprehensive overview of a file in a single interface.

Key features:

  • Indicators panel: Automatically flags suspicious characteristics — packed indicators, suspicious imports, unusual section names, missing digital signature, imports commonly used by malware
  • Import table: Every imported function with annotations indicating which ones are "interesting" from a security perspective
  • Strings: Extracted strings with relevance scoring
  • Sections: Section names, sizes, entropy, and permissions
  • VirusTotal integration: Check the file hash against VirusTotal directly from the tool
  • Version information: Product name, company, description (often spoofed by malware)

Workflow with PEStudio:

  1. Drag the sample file onto PEStudio
  2. Review the indicators panel first — red/yellow items need attention
  3. Check the sections for unusual names, entropy, or permissions
  4. Review the imports — what DLLs and functions are present
  5. Check strings for obvious URLs, paths, or suspicious content
  6. Note the compilation timestamp (verify if suspicious)
  7. Check for a digital signature (presence or absence, validity)

Detect-It-Easy (DIE) — Packer Detection

DIE (github.com/horsicq/Detect-It-Easy) identifies the tools used to create or modify a binary:

  • Compiler (Visual C++, GCC, Delphi, Go, Rust)
  • Packer (UPX, MPRESS, Themida, ASPack, etc.)
  • Protector (VMProtect, Obsidium, Enigma)
  • Linker
  • Framework (.NET, AutoIT, NSIS installer)

Why it matters: The packer or compiler tells you what analysis approach to take. A UPX-packed binary can be unpacked with a single command. A Themida-protected binary requires a skilled debugger session. A .NET binary is analyzed with dnSpy rather than Ghidra.

Unpacking UPX directly:

UPX is the most common open-source packer. Many malware families use it. If DIE identifies UPX, you can unpack it in seconds:

# On REMnux or any system with UPX installed
upx -d packed_malware.exe -o unpacked_malware.exe

Now analyze unpacked_malware.exe — the real import table and strings are now visible.

PE-bear — Deep Structure Examination

PE-bear (github.com/hasherezade/pe-bear, free) provides the most detailed view of PE structure in a visual interface:

  • Full hex view of headers with field-level annotation
  • Section viewer with raw and virtual addresses
  • Import and export table viewer
  • Reconstruction tools for malformed PEs

PE-bear is particularly useful when a PE file has been manually crafted (as is common in shellcode loaders) or when you need to verify specific header fields that look suspicious.

HxD — Hex Editor

HxD (mh-nexus.de/en/hxd) is a free, straightforward hex editor.

Common hex analysis tasks:

  • Verify file type from magic bytes (first 4–8 bytes)
  • Search for embedded files (look for MZ headers, ZIP signatures, etc. in the middle of a file)
  • Find encoded data (repeated byte patterns, blocks of high-entropy bytes)
  • Manually examine suspicious bytes flagged by other tools

Professional alternative: 010 Editor (~$100) is the professional standard hex editor with binary templates that auto-parse file formats (PE, ELF, ZIP, etc.). ImHex (free) is a modern open-source alternative with similar template support.


Putting It All Together — A Static Analysis Workflow

Here's how to apply all the concepts in this part systematically:

Phase 1 — File Identification (2 minutes)

1. file sample.exe             → Verify file type from magic bytes
2. Get-FileHash -SHA256        → Compute hash
3. VirusTotal hash lookup      → Check detection status
4. exiftool sample.exe         → Extract metadata

Phase 2 — Packer Detection (2 minutes)

5. Open in DIE                 → Identify compiler/packer
6. Check section entropy       → High entropy = likely packed
7. Count imports               → Few imports = likely packed

Decision point: If packed, go to Part 10 (debugging to unpack) before continuing. If not packed, proceed.

Phase 3 — PE Structure Analysis (10 minutes)

8. Open in PEStudio            → Review all indicators
9. Examine imports             → What DLLs and APIs are used?
10. Review sections            → Unusual names, permissions, sizes?
11. Check compilation time     → Real timestamp or 1970 epoch?
12. Digital signature?         → Present? Valid? From whom?

Phase 4 — Strings Analysis (10 minutes)

13. PEStudio strings tab       → Quick scan for obvious IOCs
14. FLOSS (next part)          → Extract obfuscated strings

Phase 5 — Document Findings

15. Record all suspicious indicators
16. List all notable imports with what they suggest
17. Record any extracted IOCs (URLs, IPs, file paths)
18. Note whether unpacking is needed
19. Update analysis notes with plan for dynamic analysis

What You've Learned to Read

After completing this part, when you look at a PE file you can answer:

  • What architecture is this? (x86, x64, ARM)
  • Was it recently compiled? (or is the timestamp suspicious)
  • Is it packed? (high entropy, few imports)
  • What does it interact with? (network, filesystem, registry, processes)
  • Is process injection likely? (VirtualAllocEx, WriteProcessMemory, CreateRemoteThread)
  • Does it establish persistence? (registry run keys, service creation)
  • Does it communicate on the network? (WS2_32 imports)
  • Is it signed? (and by whom — legitimate or stolen certificate)

All of this before running a single tool beyond a hex editor and PEStudio — and without ever executing the malware.


Verification Checklist

  • PEStudio installed and opens a sample file
  • DIE installed and identifies file types
  • HxD installed and you've viewed a file's raw bytes
  • PE-bear installed
  • You can identify the "MZ" header in a hex editor
  • You understand what the import table reveals
  • You can identify signs of packing from section entropy
  • You've practiced reading imports and identifying suspicious API calls
  • You can compute SHA-256 hashes from the command line

Previous: Part 4 — Safe Sample Acquisition, Triage, and Permissions Next: Part 6 — Static Analysis Part 2: Strings, YARA, and IOC Extraction