MALWARE ANALYSIS REFERENCE

Windows Internals & Malware Structures

A comprehensive field reference for malware analysts — covering process internals, memory structures, PE format, kernel objects, and exploitation-relevant fields. Every structure is annotated with analyst tips and attacker abuse techniques.

WINDOWS INTERNALS MEMORY ANALYSIS PE FORMAT ATTACKER ABUSE
PROCESS STRUCTURES
PEB — Process Environment Block
USERMODE HIGH-VALUE TARGET
The PEB is a user-mode structure maintained by the OS for each process. It sits at a fixed, discoverable address and contains almost everything the loader, heap manager, and runtime need — which is exactly why malware reads it constantly. Located at fs:[0x30] (x86) or gs:[0x60] (x64).
OFFSET (x64) FIELD TYPE DESCRIPTION
+0x000 InheritedAddressSpace BYTE Non-zero if process was spawned from another process's address space
+0x002 BeingDebugged BYTE 1 if a debugger is attached — classic anti-debug check point
+0x010 ImageBaseAddress PVOID Base address where the main executable was loaded in memory
+0x018 Ldr PPEB_LDR_DATA Pointer to loader data — contains linked lists of all loaded modules (DLLs). Core of manual DLL walking.
+0x020 ProcessParameters PRTL_USER_PROCESS_PARAMETERS Command line, image path, environment variables, current directory
+0x030 ProcessHeap PVOID Pointer to the default process heap
+0x058 NtGlobalFlag ULONG Set to 0x70 when running under debugger with heap debugging — another anti-debug check target
+0x068 NumberOfHeaps ULONG Count of heaps in the process
+0x078 OSMajorVersion / OSMinorVersion ULONG OS version — some malware checks this to choose code paths
+0x2EC ImageSubsystem ULONG Console (3) vs GUI (2) — IMAGE_SUBSYSTEM_*
// ANALYST TIP
  • In x64dbg: View → PEB shows a parsed view. In WinDbg: !peb or dt ntdll!_PEB @$peb
  • PEB.Ldr is your fastest path to enumerate loaded modules without calling any API — every reflective loader abuses this.
  • Check BeingDebugged and NtGlobalFlag for anti-debug patching. Flip both to 0 to bypass basic checks.
// ATTACKER ABUSE
  • API hashing / reflective loaders walk Ldr.InMemoryOrderModuleList to find kernel32.dll and ntdll.dll base addresses without calling LoadLibrary
  • Module stomping overwrites a legitimate DLL's memory region discovered via Ldr
  • Anti-debug via BeingDebugged, NtGlobalFlag, and NtQueryInformationProcess(ProcessDebugPort)
  • Process hollowing reads ImageBaseAddress to locate and overwrite the host process image
; Classic 64-bit API resolution stub (shellcode) ; Get kernel32.dll base via PEB → Ldr → InMemoryOrderModuleList mov rax, qword ptr gs:[0x60] ; rax = PEB mov rax, [rax + 0x18] ; rax = PEB.Ldr mov rax, [rax + 0x20] ; rax = Ldr.InMemoryOrderModuleList.Flink mov rax, [rax] ; skip ntdll (first entry) mov rax, [rax + 0x20] ; DllBase of kernel32.dll
TEB — Thread Environment Block
PER-THREADSHELLCODE START
The TEB (also called NT_TIB) is a per-thread structure in user space. Each thread has its own TEB. It's always accessible via fs:[0] (x86) or gs:[0] (x64) — no API call needed. It's the first thing shellcode reads to bootstrap itself.
OFFSET (x64) FIELD TYPE DESCRIPTION
+0x000 NtTib.ExceptionList PEXCEPTION_REGISTRATION SEH chain pointer (x86 only — in x64 exceptions use table-based dispatch)
+0x008 NtTib.StackBase PVOID Top of the thread's stack (highest address)
+0x010 NtTib.StackLimit PVOID Bottom of committed stack. Stack grows down toward this.
+0x030 NtTib.Self PNT_TIB Self-pointer — reading gs:[0x30] always gives the TEB address itself
+0x060 ProcessEnvironmentBlock PPEB Pointer back to the PEB of this process
+0x068 LastErrorValue ULONG Per-thread last error (what GetLastError() returns)
+0x0C0 ThreadLocalStorage PVOID[64] TLS slots — used by some malware for per-thread state hiding
+0x100 RealClientId.UniqueProcess HANDLE PID of owning process
+0x108 RealClientId.UniqueThread HANDLE TID of this thread
+0x1480 DeallocationStack PVOID Base of the memory allocation that backs this thread's stack
// ANALYST TIP
  • WinDbg: dt ntdll!_TEB @$teb | x64dbg: no native view, use gs:[0x30] as pointer and follow in memory dump
  • Reading gs:[0x60] (offset of PEB inside TEB) is the standard shellcode bootstrap to get PEB without any API call
  • Check LastErrorValue — can confirm whether a suspicious API call succeeded or failed
