Vileblood: cryptographic process fragmentation
When a single beacon is split into micro-binaries that communicate through the Windows kernel, EDR loses the process boundary it relies on for behavioural correlation. This page documents the full architecture, the detection geometry it creates, and what still remains visible to defenders.
The threat model: monolithic beacons are trivial to correlate
Modern EDR platforms do not need a signature to fire on a Cobalt-Strike-style beacon. They only need enough suspicious behaviours inside one process inside one time window: raw sockets, remote threads, RWX allocations, encryption, exfiltration. Every action chains into a single story because one process owns the whole chain.
Detection counterpart
The monolithic beacon is easy to detect because the behavioural chain is dense and local. Fragmentation does not remove the behaviour; it distributes it. The defensive question becomes: what can still be correlated when the chain crosses process boundaries through invisible IPC?
The core hypothesis
If no single process holds enough information to reconstruct the attack, and the linkage between processes is invisible to userland telemetry, then EDR behavioural correlation breaks.
| Fragment | Size | Role | What it knows | What it does not know |
|---|---|---|---|---|
| Engine | ~7 KB | Read context, strip sensitive data, forward via ALPC | Plaintext context | AES key, network destination |
| Crypto | ~7 KB | Receive raw context, AES-256-GCM seal, forward via ALPC | AES key, HMAC secret | Plaintext meaning, URL |
| Network | ~24 KB | Receive ciphertext, build V2 Segment batch, HTTPS POST | Exfiltration destination | Key, plaintext |
No single process holds enough information to reconstruct the full chain. A memory dump of one fragment is insufficient.
Why ALPC changes the geometry
The critical decision is not the split itself; it is the IPC mechanism. Most EDR platforms log named pipes and shared memory because they appear in the object-manager namespace. ALPC ports do not.
| Mechanism | EDR visibility | Forensic resistance |
|---|---|---|
Shared memory (NtCreateSection) | High — sections appear in \BaseNamedObjects\ | Low |
Named pipes (NtCreateNamedPipeFile) | High — pipes appear in \Pipe\ | Low |
ALPC (NtAlpcCreatePort) | Low — no user-visible namespace, rarely logged | High |
Key insight
ALPC is the same IPC Windows system services use. It is expected, fast, and functionally invisible to most EDR telemetry. Defensive correlation must therefore move from the IPC channel itself to the side effects of spawning, timing, and the rare platforms that do log ALPC.
The cryptographic design
Two layers matter: the current AES-256-GCM binding through bcrypt.dll, and the architectural end-state of threshold proxy re-encryption.
Current binding
The Crypto fragment uses AES-256-GCM through raw bcrypt.dll FFI, not a crypto crate. A 12-byte nonce is generated per seal; a 16-byte tag authenticates the ciphertext.
End-state: threshold PRE
The relay re-encrypts ciphertext from one public key to another without ever decrypting it. A fully compromised relay still cannot read traffic.
CONFIRMED: the current build uses AES-256-GCM through bcrypt.dll. ASSESSED: full Umbral PRE integration is gated on a no_std-friendly secp256k1/PRE stack; the structural placeholder exists but is not yet active.
Host-unique binary generation
Every deployment produces host-unique binaries. The builder derives a seed from machine entropy, then derives keys, URL selection, ALPC names, and an anonymous salt through a KDF chain.
seed = SHA-256(MachineGuid || VolumeSerialNumber || ProcessorId || BootCount) aes_key = HMAC-SHA256(preshared_seed, seed || "vileblood:aes") hmac_secret = HMAC-SHA256(preshared_seed, seed || "vileblood:auth") url_index = first-4-bytes(HMAC-SHA256(preshared_seed, seed || "vileblood:url")) mod 10 alpc_name = first-16-bytes(HMAC-SHA256(preshared_seed, seed || "vileblood:alpc")) anon_salt = first-16-bytes(HMAC-SHA256(preshared_seed, seed || "vileblood:anon"))
| Spawning mechanism | EDR visibility | Notes |
|---|---|---|
| Windows Scheduled Task | Low — taskeng.exe spawning a child is normal | Primary mechanism |
Service DLL inside svchost.exe | Very low — svchost hosting DLLs is expected | Strongest evasion |
COM surrogate (dllhost.exe) | Low — dllhost is normal | Good evasion |
| Direct spawn from extension | High — unusual parent-child | Avoided |
Detection note: the binaries are stripped, host-unique, and contain no embedded URLs or keys. Static YARA and string matching are weak against this generation model.
What EDR sees, and what it does not
The geometry shifts from single-process correlation to distributed, low-signal correlation. The defensive surface is smaller, but it is not empty.
| Detection surface | What EDR sees | Risk level |
|---|---|---|
| Process spawn | taskeng.exe or svchost.exe → small stripped binary | Low |
| Binary static analysis | ~7 KB stripped, minimal imports, no strings | Low |
| Import table | kernel32.dll + bcrypt.dll + winhttp.dll | Low |
| ALPC port creation | NtAlpcCreatePort in kernel (if logged) | Very low |
| Network beaconing | HTTPS to analytics-shaped endpoint, V2 batch structure | Low |
| Registry reads | MachineGuid, VolumeSerialNumber | Very low |
| Single-fragment memory dump | Partial secret at most | Medium |
What does not appear
- No
CreateRemoteThreadorNtWriteVirtualMemory - No
NtCreateSectionor shared memory handles - No named pipes in
\Pipe\ - No suspicious parent-child from user applications
- No embedded URLs or keys in any binary
- No periodic beaconing; timing is irregular and log-normal
Detection counterpart
The residual signals are: rare ALPC telemetry, timing distribution, analytics-shaped HTTPS batches from non-browser processes, and the co-occurrence of three tiny stripped binaries on one host. EDR platforms that can correlate sparse cross-process events have the best chance; those that depend on dense single-process chains will miss it.
Build results
The architecture is implemented in Rust. The three fragments compile to no_std + alloc; the builder is a standard Rust binary that runs once on the target.
| Binary | Release size | Target | Status |
|---|---|---|---|
vileblood-execution.exe | ~7 KB | 3-5 KB | Close |
vileblood-crypto.exe | ~7 KB | 3-5 KB | Close |
vileblood-network.exe | ~24 KB | 5-8 KB | Oversize due to WinHTTP + JSON |
| Test category | Count | Result |
|---|---|---|
| KDF chain | 7 | Passing |
| Data stripping | 6 | Passing |
| V2 batch construction | 4 | Passing |
| Mock ALPC send/receive | 3 | Passing |
| End-to-end Engine → Crypto → Network | 1 | Passing |
Internal crate names appear here for provenance. No source code, builder logic, or operational parameters are published.
The honest trade-off
Vileblood is not a better beacon for every engagement. It is a different beacon for a different threat model.
| Dimension | Monolithic beacon | Fragmented implant |
|---|---|---|
| Build time | 2-4 weeks | 10-18 weeks |
| Platforms | Multi-platform | Windows only |
| Forensic resistance | Low — single artefact | High — no single dump reveals chain |
| Relay blindness | Practical (AES) | Mathematical (PRE end-state) |
| Binary size | 16 KB (PowerShell) | ~40 KB total (3 fragments) |
| Reliability | High | Medium — service failure, ALPC deadlock risk |
Research ethics
All techniques are described for defensive education and detection engineering. Nothing on this page is deployable code, and every evasion discussed is paired with what still remains visible to a defender.
Conclusion
Vileblood demonstrates that process fragmentation combined with invisible IPC and runtime host-unique generation changes the EDR detection geometry. The question stops being "what is this binary doing?" and becomes "what set of low-signal events, across process boundaries, still correlates?"