~/f4n6 $ grep -r "Overlord RAT: Hidden Desktops, Solana-Driven C2, and a Screen the Victim Never Sees, Part 1" ./investigations/ --include="*.md"
malware

Overlord RAT: Hidden Desktops, Solana-Driven C2, and a Screen the Victim Never Sees, Part 1

Jeff Davies 09 Sep 2026 8 min read

How a routine MalwareBazaar pull turned into a full teardown of a Go surveillance implant that hides its operator on a virtual monitor and how an isolated sandbox plus a live debugger gave us the C2 without ever touching it.

TLP:AMBER contains IOC material and intended for defenders. Hashes and indicators included; no binaries are distributed.

Part 2 https://f4n6.co.uk/inside-overlord-rat-part-2-emulating-the-c2-cracking-the-command-bus-and-catching-the-implant-killing-itself/

TL;DR

We analyzed sample2 from a MalwareBazaar pull: an AES-256 password-protected ZIP containing a 31.7 MB x86-64 Windows executable built with Go 1.26.2 + CGO (mingw-w64). Inside, the Go module path doesn't hide its identity:

path	overlord-client/cmd/agent
mod	overlord-client	(devel)
build	-tags=builder_release

It is a surveillance-grade remote access trojan we call Overlord RAT (after its own artifacts: OverlordHiddenDesktop, OverlordPrivacyWindow, OverlordCriticalProcessShutdownWindow).

Live cdb debugging of the detonated agent in a network-isolated VM produced this console capture the single most useful artifact of the session:

2026/09/08 23:33:31 runtime GOOS=windows GOARCH=amd64 cfg.OS=windows cfg.Arch=amd64
2026/09/08 23:33:31 [TLS] WARNING: Certificate verification is DISABLED. This is insecure!
2026/09/08 23:33:31 connecting to wss://privatec2.uk (TLS verify: false)
2026/09/08 23:36:01 dial failed [timeout]: failed to WebSocket dial: ... Get "https://privatec2.uk/api/clients/2e230d59...64ca24efa6/stream/ws?role=client": dial tcp: lookup privatec2.uk: i/o timeout (retrying in 18.415700099s)
2026/09/08 23:36:31 dial failed [DNS]: ... lookup privatec2.uk: no such host (retrying in 18.415700099s)

(Kept raw/un-defanged for fidelity — it's a verbatim console capture. The defanged IOC list is below.)

And from the debugger's handle table, the single-instance guard:

Name : \BaseNamedObjects\Overlord-3dFr72kGzMu2NW6bMVJe.Bfw
Type : Mutant

The specimen

Delivery AES-256 ZIP, password infected (classic Bazaar convention)
Inner SHA-256 2da7c151676e991ebee73da26db28e6872f655b5487dc4b424a6024a3381472a
Inner SHA-1 / MD5 659a5696359eff19577a80ceb84aca7f95899914 / c5ec50a1192b42d07999becf34448952
Wrapper SHA-256 20658fdcbfcaee00f22c1ee556c1e4171b048347ced5a22d52032dbc49e779a0
imphash e2bf3ea500ed6add4b896d96c842dc0a
Size 31,751,794 bytes, 22 sections
Signatures No Authenticode; TLS callbacks present; exports goDataCallback, goLogCallback, goStopCallback

The exports are a tell: the same agent can be built as a library and driven by (goData/goLog/goStop)Callback host functions — a plugin-first design.

Methodology (and why you should always go this deep)

The pipeline used: deep static triage (Go buildinfo, dependency graph, package map), isolated detonation (VMware guest, host-only network, snapshot-reverted before/after), then live cdb attach with API breakpoints when the sandbox's network isolation silenced the WebSocket handshake. The screenshot below is the detonation console as the agent warmed up its capture stack (note d3d11.dll and DXGI.dll loading before the console even emits a line):

That order of module loads — Direct3D11 before any C2 traffic — is consistent with the capture subsystem standing up first.

The C2 story in three acts

Overlord RAT C2 protocol — reconstruction of discovery, enrollment and command bus

(Diagram is a reconstruction: solid green = observed live in our debugger console; dashed = synthesized from static analysis; red dashed = requires a reachable C2, so not exercised in the isolated lab.)

1) Hard-coded default. A Go string literal sits next to its buildinfo:

go1.26.2 ... privatec2.uk

The agent dials wss://privatec2[.]uk, flags certificate verification off, and authenticates with custom headers (x-agent-token, x-overlord-client-id) on the endpoint pattern /api/clients/<64-hex-agent-id>/stream/ws?role=client. Retry policy: ~18.4 s exponential-ish backoff per server, rotating through the configured list.

Taking a look at the C2 Panel

2) Solana rotator. Before touching that default, the config module (cmd/agent/config/solana.go) can resolve its server list from the Solana blockchain: it calls getSignaturesForAddress against public RPC endpoints, reads the memo of discovered transactions, and AES-GCM-decrypts them with the agent token. Our static IOC pass picked up the well-known Memo program address MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr — the sample watches for transactions on that program's flows.