EPROCESS — Executive Process Object (Kernel)
KERNELDKOM TARGET
EPROCESS is the kernel-mode process object. Every running process has one. It's allocated in the kernel pool and linked into a doubly-linked list (ActiveProcessLinks). Rootkits manipulate this list to hide processes (DKOM — Direct Kernel Object Manipulation). Offsets vary by Windows build — always verify with dt nt!_EPROCESS.
OFFSET (Win10 22H2 x64 ~) FIELD TYPE DESCRIPTION
+0x000 Pcb (KPROCESS) KPROCESS Embedded kernel process control block — scheduler uses this
+0x2E0 ActiveProcessLinks LIST_ENTRY Doubly-linked list of all processes. Rootkits unlink here to hide (DKOM)
+0x2E8 Quota PEPROCESS_QUOTA_BLOCK Pool quota tracking
+0x358 UniqueProcessId PVOID The PID. Cast to ULONG_PTR.
+0x448 ImageFileName CHAR[15] Process image name (max 14 chars, null-term). Truncated — use full path from SeAuditProcessCreationInfo for full name.
+0x550 Token EX_FAST_REF Security token. Token swapping (steal SYSTEM token) targets this offset.
+0x4B0 InheritedFromUniqueProcessId PVOID Parent PID — useful in forensics to reconstruct process tree
+0x578 Protection PS_PROTECTION PPL (Protected Process Light) flags — EDRs use this to protect their processes
+0x828 VadRoot RTL_AVL_TREE Root of Virtual Address Descriptor tree — describes all memory regions
+0x8B8 Peb PPEB Pointer to user-mode PEB
// ANALYST TIP
  • WinDbg: !process 0 0 to list all, dt nt!_EPROCESS <addr> to dump one
  • Volatility: vol.py -f mem.dmp windows.pslist (walks ActiveProcessLinks), windows.psscan (pool-scan, finds unlinked processes)
  • If pslist and psscan disagree → process is likely DKOM-hidden (rootkit present)
  • Token offset varies per build — always run dt nt!_EPROCESS to confirm on target system
// ATTACKER ABUSE
  • DKOM process hiding: unlink ActiveProcessLinks Flink/Blink — process disappears from Task Manager and most tools
  • Token theft: find SYSTEM's EPROCESS, copy its Token to attacker process for privilege escalation (e.g. used by EternalBlue shellcode)
  • PPL bypass: modify Protection to remove PPL flags on EDR processes (e.g. GhostWrite exploit)
ETHREAD — Executive Thread Object (Kernel)
KERNEL
Each thread has a kernel-mode ETHREAD structure linked to its parent EPROCESS. Key for understanding thread injection, start address spoofing, and kernel-level thread analysis.
OFFSET (Win10 x64 ~) FIELD TYPE DESCRIPTION
+0x000 Tcb (KTHREAD) KTHREAD Embedded kernel thread block — contains stack pointer, state, scheduling info
+0x428 StartAddress PVOID Original thread start address — injection sometimes overwrites this. Compare to Win32StartAddress.
+0x4B8 Win32StartAddress PVOID User-mode start address passed to CreateThread. If different from StartAddress, possible injection.
+0x510 ThreadListEntry LIST_ENTRY Links this thread into the owning EPROCESS thread list
+0x600 CrossThreadFlags ULONG Includes Terminated, HideFromDebugger, SystemThread flags
+0x6C0 Cid.UniqueProcess HANDLE Owning process PID
+0x6C8 Cid.UniqueThread HANDLE This thread's TID
// ANALYST TIP
  • WinDbg: !thread <addr> or dt nt!_ETHREAD <addr>
  • Discrepancy between Win32StartAddress and StartAddress, or start address pointing inside a non-image (anonymous) region = strong injection indicator
  • Volatility: windows.cmdline, windows.threads
