feat: add 4 new cybersecurity skills - UEBA insider threat, BeyondCorp zero trust, Linux kernel rootkits, CobaltStrike beacon hunting

This commit is contained in:
mukul975
2026-03-11 00:48:56 +01:00
parent 85fce5551d
commit ff3a9ce224
16 changed files with 1371 additions and 0 deletions
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Mahipal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,43 @@
---
name: hunting-for-cobalt-strike-beacons
description: Detect Cobalt Strike beacon network activity using default TLS certificate signatures (serial 8BB00EE), JA3/JA3S/JARM fingerprints, HTTP C2 profile pattern matching, beacon jitter analysis, and named pipe detection via Zeek, Suricata, and Python PCAP analysis.
domain: cybersecurity
subdomain: threat-hunting
tags: [cobalt-strike, beacon, threat-hunting, c2, zeek, suricata, ja3, jarm, network-forensics]
version: "1.0"
author: mahipal
license: Apache-2.0
---
# Hunting for Cobalt Strike Beacons
## Overview
Cobalt Strike is the most prevalent command-and-control framework used by both red teams and threat actors. Beacon, its primary payload, communicates with team servers using configurable HTTP/HTTPS/DNS profiles that can mimic legitimate traffic. However, default configurations and behavioral patterns remain detectable through TLS certificate analysis (default serial 8BB00EE), JA3/JA3S fingerprinting, beacon interval jitter analysis, and HTTP malleable profile pattern matching. This skill covers building detection capabilities using Zeek network logs, Suricata IDS rules, and Python-based PCAP analysis to identify beacon callbacks in network traffic.
## Prerequisites
- Zeek 6.0+ with JA3 and HASSH packages installed
- Suricata 7.0+ with Emerging Threats ruleset
- Python 3.9+ with scapy and dpkt libraries
- Network traffic captures (PCAP) or live Zeek logs
- RITA (Real Intelligence Threat Analytics) for beacon scoring
- Threat intelligence feeds with known Cobalt Strike IOCs
## Steps
### Step 1: TLS Certificate Analysis
Detect default Cobalt Strike certificates using JA3S fingerprints, certificate serial numbers, and JARM fingerprints in Zeek ssl.log.
### Step 2: Beacon Interval Analysis
Analyze connection timing patterns to identify regular callback intervals with configurable jitter, characteristic of beacon behavior.
### Step 3: HTTP Profile Detection
Match HTTP request patterns (URI paths, headers, user-agents) against known malleable C2 profiles.
### Step 4: Correlate and Score
Combine multiple indicators (TLS + timing + HTTP profile) into a composite beacon confidence score.
## Expected Output
JSON report containing detected beacon candidates with confidence scores, TLS fingerprints, timing analysis, HTTP profile matches, and recommended response actions.
@@ -0,0 +1,139 @@
# API Reference: Hunting for Cobalt Strike Beacons
## Cobalt Strike Default TLS Indicators
| Indicator | Value | Detection Confidence |
|-----------|-------|---------------------|
| Default cert serial | `8BB00EE` | 95% (unmodified teamserver) |
| Default cert issuer | `Major Cobalt Strike` | 95% |
| JA3S hash (Java TLS) | `ae4edc6faf64d08308082ad26be60767` | 80% |
| JA3S hash (alt) | `a0e9f5d64349fb13191bc781f81f42e1` | 80% |
| JARM fingerprint | `07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1` | 90% |
## Zeek Log Fields for Detection
### ssl.log Key Fields
| Field Index | Name | Use |
|-------------|------|-----|
| 0 | ts | Connection timestamp |
| 2 | id.orig_h | Source IP |
| 4 | id.resp_h | Destination IP (C2 server) |
| 5 | id.resp_p | Destination port |
| 20 | cert_chain_fps | Certificate serial number |
| 21 | ja3s | JA3S server fingerprint hash |
### conn.log Beacon Timing Fields
| Field Index | Name | Use |
|-------------|------|-----|
| 0 | ts | Connection epoch timestamp |
| 2 | id.orig_h | Beaconing host |
| 4 | id.resp_h | C2 destination |
| 5 | id.resp_p | C2 port |
| 8 | duration | Session length |
| 9 | orig_bytes | Bytes sent (check size) |
| 10 | resp_bytes | Bytes received (check size) |
## RITA Beacon Analysis
```bash
# Import Zeek logs into RITA
rita import /opt/zeek/logs/current rita_dataset
# Show beaconing connections ranked by score
rita show-beacons rita_dataset --human-readable
# Show long connections (persistent C2)
rita show-long-connections rita_dataset
# Export beacon results as CSV
rita show-beacons rita_dataset -H > beacons.csv
# Show DNS tunneling (alternate C2 channel)
rita show-exploded-dns rita_dataset
```
## Suricata Detection Rules
```yaml
# Detect default Cobalt Strike TLS certificate
alert tls any any -> any any (msg:"ET MALWARE Cobalt Strike Default Certificate"; \
tls.cert_serial; content:"8BB00EE"; sid:2029560; rev:3;)
# Detect known Cobalt Strike JA3S
alert tls any any -> any any (msg:"ET MALWARE Cobalt Strike JA3S"; \
ja3s.hash; content:"ae4edc6faf64d08308082ad26be60767"; sid:2029561; rev:2;)
# Detect Cobalt Strike default HTTP beacon URI
alert http any any -> any any (msg:"ET MALWARE CobaltStrike Beacon URI"; \
content:"GET"; http_method; pcre:"/^\/[a-zA-Z]{4}$/U"; sid:2029562; rev:1;)
# Detect Cobalt Strike named pipe (SMB beacon)
alert smb any any -> any any (msg:"ET MALWARE CobaltStrike Named Pipe"; \
content:"|MSRPC|"; content:"\\\\pipe\\\\"; content:"MSSE-"; sid:2029563; rev:1;)
```
## Malleable C2 Profile HTTP Indicators
| Pattern | URI Regex | Context |
|---------|-----------|---------|
| Default GET | `^/[a-zA-Z]{4}$` | 4-char alpha URI (e.g., /aGth) |
| submit.php | `^/submit\.php\?id=\d+$` | POST callback with numeric ID |
| Pixel tracking | `^/pixel\.(gif\|png)$` | Fake tracking pixel |
| UTM beacon | `^/__utm\.gif$` | Mimics Google Analytics |
| RSS feed | `^/updates\.(rss\|json)$` | Fake feed endpoint |
| JS beacon | `^/visit\.js$` | Fake JavaScript resource |
## Default User-Agent Strings
```
Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)
Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
```
## Beacon Timing Analysis Formula
```python
# Jitter percentage calculation
intervals = [t[i+1] - t[i] for i in range(len(t) - 1)]
avg = sum(intervals) / len(intervals)
std = sqrt(sum((x - avg)**2 for x in intervals) / len(intervals))
jitter_pct = (std / avg) * 100
# Beacon score (0-100, higher = more likely beacon)
beacon_score = max(0, 1 - (jitter_pct / 100)) * 100
# Score >= 85 = critical, >= 60 = high suspicion
```
## JARM Scanner CLI
```bash
# Scan single host for JARM fingerprint
python3 jarm.py -p 443 suspicious-host.example.com
# Known Cobalt Strike JARM
# 07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1
# Compare against threat intel JARM database
python3 jarm.py -p 8443 10.0.0.50 | grep -f cs_jarm_list.txt
```
## MITRE ATT&CK Mapping
| Technique | ID | Beacon Indicator |
|-----------|----|-----------------|
| Application Layer Protocol | T1071.001 | HTTP/HTTPS beaconing pattern |
| Encrypted Channel | T1573.002 | Default TLS cert / JA3S match |
| Non-Standard Port | T1571 | HTTPS on 8080, 8443, 444 |
| Ingress Tool Transfer | T1105 | Large resp_bytes in beacon |
| Proxy | T1090 | Redirector infrastructure |
### References
- JARM Scanner: https://github.com/salesforce/jarm
- RITA: https://github.com/activecm/rita
- JA3/JA3S: https://github.com/salesforce/ja3
- Cobalt Strike Detection: https://thedfirreport.com
- MITRE T1071.001: https://attack.mitre.org/techniques/T1071/001/
@@ -0,0 +1,190 @@
#!/usr/bin/env python3
"""Cobalt Strike Beacon Hunter - detects beacon signatures in network traffic and Zeek logs."""
import json
import argparse
import logging
import os
import re
import math
from collections import defaultdict
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
CS_DEFAULT_CERT_SERIAL = "8bb00ee"
CS_KNOWN_JA3S = [
"ae4edc6faf64d08308082ad26be60767",
"a0e9f5d64349fb13191bc781f81f42e1",
]
CS_KNOWN_JARM = "07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1"
def parse_zeek_ssl_log(ssl_log_path):
"""Parse Zeek ssl.log for default Cobalt Strike certificates."""
findings = []
try:
with open(ssl_log_path) as f:
for line in f:
if line.startswith("#"):
continue
fields = line.strip().split("\t")
if len(fields) < 22:
continue
ts, uid, src_ip, src_port, dst_ip, dst_port = fields[0], fields[1], fields[2], fields[3], fields[4], fields[5]
serial = fields[20] if len(fields) > 20 else ""
ja3s = fields[21] if len(fields) > 21 else ""
if serial.lower().replace(":", "") == CS_DEFAULT_CERT_SERIAL:
findings.append({
"indicator": "default_cs_certificate",
"src_ip": src_ip, "dst_ip": dst_ip, "dst_port": dst_port,
"cert_serial": serial, "timestamp": ts,
"severity": "critical", "confidence": 95,
})
if ja3s in CS_KNOWN_JA3S:
findings.append({
"indicator": "known_cs_ja3s",
"src_ip": src_ip, "dst_ip": dst_ip, "dst_port": dst_port,
"ja3s_hash": ja3s, "timestamp": ts,
"severity": "high", "confidence": 80,
})
except FileNotFoundError:
logger.warning("Zeek ssl.log not found: %s", ssl_log_path)
return findings
def analyze_beacon_timing(conn_log_path, min_connections=20, max_jitter_pct=25):
"""Analyze connection timing for beacon-like regular intervals."""
connections = defaultdict(list)
try:
with open(conn_log_path) as f:
for line in f:
if line.startswith("#"):
continue
fields = line.strip().split("\t")
if len(fields) < 7:
continue
ts = float(fields[0])
src_ip, dst_ip, dst_port = fields[2], fields[4], fields[5]
key = (src_ip, dst_ip, dst_port)
connections[key].append(ts)
except (FileNotFoundError, ValueError):
logger.warning("Zeek conn.log parse failed: %s", conn_log_path)
return []
beacons = []
for (src, dst, port), timestamps in connections.items():
if len(timestamps) < min_connections:
continue
timestamps.sort()
intervals = [timestamps[i + 1] - timestamps[i] for i in range(len(timestamps) - 1)]
if not intervals:
continue
avg_interval = sum(intervals) / len(intervals)
if avg_interval < 1:
continue
std_interval = math.sqrt(sum((x - avg_interval) ** 2 for x in intervals) / len(intervals))
jitter_pct = (std_interval / avg_interval) * 100 if avg_interval > 0 else 100
if jitter_pct <= max_jitter_pct:
beacon_score = round(max(0, 1 - (jitter_pct / 100)) * 100, 1)
if beacon_score >= 60:
beacons.append({
"indicator": "beacon_timing",
"src_ip": src, "dst_ip": dst, "dst_port": port,
"connections": len(timestamps),
"avg_interval_sec": round(avg_interval, 1),
"jitter_pct": round(jitter_pct, 1),
"beacon_score": beacon_score,
"severity": "critical" if beacon_score >= 85 else "high",
"confidence": int(beacon_score),
})
return sorted(beacons, key=lambda x: x["beacon_score"], reverse=True)
def check_http_profiles(http_log_path):
"""Detect known Cobalt Strike HTTP malleable C2 profile patterns."""
cs_uri_patterns = [
r"^/[a-zA-Z]{4}$", r"^/submit\.php\?id=\d+$", r"^/pixel\.(gif|png)$",
r"^/__utm\.gif$", r"^/updates\.(rss|json)$", r"^/visit\.js$",
]
cs_ua_patterns = [
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)",
]
findings = []
try:
with open(http_log_path) as f:
for line in f:
if line.startswith("#"):
continue
fields = line.strip().split("\t")
if len(fields) < 12:
continue
src_ip, dst_ip = fields[2], fields[4]
uri = fields[9] if len(fields) > 9 else ""
user_agent = fields[12] if len(fields) > 12 else ""
for pattern in cs_uri_patterns:
if re.match(pattern, uri):
findings.append({
"indicator": "cs_http_profile",
"src_ip": src_ip, "dst_ip": dst_ip,
"uri": uri, "user_agent": user_agent[:100],
"matched_pattern": pattern,
"severity": "high", "confidence": 60,
})
break
if user_agent in cs_ua_patterns:
findings.append({
"indicator": "cs_default_user_agent",
"src_ip": src_ip, "dst_ip": dst_ip,
"user_agent": user_agent,
"severity": "high", "confidence": 70,
})
except FileNotFoundError:
logger.warning("Zeek http.log not found: %s", http_log_path)
return findings
def generate_report(tls_findings, beacon_findings, http_findings):
all_findings = tls_findings + beacon_findings + http_findings
critical = sum(1 for f in all_findings if f.get("severity") == "critical")
by_dst = defaultdict(int)
for f in all_findings:
by_dst[f.get("dst_ip", "")] += 1
return {
"timestamp": datetime.utcnow().isoformat(),
"tls_certificate_hits": len(tls_findings),
"beacon_timing_detections": len(beacon_findings),
"http_profile_matches": len(http_findings),
"total_indicators": len(all_findings),
"critical_indicators": critical,
"top_suspect_destinations": dict(sorted(by_dst.items(), key=lambda x: x[1], reverse=True)[:10]),
"findings": all_findings[:30],
"cobalt_strike_likely": critical > 0,
}
def main():
parser = argparse.ArgumentParser(description="Cobalt Strike Beacon Hunting Agent")
parser.add_argument("--zeek-dir", required=True, help="Directory containing Zeek log files")
parser.add_argument("--min-connections", type=int, default=20, help="Minimum connections for beacon analysis")
parser.add_argument("--max-jitter", type=int, default=25, help="Maximum jitter percentage for beacon scoring")
parser.add_argument("--output", default="cobalt_strike_hunt_report.json")
args = parser.parse_args()
ssl_log = os.path.join(args.zeek_dir, "ssl.log")
conn_log = os.path.join(args.zeek_dir, "conn.log")
http_log = os.path.join(args.zeek_dir, "http.log")
tls_findings = parse_zeek_ssl_log(ssl_log)
beacon_findings = analyze_beacon_timing(conn_log, args.min_connections, args.max_jitter)
http_findings = check_http_profiles(http_log)
report = generate_report(tls_findings, beacon_findings, http_findings)
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("CS Hunt: %d TLS hits, %d beacons, %d HTTP matches, CS likely: %s",
len(tls_findings), len(beacon_findings), len(http_findings), report["cobalt_strike_likely"])
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()