Suspicious-file analysis¶
The malware-analysis workflow separates bounded host inspection, complex parsing, and sample
execution. Its result is always undetermined. Indicators and quiet telemetry are evidence, not a
safe or malicious verdict.
Start without executing the file¶
is-this-malware C:\Samples\unknown.exe
is-this-malware C:\Samples\unknown.exe -Json
This command reads at most 64 MiB of inert bytes. It records identity, SHA-256, size, file type, Authenticode state, Shannon entropy, printable strings, PE entry point and sections, suspicious API and living-off-the-land names, cryptocurrency markers, and x86/x64 0F 05 syscall byte patterns. It does not load the file, invoke its entry point, upload it, or run a complex third-party parser on the host.
The separate malware_hashes PATH --json command is installed from the pinned v2.5.0 GitHub release. For a target Sandbox case, planning runs that verified executable against the source on the host only when the file is within the 64 MiB host bound. The guest later runs the same read-only mapped executable against C:\Analysis\Input\sample.*. The reports retain their distinct source paths in host-evidence\malware-hashes-host.json and output\malware-hashes-sandbox.json. Clean-control jobs do not read the target with either invocation.
Both raw reports include environment metadata and are evidence-bearing JSON. PowerShell does not deserialize them. Read-MalwareEvidence.ps1 delegates validation to the bounded Python ingestor, which excludes hostname, user, and machine ID from its display/canonical record and reports deterministic hash agreement as matched, mismatch, incomplete, or not-collected. Agreement confirms only that both tools read equivalent bytes; it is not a malware verdict.
The clean-control diff excludes this static hash-provenance record from its behavioral canonical trees. Validated reports remain in their original cases and in ordinary case ingestion. Including them would make every target differ from its control because a control must not read the target.
Entropy and indicators are leads. Compressed benign software can have high entropy, strings can be decoys, a syscall byte pair can occur in data, and unsigned software is not automatically malicious.
Plan isolated work¶
Planning creates a case under the ignored evidence/malware/ directory and writes manifest.json plus analysis.wsb. It copies the sample into a narrow input directory but does not start Windows Sandbox.
malware-sandbox C:\Samples\invoice.pdf -Mode Dissect
disass C:\Samples\unknown.exe
decomp C:\Samples\unknown.exe
Dissect recognizes PDF active-content names and lists OOXML container entries inside the guest. Disassemble uses Rizin when available and writes text plus an optional SQLite line index. Decompile uses Ghidra headlessly and reports complete, partial, failed, timeout, or missing-tool; decompilation is never assumed to succeed.
Complex file parsers run in the Sandbox because analyzers can themselves contain file-format vulnerabilities. The analysis tools are optional and are not downloaded by an analysis command.
Explicit launch and detonation¶
Review the generated WSB and manifest first. Launch an isolated parser only after that review:
disass C:\Samples\unknown.exe -Run -ConfirmSandbox
decomp C:\Samples\unknown.exe -Run -ConfirmSandbox
Executing a sample requires a separate confirmation:
malware-sandbox C:\Samples\unknown.exe -Mode Detonate -DurationSeconds 30 `
-Run -ConfirmSandbox -ConfirmExecution
The runner accepts a small executable/script allowlist, uses fixed interpreters without caller-supplied sample arguments, waits for the bounded duration, and terminates the process tree on timeout. It attempts process and file snapshots, registry exports, Sysmon events when Sysmon is available, and PktMon/DNS/connection evidence only when networking was explicitly enabled.
For detonation plans, the installed Windows Performance Toolkit directory is mapped read-only as C:\Analysis\WPT. The guest runner resolves wpr.exe inside Windows Sandbox, starts GeneralProfile immediately before execution, and stops it to sandbox-behavior.etl in the case output. It refuses to overwrite an existing ETL and records missing/start/stop failures explicitly. WPA remains a host-side viewer for the resulting inert trace; it does not load the sample.
Host WPR cannot provide this process view. Windows Sandbox uses a separate kernel behind a Hyper-V boundary. Host ETW can observe the Sandbox and virtualization host processes, but it cannot provide guest process identities or guest stacks. The workflow does not fall back to an automatic host capture.
When the optional tools are present, Sysinternals Handle takes bounded guest-side snapshots of open
files for the launched sample process tree. file-handles.jsonl records UTC time, process identity,
handle value/type, and target path. This is sampling: a short-lived handle can open and close
between intervals, so an empty set is not proof that no file was accessed. Missing tooling, parse
failure, and a process that exits before observation are separate completeness states. Host process
handles are never inspected as a fallback.
Validate the complete benign evidence path only after reviewing the Sandbox boundary:
pwsh -NoProfile -File .\scripts\Test-MalwareSandboxIntegration.ps1 `
-ConfirmSandbox -ConfirmExecution
The repository fixture holds a guest-temp file open long enough for observation and leaves
networking disabled. Case evidence goes under the system temporary directory and remains local.
The host waits for result.json, not the packaged Sandbox launcher process, because recent Sandbox
versions can return from the launcher while the guest continues. A passing result requires explicit
completeness for every telemetry category and at least one normalized Handle observation. The
guest schedules its own shutdown only after the finalized result is persisted, so the Sandbox
window closes automatically. Add -KeepSandboxOpen to the reviewed plan only when an interactive
guest is required; that exception is recorded in the manifest and result. It never
uses live malware.
Diff a clean Sandbox against the target¶
Use the differential workflow for document dissection, disassembly, decompilation, and dynamic execution. The control and target plans must use the same sample hash, mode, duration, network policy, tool inventory, isolation policy, and guest-close policy:
# Planning only; neither command launches Windows Sandbox.
malware-control C:\Samples\unknown.exe -Mode Detonate -DurationSeconds 30
malware-sandbox C:\Samples\unknown.exe -Mode Detonate -DurationSeconds 30
# After separately approved and completed runs:
malware-diff -ControlCase <control-case> -TargetCase <target-case>
Review both manifests. Launch the clean control with -Run -ConfirmSandbox; launch a target
detonation separately with -Run -ConfirmSandbox -ConfirmExecution. Network access, if genuinely
needed, is a separate decision for both jobs. A control maps the same input read-only so the
configuration stays comparable, but records TargetAction=skipped-control and never reads, parses,
opens, disassembles, decompiles, or executes it. Documents remain limited to Dissect and are never
automatically opened in Office or a PDF application.
malware-diff validates compatibility, delegates bounded known-schema canonicalization to the
Python evidence boundary, retains control and target directories, and runs native Windows
git diff --no-index --no-ext-diff --text. Exit code 1 means "differences found";
other nonzero codes are failures. Git Bash, MSYS, Cygwin, BusyBox, external diff drivers, and a
private delta format are not involved. Unknown logs, traces, extracted objects, disassembly, and
decompiler output remain opaque path/size/SHA-256 rows. PowerShell does not deserialize them. The
default command reports the retained diff path without printing its content; add -ShowDiff only
to render the already escaped canonical diff. You can point another common directory-diff tool at
the two retained canonical directories.
Static/document and dynamic cases with the same SHA-256 are cross-linked in reports. Their evidence is complementary but remains separately sourced: a suspicious import, string, macro, or embedded object is not proof that it executed. Dynamic evidence must independently corroborate an execution claim. Conversely, a quiet target-versus-control diff does not prove safety, and a difference does not by itself prove target causation.
The analysis aliases name their boundary: host-static is bounded host byte
inspection, while sandbox-static is the Windows Sandbox path. wsl-dev is the developer Debian
environment and must not receive suspicious content. wsl-mw is the separate rootless
malware-analysis WSL environment used by the inert static container parser. Windows Sandbox is not
WSL.
Validate an existing completed case without launching another Sandbox:
pwsh -NoProfile -File .\scripts\Test-MalwareSandboxIntegration.ps1 -Case <case-directory>
Documents are never detonated automatically. Do not install or activate licensed Office in the Sandbox, sign in to Microsoft 365, map a browser profile, or expose identity tokens. Dissect documents as containers; use a purpose-built disposable lab image if application-level document behavior is genuinely required.
Isolation and residual attack surface¶
The generated configuration disables networking, clipboard redirection, vGPU, printers, audio input, and video input by default. The sample/input directory is read-only. Only a new case output directory is writable.
That boundary reduces risk; it does not make hostile content trustworthy:
- A guest compromise can write hostile files into the output mapping. Treat every output as untrusted bytes and never open or execute it directly on the host.
- A Sandbox or hypervisor escape is a residual platform risk. Keep Windows patched and do not use this workstation as a substitute for a dedicated malware lab for high-risk samples.
- Mapped analysis tools expose their bytes read-only to the guest. A vulnerable parser can still be compromised inside the guest.
- Timing, anti-VM logic, kernel behavior, encrypted traffic, early capture failures, and absent optional tools can leave telemetry gaps.
- A compromised parser can attempt output-file or file-count exhaustion. Per-process files, records, sample size, memory, CPU, PIDs, and total evidence ingestion are bounded, but the dedicated WSL VHD still shares host disk capacity; monitor free space and reserve high-risk work for a disposable lab with a fixed-size evidence volume.
- Network-enabled Sandbox traffic can reach networks available to the host. If network behavior is essential, use a segregated analysis network and add
-AllowNetworkonly after a separate risk decision.
Rootless static container¶
Windows Sandbox remains the only execution backend. The rootless Linux container is a static-only backend for complex Office/OLE/RTF, PDF, and binary parsing. Plan first; planning neither starts a container nor builds an image:
malware-container-status
malware-container-status -Json
malware-container C:\Samples\invoice.pdf
The gate reads the malware distribution and user from wsl.malware in ignored config.json. The managed value is the clean Debian-MW distro, not the developer Debian distro. It requires a local rootless engine, user-scoped overlay storage, seccomp, and disabled Podman API socket/services. It rejects --privileged, host namespaces, device exposure, engine sockets, and mounts that cannot be proven to stay under the case root. Rootless mode reduces runtime privilege; it is not a malware containment guarantee, and Windows executables cannot be detonated in a Linux container.
Debian-MW is also a restricted WSL role: Windows executable interop, Windows PATH injection, and automatic Windows-drive mounts are disabled. Its case files stay in the distribution VHD. Move bytes across the boundary only with the explicit, hash-recording transfer commands:
# State-changing: import a host directory into a new private Debian-MW case.
.\scripts\Import-MalwareCase.ps1 -CaseId case-001 -Source C:\Cases\case-001
# State-changing: export validated regular result files to a new host directory.
.\scripts\Export-MalwareCase.ps1 -CaseId case-001 -Destination C:\Cases\case-001-results
# Observational trust-boundary check; it does not start a stopped distribution.
.\scripts\Test-WslTrustBoundary.ps1 -Role MalwareAnalysis
.\scripts\Test-WslTrustBoundary.ps1 -Role MalwareAnalysis -Json
Import and export refuse overwrites and links, devices, pipes, or sockets. The Windows host remains a trusted administrator of the WSL VHD, so this boundary isolates ordinary intra-WSL access and accidental host sharing; it is not a defense against a compromised Windows administrator.
Use the high-level malware-container commands for analysis. For direct, read-only runtime
diagnosis, use wsl-mw podman info --format json. There is no runtime-specific PowerShell alias.
Ordinary docker ... remains routed to the separate developer Debian environment. Never use that
developer engine for suspicious files. Local pyinfra deploys declare both states, but the WSL
filesystems, storage, images, volumes, and users are separate. Debian-MW is installed cleanly; the
developer distro is never cloned into it.
The reviewed image includes file, ExifTool, YARA-X, capa; oleid, olevba, mraptor, oleobj, rtfobj,
msodde, and OOXML inventory; PDFiD, pdf-parser, qpdf, MuPDF, pdfinfo, and pdfdetach; and Detect It
Easy, Rizin, SQLite, headless Ghidra, BinExport, and BinDiff. Every tool has an inventory identity.
Each invocation has a timeout and output cap; Ghidra and decompilation remain best effort.
For two-binary work, binary-diff OLD NEW maps both files read-only and retains Ghidra-derived
BinExport call/control-flow graphs. BinDiff's .BinDiff SQLite result is the canonical structural
comparison. binary-analysis.sqlite is only the separate address-keyed code/query sidecar; raw
bytes, file versions, assembly text, and decompiler output never replace graph matching. See
Analysis and differencing cases for artifacts, queries, and failure
interpretation.
Image readiness compares both the declared tool-inventory fingerprint and a deterministic
fingerprint of every reviewed build input. Changes to the runner, evidence ingestor, local rules,
inventory, Dockerfile, or Ghidra export script therefore make the image stale until an explicit
rebuild. General parser temporary storage remains noexec; capa receives a separate bounded
executable tmpfs only for its signed release binary's self-extraction.
The image is opt-in, local, and managed separately from analysis commands:
# Read-only checks
malware-container-image -Mode Test
.\Apply-Workstation.ps1 -Mode Test -Module MalwareContainerImage -Plan
# Explicit networked, large build after review
.\Apply-Workstation.ps1 -Mode Ensure -Module MalwareContainerImage
# Static parsing only; never sample execution
malware-container C:\Samples\invoice.pdf -Run -ConfirmContainer
The run fixes networking to none, uses a read-only root and input, drops all capabilities, enables
no-new-privileges, selects numeric user 65532:65532, and bounds memory, CPU, PIDs, temporary space,
tool time, output, and record counts. There is no network override. The runner never invokes the
sample or extracted content.
All result material is hostile even though it came from a restricted parser. Use
scripts/Read-MalwareEvidence.ps1 for the bounded summary. It rejects path escapes, symlinks,
oversized or non-UTF-8 summaries, duplicate JSON keys, unexpected schema shape, excessive depth,
record counts, and string lengths. Terminal controls, ANSI sequences, embedded newlines, and
bidirectional controls are visibly escaped. A failed boundary check blocks reporting and diffing.
Report existing evidence first¶
pwsh -NoProfile -File .\scripts\Invoke-MalwareAnalysis.ps1 `
-Action Report -Case .\evidence\malware\<case>
The report records findings, failures, telemetry state, sample hash, mode, role, related same-hash static/document/reverse-engineering/dynamic cases, artifact provenance, and a limitation note. If a case already has EVTX, ETL, PCAPNG, dump, or generated output, inspect it before starting another capture.
Optional tool state¶
The optional module follows the normal dependency graph: Windows Sandbox and general packages are selected before analysis tools. It is excluded from -Module All because Ghidra is large and every additional parser adds attack surface.
# Read-only state and dependency plan
pwsh -NoProfile -File .\scripts\Set-MalwareHashesState.ps1 -Mode Test
.\Apply-Workstation.ps1 -Mode Test -Module MalwareHashes -Plan
pwsh -NoProfile -File .\scripts\Set-MalwareAnalysisToolsState.ps1 -Mode Test
pwsh -NoProfile -File .\scripts\Set-MalwareAnalysisToolsState.ps1 -Mode Test -Tool Handle
.\Apply-Workstation.ps1 -Mode Test -Module MalwareAnalysisTools -Plan
# Explicit installation after review
.\Apply-Workstation.ps1 -Mode Ensure -Module MalwareHashes
.\Apply-Workstation.ps1 -Mode Ensure -Module MalwareAnalysisTools
pwsh -NoProfile -File .\scripts\Set-MalwareAnalysisToolsState.ps1 -Mode Ensure -Tool Handle
WinGet manages Sysmon, Sysinternals Handle, YARA-X, Detect It Easy, Rizin, SQLite, and OpenJDK. The explicit module copies only those declared runtimes into a dedicated read-only Sandbox tool mapping; it does not expose the complete WinGet package cache. capa and Ghidra use release-pinned archives with SHA-256 verification. The independently default MalwareHashes module installs only the exact SHA-256-verified GitHub release into a versioned directory so that directory can be mapped read-only. The optional analysis bundle depends on both MalwareHashes and ProfilingTools, which supplies WPR/WPA. Analysis commands report missing tools and never install them silently.
-Tool Handle is the focused prerequisite for file-handle telemetry and does not install the rest
of the optional analyzer bundle. Ensure is still an explicit state change; analysis and validation
commands never invoke it automatically.
Python and pyinfra files use one pinned developer lint command:
lint-python
pwsh -NoProfile -File .\scripts\Invoke-PythonLint.ps1
References: Windows Sandbox isolation, read-only mapped folders, Sysinternals Handle, and Docker rootless mode.