KPCR / KPRCB — Processor Control Region / Block
KERNELPER-CPU
The KPCR is a per-processor structure in kernel space. On x64, gs in kernel mode points to KPCR (while in user mode gs points to TEB). It contains the current IRQL, IDT pointer, and the embedded KPRCB which holds the current thread pointer and CPU scheduling state.
OFFSET FIELD DESCRIPTION
+0x000 NtTib NT_TIB — mirrors TEB layout for compatibility
+0x180 Prcb (KPRCB) Embedded Processor Control Block — holds CurrentThread, IdleThread, scheduler queues
+0x180+0x008 Prcb.CurrentThread Pointer to the KTHREAD currently running on this CPU
+0x180+0x0C8 Prcb.ProcessorNumber Which logical CPU this KPCR belongs to
// ANALYST TIP
  • WinDbg kernel: !pcr or dt nt!_KPCR @$pcr
  • Getting current ETHREAD from KPCR: gs:[0x188] → KTHREAD → back-pointer to ETHREAD
  • Rootkits manipulating the IDT (at KPCR.IdtBase) can intercept hardware interrupts
MEMORY STRUCTURES
VAD — Virtual Address Descriptor Tree
MEMORY MAPINJECTION DETECTION
The VAD tree is a balanced AVL tree rooted at EPROCESS.VadRoot. Each node (MMVAD) describes one virtual memory region — its start/end page, protection flags, and whether it's backed by a file or is private (anonymous). It's the kernel's record of what the process's virtual address space looks like.
FIELD TYPE DESCRIPTION
StartingVpn ULONG_PTR Starting virtual page number (multiply by 0x1000 to get address)
EndingVpn ULONG_PTR Ending virtual page number (inclusive)
u.VadFlags.Protection ULONG:5 MM_PROTECT flags — 0x6 = PAGE_EXECUTE_READ_WRITE (major red flag)
u.VadFlags.PrivateMemory ULONG:1 1 = private (no backing file). RWX private region in the middle of process space = shellcode injection.
u.VadFlags.MemCommit ULONG:1 1 = memory is committed, not just reserved
Subsection PSUBSECTION For mapped files, points to the section object with file name — null for private memory
FileObject PFILE_OBJECT Backing file object. If present, you can extract the DLL/EXE path. If absent but region is executable → suspicious.
// ANALYST TIP
  • Volatility: windows.vadinfo — dumps VAD tree with file paths and protection flags
  • WinDbg: !vad or !vadex for extended info
  • Look for: RWX private regions (no backing file, executable, writable) — classic sign of code injection or reflective loading
  • Look for: RX private regions in unexpected locations — could be PE loaded reflectively without going through the loader
  • Check if a region that looks like a legitimate DLL path actually matches the file on disk (VAD path ≠ disk content = module stomping)
// ATTACKER ABUSE
  • Process injection detection: injected shellcode shows as a private RWX or RX region with no backing file
  • Reflective DLL injection: entire DLL loaded into private memory — no VAD entry with a file path, but contains a full PE image
  • VAD manipulation (rootkit): some rootkits delete or modify VAD nodes to hide memory regions from kernel-level scanners
Process Memory Layout — Windows x64
LAYOUT
Visual map of a typical 64-bit Windows process address space. Regions are approximate — ASLR randomises base addresses.
FFFF…0000
Kernel Space
EPROCESS, ETHREAD, kernel heap, drivers — inaccessible from user mode
7FFF…0000
ntdll.dll / kernel32.dll
Always highest user-mode DLLs. ntdll is the gateway to syscalls.
~7FF…
Other loaded DLLs
DLLs mapped by the loader — visible in VAD tree with file paths
~7FFE0000
KUSER_SHARED_DATA
Read-only page shared between kernel and all user processes — system time, version, etc.
variable
TEB (per thread)
One per thread. Accessible via gs:[0x30] → self pointer.
variable
PEB
One per process. ASLR'd base but reachable from TEB at gs:[0x60].
variable
Heap(s)
Default process heap + additional heaps. Private, RW. Where malloc/HeapAlloc go.
variable
Stack (per thread)
Grows down. Typically 1–8 MB reserved, committed on demand. Private, RW.
ASLR base
Main EXE image
Loaded at ASLR'd base. PEB.ImageBaseAddress points here.
0x0000…
NULL page (no access)
First 64KB not accessible. NULL dereference crashes here.
// ANALYST TIP
  • Any executable private region not backed by a file is the biggest injection red flag in memory forensics
  • DLLs loaded via LoadLibrary appear with file paths in VAD. Reflectively loaded DLLs do not.
  • Volatility windows.malfind automates the search for private executable regions
