> ## Content Index
> Fetch the complete content index at: https://f4n6.co.uk/llms.txt
> Use this file to discover other available public pages before exploring further.

# Inside Overlord RAT, Part 2: Emulating the C2, Cracking the Command Bus, and Catching the Implant Killing Itself
- URL: https://f4n6.co.uk/inside-overlord-rat-part-2-emulating-the-c2-cracking-the-command-bus-and-catching-the-implant-killing-itself/
- Published: 2026-09-10T11:19:35.000Z
- Updated: 2026-09-10T16:15:14.000Z
- Author: Jeff Davies
- Tags: Reverse Engineering, malware, Analysis, AI Security, LLM, Overlord

> Part 1 told you what Overlord RAT is. This post is the 48 hours after that: we rebuilt its C2 in an offline lane, drove the agent through a broad recovered command surface, and got recordings of features the original operators probably never wanted documented including a bug that killed the implant from the inside under sustained churn.\*

---

## TL;DR (what changed since Part 1)

Part 1 was a static + debugger teardown that ended at "we cannot see the C2 protocol live". This post closes that gap. The new material combines **live verification against the real agent binary in an isolated guest** with static reconstruction; each section distinguishes observed behavior from inferred control flow:

- **The wire protocol in depth**, reconstructed demand-driven: msgpack envelopes, hello/hello\_ack, a typed command bus (we tested 60+ candidate task names, separating accepted handlers from rejected or CLI-only names and recovering payload/result shapes for live paths), heartbeats (30 s ping / 2 min pong watchdog), reconnect/backoff behavior, and \~4 MiB chunked binary transfer framing.
- **Remote arbitrary file I/O proven**: the C2 can create directories, write files (verified byte-for-byte on the victim), hash/peek/list/dirsize/search/icon/thumbnail, and **zip a directory then exfiltrate it automatically** — we measured a 12.5 MB zip of `C:\Windows\Temp` streamed back to us (evidence archived).
- **The stage-2 plugin load contract decoded**: native PE plugins ride **in-band** in `plugin_load` messages; the agent PE-parses them, can reflectively load them through a custom MemoryModule, and hard-requires a **`PluginOnLoad`** export a real 427 KB Microsoft DLL we pushed through it reached the export gate.
- **The identity and build-token design**: a baked-in, **JWS-shaped token with an apparent 64-byte Ed25519 signature** whose embedded `iat` decodes to **2026-09-08 18:01:33 UTC** hours before *our* detonation plus a **machine-derived runtime key** (HKDF over the victim's `MachineGuid`) that produces the `[purgatory] identity fingerprint=...` line.
- **The keylogger's recovered on-disk contract**, including the `ovd-` filename prefix gate and `%LOCALAPPDATA%\Temp` location actionable for hunting where Part 1 only said "logs under %APPDATA%".
- **The hidden-desktop install chain** as machine-readable state: `virtual_start {mode}` → (missing VDD) → driver download URL at `OVERLORD_virtual_DRIVER_URL` → PowerShell Expand-Archive → `pnputil /add-driver` → re-detect and how our sustained high-churn run ended after \~15 minutes in **the agent's own Windows callback exhaustion** (`fatal error: too many callback functions`), captured with full Go stacks.
- **A recovered env/config map** of the agent (`OVERLORD_*` keys, `config/settings.json`, `config/server_index.json` and its `last_working_index` rotation pointer).

Everything below was obtained **without contacting the real C2** we stood up a fake C2 in the isolated lane and made the agent believe it.

---

## Related public research

While this work was underway, Huorong Security published an independent technical analysis of Overlord RAT on 3 September 2026\. Its report corroborates core static findings including the Go/WSS/MessagePack design, HVNC and reflective injection paths, command dispatch, and the Solana Memo fallback. This Part 2 focuses on a different evidence layer: exercising a v2.6.0 agent against a purpose-built C2 emulator, recording exact payload behavior and failure semantics, moving real data through the wire protocol, and stress-testing long-running subsystems. Overlapping findings are independent corroboration, not a claim of first disclosure.

[Huorong Security: technical analysis of Overlord RAT](https://www.huorong.cn/document/tech/vir%5Freport/2049?ref=f4n6.co.uk)

## The test rig: a C2 built to be lied to

The agent talks `wss://privatec2[.]uk` — which we resolved (hosts-file) to our REMnux lane, where a \~13 KB Python WebSocket server we call **c2emu** impersonates the C2:

- accepts the agent's TLS 1.3 session (self-signed, `TLS_AES_256_GCM_SHA384`, verification disabled on the agent's side — Part 1's odd runtime warning, now understood),
- serves `hello_ack` with an empty command set,
- then drives **batches** of commands stored as JSON and dispatched per agent reconnect.

Because the agent dispatches freshly reconnected sessions, every wire probe followed the same harness loop: stage a JSON batch into the fake C2's queue → force reconnect → watch command results, typed result events, and the agent's own stderr (which we redirected to a file in-guest using a `cmd /c` launcher script with an environment tailored to each experiment the honest way to run 14 relaunches over two nights without a debugger).

**The console is a second oracle.** The agent logs `dispatcher: handling command type=%s` for every recognized task and `command: unknown action=%s` / `dispatcher: unknown message type=%v` for everything else which made name-hunting cheap and unambiguous even when a command produced no wire result.

A second fake server (`solrpc.py`) impersonates the Solana RPC (see the rotation section), and `vddserve.py` serves the virtual display driver the agent tries to download. Both are part of the same host-only lane; nothing ever left it.

## The wire protocol as observed

**Framing.** WebSocket, binary opcode, **msgpack** payloads. Heartbeat = agent sends `{"type":"ping"}` every 30 s; C2 must answer `{"type":"pong"}` — if there was no pong in \~2 minutes, the agent force-reconnects (we reproduced this clock drift as sessions aged and watched the reconnect churn).

**Hello / enrollment.**

```json
{"type":"hello", "id":"2e230d59...24efa6", "hwid":"2e230d59...24efa6",
 "host":"FOR710-WIN-VM", "os":"Windows 10 Enterprise 21H1", "arch":"amd64",
 "hostArch":"amd64", "version":"2.6.0", "user":"REM"}

```

The agent detects our fake C2 as a *legacy* server and takes the no-challenge path (`[purgatory]` lines in Part 1). With a challenge-capable server, the enrollment puts the agent through purgatory; there is an `enrollment_challenge` code path plus an `Ed25519 verification failure` path the challenge flow contains Ed25519 signing and verification logic (identity section below). HTTP-side authentication is a staple of the family: the upgrade request carries `x-agent-token` and `x-overlord-client-id` headers (values defanged in the IOC list).

**Commands.** One shape, richly typed:

```json
{"type":"command", "commandType":"file_peek", "commandId":16101, "payload":{...}}

```

with the governing rule we cracked mid-analysis:

> **Every multi-parameter task takes its arguments in a nested `payload` object. Top-level spellings of the same field names are parsed but ignored.**

That single discovery turned a whole family of "accepted but no-op" behaviors into working captures. Replies split into three families:

1. **Plain result** `{"type":"command_result","ok":true}` (+ `message` on failures),
2. **Typed results** their own msgpack type with a `_result` suffix, e.g. `file_list_result`, `file_hash_result`, `process_list_result`, `backstage_window_list_result`, `keylog_file_list`,
3. **Events/progress** e.g. `{"type":"command_progress","message":"Zipping 30/74 files..."}` during archive ops, and `plugin_event{pluginId,event,error}` from the plugin subsystem. Deferred results can be queued and flushed on the *next* hello.

**Big transfers use \~4 MiB binary chunks**, which we captured to disk during live exfiltration (see below).

---

## The command battery — what the agent actually executes

Driven through the fake C2, this is the recovered map of candidate command names (spellings exact). Unmarked names were accepted and exercised as handlers in this build; `*†` names were explicitly rejected or resolved as non-wire/CLI-only paths; `‡` reached a real build-gated handler:

| Family            | Candidate names tested (state as observed)                                                                                                                                                                                                                       |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Files             | file\_list,file\_peek,file\_read,file\_hash,file\_search,file\_dirsize,file\_mkdir,file\_write,file\_delete,file\_copy,file\_move,file\_chmod\*†,file\_icon,file\_thumb,file\_zip,file\_download,file\_upload(+HTTP),file\_request\_access                       |
| Processes         | process\_list,process\_icon*†,process\_suspend,process\_resume*†,kill\_process*†,query\_process*†,execute\_file,exec                                                                                                                                             |
| Streams           | desktop\_start,desktop\_stop,desktop\_select,desktop\_set\_bitrate/quality/profile,desktop\_mouse\_\*,desktop\_keyboard*†,backstage\_\* (start/stop/mouse/keyboard/cursor/window\_list/enable\_uia/kill\_all/lookup),virtual\_\* (mirror set),stream\_interval*† |
| Voice/webcam      | voice\_session\_start/stop,voice\_capabilities,webcam\_set\_quality,webcam\_start\*†                                                                                                                                                                             |
| Clipboard/privacy | clipboard\_sync\_start/set/stop,privacy\_start/stop/status,keylog\_\* (see below)                                                                                                                                                                                |
| Networking        | proxy\_connect,proxy\_close*†,tunnel\_\**†,webrtc\_publish‡                                                                                                                                                                                                      |
| Hidden desktop    | virtual\_start,virtual\_start\_process,virtual\_kill\_all,backstage\_enable\_uia                                                                                                                                                                                 |
| Self              | agent\_update*†,config\_id*†,refresh\_server\_list\*† (rotation is NOT wire-triggerable — see Solana section)                                                                                                                                                    |

`*†` \= exact spelling verified as **not** a wire task / refused on the wire (renames or CLI-only paths); `‡` \= real handler, but **this build is not compiled with WebRTC**: the agent answers `"webrtc support not compiled in (build with -tags overlord_webrtc)"` a build-tag-gated feature.

Notable *observable* behaviors we confirmed end-to-end (results received over the wire, artifacts verified in the guest):

- `proxy_connect {host,port}` performs a **real socket dial** we pointed it at our lane and recorded the OS-level "actively refused" error text returned verbatim to the C2\. This is a working pivot primitive.
- `file_mkdir {path}` \+ `file_write {path,content}` created `C:\Windows\Temp\olt\` and a file containing an exact attacker-controlled string. **Remote arbitrary write, proven.**
- `file_zip {path}` zipped `C:\Windows\Temp` → posted `Zipping 0→70/74 files...` progress events → `Zip created` → and then **the agent automatically uploaded the artifact** to us as `file_download` in three \~4.2 MiB chunks (12,567,266 bytes total). Exfil-by-accomplice: the *upload follows the zip without a second command*.
- `file_search` streams typed results (`searchId` passthrough, path-substring `keywords`, `maxResults`, `complete` flag) a full directory crawler for the operator.
- `file_download {path}` \= chunked pull of any file, 4 MiB at a time; `file_upload` supports both C2-relay and direct-mode HTTP via `resolveUploadPullURL` (the agent fetches `/api/file/upload/pull/...` and pushes with `x-overlord-client-id` headers).

**Keylogger details worth more than a bullet:** the store directory is **`%LOCALAPPDATA%\Temp\`**, filenames carry a mandatory **`ovd-` prefix** (the read/delete path validates it — anything else is refused with `invalid filename prefix`), files rotate (`%s%s-%d.log`), and listing/reading/deleting/clearing are each separate tasks with typed results (`keylog_file_list`, `keylog_file_content`, `keylog_delete_result`, `keylog_clear_result`). There is also a **permission gate** (`keylog_permission_result`, `NeedsPermissionGate`, `keylog_request_permission`). On session-less boot (like our lab), the capture loop starts yet never materializes a file; interactive victim sessions are the condition under which the implementation is designed to create them. That `ovd-*` temp glob is a high-value hunt lead.

Process enumeration reports a shape-fit for IR engineering: per-process `pid/ppid/name/exePath/cpu/memory/username` plus a `type` field tagging **`own`** (the agent's own processes get first-class treatment) operator-friendly triage writing, and, for defenders, a reminder the implant knows exactly what its own tree looks like.

## Identity and the build token (fresh-build forensics)

Part 1 documented the purgatory fingerprint. Part 2 sourced it.

**Runtime identity.** `config.DeriveIdentity` HKDF-derives an **Ed25519 key pair from the victim's `MachineGuid`** (`HKLM\SOFTWARE\Microsoft\Cryptography`) with the unwrapped info string `**overlord-identity**` (`[identity] HKDF derivation failed`, warnings if no OS machine ID is readable). The derived public key is what the purgatory line hashes into `identity fingerprint=e11f00d9a0e276c3...` stable per machine, opaque across machines, and therefore a poor cross-victim correlator.

**The build token.** The agent carries a hardcoded default "build tag":

```
config.DefaultBuildTag (const, .rdata)
eyJ2IjoxLCJiaWQiOiI0ODNkYTI4OC1iNjVlLTQwMmQtYjE4MC0zNWQ1ZGQ2YzJkZDYiLCJ1aWQiOjksImlhdCI6MTc4ODg5MDQ5M30.
RGNEOAeugclwOQ8DRRoErJ_PGSmITne-8xP8qNWrL0Umk7oznBlCTfCwK292J6yD3WB8j_biIwItMCeZImREBw

```

That's a **JWS-shaped token**: a base64url JSON payload plus a 64-byte second component consistent with an Ed25519 signature:

```json
{"v":1,"bid":"483da288-b65e-402d-b180-35d5dd6c2dd6","uid":9,"iat":1788890493}

```

The embedded `iat` decodes to **2026-09-08 18:01:33 UTC**, hours before our detonation. We did not validate the token against an operator public key or build logs, so treat that value as a claimed issuance time rather than proof of compilation time. Static call-path analysis suggests validation belongs on the server side: the agent includes signing functionality but no corresponding verify-with-public-key path in the client config ledger. The precise meanings of `bid` and `uid` are not independently established. Defensively, the exact token and its `bid`/`uid`/`iat` fields remain useful clustering pivots: a matching token or `bid` can associate infections with the same apparent release cohort without, by itself, proving a campaign or operator identity.

## Solana rotation, decoded

Part 1 described the mechanism from strings. Part 2 has the control flow and its gates:

```
main.runClient
 ├─ ensureServerURLs            (only when the configured server list is EMPTY)
 │   ├─ "No server URLs configured. Fetching raw list from %s"   (OVERLORD_SERVER_RAW / raw server URL)
 │   └─ "No server URLs configured. Resolving from Solana memo (address %s)"
 │       └─ config.tryRefreshServerList → refreshServerList
 │           └─ config.refreshServerURLsFromSolana → LoadServerURLsFromSolana
 │               ├─ config.getSignatures (RPC getSignaturesForAddress)
 │               ├─ RPC getTransaction → memo instruction (Memo program)
 │               └─ decrypt memo → "resolved %d server URL(s) from memo in tx %s"
 └─ runSession  (per-URL connect loop; retry backoff ~14–27 s jitter)
     └─ deadline helper (0x14052d0e0): refresh is DUE only if
        [ bool-gate != 0  AND  2 pointer-gates != 0 ]  AND  now ≥ deadline1|deadline2

```

Decode of the constants: the retry interval is **120 s** (immediate `0x1bf08eb000` ns) and the rotated-client trusts its own `**last_working_index**` pointer in `config/server_index.json` i.e., rotation state persists across restarts on disk.

Our three live outage windows (fake C2 down 6–23 min, all `OVERLORD_SOL_*` envs set, mock RPC armed) produced **zero** RPC calls: the deadline helper's precondition bool + two pointers were never satisfied in-lab. Static analysis ties those preconditions to configuration fields the server can influence, suggesting that rotation may be **C2 directed** but we did not observe the real server schedule it. We additionally confirmed from error strings the memo crypto shape: the memo is decrypted with a key **derived from the agent token** (`agent token required for solana memo decryption`, `[solana] using key hash prefix: %x (token len=%d)`), and the count of decryptable memos limits the URL list (`no valid decryptable memo found in recent transactions`).

The recovered env/config map (all verified from the binary's config loader references):

| Key                                                                                                                                  | Purpose                                                                  |
| ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| OVERLORD\_SERVER / OVERLORD\_SERVER\_RAW / OVERLORD\_SERVER\_SOL                                                                     | primary server list override / raw-list HTTP fetch URL / Solana RPC list |
| OVERLORD\_SOL\_ADDRESS, OVERLORD\_SOL\_RPC\_ENDPOINTS                                                                                | Solana watch address, RPC endpoint set                                   |
| OVERLORD\_AGENT\_TOKEN                                                                                                               | the enrollment/auth token, also keys the memo decryption                 |
| OVERLORD\_ENABLE\_PERSISTENCE, OVERLORD\_DISABLE\_CAPTURE, OVERLORD\_CAPTURE\_INTERVAL, OVERLORD\_MUTEX, OVERLORD\_FETCH\_PUBLIC\_IP | behavior switches                                                        |
| OVERLORD\_TLS\_CA, OVERLORD\_TLS\_CLIENT\_CERT/KEY, OVERLORD\_TLS\_INSECURE\_SKIP\_VERIFY                                            | transport trust                                                          |
| config/settings.json, config/server\_index.json                                                                                      | local config + rotation pointer (last\_working\_index)                   |

## The stage-2 loader: in-band PE plugins with a `PluginOnLoad` contract

Part 1 said "the agent ships its own reflective loader and expects DLLs from the server". Part 2 pinned the exact contract, live:

```json
{"type":"command","commandType":"plugin_load","commandId":9301,
  "payload":{
    "manifest":{"id":"ws3","runtime":"native","nativeEntrypoints":[...]},
    "binary": <msgpack bin, raw PE bytes> }}

```

Observed behavior chain, each verified against the real agent:

| Probe                                         | Response                                   |
| --------------------------------------------- | ------------------------------------------ |
| plugin\_load w/o payload                      | missing payload                            |
| payload w/o a recognizable manifest           | missing plugin id                          |
| {manifest:{id}} (nested map!)                 | empty plugin binary                        |
| manifest + 16-byte string as binary           | pe load: pe: data too small for DOS header |
| manifest + **real ws2\_32.dll (427 KB, x64)** | pe: export "PluginOnLoad" not found        |

So: the bytes parse as a full PE, then the loader demands a `**PluginOnLoad**` export (a fixed requirement `nativeEntrypoints` did not change it). On success plugins are staged to a disk **cache** (`create/write/finalize plugin cache`, path sanitizer included) and can be loaded via either the OS `LoadLibrary` path or the agent's own **reflective MemoryModule** (`plugins.(*MemoryModule)`: `processRelocations`, `resolveImports`, `parseExports`, `getProcByOrdinal`, `setupTLS` — the full relocation/import rebind + TLS callback walk) i.e., fileless-capable even when staged. Every load publishes lifecycle events back: `plugin_event{pluginId, event:"error"|"load"|..., error}` the same channel the C2 watches to confirm a stage-2 came up.

For **WASM** plugins the runtime is wazero: manifest `runtime:"wasm"`, binary key sibling `wasm`, and the host module surface exposes note carefully the **same primitives as the command bus, callable from sandboxed WASM**: `overlord_fs_read/write/list/mkdir/delete`, plus `overlord_emit`, `overlord_on_load/on_unload/on_event`, `overlord_alloc`. In other words, the design gives operators a plugin sandbox for crash-isolated capability modules that inherit file I/O and messaging without recompiling the agent; we established the capability, not observed operator use of a WASM module.

And the same loader feeds the **browser/hidden-injection** story from Part 1: `extractDLLBytes` / `extractCaptureDLLBytes` (fields `dll,url,fps,exe,pid,cwd`), `StartbackstageProcessInjected` ("empty DLL bytes"), `RDI_DLL_SIZE`/`RDI_DLL_SECTION`/`RDI_SEARCH_PATH` env knobs, and the reflective guard `pe: only PE32+ (64-bit)...`, `invalid PE signature`. The GPU-process DLL stream (`Local\backstage_rdi_%d`) is delivered by the same in-band binary primitive — the two halves of the design (plugin manager + injection RDI) share one loader lineage.

## Hidden desktop install chain — and the crash we reproduced under churn

The `virtual_*`/`backstage_*` tasks are one state machine:

```
virtual_start {mode:"mttvdd"}      -> "hidden: start requested (autoStartExplorer=false)"
virtual_start_process {path:X}     -> "hidden: start process <X>"
                                   -> if no virtual monitor: "virtual mode not initialized"
                                        └─ InitializeVirtualMode (virtual_windows.go:95)
                                             ├─ findVirtualMonitor (…:1069)
                                             ├─ "virtual: no virtual monitor found, attempting driver install"
                                             ├─ installVirtualDriver:
                                             │    download driver.zip from OVERLORD_virtual_DRIVER_URL
                                             │    PowerShell Expand-Archive -Path '%s' -DestinationPath '%s' -Force
                                             │    find *.inf  ("no INF file found in extracted driver")
                                             │    pnputil /add-driver *.inf /install
                                             │    ("Driver package added successfully" / "no driver INF installed successfully",
                                             │      stage=%d hr=0x%x)
                                             └─ re-detect -> "virtual: initialized virtual monitor %q index=%d bounds=…"

```

We ran this against our fake VDD server (`driver.zip` containing a well-formed INF). The install chain unfolded exactly as the strings predicted — and then, in a longer session, we watched it die:

```
capture: capture failed: invalid argument (sending black frame, consecutive=996)
fatal error: too many callback functions
syscall.compileCallback  runtime/syscall_windows.go:334
… capture.win_duplication.go:152
… capture.findVirtualMonitor  capture/virtual_windows.go:1069
… capture.InitializeVirtualMode  capture/virtual_windows.go:95
… handlers.HandleCommand.func19   handlers/command.go:2142

```

That is the VDD install being strangled by the leak. It is worth unpacking because it is **the agent's own stability defect, reproduced in our sustained high-churn stress test**:

- The failing path repeatedly reaches Windows `**syscall.NewCallback**` from capture re-entry. Go's runtime carves callback trampolines from a finite table, and this path recreates callbacks without a corresponding release mechanism. Under our fast-reconnect, streamed-screen and UIA churn, the table exhausted and the process died loudly (`fatal error`, not a catchable panic `panic_guard.go` wraps only ordinary panics).
- The consequences for operators: repeated session and capture churn can make persistence fragile. The preceding night we also observed an \~18k-goroutine buildup before a similar death, although that observation alone does not establish that the goroutine growth caused callback exhaustion.
- The consequences for defenders: a **self-termination timeline** (if your EDR watches for abrupt Go-runtime fault exits of the same binary) and a timeline you can correlate across victims to see the same churn pattern.

The screen-capture stack remains exactly as Part 1 mapped it (DXGI duplication → WMF H264 MFT selected; `NVENC` missing in our VM `stage=4 hr=0x8007007e`; JPEG fallback; black-frame filler at `consecutive=996` by the time we crashed it). At `backstage_start` the agent targets **fps 120** for the hidden stream and prints its favorite confession per tick: `backstage capture: no windows drawn for display=0 source=monitor=0 name="WinDisc" bounds=(0,0)-(1024,768)`.

## Reconstructions: what one repair session owed us

A combined static/dynamic reconstruction of the core client design as shipped in v2.6.0 with observed-live vs statically-proven annotations:

- **Dispatch core**: `HandleCommand` (a \~5 KB switch table in `handlers/command.go`) + `payloadAsMap/payloadInt/payloadInt32/envelopePayloadString/Ints` accessors; `registerCancellableCommand`/`cancelCommand`/`cancelAllCommands` — long-running commands are **cancellable from the C2**.
- **Reconnect discipline**: `resetForReconnect` tears down every session-scoped subsystem (keylogger, clipboard/activewindow monitors, voice/audio sessions, tunnel sockets, virtual/backstage streams, uploads in flight — `cleanupPendingUpload`, `pendingUploads` ledger); `waitStreamStop`/`stopVirtualStreamLocked` gate the capture goroutines. Every session restart re-registers hooks (the callback-leak source above).
- **Self-update machinery** (from `HandleAgentUpdate`): `resolveCurrentExecutableOnDisk`, `ensureRegularFileOnDisk`, `copyExecutableAtomic`, `verifyFileHash`, `backupExecutable`, `startupTargetPath`, `runAgentUpdate` with a **deferred updater** (`writeDeferredUpdateBatch` \+ `launchDeferredUpdate script` the `overlord-update-*.bat` family), and `shouldLaunchUploadedBinaryDirectly` i.e., C2-pushed binary replacements with integrity checks and a backup/rollback path.
- **Purgatory**: state machine (`status=denied/pending/approved`) driven by server packets, with the Ed25519 challenge verification trail and `Enrollment_challenge` material.
- **Privacy** (`handlePrivacyStart/Stop/Status`): the "OverlordPrivacyWindow" overlay pauses/masks the display during operator sessions.
- **On-disk side artifacts**: `ovd-CTRL.tmp` (panel control marker), `Local\backstage_evt` event object, `overlord_fs_*`, `%TEMP%\ovd-*.log` keylog store, plugin cache directory, `config/server_index.json` (+`last_working_index`), `agent-*.tmp`, `crashlogC.log`.

## IOC additions (defanged)

Adds to Part 1's list:

| Type              | Value                                                                                                                                                                        |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Env key           | OVERLORD\_virtual\_DRIVER\_URL (note the mixed case)                                                                                                                         |
| Artifact URLs     | https://<c2>/add-driver (driver.zip pull), /api/file/upload/pull/... (agent uploads), /api/clients/<64-hex>/stream/ws?role=client                                            |
| Files on disk     | %LOCALAPPDATA%\\Temp\\ovd-\*.log (keylog store), driver.zip staging dir + extracted \*.inf/mttvdd.sys, plugin cache dir, config/settings\[.\]json, config/server\_index.json |
| Registry/identity | build token {"v":1,"bid":"483da288-b65e-402d-b180-35d5dd6c2dd6","uid":9,"iat":1788890493} (JWS-shaped; apparent signature and bid clustering pivot, provenance unverified)   |
| Wire fields       | commandType, commandId, payload{...}, typed \*\_result, searchId, transferId, whipPath, pluginId                                                                             |
| Mutex/install     | Local\\backstage\_rdi\_<pid>, Local\\backstage\_evt, ovd-CTRL.tmp                                                                                                            |

## Detection

Hunt targets (EDR/SIEM additions Part 1 list stays valid):

- **Files named `ovd-*.log` in `%LOCALAPPDATA%\Temp`** (keylogger store) a high-value static hunt lead.
- `pnputil /add-driver` invoked from a temp-dir context at all; any process doing `Expand-Archive` on `driver.zip` fetched from a non-Microsoft origin.
- Go-runtime `fatal error: too many callback functions` crash text in live-incident memory (or crashlogC\[.\]log content) — a candidate behavioral marker when correlated with this RAT's other artifacts.
- An outbound WebSocket client upgrade carrying the **JWS-shaped build token** (`{"v":1,"bid":...}` decoded payload) — a correlation key across victims; cryptographic provenance remains unverified without the corresponding public key.
- Msgpack command frames carrying `commandType`+`payload` over WSS; `file_write`/`file_zip`+same-session `file_download` pairings = **exfil chain signature**.

Updated YARA (Part 1 rule + plugin/store rows):

```yara
rule Overlord_Go_RAT_v2 {
  meta:
    author = "malbox analysis session LR-2"
    description = "Overlord Go RAT agent incl. plugin/store artifacts (v2.6.0 era)"
  strings:
    $pkg1 = "overlord-client/cmd/agent" ascii
    $plug = "PluginOnLoad" ascii
    $ovl  = "overlord_fs_" ascii
    $vd   = "OVERLORD_virtual_DRIVER_URL" ascii
    $bak  = "backstage_start" ascii
  condition:
    uint16(0) == 0x5A4D and ($pkg1) and 2 of ($plug,$ovl,$vd,$bak)
}

```

## Methodology notes for reproducers (the honest list)

1. **Console > wire.** The agent's own stderr told us `handling command type=X` vs `unknown action=Y` even when results failed silently. Redirect it from the launcher script, not the debugger (the debugger serializes; the console doesn't).
2. **Payload nesting** is the protocol's real shape all top-level field attempts optically "accepted". If a Go handler accepts a map, assume it nests.
3. **Msgpack BIN vs STR** is not cosmetic: `[]byte` fields decode only from true binary elements. Our fake C2 had to learn to tag binary values explicitly (we used a `{"__bin__": "...b64..."}` marker).
4. **Dispatch-on-hello**: force a reconnect to inject commands; keep batches small; re-verify your queue state after restarts index state can roll back under flaky guest tooling.
5. **Session 0 artifacts don't exist**: hooks/files that need an interactive desktop won't materialize under a VIX-launched process. Don't infer absence from silence.
6. **Impose an outage deliberately** when testing fallback paths: the solana rotation only triggers off an empty server list; the VDD install only off a missing monitor; the different layers of resilience mean the *initial* conditions decide which channel gets exercised.

## Closing

After two nights we can say precisely what Overlord RAT is when it works: a **plugin-capable surveillance agent whose design lets operators extend it through WASM and x64 modules, rotate victims through a blockchain-annotated server list, record keystrokes into `ovd-` files, and operate a display the user cannot see with an embedded build token that provides an apparent release-cohort pivot.** Under sustained capture and reconnect churn, our agent then died in the most Go way imaginable: it leaked callback trampolines until the runtime threw up its hands. That failure is terminal inside the running process, but the death rattle remains in the logs.

If you're tracking this family: pivot and cluster candidates by their baked build-token fields (`bid`/`uid`/`iat`), watch for the `ovd-` temp glob and `pnputil /add-driver` chains, and if you ever get a live one, **don't let it churn** its callback budget is finite, and so is its usefulness to you.

*Analysis performed on a network-isolated VMware lane with a purpose-built fake C2 (c2emu), Solana RPC mock (solrpc2), and driver-server (vddserve). No traffic left the lab; no attribution beyond the family's own internal identifiers; all external indicators defanged.*

---

*Part 1 credit: MalwareBazaar ('sample2'). MITRE ATT&CK across both parts: T1219, T1071.001, T1573.002, T1055.001, T1055.003, T1105, T1059.001, T1113, T1125, T1123, T1056.001, T1115, T1547.001, T1090.001, T1005, T1041 (exfiltration over the existing C2 channel).*

Working on a difficult security investigation or building similar tooling? I’d be interested to hear from you.

**AI Platform Investigation** hello@f4n6.co.uk