Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
9673b8b
doc: trim the README and the architecture page
williballenthin Nov 4, 2026
b862895
doc: reword the pages and fix stale claims
williballenthin Nov 4, 2026
6cf8d0c
api_resolution: shorten the comments of the handler candidates
williballenthin Nov 4, 2026
680a705
export_manifest: reword the field descriptions
williballenthin Nov 4, 2026
104cb83
seh: fix the broken docstring of the top-level filter and reword the …
williballenthin Nov 4, 2026
b4d9092
api_registry: reword the comments with the terms of the architecture …
williballenthin Nov 4, 2026
d804b15
mapped_imports: correct the skip rules in the docstring
williballenthin Nov 4, 2026
b5dbfe8
winemu: reword the comments of the native exits and the machine setup
williballenthin Nov 4, 2026
9398fa7
objman: shorten the comments and docstrings
williballenthin Nov 4, 2026
683a525
struct: reword the comments and docstrings
williballenthin Nov 4, 2026
888f270
seh32: reword the comments and add the layout of the MSVC frame
williballenthin Nov 4, 2026
9501469
api_image: replace the spec terms in the comments
williballenthin Nov 4, 2026
a58d70c
fileman: shorten the comments and docstrings
williballenthin Nov 4, 2026
1f20649
seh64: reword the comments
williballenthin Nov 4, 2026
543f2ec
winemu: reword the comments of the managers and the run preparation
williballenthin Nov 4, 2026
331fa3c
errors, report_types: shorten the docstrings
williballenthin Nov 4, 2026
04397e5
regman: shorten the comments and docstrings
williballenthin Nov 4, 2026
1f189e5
compare: reword the case fold comments
williballenthin Nov 4, 2026
ce7af2d
winapi: reword the handler call docstrings
williballenthin Nov 4, 2026
5f9f03f
codepage: name the table and the functions in the docstrings
williballenthin Nov 4, 2026
515640d
arch: reword the register name comment
williballenthin Nov 4, 2026
9891a5d
netman: shorten the comments and docstrings
williballenthin Nov 4, 2026
eefc394
unwind: reword the comments
williballenthin Nov 4, 2026
26ec5f1
sessman: shorten the comments and docstrings
williballenthin Nov 4, 2026
021060c
cryptman: reword the docstrings
williballenthin Nov 4, 2026
ca2676d
guest_callbacks: reword the docstrings and drop the list of entry points
williballenthin Nov 4, 2026
ae1735c
module_loader: correct the rollback claim in the class docstring
williballenthin Nov 4, 2026
e06b4f2
common: reword the comments and docstrings
williballenthin Nov 4, 2026
8cf0583
winemu: reword the comments of the run loop
williballenthin Nov 4, 2026
a17e351
handles: describe the tables that are not process tables
williballenthin Nov 4, 2026
352aed3
com: reword the docstrings and comments
williballenthin Nov 4, 2026
2889db8
driveman, ioman: reword the docstrings and remove comments
williballenthin Nov 4, 2026
61a7cec
cpu_exceptions: reword the comments
williballenthin Nov 4, 2026
37b633b
speakeasy: reword the docstrings of the public interface
williballenthin Nov 4, 2026
6b0114d
thread_context: reword the comments
williballenthin Nov 4, 2026
180f29d
hardware_breakpoints: reword the comments
williballenthin Nov 4, 2026
86d0579
exception_apis: reword the module docstring
williballenthin Nov 4, 2026
3d35cd8
hammer: reword the docstrings and remove comments
williballenthin Nov 4, 2026
86ce43b
volmgr: describe the IOCTL answer
williballenthin Nov 4, 2026
31545e0
volumes: reword the docstrings and remove comments
williballenthin Nov 4, 2026
95aa581
loaders: replace the spec terms in the pefile comments
williballenthin Nov 4, 2026
571ccf5
winemu: reword the comments of the processes, threads and the loader
williballenthin Nov 4, 2026
0f72168
common: reword the docstrings of the hook classes
williballenthin Nov 4, 2026
04a0444
speakeasy: say when the dynamic code hook fires
williballenthin Nov 4, 2026
5a1b5b1
gdb: reword the comments and docstrings
williballenthin Nov 4, 2026
047dc7b
config: reword the field descriptions
williballenthin Nov 4, 2026
4a86d5f
api: reword the docstrings of the handler base class
williballenthin Nov 4, 2026
3824310
cli: reword the help of the flags
williballenthin Nov 4, 2026
08d0d30
sigfmt: drop the Sphinx roles and reword the comments
williballenthin Nov 4, 2026
7111b87
winemu: reword the comments of the memory hooks and the faults
williballenthin Nov 4, 2026
f81c046
profiler: reword the docstrings
williballenthin Nov 4, 2026
d304114
binemu: reword the docstrings and fix the hook order and cdecl comments
williballenthin Nov 4, 2026
1800b2d
win32: reword the docstrings and fix the export run comment
williballenthin Nov 4, 2026
5cc4dd1
sigdb: drop the Sphinx roles and reword the docstrings
williballenthin Nov 4, 2026
5e735ff
winemu: reword the comments of the API dispatch
williballenthin Nov 4, 2026
cc09b1b
kernel: reword the docstrings and fix the delayed run comment
williballenthin Nov 4, 2026
c7d87ac
winemu: reword the comments of the module loading, the SEH dispatch a…
williballenthin Nov 4, 2026
ad573e1
engines: reword the docstrings and the hook_add comments
williballenthin Nov 4, 2026
c05f937
profiler_events: reword the event descriptions
williballenthin Nov 4, 2026
3024631
memmgr: reword the docstrings and drop the comments that restate the …
williballenthin Nov 4, 2026
0ae523c
report: reword the report descriptions
williballenthin Nov 4, 2026
ce2277f
profiler_events: reword the handle and access flag descriptions
williballenthin Nov 4, 2026
0b213a4
doc: list mem_free as unused and describe the report clock fields
williballenthin Nov 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 9 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Speakeasy