Heap Internals — NT Heap / Segment Heap
HEAPEXPLOIT TARGET
Windows uses two heap implementations: the legacy NT Heap (used by most processes) and the newer Segment Heap (used by UWP and some system processes). Both are relevant for understanding exploit primitives and heap-based shellcode execution.
FIELD (NT Heap) TYPE DESCRIPTION
_HEAP.Signature ULONG 0xEEFFEEFF = valid NT heap. Corruption of this = heap is compromised.
_HEAP.FrontEndHeap PVOID Pointer to LFH (Low Fragmentation Heap) or NULL if disabled
_HEAP_ENTRY.Size USHORT Chunk size in 8/16-byte granules. Encoded with a key (XOR) since Win Vista to resist overwrites.
_HEAP_ENTRY.Flags BYTE HEAP_ENTRY_BUSY (0x01), HEAP_ENTRY_EXTRA_PRESENT (0x02), etc.
_HEAP_FREE_ENTRY.Flink/Blink LIST_ENTRY Free list links — classic target for freelist unlink attacks (mostly mitigated post-Vista)
// ANALYST TIP
  • WinDbg: !heap -a (all heaps), !heap -h <addr> (specific heap), !heap -p -a <addr> (allocation at address)
  • For page heap / heap debugging: gflags.exe /i <process> +hpa — enables full page heap (every allocation gets a guard page)
  • Heap spraying detection: look for large blocks of repeated bytes (shellcode NOP sleds) via Volatility or raw memory search
PE FORMAT — PORTABLE EXECUTABLE
IMAGE_DOS_HEADER — DOS Stub + e_lfanew
PE ENTRY
Every PE file starts with the DOS header at offset 0. The only field that truly matters for modern PE parsing is e_magic (the MZ signature) and e_lfanew — the offset to the NT Headers.
OFFSET FIELD TYPE DESCRIPTION
+0x000 e_magic WORD 0x5A4D = "MZ" — PE signature. Every PE file starts with this.
+0x002–0x003A e_cblp … e_oemid WORD[…] Legacy DOS fields — usually irrelevant, sometimes used to hide data
+0x03C e_lfanew LONG File offset of the NT Headers (IMAGE_NT_HEADERS). Jump here to start real PE parsing.
+0x040–~0x0C0 DOS Stub BYTE[] "This program cannot be run in DOS mode" — sometimes replaced with hidden data or zeroed by packers
// ANALYST TIP
  • Quick check: first two bytes of any PE file = 4D 5A (MZ). When carving PE files from memory dumps, search for this signature.
  • Malware sometimes modifies the DOS stub or uses the region between DOS stub and NT Headers to hide shellcode or a config blob
  • e_lfanew value > 0x200 is suspicious — large gap to NT headers may contain hidden data
IMAGE_NT_HEADERS — PE Signature + File/Optional Headers
CORE
Located at ImageBase + e_lfanew. Contains the PE signature, then the File Header and Optional Header inline.
OFFSET FIELD TYPE DESCRIPTION
+0x000 Signature DWORD 0x00004550 = "PE\0\0". Must be present or loader rejects file.
+0x004 FileHeader IMAGE_FILE_HEADER Machine type, section count, timestamp, characteristics
+0x018 OptionalHeader IMAGE_OPTIONAL_HEADER Entry point, image base, section alignment, data directories
IMAGE_FILE_HEADER — COFF Header
PE
OFFSET FIELD TYPE DESCRIPTION
+0x000 Machine WORD 0x014C = x86 (IMAGE_FILE_MACHINE_I386) · 0x8664 = x64 (AMD64) · 0xAA64 = ARM64
+0x002 NumberOfSections WORD Count of section headers that follow the Optional Header. Validate against actual size.
+0x004 TimeDateStamp DWORD Unix timestamp of compilation. Often 0 or faked by malware. Zeroed by some linkers.
+0x008 PointerToSymbolTable DWORD Usually 0 in modern PE (debug symbols stripped)
+0x010 SizeOfOptionalHeader WORD Must match expected size. Corrupt value = parser bypass technique.
+0x012 Characteristics WORD 0x0002 = Executable · 0x2000 = DLL · 0x0100 = 32-bit · 0x0020 = Large Address Aware
// ANALYST TIP
  • Timestamp 0 or a future date = likely packed, compiled by a tool that strips timestamps, or deliberately tampered
  • Tools: pefile Python lib, CFF Explorer, PE-bear, pecheck.py
