A Windows network traffic interception tool for malware analysis.
- What is Argus?
- Usage
- Configuration
- Customising fake HTTP responses
- HTTPS interception (MITM)
- ForwardTo — external traffic modifier
- Request/response capture
- Listeners
- Building
- Legal Notice
- License
Argus intercepts all outgoing network traffic from a system and responds with fake but plausible responses. This tricks malware into thinking it has successfully connected to its C2 server, allowing analysts to observe its behaviour in a controlled environment.
Key capabilities:
- Automatic traffic redirection — WinDivert rewrites packet destinations at kernel level; no manual DNS or proxy configuration needed
- Protocol-aware listeners — HTTP/HTTPS, DNS, SMTP, POP3, raw TCP/UDP
- Per-host HTTPS interception — automatic CA generation with dynamic per-hostname leaf certificates (mitmproxy-style); install the CA once and all TLS traffic is decryptable
- Process identification — every intercepted connection is attributed to the originating process name and PID
- Selective passthrough — chosen processes, PIDs, IPs, domains, or URL patterns are passed through transparently to the real destination instead of receiving a fake response
- Selective logging suppression —
NoLogentries exclude matching connections from capture files - Request/response capture — every intercepted transaction is saved to disk under
capture/<process>/<pid>/<protocol>/ - Customisable fake responses — drop files into
default_files/to serve custom content per path; built-in fallback if the directory or file is absent
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
Edit configs\default.ini to enable/disable listeners and customise responses.
[Argus] DivertTraffic: Yes # Enable WinDivert automatic traffic redirection
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 | 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 |
127.0.0.1:9999 |
ServicePortvsListenerPort—ServicePortis the well-known port the monitored application connects to (e.g. 80 for HTTP). WinDivert transparently redirects that traffic toListenerPort, which is where Argus actually listens. Using a high-numberedListenerPortavoids privilege conflicts when two listeners would otherwise share the same port.
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
Hostheader 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.
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
[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
[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.
[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.
[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.
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"
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.
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.
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 |
|---|---|---|
request_id |
number | Unique ID shared by a request and its associated response, for correlation |
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) |
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.
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/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.
Every intercepted transaction is saved to disk. The directory layout is:
capture/
└── <process_name>/
└── <pid>/
└── <protocol>/
├── 20260515_143201_0001_req.log
├── 20260515_143201_0001_rsp.log
├── 20260515_143205_0002_req.log
└── ...
<process_name>— sanitised executable name (e.g.malware.exe)<pid>— numeric PID; a new subdirectory is created if the process restarts<protocol>—http,https,smtp,pop,dns,raw- filename —
<yyyymmdd>_<HHMMSS>_<NNNN>_<req|rsp>.logwhereNNNNis a per-PID transaction counter
The default capture directory is capture\ relative to the working directory. Override with --log-dir.
| 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 |
- Windows 10/11 or Windows Server 2016+
- Administrator privileges (required for WinDivert and privileged ports)
- WinDivert —
WinDivert.dllandWinDivert64.sysmust be in the same folder asargus.exe(included in the repository)
winget install Rustlang.Rustup
# or download the installer from https://rustup.rscargo build --releaseThe build.rs script automatically:
- Adds
WinDivert-2.2.2-A/x64/to the linker search path soWinDivert.libis found at compile time - Copies
WinDivert.dllandWinDivert64.sysnext toargus.exe - Embeds a UAC manifest requesting
requireAdministrator
Binary location: argus.exe
This tool is intended for legitimate malware analysis and security research. Use only in isolated lab environments. Do not use on production networks.
PolyForm Noncommercial 1.0.0 — free for personal use, security research, and non-commercial purposes. Commercial use is prohibited.