No server URLs configured. Resolving from Solana memo (address: %s)
[config] resolved server URLs from Solana memo (%d servers)
[solana] RPC %s failed for getSignatures: %v
[solana] failed to decrypt memo from tx %s: %v

3) Purgatory gate. On successful connection the server can hold the agent in a staging queue before any commands are honored:

[purgatory] identity fingerprint=%s
purgatory: status=denied / pending / approved

This is an anti-automation tripwire: an agent that can stall low-value sandbox victims until an operator verdicts their host as "interesting". Our isolated detonation never connected which is exactly why we went a step further and chased the handshake at the debugger instead.

What passive sources say about the infrastructure

We did not contact the C2. The following is from public archives and registries only (collected 2026-09-09):

Source Finding
Nominet WHOIS (.uk registry) Registered 16-May-2026 via Cloudflare, Inc.; expires 2027-05-16; Nominet could not match the registrant's name/address against a 3rd-party source (same day as registration — a throwaway-look registrant profile)
Name servers arch.ns[.]cloudflare[.]com, jillian.ns[.]cloudflare[.]com — full Cloudflare coverage
CT logs (crt[.]sh) First certificates on 16-May-2026 (Let's Encrypt E8 + Sectigo E36, exactly matching the domain's registration day); Let's Encrypt renewal 14-Jul-2026; a Cloudflare TLS ECC cert from 21-Jul-2026 — the C2 is served behind Cloudflare's edge
Cert SANs privatec2.uk + wildcard *.privatec2.uk — the wildcard hints at per-agent or per-region sub-hosts
Public archives URLScan: 0 scans; Wayback Machine: 0 captures — no indexed web face (as expected: enrollment is over WSS, not a login form)

Two observations worth pondering. First, the Cloudflare fronting + Ed25519/X25519 identity + pinned custom CA explains the agent's odd console warning — with an edge TLS-terminator in front and a per-operator CA, "verification disabled" is how the agent trusts its own pinned material instead. Second, the domain itself is only ~4 months old and wholly Cloudflare-fronted: resilience comes from the Solana memo rotator, not from domain age. Blocking the domain is durability theater; the reach is the blockchain and the token.

Core capabilities what the agent code does

From the Go package tree and the behaviors compiled in:

The screen the victim never sees. A virtual_windows module installs (or enables) a Virtual Display Driver — it even fetches a legitimate one from the public VirtualDrivers/Virtual-Display-Driver GitHub releases API — then starts a process (typically a browser) on the virtual monitor. virtual: moved window hwnd=0x%x (pid=%d) to virtual monitor is the quote that matters: operator actions happen on a display ChangeDisplaySettingsExW-hidden from the user.

"Backstage" browser injection (the crown jewel). capture/backstage_* + handlers/backstage* clone a browser profile, apply an Opera patch (opera_patch=%v), kill the real browser (kill_exe), and relaunch the clone with a DLL injected into its GPU child process (backstage inject: found GPU child process PID %d for browser PID %d). The injected DLL streams video directly to the agent via a DXGI shared-memory bridge (Local\backstage_rdi_%d). The displaced user gets auto-start explorer re-launched so the desktop looks normal. The target set is broad: Chromium family (Chrome, Edge, Arc, Brave, Vivaldi, Yandex, Opera GX), Firefox and Waterfox, with browser-pinned UA walkers for input simulation.

Capture stack with no mercy for missing hardware. A display-encoding waterfall: native NVIDIA NVENC (D3D11 texture) → AMD AMF → Intel QSV/oneVPLWindows Media Foundation H.264 MFT (software/HW) → raw JPEG/black-frame filler. HEVC where available; multi-display, per-display fps/bitrate/quality set remotely.

Sensors. Webcam via Media Foundation (windows-camera-go), microphone/voice sessions (gen2brain/malgo), clipboard sync, an active-window monitor, file search, installed-apps with icon extraction — and the mandatory keylogger with rotating logs under %APPDATA%.

Control plane. Mouse/keyboard injection on chosen displays (three modes: plain, backstage, virtual), a real CONPTY shell, scripts (its updater literally ships a PowerShell Invoke-WebRequest snippet), UAC elevation, process suspend/resume/kill, file upload/pull zipped from anywhere, SOCKS5 pivot, WebRTC push (whipPath/WHIP+stun/turn) for the "overlord_webrtc" build.

Self-sustainment. Startup-folder persistence, a .bat self-updater (the notorious overlord-update-*.bat + ping -n 3 127.0.0.1 >NUL delay trick), self-replacement of the running image, and self-defense by becoming a critical process (NtSetInformationProcess(ProcessBreakOnTermination) — so killing it takes the box down).

Pluggability. Both a WASM plugin runtime (wazero; plugins must export overlord_alloc) and a native PE plugin loader, with plugin payload pull over the C2 HTTP plane and callbacks wired through the goDataCallback/goLogCallback/goStopCallback exports. Stage-2 DLLs for injection are delivered as dispatcher messages (dllSize=%d), zero-written to disk — stored in shared memory and mapped into the GPU process.