IMAGE_OPTIONAL_HEADER — Entry Point, Base, Data Directories
CRITICALPACKER INDICATOR
Despite the name, this header is mandatory. It's larger for x64 (PE32+). The Magic field differentiates: 0x010B = PE32, 0x020B = PE32+.
OFFSET (PE32+) FIELD TYPE DESCRIPTION
+0x000 Magic WORD 0x010B = PE32 (x86) · 0x020B = PE32+ (x64)
+0x002 MajorLinkerVersion BYTE Linker version — can hint at compiler/packer
+0x008 SizeOfCode DWORD Total size of all code sections. Should roughly match .text section size.
+0x010 AddressOfEntryPoint DWORD RVA of where execution begins. If this points into a section other than .text, likely packed/shellcode.
+0x018 ImageBase ULONGLONG Preferred load address. EXEs default 0x140000000 (x64), DLLs 0x180000000. ASLR changes actual load address.
+0x020 SectionAlignment DWORD Alignment of sections in memory. Usually 0x1000 (page size).
+0x024 FileAlignment DWORD Alignment of sections in the file. Usually 0x200 (512 bytes).
+0x038 SizeOfImage DWORD Total size of PE in memory (must be multiple of SectionAlignment). Used by loader to allocate space.
+0x040 SizeOfHeaders DWORD Combined size of DOS header, NT headers, section headers — rounded to FileAlignment
+0x044 CheckSum DWORD PE checksum. Most EXEs have 0 (not verified). Drivers and DLLs in %system32% are verified.
+0x04C Subsystem WORD 2 = GUI (Windows) · 3 = Console (CUI) · 1 = Native (driver-like)
+0x04E DllCharacteristics WORD 0x0040 = DYNAMIC_BASE (ASLR) · 0x0100 = NX_COMPAT (DEP) · 0x4000 = GUARD_CF (CFG)
+0x090 DataDirectory[16] IMAGE_DATA_DIRECTORY[16] Array of RVA+Size pairs pointing to: Import Table [1], Export Table [0], Resource [2], Exception [3], Security [4], Reloc [5], Debug [6], TLS [9], IAT [12], Delay Import [13]
// ANALYST TIP
  • Packer indicators: AddressOfEntryPoint in a non-.text section, high entropy in code sections, SizeOfCode vs actual section sizes mismatch, section names like .UPX0, .themida, .MPRESS
  • Missing DllCharacteristics flags (no ASLR, no DEP, no CFG) on a 2020+ binary = suspicious (legitimate modern compilers enable all three by default)
  • Native subsystem (1) on a non-driver EXE = very suspicious — runs before the Win32 subsystem initialises
IMAGE_SECTION_HEADER — Section Metadata
PEPACKER INDICATOR
OFFSET FIELD TYPE DESCRIPTION
+0x000 Name BYTE[8] Section name (not null-terminated if exactly 8 chars). .text · .data · .rdata · .rsrc · .reloc · .tls
+0x008 VirtualSize DWORD Actual size of section data in memory
+0x00C VirtualAddress DWORD RVA of section start in memory
+0x010 SizeOfRawData DWORD Size of section in file (aligned to FileAlignment)
+0x014 PointerToRawData DWORD File offset to section data
+0x024 Characteristics DWORD 0x20 = code · 0x40 = initialized data · 0x80 = uninitialized data · 0x20000000 = executable · 0x40000000 = readable · 0x80000000 = writable
// ANALYST TIP — Common sections
.text
CODE · RX
Executable code. High entropy here = likely packed.
.data
DATA · RW
Initialised global/static variables.
.rdata
DATA · R
Read-only data: strings, import/export tables, debug info.
.rsrc
RESOURCE · R
Icons, version info, manifest, embedded files.
.reloc
DATA · R
Base relocation table — needed when ASLR changes ImageBase.
.tls
DATA · RW
Thread-local storage — TLS callbacks run BEFORE OEP, used as anti-debug.
  • Writable + Executable section (W+X) = self-modifying code or packer stub. Major red flag.
  • High entropy (>7.0) in any section = likely encrypted/compressed payload (packed)
  • VirtualSize >> SizeOfRawData = unpacking into zero-filled memory at runtime
