enkomio/argus
GitHub: enkomio/argus
Stars: 1 | Forks: 0
# Argus
///`
- **Customisable fake responses** — drop files into `default_files/` to serve custom content per path; built-in fallback if the directory or file is absent
## Usage
Argus requires an elevated terminal or UAC prompt (the embedded manifest requests `requireAdministrator` automatically).
# Run with default config
argus.exe
# Custom config file
argus.exe -c configs\default.ini
# Verbose / debug logging
argus.exe -v
# Bind listeners to a specific address
argus.exe -b 0.0.0.0
# Write log output to file
argus.exe -l argus.log
# Override capture directory (default: capture\)
argus.exe --log-dir D:\analysis\run1
# List available listener types
argus.exe --list-listeners
## Configuration
Edit `configs\default.ini` to enable/disable listeners and customise responses.
### Global options
[Argus]
DivertTraffic: Yes # Enable WinDivert automatic traffic redirection
### Listener configuration reference
Every listener section (except `[Argus]`) accepts the following keys:
| Key | Required | Applies to | Description | Example values |
|-----|----------|------------|-------------|----------------|
| `Listener` | ✅ | All | Listener type to use | `HTTPListener`, `DNSListener`, `SMTPListener`, `POPListener`, `RawListener` |
| `Enabled` | ✅ | All | Whether to start this listener | `True`, `False` |
| `ServicePort` | ✅ | All | Port WinDivert intercepts (traffic destined for this port is captured) | `80`, `443`, `53`, `25` |
| `ListenerPort` | ✖️ | All | Port Argus actually binds on locally. Defaults to `ServicePort` when omitted | `18080`, `18443`, `10053` |
| `Protocol` | ✖️ | All | Transport protocol. Default: `TCP` | `TCP`, `UDP` |
| `UseSSL` | ✖️ | HTTP | Enable TLS termination (HTTPS MITM). Default: `No` | `Yes`, `No` |
| `Timeout` | ✖️ | All | Idle connection timeout in seconds. Default: `10` | `5`, `10`, `30` |
| `Banner` | ✖️ | SMTP, POP3 | Greeting line sent to the client on connect | `220 mail.example.com ESMTP`, `+OK POP3 ready` |
| `ResponseA` | ✖️ | DNS | IP address returned for **A** queries | `127.0.0.1`, `192.168.1.1` |
| `ResponseMX` | ✖️ | DNS | Hostname returned for **MX** queries | `mail.argus.local` |
| `ResponseTXT` | ✖️ | DNS | String returned for **TXT** queries | `v=spf1 -all`, `ARGUS` |
| `Passthrough` | ✖️ | All | Comma-separated list of processes / IPs / domains to pass through to the real destination instead of intercepting. See [Selective passthrough](#selective-passthrough) | `curl.exe`, `93.184.216.34`, `.*\.microsoft\.com` |
| `NoLog` | ✖️ | All | Comma-separated list of processes / IPs / domains whose traffic is **not** written to capture files. Same entry format as `Passthrough` | `analytics\.example\.com`, `telemetry\.microsoft\.com` |
| `ForwardTo` | ✖️ | All | `IP:PORT` of an external TCP endpoint that can inspect and modify every request and response before it is processed. See [ForwardTo](#forwardto--external-traffic-modifier) | `127.0.0.1:9999` |
### Selective passthrough
The `Passthrough` key accepts a comma-separated list of entries. A connection is passed through to the real destination when **any** entry matches. Supported entry types:
| Entry type | Example | Matched against |
|--------------|--------------------------------------|------------------------------|
| Process name | `curl.exe`, `curl.*` | originating process name |
| PID | `1234` | originating process PID |
| IPv4 address | `93.184.216.34` | original destination IP |
| IPv6 address | `2606:2800:220:1:248:1893:25c8:1946` | original destination IP |
| Domain | `example.com`, `.*\.microsoft\.com` | HTTP `Host` / DNS query name |
| Domain+path | `example.com/api/v1` | HTTP `Host` + URI prefix |
| URL | `http://example.com/path` | scheme stripped, then above |
**Process names and domain/URL entries are treated as case-insensitive regular expressions** anchored at the start (`^`). A plain name like `curl.exe` still works — the `.` in regex matches any character, which is harmless in practice. Use `curl\.exe` for a precise literal-dot match.
# Pass through any process whose name starts with "curl"
Passthrough: curl.*
# Pass through DNS queries for any Microsoft domain
Passthrough: .*\.microsoft\.com
# Pass through requests to a specific path only
Passthrough: updates\.vendor\.com/v2/check
# Mix multiple entry types
Passthrough: malware_loader.exe, 93.184.216.34, c2\.evil\.io
When an HTTP connection is passed through:
- **Phase 1** (before reading data): PID, process name, and IP entries are checked. If matched, the raw TCP stream is proxied transparently (`copy_bidirectional`).
- **Phase 2** (after reading HTTP headers): domain and URL entries are checked against the `Host` header and URI. The already-read request bytes are replayed to the real server before proxying the rest.
Argus's own outbound connections (created during passthrough) are automatically excluded from WinDivert interception to prevent redirect loops.
### Selective logging suppression
The `NoLog` key uses the same entry format as `Passthrough`. Matching connections are not written to capture files (they are still logged to the console/log file at `info` level).
NoLog: analytics\.example\.com, telemetry\.microsoft\.com
### Listener example — HTTP / HTTPS
[HTTPListener]
Enabled: True
Listener: HTTPListener
ServicePort: 80
ListenerPort: 18080
Protocol: TCP
UseSSL: No
Timeout: 10
[HTTPSListener]
Enabled: True
Listener: HTTPListener
ServicePort: 443
ListenerPort: 18443
Protocol: TCP
UseSSL: Yes
Timeout: 10
# Optional: pass through matching connections to the real destination.
# Passthrough: curl.exe, 1234, 93.184.216.34, example\.com, http://evil\.com/payload
# Optional: suppress capture files for matching connections.
# NoLog: curl.exe, 1234, 93.184.216.34, analytics\.example\.com
### Listener example — DNS
[DNSListener]
Enabled: True
Listener: DNSListener
ServicePort: 53
ListenerPort: 10053
Protocol: UDP
ResponseA: 127.0.0.1 # IP returned for A queries
ResponseMX: mail.argus.local
ResponseTXT: ARGUS
# Optional: forward matching queries to the real DNS server.
# Passthrough: curl.exe, 1234, 93.184.216.34, example.com, .*\.microsoft\.com
# Optional: suppress capture files for matching queries.
# NoLog: curl.exe, 1234, 93.184.216.34, analytics\.example\.com
Supported record types: `A`, `AAAA` (returns `::1`), `MX`, `TXT`. Any other query type receives an `A` record fallback.
### Listener example — SMTP / POP3
[SMTPListener]
Enabled: True
Listener: SMTPListener
ServicePort: 25
ListenerPort: 10025
Protocol: TCP
Banner: 220 argus.local SMTP Service Ready
[POPListener]
Enabled: True
Listener: POPListener
ServicePort: 110
ListenerPort: 10110
Protocol: TCP
Banner: +OK Argus POP3 Server Ready
Each listener logs credentials (USER/PASS) and protocol commands, and replies with plausible success responses to keep the malware running.
### Listener example — Raw TCP/UDP
[RawTCPListener]
Enabled: True
Listener: RawListener
ServicePort: 1337
ListenerPort: 11337
Protocol: TCP
Timeout: 5
[RawUDPListener]
Enabled: True
Listener: RawListener
ServicePort: 1338
ListenerPort: 11338
Protocol: UDP
Timeout: 5
Accepts any traffic, echoes it back, and logs a hex dump of up to 256 bytes per packet. Used as the default catch-all for ports with no dedicated listener.
## Customising fake HTTP responses
The HTTP listener looks for files in the `default_files/` directory before falling back to the built-in response.
Resolution order for each incoming request:
| Priority | Path tried | Example for `GET /login.html` |
|----------|-----------|-------------------------------|
| 1 | `default_files/{uri}` | `default_files/login.html` |
| 2 | `default_files/index.html` | generic HTML fallback |
| 3 | Built-in constant | always available |
Place any file in `default_files/` and it will be served automatically when the URI matches. The URI path is sanitised before use (leading `/` stripped, `..` components removed) to prevent directory traversal.
default_files/
├── index.html ← served for "/" and any unmatched HTML request
├── login.html ← served for "/login.html"
├── api/
│ └── status.json ← served for "/api/status.json"
└── update.exe ← served for "/update.exe"
## HTTPS interception (MITM)
On first run, Argus generates a CA key pair in `configs/`:
configs/
├── argus-ca.crt ← install this in Windows Certificate Store (Trusted Root CAs)
└── argus-ca.key ← private key — keep secret
For each TLS connection, Argus dynamically generates a leaf certificate for the requested hostname, signed by the CA. Once the CA is trusted by the OS, browsers and HTTP clients accept the leaf certificates without errors.
**Installing the CA on Windows:**
certutil -addstore -f "Root" configs\argus-ca.crt
**Removing the CA:**
certmgr.msc → Trusted Root Certification Authorities → Certificates → right-click "Argus CA" → Delete
The CA certificate expires after one year. Argus warns at startup if the loaded CA is expired.
## ForwardTo — external traffic modifier
The `ForwardTo` key lets you route every intercepted request and response through an external TCP endpoint before Argus processes or returns it. The endpoint can inspect and modify the bytes; whatever it returns is used in place of the original payload. If the endpoint is unreachable or returns an error, Argus falls back to the original payload — traffic is never dropped.
### Wire protocol
Argus → endpoint:
[4 bytes BE uint32] length of JSON metadata header
[N bytes UTF-8] JSON metadata
[4 bytes BE uint32] length of raw payload
[M bytes] raw payload
endpoint → Argus:
[4 bytes BE uint32] length of (possibly modified) payload
[K bytes] payload to use in place of the original
**JSON metadata fields:**
| Field | Type | Description |
|------------|--------|------------------------------------------------------|
| `direction`| string | `"request"` or `"response"` |
| `protocol` | string | `"http"`, `"https"`, `"dns"`, `"smtp"`, `"pop3"`, `"raw"` |
| `src_ip` | string | Client source IP |
| `src_port` | number | Client source port |
| `dst_ip` | string | Original destination IP (before WinDivert) |
| `dst_port` | number | Original destination port |
| `process` | string | Originating executable name (e.g. `"malware.exe"`) |
| `pid` | number | Originating PID (`0` = unknown) |
### Configuration
Add `ForwardTo` to any listener section in `configs\default.ini`:
[HTTPListener]
Enabled: True
Listener: HTTPListener
ServicePort: 80
ListenerPort: 18080
Protocol: TCP
ForwardTo: 127.0.0.1:9999
[DNSListener]
Enabled: True
Listener: DNSListener
ServicePort: 53
ListenerPort: 10053
Protocol: UDP
ForwardTo: 127.0.0.1:9999
Multiple listeners can point to the same endpoint, or each to a different one.
### Running the example Python forwarder
A ready-to-use example is provided in `python/example_interceptor.py`. It requires no external dependencies (standard library only).
**Terminal 1 — start the forwarder first:**
cd python
python example_interceptor.py # listens on 0.0.0.0:9999
python example_interceptor.py --port 8888 # custom port
python example_interceptor.py --debug # verbose output
**Terminal 2 — start Argus (elevated):**
argus.exe -c configs\default.ini
**Expected output (interceptor terminal):**
12:34:01 INFO Argus interceptor listening on 0.0.0.0:9999
12:34:01 INFO Handling protocols: http, https, dns, smtp, raw
12:34:01 INFO Add 'ForwardTo: 127.0.0.1:9999' to any listener in configs/default.ini
12:34:01 INFO ────────────────────────────────────────────────────────────
12:34:45 INFO [HTTP req] malware.exe → GET /c2/beacon
12:34:45 INFO [HTTP rsp] 200 OK (1024 bytes) title tag patched
12:34:46 INFO [DNS req] malware.exe → A evil-c2.example.com
12:34:46 INFO [DNS rsp] evil-c2.example.com A TTL=60 127.0.0.1
### Python library (`argus_interceptor`)
`python/argus_interceptor.py` is a zero-dependency library that handles all socket framing. Implement only the handlers you need:
from argus_interceptor import (
create_interceptor,
http_request, http_response,
dns_message,
smtp_response, smtp_command,
)
def handle_http(meta, payload):
if meta.direction == "response":
resp = http_response(payload)
resp.body = resp.body.replace(b"foo", b"bar")
return resp.to_bytes() # Content-Length auto-updated
return payload
def handle_dns(meta, payload):
msg = dns_message(payload)
for q in msg.questions:
print(f"{q.type_name} query for {q.name}")
return payload
interceptor = create_interceptor(
http_handler=handle_http,
https_handler=handle_http,
dns_handler=handle_dns,
)
interceptor.serve()
**Built-in protocol parsers:**
| Function | Returns | Key fields |
|---|---|---|
| `http_request(payload)` | `HttpRequest` | `.method`, `.path`, `.headers`, `.body` |
| `http_response(payload)` | `HttpResponse` | `.status_code`, `.status_text`, `.headers`, `.body` |
| `dns_message(payload)` | `DnsMessage` | `.questions`, `.answers`, `.is_query`, `.rcode` |
| `smtp_response(payload)` | `SmtpResponse` | `.code`, `.lines` |
| `smtp_command(payload)` | `SmtpCommand` | `.command`, `.args` |
`Headers` is a case-insensitive dict; `HttpRequest.to_bytes()` and `HttpResponse.to_bytes()` rebuild the raw message and update `Content-Length` automatically.
`DnsRecord.address` returns the decoded IP for A/AAAA records; `DnsRecord.text` returns the decoded string for TXT records. `DnsMessage.to_bytes()` re-serialises the DNS wire format.
The interceptor can also be implemented in any other language — the wire protocol is language-agnostic (see wire format above). ForwardTo is called for both intercepted **and** passthrough connections, so the endpoint sees all traffic regardless of whether Argus is returning a fake response or proxying to the real server.
## Request/response capture
Every intercepted transaction is saved to disk. The directory layout is:
capture/
└── /
└── /
└── /
├── 20260515_143201_0001_req.log
├── 20260515_143201_0001_rsp.log
├── 20260515_143205_0002_req.log
└── ...
- **``** — sanitised executable name (e.g. `malware.exe`)
- **``** — numeric PID; a new subdirectory is created if the process restarts
- **``** — `http`, `https`, `smtp`, `pop`, `dns`, `raw`
- **filename** — `___.log` where `NNNN` is a per-PID transaction counter
The default capture directory is `capture\` relative to the working directory. Override with `--log-dir`.
## Listeners
| Listener | Port | Protocol | Description |
|---------------|-------|----------|-----------------------------------------------------|
| HTTPListener | 80 | TCP | HTTP — serves fake HTML/files, passthrough on match |
| HTTPSListener | 443 | TCP | HTTPS — per-host MITM certs, decrypted capture |
| DNSListener | 53 | UDP | DNS — configurable A/AAAA/MX/TXT, passthrough |
| SMTPListener | 25 | TCP | Email sending — logs credentials and message body |
| POPListener | 110 | TCP | POP3 email retrieval — logs credentials |
| RawListener | any | TCP/UDP | Catch-all — hex dump logging, echo response |
## Building
### Requirements
- Windows 10/11 or Windows Server 2016+
- **Administrator privileges** (required for WinDivert and privileged ports)
- [WinDivert](https://reqrypt.org/windivert.html) — `WinDivert.dll` and `WinDivert64.sys` must be in the same folder as `argus.exe` (included in the repository)
### 1. Install Rust
winget install Rustlang.Rustup
# or download the installer from https://rustup.rs
### 2. Build
cargo build --release
The `build.rs` script automatically:
- Adds `WinDivert-2.2.2-A/x64/` to the linker search path so `WinDivert.lib` is found at compile time
- Copies `WinDivert.dll` and `WinDivert64.sys` next to `argus.exe`
- Embeds a UAC manifest requesting `requireAdministrator`
Binary location: `argus.exe`
## Legal Notice
This tool is intended for legitimate malware analysis and security research. Use only in isolated lab environments. Do not use on production networks.
## License
[PolyForm Noncommercial 1.0.0](LICENSE) — free for personal use, security research, and non-commercial purposes. Commercial use is prohibited.
标签:通知系统