Analyst's note on "embedded PE". Our first YARA hit promised an embedded PE (malbox_embedded_pe). It wasn't. Every MZ occurrence beyond the header resolves to machine code of the loader's header validator (66 81 38 4D 5Acmp word [rax],'MZ'; 81 38 50 45 00 00cmp dword [rax],'PE\0\0'). The only PK\x03\x04/PK\x05\x06 bytes are Go's archive/zip writer constants compiled inline. Forget carving; this is a loader, not a dropper. Stage-2 lives at C2 or in victim memory.

Dependencies red-flag the design intent

From the Go buildinfo blob (file offset 0x631C20):

Dependency Purpose in this agent
nhooyr.io/websocket v1.8.10 C2 channel
github.com/vmihailenco/msgpack/v5 wire protocol (dispatcher messages)
github.com/tetratelabs/wazero v1.12.0 WASM plugin runtime
github.com/UserExistsError/conpty v0.1.4 hidden console shell
github.com/gen2brain/malgo v0.11.24 audio capture/playback
github.com/Kirizu-Official/windows-camera-go v0.2.0 webcam capture
github.com/lxn/win raw Win32 bindings
golang.org/x/crypto v0.49.0 Ed25519/X25519/HKDF identity + ChaCha20Poly1305/AES-GCM channel crypto

Combined with -trimpath and tag builder_release, this is a professionally maintained codebase — a quality signal relevant to attribution discussions (internal self-identifiers only; no actor attribution asserted).

Indicators of Compromise (defanged)

Type Value
Domain privatec2[.]uk
URL wss://privatec2[.]uk/api/clients/<64-hex>/stream/ws?role=client
HTTP headers x-agent-token, x-overlord-client-id
Mutex \BaseNamedObjects\Overlord-<random-suffix>
Files config/settings[.]json, config/server_index.json, overlord-update-*.bat, overlord-update-%d.log, crashlogC[.]log, agent-*.tmp
Env keys OVERLORD_SOL_ADDRESS, OVERLORD_AGENT_TOKEN, OVERLORD_DISABLE_CAPTURE, OVERLORD_DESKTOP_MAX_FPS, OVERLORD_JPEG_QUALITY, OVERLORD_PING_INTERVAL_MS, OVERLORD_BLOCK_CODEC, OVERLORD_FETCH_PUBLIC_IP, OVERLORD_TLS_CLIENT_CERT/KEY
Hash (inner) 2da7c151676e991ebee73da26db28e6872f655b5487dc4b424a6024a3381472a
Hash (wrapper) 20658fdcbfcaee00f22c1ee556c1e4171b048347ced5a22d52032dbc49e779a0
imphash e2bf3ea500ed6add4b896d96c842dc0a

Detection

YARA (from the malbox session; matches indicate resemblance):

rule Overlord_Go_RAT {
  meta:
    author = "malbox analysis session"
    description = "Overlord Go RAT agent (overlord-client/cmd/agent)"
  strings:
    $pkg1 = "overlord-client/cmd/agent" ascii
    $pkg2 = "overlord-client/cmd/agent/capture" ascii
    $hd   = "OverlordHiddenDesktop" ascii
    $cp   = "OverlordCriticalProcessShutdownWindow" ascii
    $pw   = "OverlordPrivacyWindow" ascii
    $cb1  = "goDataCallback" ascii
    $cb2  = "goLogCallback" ascii
    $cb3  = "goStopCallback" ascii
    $sol  = "Resolving from Solana memo" ascii
  condition:
    uint16(0) == 0x5A4D and (2 of ($cb*) or (2 of ($pkg1,$pkg2))) and ($hd or $cp or $pw or $sol)
}

Hunt targets worth logging in EDR/SIEM:

  • New mutexes matching \BaseNamedObjects\Overlord-
  • Any query/lookup for privatec2[.]uk
  • WebSocket processes emitting x-overlord-client-id headers
  • pnputil/PnP installs of unfamiliar display drivers (VDD)
  • .bat files named overlord-update-*.bat executed from temp dirs
  • A browser process whose parent created a mutex inside Local\backstage_* named shared-memories

Closing

Overlord RAT is a reminder that "Go + CGO + a virtual display driver" is now a state of the art for surveillance-first implants, and that blockchain-native C2 rotation (Solana memos, in this family) robs IR teams of their favorite class of static IOCs. Sample-level carving gets you nothing: the agent ships its own reflective loader and expects the DLLs from the server. If you're hunting something like this live, capture memory first — the stage-2 artifacts only exist in RAM once injections start.

Analysis performed in a network-isolated detonation environment with live debugger introspection. No attribution claims are asserted beyond internal family identification; the "overlord-client" names are sample-internal self-identifiers.

This analysis was performed by an AI agent that is currently being developed to further enhance its capabilities going forward


Sample credit: MalwareBazaar ('sample2' delivery package). All tactics in this post correspond to MITRE ATT&CK: T1219, T1071.001, T1573.002, T1055.001, T1055.003, T1105, T1059.001, T1113.003, T1125, T1123, T1056.001, T1115, T1547.001, T1090.001.

Post this to LinkedIn
Formatting is converted automatically — headings, bullets, a link back & hashtags. Paste straight in.
J
Jeff Davies
// read next

Related