Import Table (IAT / IDT) — IMAGE_IMPORT_DESCRIPTOR
IMPORTSIAT HOOK
The Import Directory Table (IDT) is an array of IMAGE_IMPORT_DESCRIPTOR structs (one per DLL), terminated by a null entry. Each descriptor links to the IAT (Import Address Table) which the loader fills with actual function addresses at load time. This is what malware hooks, patches, and walks to resolve APIs.
OFFSET FIELD TYPE DESCRIPTION
+0x000 OriginalFirstThunk DWORD (RVA) RVA to INT (Import Name Table) — array of IMAGE_THUNK_DATA. Preserved after load — use this to see what was imported.
+0x004 TimeDateStamp DWORD 0 before binding, -1 (0xFFFFFFFF) after bound import. Bound imports = IAT pre-patched with hardcoded addresses.
+0x00C Name DWORD (RVA) RVA to null-terminated DLL name string (e.g. "kernel32.dll")
+0x010 FirstThunk DWORD (RVA) RVA to IAT — loader overwrites this with actual function addresses. This is what code calls at runtime.
// ATTACKER ABUSE
  • IAT hook: overwrite entries in the IAT (FirstThunk array) to redirect API calls to malicious code. Cheap and easy, detected by comparing IAT values to actual module addresses.
  • Import-free malware: no IDT entries at all — uses PEB walking + API hashing to resolve everything at runtime. Extremely common in shellcode and advanced loaders.
  • Delay imports: suspicious DLLs imported via delay-load table (DataDirectory[13]) — only resolved on first call, evades static import analysis
// HIGH-VALUE IMPORT WATCHLIST
VirtualAlloc / VirtualAllocEx
Memory allocation for injection
WriteProcessMemory
Remote memory write = injection
CreateRemoteThread / NtCreateThreadEx
Remote thread = injection
SetWindowsHookEx
Keylogger / DLL injection via hook
OpenProcess
Accessing other processes
CryptEncrypt / CryptDecrypt
Crypto — ransomware, comms
RegSetValueEx
Registry persistence
WinExec / CreateProcess / ShellExecute
Execution
InternetOpenUrl / HttpSendRequest
C2 communication
IsDebuggerPresent / CheckRemoteDebuggerPresent
Anti-debug
Export Table — IMAGE_EXPORT_DIRECTORY
DLL EXPORTS
Present in DLLs (and some EXEs). The export directory lets other modules find functions by name or ordinal. Reflective loaders walk this to find GetProcAddress and LoadLibraryA in kernel32 before any other API is available.
OFFSET FIELD TYPE DESCRIPTION
+0x000 Characteristics DWORD Reserved, always 0
+0x004 TimeDateStamp DWORD Timestamp — same caveats as File Header timestamp
+0x00C Name DWORD (RVA) RVA to DLL name string — the name the DLL calls itself (can differ from filename!)
+0x010 Base DWORD Starting ordinal number (usually 1)
+0x014 NumberOfFunctions DWORD Total exported functions (including ordinal-only, no name)
+0x018 NumberOfNames DWORD Number of named exports
+0x01C AddressOfFunctions DWORD (RVA) Array of RVAs to exported functions, indexed by ordinal
+0x020 AddressOfNames DWORD (RVA) Array of RVAs to function name strings
+0x024 AddressOfNameOrdinals DWORD (RVA) Array of WORDs — maps name index → ordinal. Use: Names[i] → NameOrdinals[i] → Functions[ordinal]
// ANALYST TIP
  • Export forwarding: when AddressOfFunctions[i] points inside the export directory itself, it's a forwarded export (string like "NTDLL.RtlAllocateHeap")
  • Malicious DLLs often export functions named to match legitimate DLLs (DLL hijacking via export forwarding or proxy DLL)
  • DLL Name field mismatch (DLL calls itself "kernel32.dll" but is loaded as "evil.dll") → strong indicator of DLL impersonation