Speakeasy is a Windows malware emulation framework that executes binaries, drivers, and shellcode in a modeled Windows runtime instead of a full VM. It emulates APIs, process/thread behavior, filesystem, registry, and network activity so samples can keep moving through realistic execution paths. You can run it from the `speakeasy` CLI for fast triage or embed it as a Python library and consume structured JSON reports.
Speakeasy is a Windows malware emulation framework that executes binaries, drivers and shellcode in a modeled Windows runtime instead of a full VM. It emulates the Windows APIs, processes, threads, file system, registry and network that a sample uses, so the sample runs further than in a bare CPU emulator. The `speakeasy` CLI writes the report as JSON for triage, and the Python library returns the same report to your code.

Background context: [Mandiant's overview post](https://cloud.google.com/blog/topics/threat-intelligence/emulation-of-malicious-shellcode-with-speakeasy/).
For background, see Mandiant's [introduction to Speakeasy](https://cloud.google.com/blog/topics/threat-intelligence/emulation-of-malicious-shellcode-with-speakeasy/).

## Quick start

Expand All @@ -12,55 +12,26 @@ Install from PyPI:
python3 -m pip install speakeasy-emulator
```

Run a sample and inspect high-level report fields (replace `sample.dll` with your target):
Emulate a sample and print some fields of the report. Replace `sample.dll` with your file:

```console
speakeasy -t sample.dll --no-mp -o report.json 2>/dev/null
jq '{sha256, arch, file_type, entry_points: (.entry_points | length)}' report.json
```

The values depend on the sample:

```json
{
"sha256": "30ec092d122a90441a2560f6778ef8233c98079cd34b7633f7bbc2874c8d7a45",
"sha256": "…",
"arch": "x86",
"file_type": "dll",
"entry_points": 3
}
```

## Documentation map

### Start here

- [Installation](https://github.com/mandiant/speakeasy/blob/master/doc/install.md)
- [Python library usage](https://github.com/mandiant/speakeasy/blob/master/doc/library.md)
- [Help and troubleshooting](https://github.com/mandiant/speakeasy/blob/master/doc/help.md)
- [Documentation index](https://github.com/mandiant/speakeasy/blob/master/doc/index.md)

### CLI usage

- [CLI reference](https://github.com/mandiant/speakeasy/blob/master/doc/cli-reference.md)
- [CLI analysis recipes](https://github.com/mandiant/speakeasy/blob/master/doc/cli-analysis-recipes.md)
- [CLI environment overrides](https://github.com/mandiant/speakeasy/blob/master/doc/cli-environment-overrides.md)
- [CLI execution controls](https://github.com/mandiant/speakeasy/blob/master/doc/cli-execution-controls.md)

### Reports, configuration, and runtime behavior

- [Configuration walkthrough](https://github.com/mandiant/speakeasy/blob/master/doc/configuration.md)
- [Report walkthrough](https://github.com/mandiant/speakeasy/blob/master/doc/reporting.md)
- [Memory management](https://github.com/mandiant/speakeasy/blob/master/doc/memory.md)
- [Limitations](https://github.com/mandiant/speakeasy/blob/master/doc/limitations.md)

### Debugging and extension

- [GDB debugging reference](https://github.com/mandiant/speakeasy/blob/master/doc/gdb.md)
- [Mounting host files with `--volume`](https://github.com/mandiant/speakeasy/blob/master/doc/volumes.md)
- [Adding API handlers](https://github.com/mandiant/speakeasy/blob/master/doc/api-handlers.md)
- [Examples directory](https://github.com/mandiant/speakeasy/tree/master/examples)
- [Speakeasy 2 walkthrough outline](https://github.com/mandiant/speakeasy/blob/master/doc/speakeasy2-walkthrough.md)

## Questions and help
## Documentation

Start with [doc/help.md](https://github.com/mandiant/speakeasy/blob/master/doc/help.md).
Start with [installation](https://github.com/mandiant/speakeasy/blob/master/doc/install.md), [Python library usage](https://github.com/mandiant/speakeasy/blob/master/doc/library.md) and the [CLI reference](https://github.com/mandiant/speakeasy/blob/master/doc/cli-reference.md). The [documentation index](https://github.com/mandiant/speakeasy/blob/master/doc/index.md) lists all pages.

If you still need help, open an issue at [github.com/mandiant/speakeasy/issues](https://github.com/mandiant/speakeasy/issues).
For help, see [doc/help.md](https://github.com/mandiant/speakeasy/blob/master/doc/help.md) or open an issue at [github.com/mandiant/speakeasy/issues](https://github.com/mandiant/speakeasy/issues).
61 changes: 35 additions & 26 deletions doc/api-handlers.md

Large diffs are not rendered by default.

54 changes: 27 additions & 27 deletions doc/architecture.md

Large diffs are not rendered by default.

57 changes: 13 additions & 44 deletions doc/cli-analysis-recipes.md
Original file line number Diff line number Diff line change
@@ -1,121 +1,92 @@
# CLI analysis recipes

This page focuses on practical flag combinations for common analysis outputs.
Each recipe gives the flags for one kind of analysis output and a command that checks the output.

<a id="recipe-memory-snapshots"></a>
## In-report memory snapshots (`--snapshot-memory-regions`)

Command:

```bash
speakeasy -t sample.exe --snapshot-memory-regions -o report.json
```

Expected artifact:
- `entry_points[*].memory.layout[*].data_ref` populated with SHA-256 refs
- top-level `data` populated with `base64(zlib(raw_bytes))` entries

Quick verification:
The memory regions in `entry_points[*].memory.layout` get a `data_ref`, the SHA-256 of their bytes. Stack and heap regions do not get one. The top-level `data` object maps each SHA-256 to the bytes, compressed with zlib and encoded with base64. To list the regions that have data:

```bash
jq '.entry_points[].memory.layout[] | select(.data_ref != null) | .tag' report.json
```

Tradeoff:
- report size increases, but repeated payloads deduplicate across runs
The report gets larger. Regions with the same bytes share one entry in `data`, also across runs.

<a id="recipe-analysis-coverage"></a>
## Coverage collection (`--analysis-coverage`)

Command:

```bash
speakeasy -t sample.exe --analysis-coverage -o report.json
```

Expected artifact:
- `entry_points[*].coverage` contains executed instruction addresses

Quick verification:
`entry_points[*].coverage` has the addresses of the instructions that each run executed. To count them:

```bash
jq '.entry_points[] | {start_address, coverage_count: (.coverage // [] | length)}' report.json
```

Tradeoff:
- extra tracing overhead increases runtime
The emulator runs a hook for each instruction, so emulation is slower.

<a id="recipe-memory-tracing"></a>
## Memory tracing (`--analysis-memory-tracing`)

Command:

```bash
speakeasy -t sample.exe --analysis-memory-tracing -o report.json
```

Expected artifact:
- per-region access counters in `memory.layout[*].accesses`
- symbol access summaries in `symbol_accesses`

Quick verification:
The regions in `entry_points[*].memory.layout` that the run accessed get `accesses`, with the counts of reads, writes and executes. `entry_points[*].symbol_accesses` has the counts for each symbol. To count the symbols that each run accessed:

```bash
jq '.entry_points[] | {start_address, symbol_accesses: (.symbol_accesses // [] | length)}' report.json
```

Tradeoff:
- substantial runtime impact on memory-heavy samples
The emulator runs a hook for each instruction, memory read and memory write. A sample that accesses much memory runs much slower.

<a id="recipe-analysis-strings"></a>
## String extraction controls (`--analysis-strings` / `--no-analysis-strings`)

Enable:
String extraction is on by default, so `--analysis-strings` has an effect only when a `--config` file turns it off:

```bash
speakeasy -t sample.exe --analysis-strings -o report.json
```

Disable:
`--no-analysis-strings` turns it off:

```bash
speakeasy -t sample.exe --no-analysis-strings -o report.json
```

Quick verification:
The strings are in the top-level `strings` object. Without string extraction, the report has no `strings`:

```bash
jq '.strings' report.json
```

Tradeoff:
- disabling strings reduces report size and post-processing time
Without string extraction, the report is smaller and Speakeasy finishes sooner.

<a id="recipe-dropped-files"></a>
## Dropped files archive (`--dropped-files-path`)

Command:

```bash
speakeasy -t sample.exe --dropped-files-path dropped.zip
```

Expected artifact:
- `dropped.zip` with files written during emulation and a manifest

Quick verification:
`dropped.zip` has the files that the sample wrote and `speakeasy_manifest.json`, which gives the emulated path, size and SHA-256 of each file. If the sample wrote no files, the CLI does not make the archive. To list the archive:

```bash
unzip -l dropped.zip
```

Tradeoff:
- captures useful payload artifacts but adds archive creation overhead

<a id="recipe-combined-triage"></a>
## Combined triage profile

Command:
This command collects all the outputs of this page in one run. `--max-run-time 30` limits each run to 30 seconds:

```bash
speakeasy -t sample.exe \
Expand All @@ -127,8 +98,6 @@ speakeasy -t sample.exe \
-o report.json
```

Use this profile when you want broad telemetry and artifact capture in one run.

## Related docs

- [Project README](../README.md)
Expand Down
59 changes: 17 additions & 42 deletions doc/cli-environment-overrides.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
# CLI environment overrides

This page covers flags that shape the emulated host environment and behavior determinism.
These flags set the identity of the emulated host, the process environment, DNS answers, module loading and API hammering mitigation. At the start of each emulation, Speakeasy logs the active config to stderr, so you can check the values in the log.

## Host, user, and OS identity
## Host, user and OS identity

Primary flags:
- `--hostname`
- `--domain`
- `--user-name`
- `--user-is-admin` / `--no-user-is-admin`
- `--user-sid`
- `--os-ver-major`, `--os-ver-minor`, `--os-ver-build`

Example: enterprise-like identity profile
The flags are `--hostname`, `--domain`, `--user-name`, `--user-is-admin` / `--no-user-is-admin`, `--user-sid`, `--os-ver-major`, `--os-ver-minor` and `--os-ver-build`. This example gives a workstation in a corporate domain, with a user that is not an administrator:

```bash
speakeasy -t sample.exe \
Expand All @@ -27,45 +19,35 @@ speakeasy -t sample.exe \
-v -o report.json 2> run.log
```

Quick verification:
To check the values:

```bash
rg "(hostname|domain|user\.name|user\.sid|user\.is_admin|os_ver\.)" run.log
```

## Process context and environment variables

Primary flags:
- `--current-dir`
- `--command-line`
- `--env KEY=VALUE` (repeatable)

Names are not case-sensitive, and the config stores them in lower case, so `--env TEMP=...` replaces the default `temp`.

Example:
The flags are `--current-dir`, `--command-line` and `--env KEY=VALUE`. `--env` is repeatable, and each value adds or replaces one variable. The config stores variable names in lower case, because Windows variable names are not case-sensitive. So `--env TEMP=...` replaces the default `temp`.

```bash
speakeasy -t sample.exe \
--current-dir 'C:\\ProgramData\\Microsoft' \
--current-dir 'C:\ProgramData\Microsoft' \
--command-line 'svchost.exe -k netsvcs -p' \
--env TEMP=C:\\Windows\\Temp \
--env APPDATA=C:\\Users\\jdoe\\AppData\\Roaming \
--env COMPUTERNAME=WS-3471 \
-v -o report.json 2> run.log
```

Quick verification:
To check the values:

```bash
rg "(current_dir|command_line|env =)" run.log
```

## DNS override mappings

Primary flag:
- `--network-dns-names HOST=IP` (repeatable)

Example: force known C2 host resolutions
`--network-dns-names HOST=IP` sets the address that a DNS lookup gives for a host name. The flag is repeatable. This example gives addresses for two C2 hosts:

```bash
speakeasy -t sample.exe \
Expand All @@ -74,23 +56,21 @@ speakeasy -t sample.exe \
-o report.json
```

Quick verification:
Each lookup gives a `net_dns` event in the report:

```bash
jq '.entry_points[].events[]? | select(.event == "net_dns") | {query, response}' report.json
```

## Module load policy and module directories

Some samples (especially shellcode) parse PE export tables directly to resolve API pointers. When expected modules or exports are missing, these controls let you choose strict or permissive behavior.
By default, `LoadLibrary` fails for a DLL that Speakeasy does not know. With `--modules-always-exist`, the emulator makes a synthetic module for the DLL.

By default, `GetProcAddress` fails for a function that Speakeasy does not know, and a call to such a function ends the run with an `unsupported_api` error. With `--modules-functions-always-exist`, `GetProcAddress` gives an address for the function, and a call to it returns 1.

Primary flags:
- `--modules-always-exist` / `--no-modules-always-exist`
- `--modules-functions-always-exist` / `--no-modules-functions-always-exist`
- `--module-directory-x86`
- `--module-directory-x64`
`--module-directory-x86` and `--module-directory-x64` give the directories of real x86 and x64 DLLs. The emulator loads a DLL from the directory of the input architecture in place of its synthetic API module.

Example: relaxed unresolved-import policy with native DLL directories
This example allows unknown modules and functions, and loads real DLLs from two directories:

```bash
speakeasy -t sample.exe \
Expand All @@ -101,16 +81,11 @@ speakeasy -t sample.exe \
-o report.json
```

Use this when triaging samples that otherwise stop early on missing modules/APIs.
Use these flags when a sample stops early because a module or a function is missing.

## API hammering controls

Primary flags:
- `--api-hammering-enabled` / `--no-api-hammering-enabled`
- `--api-hammering-threshold`
- `--api-hammering-allow-list VALUE` (repeatable)

Example:
Some samples call an API many times in a loop to make emulation slow. With `--api-hammering-enabled`, the emulator counts the calls to each API from each call site. When the count reaches `--api-hammering-threshold`, the emulator patches an x86 call site so that the call gives 0 and does not run the API. The emulator does not patch x64 code. `--api-hammering-allow-list` gives an API in the form `dll.name` that the emulator does not patch. The flag is repeatable, and its values replace the default allow list.

```bash
speakeasy -t sample.exe \
Expand All @@ -121,7 +96,7 @@ speakeasy -t sample.exe \
-o report.json
```

This is useful when balancing anti-loop containment with legitimate hot API usage.
Increase the threshold if the emulator patches calls that the sample needs.

## Related docs

Expand Down
Loading
Loading