Chapter 72

Emotet Traffic Walkthrough

Emotet is a modular banking trojan and loader that delivers secondary payloads (QBot, IcedID, Ryuk). Its network behavior is distinctive: initial HTTP download from a compromised WordPress site, POST-based C2 check-ins with a specific URL pattern, and SMB worm propagation across the internal network. This walkthrough traces an Emotet infection from initial download through module delivery and lateral spread.

Scenario

A Suricata alert fires: "ET MALWARE Emotet HTTP C2 Check-in" from 10.0.2.15. You pull the Zeek logs for that host and trace the infection backwards to a Word macro document downloaded 20 minutes earlier, and forwards to 6 internal hosts that received SMB spreader connections in the following 90 minutes.

Emotet Traffic Signatures

  Emotet Network Timeline
  ═══════════════════════════════════════════════════════════════════

  T+0:00 — Malicious document delivery (email → Outlook download)
  └── smtp.log: inbound email with .docm or .xlsm attachment
  └── http.log: Outlook opens URL from email body
      OR: files.log: .docm file extracted from email

  T+0:05 — Macro executes, downloads Emotet dropper
  └── http.log: GET from 10.0.2.15 to compromised WordPress site
      URI pattern: /wp-content/uploads/2024/01/[random_filename].exe
      UA: PowerShell or cmd.exe UA
      Response: 200 OK, ~120KB binary

  T+0:06 — Emotet binary runs, initial C2 contact
  └── http.log: POST to one of several hardcoded C2 IPs
      URI: /[random_string]/
      Content-Type: application/x-www-form-urlencoded
      User-Agent: Mozilla/5.0 Windows NT (legitimate-looking)
      Request: ~100 bytes (host fingerprint + bot ID)
      Response: ~100-2000 bytes (instructions or module)

  T+0:10 — Module delivery (Spam module or SMB spreader)
  └── Additional HTTP POSTs to C2 IPs → larger response (module binary)

  T+0:15 — SMB spreader activates
  └── conn.log: 10.0.2.15 → many internal IPs on port 445
      Using legitimate NTLM credentials harvested from host
      Fan-out to 6+ unique internal hosts in 10 minutes

  T+1:30 — Secondary payload (QBot) delivery
  └── Additional HTTP GET/POST from infected internal hosts
  └── QBot C2 uses encrypted POST similar to Emotet

Emotet Detection and Investigation

bashemotet-investigation.sh
#!/bin/bash
LOG_DIR="${1:-/opt/zeek/logs/current}"
SUSPECT="${2:-10.0.2.15}"

echo "=== 1. Initial dropper download (check before infection time) ==="
# Look for executable downloads to suspect host
awk 'NR>8 && !/^#/' "$LOG_DIR/http.log" 2>/dev/null | \
  awk -v src="$SUSPECT" -F'\t' '
    $3==src &&
    ($9 ~ /\.exe|\.dll|\.zip|\.ps1|\.bat/ ||
     $16 ~ /application\/octet-stream|application\/exe/) {
      print $1"\t"$5"\t"$9"\t"$17"\t"$16
    }' | head -20

echo ""
echo "=== 2. Emotet HTTP C2 check-ins (POST to external with short URI) ==="
awk 'NR>8 && !/^#/' "$LOG_DIR/http.log" 2>/dev/null | \
  awk -v src="$SUSPECT" -F'\t' '
    $3==src &&
    $7=="POST" &&
    $5 !~ /^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)/ &&
    length($9) < 50 {
      print $1"\t"$5"\t"$9"\t"$13
    }' | head -20

echo ""
echo "=== 3. C2 beacon regularity ==="
awk 'NR>8 && !/^#/' "$LOG_DIR/conn.log" | \
  awk -v src="$SUSPECT" -F'\t' '
    $3==src &&
    $6=="80" &&
    $5 !~ /^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)/ {
      print $1"\t"$5
    }' | sort | uniq -c | sort -rn | head -10