SECURITY STRUCTURES
Access Token — _TOKEN (Kernel)
PRIVESCTOKEN THEFT
Every process and thread has an Access Token — the OS's proof of identity. It contains the user's SID, group SIDs, privileges, and integrity level. This is what determines what a process can access. Token manipulation is the core of Windows privilege escalation.
FIELD DESCRIPTION
UserAndGroups Array of SID_AND_ATTRIBUTES — user SID + all group SIDs the token belongs to
Privileges Array of LUID_AND_ATTRIBUTES — privileges held (e.g. SeDebugPrivilege, SeImpersonatePrivilege). Enabled vs just present.
IntegrityLevel Low (0x1000) · Medium (0x2000) · High (0x3000) · System (0x4000). Mandatory Integrity Control (MIC).
TokenType TokenPrimary (1) = process token · TokenImpersonation (2) = thread impersonation token
ImpersonationLevel SecurityAnonymous / Identification / Impersonation / Delegation — how much identity is delegated
SessionId Logon session — used by UAC to detect console vs remote sessions
// ATTACKER ABUSE
  • Token theft / impersonation: OpenProcessToken on a SYSTEM process → DuplicateToken → ImpersonateLoggedOnUser → attacker code runs as SYSTEM
  • Token manipulation in kernel: directly overwrite EPROCESS.Token pointer with address of SYSTEM process token (used by kernel exploits like EternalBlue)
  • SeImpersonatePrivilege abuse: service accounts with this privilege are vulnerable to potato-class attacks (PrintSpoofer, RoguePotato, GodPotato)
  • UAC bypass: elevate token from Medium → High integrity without a UAC prompt using auto-elevated COM objects
// ANALYST TIP
  • WinDbg: !token <addr> or dt nt!_TOKEN <addr>
  • Check if a process running as a non-admin user has SeDebugPrivilege enabled — should only be Administrators
  • Volatility: windows.privileges — lists privileges per process including whether they're enabled
PEB_LDR_DATA / LDR_DATA_TABLE_ENTRY — Module Loader Lists
API RESOLUTION
PEB.Ldr points to PEB_LDR_DATA which contains three doubly-linked lists of LDR_DATA_TABLE_ENTRY — one in load order, one in memory order, one in init order. These are the foundation of all shellcode API resolution.
OFFSET (x64) FIELD DESCRIPTION
+0x000 InLoadOrderLinks LIST_ENTRY — load order list (first = main EXE, second = ntdll, third = kernel32)
+0x010 InMemoryOrderLinks LIST_ENTRY — most common list walked by shellcode. Second entry is ntdll, third is kernel32.
+0x020 DllBase Base address where this module is loaded in memory
+0x028 EntryPoint DLL entry point (DllMain) address
+0x030 SizeOfImage Size of the module in memory
+0x038 FullDllName UNICODE_STRING — full path to the DLL on disk
+0x048 BaseDllName UNICODE_STRING — just the filename (e.g. "kernel32.dll") — what shellcode compares to find target DLL
// ANALYST TIP
  • When a DLL is loaded normally: it appears in all three lists AND has a VAD entry with a file path. Manually loaded (reflective) DLLs may be absent from these lists.
  • Volatility: windows.dlllist (walks LDR lists) vs windows.ldrmodules (cross-references LDR with VAD — discrepancy = hidden module)
  • Unlinking from LDR lists (while keeping the VAD entry) hides a DLL from most API-based enumeration but not from VAD inspection
Handle Table — HANDLE_TABLE / Object Manager
KERNEL
Every process has a private handle table managed by the Object Manager. Handles are integer indices into this table. Each entry holds a pointer to the kernel object and the access rights granted. Understanding handle tables helps understand object access, inheritance, and handle-based C2 techniques.
// ANALYST TIP
  • WinDbg: !handle 0 0 (all handles in current process), !handle <value> f (details)
  • Volatility: windows.handles — list all handles per process including type (File, Process, Thread, Key, Event…)
  • A process holding handles to other processes (type = Process) is often performing injection or monitoring
  • Ransomware often opens handles to many files before encrypting — bulk file handles are an indicator
