Skip to content
Open
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,66 @@ For DPAPI-specific post-exploitation workflows, see [DPAPI - Extracting Password
When a transport module exposes generic verbs such as `get`, `send`, `upload`, `ws`, or `sendws`, check for a **small fixed XOR key** and text-safe wrappers such as **Base64** before assuming heavy crypto. Reused static headers, fixed URL paths, and custom tokens are often better detection pivots than domains alone.


## DNS-controlled multi-transport C2 and modular DLL brokers

### Treat DNS answers as control messages, not only tunneled data

A transport module can query authoritative DNS **before every poll and result upload**, encoding a nonce, client identifier, and the previous transport error in the name. The A-record answer then selects direct HTTPS, a trusted-service relay, rejection, or failure handling for that single transaction. Because a new transaction resets the state, reconstruct decisions as `(query state, returned IPv4)` pairs instead of classifying an IP address in isolation.<sup>[[24]](#references)</sup>

A representative query grammar is:<sup>[[24]](#references)</sup>

```text
<random-uppercase-nonce><state>.<hex-client-id>.m.<actor-domain>
```

In one implementation, `0` meant no error, `1` a relay-ID failure, `2` a relay transport failure, and `3` a direct transport failure. The last A-record octet acted as an opcode: `120` and `130` selected different channels according to that state, `140` raised an exception, other values defaulted to the cloud relay, and one exact IPv4 value rejected the client. This design gives the authoritative server transaction-by-transaction failover without changing the implant configuration.<sup>[[24]](#references)</sup>

### Rotate cloud relay identifiers through A records

A cloud deployment identifier can also be maintained over DNS. The implant first compares a truncated digest of the stored identifier with an A-record response; on mismatch, it requests sequential answers that reconstruct a replacement. In the observed layout, the initial IPv4 answer encoded one total-length byte plus three data bytes, while each continuation answer supplied four more bytes. The client persisted the resulting identifier and immediately used the new relay.<sup>[[24]](#references)</sup>

```text
<nonce>.<hex-client-id>.q.<actor-domain> # digest/freshness check
<nonce>.<hex-client-id>.p.<actor-domain> # sequential replacement chunks
```

A four-byte MD5 prefix provides only a **32-bit freshness marker**, not authenticated configuration. During PCAP analysis, order the `.p` answers, interpret their IPv4 octets as raw bytes, use the first byte as the declared length, concatenate the remaining bytes, and trim padding only after reaching that length.<sup>[[24]](#references)</sup>

### Trusted-service relay and header-gated fallback

A Google Apps Script deployment can hide an application-layer proxy behind normal Google endpoints. The client POSTs a wrapper containing a relay key, an upstream method, forwarded headers, an optional body/content type, and a redirect flag. Apps Script performs the actor-facing request, responds with a `302` to `script.googleusercontent.com/macros/echo`, and returns a JSON-wrapped upstream response after redirects are followed.<sup>[[24]](#references)</sup>

```json
{"k":"<relay-key>","m":"GET","h":{"X-Client-Id":"<id>","User-Agent":"<ua>"},"b":null,"ct":null,"r":true}
```

The direct HTTPS fallback may be **header gated**: scanners receive a generic failure body, while an expected custom client header unlocks tasking. Do not stop decoding after the relay's outer JSON—its body can contain another Base64 value and then a fixed-byte XOR layer. Packet type fields may decide whether the transport handles a task locally or forwards it to an internal broker.<sup>[[24]](#references)</sup>

### Metadata-driven hot loading

A local broker can continuously rescan its directory, group DLLs by version metadata such as `CompanyName`, select the highest-version candidate per group, validate a common export contract, and load or replace components without restarting the host. A replacement may need both a higher version **and a new path** because overwriting a previously discovered file in place is not necessarily noticed. Before `FreeLibrary`, a designated export can request shutdown and allow worker threads to exit.<sup>[[24]](#references)</sup>

This pattern adds useful static and runtime pivots:<sup>[[24]](#references)</sup>

- Cluster colocated DLLs by `CompanyName`, version, export set, and creation timeline rather than filename alone.
- Compare suspicious exports against the legitimate library being impersonated; a plausible public export may secretly initialize the broker.
- Look for rapid repeated directory enumeration followed by version-resource reads, `LoadLibrary`, the shutdown export, and `FreeLibrary`.
- Trace broker routing errors. A component may intentionally message a nonexistent fixed destination, parse an `Err1::<destination>` response to learn the broker identity, and then register its callback.
- Treat broker inventory commands that return loaded names, versions, and paths as framework reconnaissance, even when no host reconnaissance command follows.

If the same framework exposes a command that Base64-decodes, GZip-decompresses, and writes attacker bytes to an arbitrary path, correlate writes with the broker scan. A new higher-version DLL under a new path is a plausible delivery-to-hot-load chain, but record it as an inference unless execution or broker-loading telemetry proves the full sequence.<sup>[[24]](#references)</sup>

### Investigation pivots

For this class of implant, combine DNS, proxy, and endpoint telemetry rather than relying on a domain IOC alone:<sup>[[24]](#references)</sup>

- Hunt for high-frequency A queries whose leftmost label ends in a small state digit, followed by a hex-looking client label and stable mode labels such as `.m`, `.q`, and `.p`.
- Detect bursts of sequential `.p` answers whose four octets decode to printable deployment-ID bytes, preceded by a `.q` four-byte digest comparison.
- Search proxy bodies for the stable Apps Script wrapper keys `k`, `m`, `h`, `b`, `ct`, and `r`, particularly `"m":"GET"`, custom client-ID headers, and the Apps Script-to-`googleusercontent.com/macros/echo` redirect chain.
- Carve nested Base64 layers and test small constant XOR keys; correlate decoded broker packets with subsequent DLL enumeration or filesystem writes.
- Hunt for the shared exports `GroupByCategory`, `CheckAvailability`, `IsPrimeNumber`, and `OrderByDate` across otherwise unrelated DLL names, and for version-incremented replacements created at new paths.



## References

Expand All @@ -790,4 +850,5 @@ When a transport module exposes generic verbs such as `get`, `send`, `upload`, `
- [21] [Recovering Metadata from .NET Native AOT Binaries](https://blog.washi.dev/posts/recovering-nativeaot-metadata/)
- [22] [YARA-X CLI commands](https://virustotal.github.io/yara-x/docs/cli/commands/)
- [23] [Dynamic capa: Exploring Executable Run-Time Behavior with the CAPE Sandbox](https://cloud.google.com/blog/topics/threat-intelligence/dynamic-capa-executable-behavior-cape-sandbox/)
- [24] [Project CAV3RN Continues: DNS-Controlled Multi-Transport C2 and a Modular DLL Broker](https://securelist.com/project-cav3rn-continues/120991)
{{#include ../../banners/hacktricks-training.md}}