Read the Windows APIs a binary reaches for, and map them to behaviours and MITRE ATT&CK techniques — before you run it. A fast, zero-dependency, offline PE triage tool in pure C. It executes nothing; it reads bytes.
imprint sample.exe
PE32+ executable, console subsystem
64 imports from 12 modules - 24 flagged - 9 capabilities
CAPABILITIES
● Process Injection T1055
Allocates/writes memory in another process and starts code there
via: VirtualAllocEx, WriteProcessMemory, CreateRemoteThread
● Registry Persistence T1547.001
Writes a registry key, a classic autorun persistence location
via: RegSetValueExW, RegCreateKeyExW
● Anti-Debugging T1622
Checks for a debugger and may alter behaviour under analysis
via: IsDebuggerPresent
...
When a malware analyst gets a new Windows sample, the first question is what can this thing do — before committing to a full dynamic run in a sandbox. A binary announces a lot about its intentions in its import table: the list of Windows APIs it links against. VirtualAllocEx + WriteProcessMemory + CreateRemoteThread is process injection. RegSetValueExW on a Run key is persistence. IsDebuggerPresent is an analyst-aware sample.
The famous tool for reading this by hand — rohitab's API Monitor — has not seen a release since 2013 and does not run on modern Windows. Reading imports manually with dumpbin or a hex editor works but is slow and remembers nothing about what each API means. imprint does that reading for you, and it knows what the functions are for.
imprint parses a PE file's import, delay-import and export directories, looks each imported function up in a curated Windows API behaviour database, and then composes the individual imports into higher-order capabilities mapped to MITRE ATT&CK.
Two layers, on purpose:
- Flagged imports — every imported API that carries meaning, tagged with a category (injection, persistence, anti-analysis, network…), a severity, and its ATT&CK technique. This is a fact about the file.
- Capabilities — the composition. A single import rarely proves intent;
WriteProcessMemoryalone is used by debuggers. Paired with a remote allocation and a remote thread it is injection. Each fired capability lists the exact imports that triggered it as evidence, so the finding is auditable, not a verdict from nowhere.
It reads bytes on disk. It never maps, relocates, or executes the image, makes no network calls, and is safe to run against a live malware sample on any machine.
- Static analysis is instant and safe — no VM, no detonation, no risk. It is the right first pass on an unknown file, and it also works on samples that would refuse to run under analysis.
- Pure C11, zero dependencies. One
gcccommand builds it. The PE parser reads untrusted bytes through a bounded reader that never casts a struct over attacker-controlled memory and never reads a byte outside the buffer — malformed samples are the norm, not the exception, and the parser is written for that. - Portable: the analyzer compiles and runs on Windows, Linux, and macOS. You can triage Windows malware from a Linux box.
git clone https://github.com/hamodywe/imprint
cd imprint
make # or: gcc -std=c11 -O2 src/*.c -o imprintRequires a C11 compiler (gcc, clang, or MinGW). No libraries beyond libc.
imprint sample.exe # full report
imprint sample.exe --min notable # hide low-signal (info) imports
imprint sample.exe --all # also list every import, classified or not
imprint sample.exe --json # machine-readable report
imprint sample.exe --strict # exit 1 if any capability is inferred (for pipelines)| Option | Purpose |
|---|---|
--json |
Emit a JSON report |
--all |
List every import, not only flagged ones |
--min <level> |
Hide flagged imports below info / notable / suspicious |
--strict |
Exit 1 if any capability is inferred |
--color / --no-color |
Force colour (default: auto) |
-v, --version / -h, --help |
Version / help |
| Code | Meaning |
|---|---|
0 |
Analysed (or, with --strict, no capabilities) |
1 |
--strict and at least one capability inferred |
2 |
Usage or read/parse error |
Composed from the import set, each with ATT&CK mapping and evidence:
Process Injection (T1055) · Process Hollowing (T1055.012) · APC Injection (T1055.004) · Dynamic API Resolution (T1027) · Anti-Debugging (T1622) · Sandbox / Timing Evasion (T1497) · Locale Geofencing (T1614) · Registry Persistence (T1547.001) · Service Persistence (T1543.003) · Keystroke Logging (T1056.001) · Screen Capture (T1113) · Clipboard Capture (T1115) · Credential Access / DPAPI (T1555) · File Encryption / ransomware shape (T1486) · Payload Download (T1105) · Network Communication (T1071) · Process Discovery (T1057) · Privilege Manipulation (T1134.001) · Embedded Payload in Resources (T1027.009) · Indicator Removal (T1070.004).
The underlying API database holds 130+ curated functions across memory, process, thread, injection, dynamic-load, hooking, registry, filesystem, service, network, crypto, anti-analysis, privilege, discovery, execution, desktop/capture, resources and COM.
{
"tool": "imprint",
"version": "0.1.0",
"file": "sample.exe",
"format": { "arch": "PE32+", "machine": "x64", "type": "executable", "subsystem": "console", ... },
"stats": { "imports": 64, "modules": 12, "capabilities": 9 },
"capabilities": [
{ "name": "Process Injection", "attack": "T1055",
"description": "Allocates/writes memory in another process and starts code there",
"evidence": ["VirtualAllocEx", "WriteProcessMemory", "CreateRemoteThread"] }
],
"imports": [
{ "name": "VirtualAllocEx", "dll": "kernel32.dll", "category": "injection",
"severity": "suspicious", "attack": "T1055", "delay": false }
]
}file bytes ──▶ pe.c (bounded PE parser) ──▶ imports[]
│
apidb.c (API → category / ATT&CK / note)
│
rules.c (compose imports → capabilities + evidence)
│
report.c (text / JSON, control-char safe)
src/buf.h— the bounded little-endian reader every byte access goes through.src/pe.c— DOS/NT/optional headers, sections, and the import/delay/export directories, with RVA→offset translation. Defensive against malformed input by construction.src/apidb.c— the curated API knowledge base.src/rules.c— capability composition, each rule collecting its evidence.src/report.c— text and JSON, both sanitising control characters out of untrusted names so a crafted DLL name cannot rewrite your terminal.
See docs/architecture.md and docs/internals.md — the latter is a short guided tour of the PE format for anyone learning Windows internals from this code.
Stated plainly, because a triage tool that oversells is worse than none:
- Imports are intent, not proof of execution. A binary that imports
CreateRemoteThreadcan inject; it does not prove it will.imprintreports capability, not behaviour. - The static import table is not the whole story. Malware that resolves APIs at runtime via
LoadLibrary+GetProcAddresshides them from the table — soimprintflags that pattern itself (Dynamic API Resolution) as the tell, but it cannot name the functions hidden behind it. - Packed samples show the packer's imports, not the payload's. A tiny, capability-light import set on a large file is itself a signal worth noting; unpack first, then re-run.
- Ordinal-only imports (common in
ws2_32) are counted but not named — resolving ordinals to names is on the roadmap. - The database is curated, not exhaustive — it covers the functions that carry triage signal, and grows by pull request.
Ordinal→name resolution · a --diff of two samples' capability sets · export-name behaviour tags for DLLs · a companion dynamic API tracer (the live half of the vision) that records decoded call arguments in a sandbox. See ROADMAP.md.
New API entries and capability rules are very welcome — the bar is that every entry is a claim checkable against Microsoft's documentation, and every rule ships with a test. See CONTRIBUTING.md.
MIT © hamodywe