NETWORK
TCP Endpoint / _TCP_ENDPOINT — tcpip.sys Kernel Structure
C2 DETECTION
Network connections are tracked in kernel by tcpip.sys in a pool of _TCP_ENDPOINT objects (tagged TcpE in the pool). Volatility scans pool memory for these tags to find all connections — including those hidden by rootkits from usermode APIs like netstat.
KEY FIELD DESCRIPTION
State TCP state: ESTABLISHED, LISTENING, CLOSE_WAIT, TIME_WAIT…
LocalAddress / LocalPort Source IP:port of this endpoint
RemoteAddress / RemotePort Destination IP:port — your C2 pivot point
OwningProcess Pointer back to the EPROCESS that owns this socket
CreateTime Timestamp of connection creation — valuable for timeline correlation
// ANALYST TIP
  • Volatility: windows.netstat — walks pool to find TCP/UDP endpoints including closed ones still in memory
  • WinDbg: search pool for tag TcpE!poolfind TcpE
  • ESTABLISHED connections from unexpected processes (svchost → unusual remote port 443/80 to rare IP) = strong C2 indicator
  • Pool scanner finds connections that rootkits hid from the OS's live APIs
QUICK REFERENCE
Key Registers & Segment Shortcuts
x86 / x64
x86 — TEB
fs:[0x18]
Self-pointer to TEB
x86 — PEB
fs:[0x30]
PEB from TEB
x64 — TEB
gs:[0x30]
Self-pointer to TEB
x64 — PEB
gs:[0x60]
PEB from TEB
x64 — KPCR (kernel)
gs:[0x00]
gs in kernel mode → KPCR
x64 — CurrentThread
gs:[0x188]
KTHREAD of running thread (kernel)
x86 — ExceptionList
fs:[0x00]
SEH chain head
x64 — LastError
gs:[0x68]
Per-thread last error value
Key Syscalls & NT APIs — Malware-Relevant
EVASION / INJECTION
NT API WIN32 WRAPPER USAGE BY MALWARE
NtAllocateVirtualMemory VirtualAllocEx Allocate RWX memory in target process for injection
NtWriteVirtualMemory WriteProcessMemory Write shellcode/DLL into allocated region
NtCreateThreadEx CreateRemoteThread Execute injected code in target process
NtMapViewOfSection MapViewOfFile Map shared section into target process (process doppelgänging, mapping injection)
NtUnmapViewOfSection UnmapViewOfFile Process hollowing — unmap legitimate image before replacing with malware
NtSetContextThread SetThreadContext Process hollowing — redirect entry point to malicious code
NtQuerySystemInformation Enumerate processes, modules, handles — many EDR queries and malware recon use this
NtQueryInformationProcess Anti-debug via ProcessDebugPort (class 7), ProcessDebugFlags (31), check for debugger
NtProtectVirtualMemory VirtualProtect Change page permissions — make shellcode executable after writing it as RW
NtOpenProcess OpenProcess Get handle to target process — first step in all injection chains
NtQueueApcThread QueueUserAPC APC injection — queue malicious APC to alertable thread
// ANALYST TIP — Direct Syscall Evasion

Advanced malware bypasses EDR usermode hooks by calling NT syscalls directly (e.g. via SysWhispers2/3, HellsGate, TartarusGate). Look for syscall instructions outside of ntdll.dll in a disassembler or dynamic trace — execution that goes directly to kernel without passing through ntdll is a strong indicator of hook bypass.

Structure → MITRE Technique Map
MITRE ATT&CK
STRUCTURE / CONCEPT TECHNIQUE ID NAME
PEB.Ldr walking T1027 Obfuscated Files or Information — API hiding via PEB walk + hashing
PEB.BeingDebugged T1497.001 Virtualization/Sandbox Evasion: System Checks
IAT Hooking T1055.012 Process Injection: Process Hollowing (often combined)
VAD private RWX injection T1055.001 Process Injection: DLL Injection
NtCreateThreadEx remote T1055.003 Process Injection: Thread Execution Hijacking
NtMapViewOfSection injection T1055.015 Process Injection: ListPlanting / Section injection
NtUnmapViewOfSection + replace T1055.012 Process Injection: Process Hollowing
EPROCESS.ActiveProcessLinks unlink T1014 Rootkit — process hiding via DKOM
EPROCESS.Token steal T1134.001 Access Token Manipulation: Token Impersonation/Theft
LDR list manipulation T1055 Process Injection — module hiding
TLS callback (PE .tls section) T1497.001 Anti-debug — code before OEP via TLS callback
NtQueueApcThread T1055.004 Process Injection: Asynchronous Procedure Call
Direct syscall (SysWhispers) T1562.001 Impair Defenses: Disable or Modify Tools — EDR hook bypass