echo ""
echo "=== 4. SMB spreader: internal fan-out from suspect ==="
awk 'NR>8 && !/^#/' "$LOG_DIR/conn.log" | \
  awk -v src="$SUSPECT" -F'\t' '
    $3==src &&
    $6=="445" &&
    $5 ~ /^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)/ {
      print $5
    }' | sort -u | head -20

echo ""
echo "=== 5. Check which internal hosts also C2'd after SMB contact ==="
# Find internal hosts that had SMB contact from the suspect
INTERNAL_VICTIMS=$(awk 'NR>8 && !/^#/' "$LOG_DIR/conn.log" | \
  awk -v src="$SUSPECT" -F'\t' '$3==src && $6=="445" {print $5}' | \
  sort -u)

echo "Potential Emotet spread victims (checking for their C2 activity):"
echo "$INTERNAL_VICTIMS" | while read -r victim; do
    count=$(awk 'NR>8 && !/^#/' "$LOG_DIR/http.log" 2>/dev/null | \
        awk -v src="$victim" -F'\t' '$3==src && $7=="POST" &&
            $5 !~ /^(10\.|172\.)/ && length($9)<50' | wc -l)
    if [ "$count" -gt 0 ]; then
        echo "  $victim: $count suspicious POST requests (possible Emotet spread)"
    fi
done

echo ""
echo "=== 6. TLS fingerprint check (some Emotet variants use HTTPS) ==="
awk 'NR>8 && !/^#/' "$LOG_DIR/ssl.log" | \
  awk -v src="$SUSPECT" -F'\t' '$3==src && $7 ~ /^[0-9]+\./ {
    print $1"\t"$5"\t"$7"\t"$(NF-1)
  }' | head -10
Why Emotet uses compromised WordPress sites for hosting malware

Emotet's initial dropper download from legitimate-but-compromised WordPress sites is an intentional evasion tactic. Domain reputation filters and web proxies use domain reputation databases: wordpress.example.com has been running for 3 years, has legitimate pagerank, and appears in no malware blocklist — the download is allowed. If Emotet used attacker-controlled infrastructure (attacker.com registered last week), it would be blocked by most enterprise web proxies. By compromising established legitimate sites and hosting malware in /wp-content/uploads/ directories, Emotet downloads bypass URL-based controls. Detection from network telemetry: the User-Agent and URI pattern are still anomalous — PowerShell or cmd.exe UAs are not expected from a user's workstation to a WordPress site, and /wp-content/uploads/2024/01/random.exe is not a pattern seen in legitimate web browsing. The domain reputation is clean; the transaction is not. This is why content-level analysis (User-Agent + URI + response type) is more effective than domain reputation alone for detecting malware downloads.

Q & A

Q: Emotet's C2 rotates IP addresses frequently. How do I detect new C2 IPs before threat intel feeds catch up?

Emotet C2 detection without relying on threat intel feeds requires behavioral analysis: (1) POST to raw IP addresses: Emotet C2s are often accessed by raw IP, not domain name. Zeek http.log shows requests where Host: header contains an IP address rather than a domain name. Browsers occasionally do this, but workstations doing regular HTTP POSTs to raw IPs on port 80 is highly unusual. (2) Short URI POST pattern: Emotet's check-in URI is typically 1-15 random characters: /aXyZ9b/. A POST to this kind of URI on port 80 to an external IP from a workstation is nearly always malicious. (3) Response size pattern: Emotet heartbeat responses are tiny (<200 bytes). Large malware downloads arrive as the response to a subsequent POST. Consecutive small-response POSTs from the same source to the same external IP is the Emotet C2 pattern. (4) TLS cert age: when Emotet shifts to HTTPS, the certificates on new C2 IPs are fresh. ssl.log's notbefore field <14 days + external IP + POST to short URI = high confidence. (5) Network behavior anomaly: a workstation that has never sent POSTs to external port 80 suddenly starts — regardless of the specific destination IP — is an anomaly worth investigating.