Choosing a DLL for Read-only Inspection

Choosing a DLL for Read-only Inspection

Summary

Start here to learn what a DLL is on disk, how Windows loads it, and where the kernel fits — then pick a practice target. The workflow in Read-only DLL inspection — Part 1 works on any DLL; amsi.dll is the worked example, not the only valid choice.

Prerequisites Windows lab VM, tools from Part 1 Step 0
Worked example in vault amsi.dllPart 1 walkthrough
Lab snapshot Verified 2026-08-31 on 192.168.50.252 (Win10 Pro 19045) — see Verified lab inventory
Deep PE reference Portable Executable Structure
Syscall / kernel path Syscalls Flow
Read-only boundary

Do not rename, patch, replace, or take ownership of system DLLs. Pick targets you will only copy and inspect.

Table of Contents


Windows DLL structure (on disk)

0. Purpose

Before you pick amsi.dll vs version.dll, you need a mental model of what any DLL file actually is — a Portable Executable (PE) on disk that the loader maps into process memory.

1. High level (explain like I'm five)

A DLL is a shared instruction book stored as a file. Programs do not copy the whole book into themselves — they borrow pages from it when needed.

On disk, that book has:

.exe and .dll use the same PE format. The difference is mainly how the loader uses them: an EXE is the program entry point; a DLL is a library module loaded into an already-running process.

2. Deep breakdown — PE layout

Every inbox DLL under %windir%\System32\ follows the same skeleton. When you open a copy in PE-bear or Ghidra, read in this order:

flowchart TD
  DOS[DOS / MZ header
e_magic = MZ] -->|e_lfanew| NT[NT headers
PE signature] NT --> FH[File header
machine, characteristics] NT --> OH[Optional header
image base, subsystem, entry] OH --> DD[Data directories] DD --> EXP[Export table] DD --> IMP[Import table] DD --> RES[Resources] DD --> SEC[Security / Authenticode] NT --> ST[Section table] ST --> TEXT[.text — executable code] ST --> RDATA[.rdata — read-only constants] ST --> DATA[.data — writable globals] ST --> REL[.reloc / .pdata — loader fixups]
PE region What it tells you Inspection question
DOS + NT headers Valid PE? 32- vs 64-bit? Preferred load address? Does architecture match System32 vs SysWOW64?
Sections Where code and data live; memory permissions Is .text separate from .rdata? Any unusual empty gaps?
Import table Which DLLs/APIs this module depends on What facilities does it need? (kernel32, ntdll, ole32, …)
Export table Which functions this module offers to others What is the public surface? (AmsiScanBuffer, …)
Resources Version info, manifests Does FileVersion match Get-Item / Windows Update?
Security directory Signing metadata Does Authenticode match Step 2 in Part 1?

Key terms:

Term Meaning
RVA (Relative Virtual Address) Offset from the module's image base once loaded — not the same as file offset on disk
VA (Virtual Address) Image Base + RVA — actual address in a live process
Image base Preferred load address (ASLR may relocate at runtime)
File on disk ≠ memory at runtime

Static tools read the file. Process Explorer and debuggers show the mapped module in a process. Exports/imports describe the link contract; the loader + ASLR decide final addresses.

Full header walkthrough: Portable Executable Structure.


How DLLs relate to the kernel

0. Purpose

Understand which DLLs talk to the kernel, which stay in user mode, and why that matters when you pick an inspection target.

1. High level (explain like I'm five)

Windows splits work into two floors:

User programs cannot walk upstairs directly. They must ask through a reception desk — a chain of DLLs that ends in a syscall (a controlled door into the kernel).

Most DLLs you will inspect (amsi.dll, version.dll, crypt32.dll) live on the user-mode floor. They may request kernel services, but they are not the kernel.

2. Deep breakdown — the call chain

flowchart TB
  subgraph ring3 [User mode — Ring 3]
    APP[Application
powershell.exe, your.exe] SPEC[Specialty DLLs
amsi.dll, crypt32.dll, sspicli.dll] WIN32[Win32 surface
kernel32.dll, advapi32.dll, user32.dll] NTDLL[Native API gateway
ntdll.dll] end subgraph ring0 [Kernel mode — Ring 0] KERN[ntoskrnl.exe
Windows kernel] DRV[Drivers .sys] end APP --> SPEC APP --> WIN32 SPEC --> WIN32 SPEC --> NTDLL WIN32 --> NTDLL NTDLL -->|syscall instruction| KERN KERN --> DRV
Layer Representative modules Role
Application powershell.exe, mmc.exe Calls documented APIs
Feature / policy DLLs amsi.dll, wldp.dll, bcrypt.dll Domain logic — scanning, policy, crypto
Win32 DLLs kernel32.dll, advapi32.dll Stable documented Win32 API
Native API ntdll.dll Thin wrappers + syscall stubs (Nt* / Zw*)
Kernel ntoskrnl.exe Memory, processes, objects, I/O — not a DLL you inspect like user modules
Drivers *.sys Kernel-mode modules — different analysis path

Typical path for a privileged operation (allocate memory, open a process, read a file):

  1. App calls VirtualAlloc in kernel32.dll (Win32)
  2. kernel32 calls NtAllocateVirtualMemory in ntdll.dll (NT API)
  3. ntdll executes syscall → CPU enters kernel mode
  4. ntoskrnl.exe performs the work and returns an NTSTATUS
  5. Result unwinds back to the application

See: Syscalls Flow and Syscalls Deep dive.

Syscalls live in the kernel, not in ntdll

ntdll.dll holds the user-mode stub that triggers the syscall. The actual kernel implementation is in ntoskrnl.exe. When blogs say "resolve syscalls from ntdll," they mean reading the stub / system service number (SSN) — not that the kernel code is inside the DLL file.

How the loader wires DLLs together

When a process starts (or calls LoadLibrary):

  1. NT loader (ntdll + kernel) maps the EXE and required DLLs into the process address space
  2. The PEB (Process Environment Block) records which modules loaded and at which base addresses — see PEB / TEB
  3. The loader resolves imports — fills in pointers to exported functions from dependency DLLs
  4. For DLLs with DllMain, the loader runs attach logic (user-mode code — another reason to inspect read-only first)

Why this matters for target selection:

If you inspect… You are learning… Kernel relationship
version.dll Minimal PE + exports Usually no direct syscalls — simple user helper
amsi.dll Defensive component surface Calls Win32/NT APIs to reach scanners; stays user mode
crypt32.dll Trust and certificate plumbing Heavy Win32/CryptoAPI; may reach kernel crypto providers indirectly
ntdll.dll Syscall gateway Direct kernel boundary — advanced, easy to scope-creep
kernel32.dll Win32 façade over NT APIs Almost everything eventually touches ntdll

64-bit vs 32-bit on one machine:

Path Bitness Notes
%windir%\System32\*.dll 64-bit on x64 Windows Confusing name — System32 is 64-bit on x64 OS
%windir%\SysWOW64\*.dll 32-bit WOW64 copies Separate PE files — inspect separately (lab .252: amsi.dll is 103,936 vs 78,848 bytes)

Where system DLLs live

Location What lives here Kernel?
C:\Windows\System32\ Inbox OS DLLs (64-bit on x64) User-mode modules only
C:\Windows\SysWOW64\ 32-bit compatibility copies User-mode
C:\Windows\System32\drivers\ *.sys kernel drivers Kernel mode — different playbook
C:\Windows\System32\ntoskrnl.exe Kernel image (not a DLL) Kernel
C:\Program Files\...\ Vendor components (e.g. MpClient.dll) User-mode; may not be in System32

Choosing a practice target

Now that you know what a DLL is and where the kernel sits, use the sections below to pick a file that matches your learning goal.

Why not start with kernel32.dll?

Almost every Windows program loads kernel32.dll and ntdll.dll. They are foundational — and enormous. For your first passes you want:

amsi.dll was chosen for the walkthrough because it hits that sweet spot and connects to script-scanning / defensive-component literacy. It is not automatically the best first DLL for everyone.


Verified lab inventory (192.168.50.252)

Fact-check source

Collected 2026-08-31 via SSH + PowerShell on the C2 lab Windows VM (DESKTOP-CR3AD2C, Windows 10 Pro build 19045.6466). Signatures were Valid for every DLL present below.

DLL In System32? Size (KB) File version Last write
version.dll Yes 31.8 10.0.19041.3636 2025-06-16
amsi.dll Yes 101.5 10.0.19041.4355 2025-06-16
wldp.dll Yes 165.6 10.0.19041.1 2025-06-16
bcrypt.dll Yes 143.5 10.0.19041.1 2025-06-16
sspicli.dll Yes 185.2 10.0.19041.6328 2026-04-09
sechost.dll Yes 627.6 10.0.19041.1 2026-04-09
crypt32.dll Yes 1379.1 10.0.19041.1 2026-04-09
wer.dll Yes 906.5 10.0.19041.1 2026-04-09
dbghelp.dll Yes 1940.0 10.0.19041.5848 2025-06-16
mpclient.dll No

amsi.dll on this host (extra checks):

Copy Path Size (bytes) Signature
64-bit C:\Windows\System32\amsi.dll 103,936 Valid — CN=Microsoft Windows
32-bit C:\Windows\SysWOW64\amsi.dll 78,848 Valid (not re-listed; same build family)

MpClient.dll (Defender) — not in System32:

Path Size (bytes) File version
C:\Program Files\Windows Defender\MpClient.dll 942,520 4.18.1909.6
C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.26080.3-0\MpClient.dll 1,882,136 4.18.26080.3
Do not assume System32\mpclient.dll

On this Win10 lab image, lowercase mpclient.dll is absent from System32. Defender ships MpClient.dll (capital M and P) under Program Files\Windows Defender\ and versioned folders under ProgramData. Always resolve the real path with Get-ChildItem -Recurse -Filter MpClient.dll before inspection.

Reproduce on any lab host:

# Quick inventory (same script shape used on .252)
'version.dll','amsi.dll','wldp.dll','crypt32.dll','bcrypt.dll','dbghelp.dll','wer.dll','mpclient.dll' |
  ForEach-Object {
    $p = Join-Path $env:windir "System32\$_"
    [PSCustomObject]@{
      DLL = $_
      Present = Test-Path $p
      SizeKB = if (Test-Path $p) { [math]::Round((Get-Item $p).Length/1KB,1) } else { $null }
      Signature = if (Test-Path $p) { (Get-AuthenticodeSignature $p).Status } else { $null }
    }
  } | Format-Table -AutoSize

Fact-check notes (Microsoft docs + lab)

Claim Verdict Source / correction
AMSI has six exports Incorrect Microsoft documents eight AMSI APIs in Amsi.h: add AmsiNotifyOperation and AmsiResultIsMalware to the list in Part 1
AMSI is Defender-only Incorrect AMSI portal: AMSI is vendor-agnostic — any registered antimalware provider can integrate
mpclient.dll lives in System32 Often wrong Not present on lab .252; use Defender paths above
wldp.dll = “lockdown policy” Partially correct WLDP APIs cover execution policy; newer WldpCanExecute* functions require Windows 11 build 22621+ — Win10 labs still have WldpGetLockdownPolicy
Beginner DLLs are all <500 KB Mostly true Tier-1 picks on .252 are 32–166 KB; wer.dll and crypt32.dll in System32 are 900 KB–1.9 MB — keep them in Tier 2+
Ghidra PE view always matches runtime Caution Ghidra #9170: malformed or unusual PointerToRawData can desync file vs loaded view — cross-check exports with dumpbin or a second tool

GitHub and official references (read-only learning)

Curated via agentctl research + manual verification. Use for parsing literacy, not bypass tradecraft.

Resource URL Use for
pefile (Python) github.com/erocarrera/pefile Scriptable PE headers, sections, imports/exports
LIEF github.com/lief-project/LIEF Cross-platform PE object model; compare with Ghidra
pe-parse (Trail of Bits) github.com/trailofbits/pe-parse Correctness-focused C++ PE parser for tooling authors
PE-bear github.com/hasherezade/pe-bear GUI PE tree — matches Part 1 Step 4
Dependencies github.com/lucasg/Dependencies Import/export dependency graph
Ghidra github.com/NationalSecurityAgency/ghidra Disassembly/decompiler; see PE loader issues above
AMSI functions (official) learn.microsoft.com/.../amsi/ Authoritative export names and signatures
WLDP functions (official) learn.microsoft.com/.../wldp/ Lockdown / execution-policy APIs

Prioritization rubric

Score each candidate 0–2 per row (0 = poor fit, 2 = great fit). Prefer DLLs with higher totals for your current goal.

Criterion Ask yourself Weight
Learning fit Does this DLL teach the skill I am practicing this week (PE basics, exports, defensive component, crypto, etc.)? High
Size / complexity Can I review all exports in one session? Tier-1 DLLs on lab .252 are 32–166 KB; under ~200 KB and <30 named exports is ideal for beginners High
Documentation Are exports on Microsoft Learn or a clear vendor page? Medium
Runtime visibility Can I trigger a load in a lab VM and find it with Process Explorer (Ctrl+F)? Medium
Signature clarity Is it inbox, Microsoft-signed, and stable on my build? Medium
Track relevance Does it connect to my path (AMSI/EDR, identity, code integrity, malware analysis)? Low–medium
Sensitivity Am I only inspecting — not planning to patch or bypass this exact file? Gate — must pass

Gate rule: If you are studying a DLL primarily to bypass or weaken it, you are past read-only inspection — use a different lab design and explicit authorization.


Tiered candidates (inbox System32)

All paths below are %windir%\System32\ on 64-bit Windows. On 64-bit, also check the SysWOW64 copy when the architecture matters.

Tier 1 — Best first targets

DLL Rough role Why start here Tradeoff
version.dll Version resource queries Tiny, few exports, fast PE/Ghidra win Less security-story relevance
amsi.dll Antimalware Scan Interface (script/content scan) Documented exports, easy to observe in PowerShell, defensive literacy Security blogs discuss bypass — stay read-only
wldp.dll Windows Lockdown Policy / code-integrity checks Bridges “policy” and execution trust Slightly less hand-holding than AMSI docs

Suggested path: version.dll for pure PE practice → amsi.dll or wldp.dll for defensive components.

Tier 2 — After one full pass

DLL Rough role Why next Tradeoff
crypt32.dll Certificates, chains, Authenticode helpers Pairs with signature verification (Part 1 Step 2) Large import surface
bcrypt.dll Modern crypto primitives (CNG) Clean export names (BCrypt*) Crypto depth can distract from PE basics
dbghelp.dll Symbol resolution, minidumps, stack walks Useful for crash/debug literacy Some exports are sparsely documented
wer.dll Windows Error Reporting client Medium size, real-world telemetry story Fewer “hero” exports

Tier 3 — Specialty / blue-team depth

DLL Rough role Why later Tradeoff
MpClient.dll Defender client interface (scan/WMI plumbing) Ties inspection to endpoint AV Not in System32 on Win10 .252 — lives under Program Files\Windows Defender\ and ProgramData\...\Platform\<version>\; versioned copies change with Defender updates
sechost.dll Security host / RPC glue for LSASS-adjacent services Important for Windows security architecture Large (~628 KB on .252), easy to get lost
sspicli.dll SSPI client (auth packages) Identity and logon path literacy Auth state is easy to misread statically

Tier 4 — Foundational but advanced

DLL Rough role When Warning
kernel32.dll Core Win32 API After you can navigate exports + Ghidra comfortably Huge — weeks of material
ntdll.dll Native API / syscalls gateway Same as above Often tied to EDR/syscall research — scope creep risk

Quick comparison in PowerShell

Run on your lab VM to compare size and signature before you commit a Saturday to Ghidra:

$candidates = @(
  'version.dll',
  'amsi.dll',
  'wldp.dll',
  'crypt32.dll',
  'bcrypt.dll',
  'dbghelp.dll',
  'wer.dll'
  # MpClient.dll is NOT in System32 — search separately:
  # Get-ChildItem 'C:\Program Files\Windows Defender','C:\ProgramData\Microsoft\Windows Defender' -Recurse -Filter MpClient.dll -EA SilentlyContinue
)

$candidates | ForEach-Object {
  $path = Join-Path $env:windir "System32\$_"
  if (-not (Test-Path $path)) {
    [PSCustomObject]@{ DLL = $_; Present = $false }
    return
  }
  $item = Get-Item $path
  $sig = Get-AuthenticodeSignature $path
  [PSCustomObject]@{
    DLL           = $_
    SizeKB        = [math]::Round($item.Length / 1KB, 1)
    FileVersion   = $item.VersionInfo.FileVersion
    Signature     = $sig.Status
    LastWriteTime = $item.LastWriteTime.ToString('yyyy-MM-dd')
  }
} | Format-Table -AutoSize

Optional — export count when dumpbin is available (Visual Studio Build Tools):

function Get-ExportCount {
  param([string]$DllPath)
  $dumpbin = Get-Command dumpbin -ErrorAction SilentlyContinue
  if (-not $dumpbin) { return $null }
  $section = & dumpbin /exports $DllPath 2>$null |
    Select-String -Pattern '^\s+\d+\s+[0-9A-F]+\s+[0-9A-F]+\s+\w' 
  if ($section) { return @($section).Count }
  return $null
}

'version.dll','amsi.dll','wldp.dll' | ForEach-Object {
  $path = Join-Path $env:windir "System32\$_"
  [PSCustomObject]@{
    DLL = $_
    ExportCount = Get-ExportCount $path
  }
} | Format-Table -AutoSize

Prefer DLLs with fewer exports when you are still learning the workflow.


Decision tree

flowchart TD
  A[Pick a learning goal] --> B{First time opening PE/Ghidra?}
  B -->|Yes| C[version.dll]
  B -->|No| D{Defensive / script-scan focus?}
  D -->|Yes| E[amsi.dll]
  D -->|No| F{Policy / code integrity?}
  F -->|Yes| G[wldp.dll]
  F -->|No| H{Certificates / trust?}
  H -->|Yes| I[crypt32.dll]
  H -->|No| J[Re-score candidates with rubric]
  C --> K[Run Part 1 workflow on a copy]
  E --> K
  G --> K
  I --> K
  J --> K

Mapping goals → starting DLL

Your goal this week Start with Then
Learn PE headers/sections/exports version.dll amsi.dll
Understand script scanning / AMSI amsi.dll Attack AMSI (after read-only pass)
Code integrity / “why is this blocked?” wldp.dll crypt32.dll
Certificate and signature literacy crypt32.dll Re-run Part 1 Step 2 on other signed binaries
Endpoint AV plumbing MpClient.dll (Defender path, not System32) Defender docs + version pinning on VM snapshot
Win32 API breadth kernel32.dll Only after Tier 1–2 complete

Using a non-amsi.dll target in Part 1

The Part 1 steps are DLL-agnostic. Substitute your chosen name everywhere amsi.dll appears:

Step Change
1 — Locate $path = "$env:windir\System32\<your>.dll"
2 — Signature Same command, new path
3 — Process Explorer Search <your>.dll
4 — PE tools Copy to Desktop as <your>-copy.dll
5 — Ghidra Import the copy; browse Exports for that DLL's API names
6 — Assembly Same vocabulary — pick any exported function
Trigger a load for observation

  • amsi.dll: open PowerShell and run 'test' \| Out-Null
  • version.dll: almost any GUI app — or powershell -Command "[System.Diagnostics.FileVersionInfo]::GetVersionInfo('kernel32.dll')"
  • wldp.dll: often loaded by policy-aware hosts; try opening Microsoft Edge or running Get-AppxPackage in PowerShell, then search ProcExp


Windows Playbook · Part 1 walkthrough