Build a Network Threat Detector
Capture live traffic and flag threats with MITRE ATT&CK mappings.
Introduction
30 Second Summary
Your computer is constantly talking to the internet, sending hundreds of network packets every minute. Some of those packets could be signs of an attack, but only if you know what to look for.
In this project, you will build a network threat detector with Python and Scapy that captures live traffic on your Windows machine, analyzes packets against custom detection rules, and maps each finding to a MITRE ATT&CK technique. The results display in a Streamlit dashboard that looks like a miniature SOC analyst workstation.
What You'll Build
Run your detector, browse a website, and watch detection alerts fire in real time as your custom rules catch port scans and suspicious DNS queries flowing through your own machine.
By the end of this project, you'll have:
- A live packet sniffer that shows your own DNS queries, TCP handshakes, and ARP messages flowing through your machine in real time.
- Detection rules that flag port scans, suspicious DNS queries, and connections to known-bad ports, each mapped to a MITRE ATT&CK technique ID. A test traffic generator lets you simulate attacks and verify every rule fires.
- A professional SOC dashboard with severity metric cards, a scrollable alert feed, and ATT&CK coverage bar charts that update on demand.
- Secret Mission: Add entropy-based DNS tunneling detection to catch covert command-and-control channels.
Are there any prerequisites?
This project assumes intermediate Python experience. You should be comfortable writing scripts, using pip, and working with JSON.
Before We Start
You have experience building offensive security tools like keyloggers and reverse shells. Before diving into code, let's lock in what you're about to build and why a defensive detection project belongs alongside those skills.
Set Up Your Detection Lab
Your threat detector needs to read raw network packets flowing through your machine. On Windows, applications cannot access raw packet data without a special driver sitting between the operating system's network stack and your Python code.
In this step, you will install Npcap (the packet capture driver), set up a Python project with Scapy and Streamlit, and verify everything connects properly.
In this step, get ready to:
- Install Npcap, the Windows packet capture driver.
- Create a Python project with Scapy and Streamlit.
- Verify Scapy can access your network interfaces.
Install Npcap
Npcap is a packet capture driver for Windows. It gives Scapy permission to see raw network packets that normally only the operating system can access. Without it, your detector is blind.
- Open your browser and navigate to npcap.com.
- Download the Npcap 1.88 installer.
- Run the downloaded installer.
- When the installer shows its options, uncheck Install Npcap in WinPcap API-compatible Mode. Leave all other options at their defaults.
- Click Install and wait for the installation to complete.
- Click Finish to close the installer.
What is Npcap?
Npcap sits between your operating system's network stack and your Python code. It intercepts copies of every packet flowing through your network interfaces and hands them to Scapy for analysis.
Npcap is free for personal use on up to 5 systems. It is the modern replacement for WinPcap, which has been unmaintained since 2013.
Installer blocked by Windows?
If Windows shows a security warning when you run the installer, allow it to proceed. Npcap drivers are tested and co-signed by Microsoft.
If you have an existing WinPcap installation, the Npcap installer will remove it automatically.
Help me troubleshoot my Npcap installation on Windows.
Create the project and install Python libraries
You need a dedicated project folder and a virtual environment to keep your detector's dependencies separate from any other Python projects on your machine.
- Press the Windows key to open your search bar.
- Type cmd and press Enter to open Command Prompt.
- Move to your Desktop and create the project folder by running these commands:
cd %USERPROFILE%\Desktop
mkdir threat-detector
cd threat-detector
Your Command Prompt now shows the path to threat-detector in the prompt line.
- Create a Python virtual environment and activate it by running these commands:
python -m venv venv
venv\Scripts\activate
Your prompt now starts with (venv), confirming the virtual environment is active.
What does (venv) mean?
The (venv) prefix means Python is now using the isolated environment inside your threat-detector folder. Any packages you install with pip go here instead of your system-wide Python installation.
- Install Scapy and Streamlit by running:
pip install scapy==2.7.0 streamlit==1.62.0
When the install finishes, pip lists all the packages it installed along with their dependencies.
Seeing a pip install error?
Make sure your prompt shows (venv) before running the install command. If it does not, run venv\Scripts\activate first.
If you see a network timeout, check your internet connection and try the command again.
Help me fix my pip install for Scapy and Streamlit.
Verify Scapy sees your network
Scapy requires administrator privileges on Windows to access raw packet capture through Npcap. You need to open a new Command Prompt with elevated permissions.
Why does Scapy need admin privileges?
Reading raw network packets means intercepting data at the driver level. Windows restricts this to administrator accounts to prevent unauthorized programs from snooping on your traffic. Your detector is authorized, but you need to prove it by running as admin.
- Press the Windows key to open your search bar.
- Type cmd but do not press Enter yet.
- Right-click Command Prompt in the search results and select Run as administrator.
- If Windows asks for confirmation, click Yes.
- Navigate to your project folder and activate the virtual environment by running:
cd %USERPROFILE%\Desktop\threat-detector
venv\Scripts\activate
Your prompt should show (venv) and the threat-detector path, just like before.
Before you run the next command, what do you think it will print if Npcap is correctly installed?
- Test that Scapy can use Npcap by running:
python -c "from scapy.all import conf; print(conf.use_pcap)"
It prints True. This confirms Scapy found Npcap and can use it for packet capture.
- List your available network interfaces by running:
python -c "from scapy.all import get_if_list; print(get_if_list())"
You should see a Python list of your network interface names. The exact names depend on your machine, but you will see entries for your Wi-Fi adapter, Ethernet adapter, and the Npcap loopback interface.
Seeing False or an import error?
If conf.use_pcap prints False, Npcap is not installed correctly. Rerun the Npcap installer and make sure it completes without errors.
If you see ModuleNotFoundError, the virtual environment is not active. Run venv\Scripts\activate and try again.
If you see a permissions error, make sure you opened Command Prompt with Run as administrator.
Help me troubleshoot my Scapy and Npcap setup on Windows.
Your detection lab is ready. Next up, you will capture your first live network packets and see what raw traffic looks like flowing through your machine.
Capture Your First Packets
Your detection lab is ready. Npcap is installed, Scapy loads successfully, and your virtual environment is activated.
Before you can write rules that detect threats, you need to understand what raw network data actually looks like. In this step, you will build a basic sniffer that captures your live traffic and prints every packet to the terminal.
In this step, get ready to:
- Create a Python packet sniffer that captures live network traffic.
- Run the sniffer and observe DNS queries, TCP connections, and ARP messages flowing through your machine.
Create the basic packet sniffer
Scapy's sniff() function listens on your network interface and hands every captured packet to a callback function you define. Your sniffer will use this callback to print a one-line summary of each packet.
- Press the Windows key to open your search bar.
- Type VS Code and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Select the threat-detector folder on your Desktop, then click Select Folder.
- Create a new file by pressing Ctrl+N.
- Save it immediately by pressing Ctrl+S and typing detector.py as the file name.
- Add the following code to detector.py:
from scapy.all import sniff
# Callback function that runs for each captured packet
def process_packet(packet):
# Print a one-line summary of the packet
print(packet.summary())
# Start continuous capture without storing packets in memory
sniff(prn=process_packet, store=False)
What does this code do?
- process_packet is a callback function that Scapy calls for every single packet it captures from your network interface.
- packet.summary() returns a one-line text description showing the protocol layers and addresses in that packet.
- store=False tells Scapy not to keep packets in memory. Without this, the sniffer would run out of RAM during a long capture session.
- Save detector.py by pressing Ctrl+S.
- In the Explorer sidebar on the left, confirm that detector.py now appears under the threat-detector folder.
Don't see detector.py in the sidebar?
- Make sure you saved the file with the exact name detector.py (not detector.txt or Detector.py).
- Check that the threat-detector folder is the one open in VS Code. You should see the venv folder already listed in the sidebar.
- Still having trouble? Help me create detector.py in the correct folder.
✔️ Awesome, I've got everything!
Great. Make sure you have saved detector.py and you are ready to run it.
ⓧ I'd like to double check the full code
Here is the complete detector.py file. Compare it against yours and fix any differences.
from scapy.all import sniff
def process_packet(packet):
print(packet.summary())
sniff(prn=process_packet, store=False)
Watch your own traffic
Your sniffer is written. Time to point it at your live network and see what is actually flowing through your machine.
- Return to your admin Command Prompt from the previous step.
- Confirm you are inside the threat-detector folder and the virtual environment is activated (you should see (venv) at the start of your prompt).
Before you run the sniffer, what do you think you will see? Will your machine be sending a lot of traffic, or barely anything?
- Start the sniffer by running this command:
python detector.py
The terminal looks quiet at first. Scapy is now listening on your network interface, waiting for packets.
- Open your web browser and visit any website (for example, https://example.com).
- Switch back to your Command Prompt.
Packet summaries are now scrolling through the terminal. You should see lines mentioning DNS queries (your browser looking up website addresses), TCP connections (the actual data transfers), and ARP messages (devices on your local network announcing themselves).
What are all these packets?
Even loading a single web page generates dozens of packets. Your browser sends DNS queries to look up the domain name, opens TCP connections to the server, transfers data over those connections, and your network adapter broadcasts ARP replies in the background.
This is the core problem: hundreds of packets scroll by with no way to tell what is normal and what might be suspicious. A real attacker's port scan or C2 beacon would be buried in this flood of legitimate traffic.
- Press Ctrl+C to stop the sniffer.
Not seeing any packet output?
- Make sure you ran the Command Prompt as Administrator. Right-click on Command Prompt and select Run as administrator. Scapy requires admin privileges to capture packets on Windows.
- Confirm Npcap is installed by checking Settings > Apps > Installed Apps for "Npcap". If it is missing, download and install it from npcap.com.
- Check that your virtual environment is activated. You should see (venv) at the start of your prompt. If not, run venv\Scripts\activate.
- Still stuck? Help me troubleshoot why my packet sniffer is not capturing any traffic.
You have seen your own network traffic in raw form. The problem is clear: there is way too much data to read manually. In the next step, you will build detection rules that filter this flood and flag only the packets that match known attack patterns.
Build Threat Detection Rules
In the previous step, you saw your own network traffic scrolling through the terminal. DNS queries, TCP handshakes, ARP messages all flew past at hundreds of packets per minute.
That flood of raw data is exactly what makes network security hard. You need rules that automatically analyze each packet against known attack patterns. In this step, you will build four detection rules that flag suspicious activity and map each finding to a MITRE ATT&CK technique ID.
In this step, get ready to:
- Write detection rules that flag port scans, suspicious DNS queries, connections to known-bad ports, and ARP anomalies.
- Create a test traffic generator that simulates attacks so you can verify your rules fire correctly.
Replace the print callback with detection rules
The current detector.py just prints every packet's summary. You will replace the entire file with a detection engine that checks each packet against four rules and logs structured alerts to a JSON file.
- Open detector.py in VS Code.
- Select all the existing code and delete it.
- Paste the following alert infrastructure code into the now-empty file:
import json
import os
from datetime import datetime
from collections import defaultdict
from scapy.all import sniff, IP, TCP, UDP, DNS, ARP
# File where alerts get saved for the dashboard
ALERTS_FILE = "alerts.json"
# Track connections per source IP to detect port scans
connection_tracker = defaultdict(list)
def load_alerts():
"""Read existing alerts from the JSON file."""
if os.path.exists(ALERTS_FILE):
with open(ALERTS_FILE, "r") as f:
return json.load(f)
return []
def save_alert(alert):
"""Append a new alert to the JSON file and print it."""
alerts = load_alerts()
alerts.append(alert)
with open(ALERTS_FILE, "w") as f:
json.dump(alerts, f, indent=2)
severity = alert["severity"]
rule = alert["rule"]
technique = alert["technique"]
detail = alert["detail"]
print(f" [{severity}] {rule} | {technique} | {detail}")
What does this code do?
- The imports pull in specific Scapy layers (IP, TCP, DNS, ARP) so each detection rule can inspect the right part of a packet.
- connection_tracker is a dictionary that records recent connection attempts per source IP. The port scan rule uses it to count how many unique ports a single IP probes.
- save_alert() writes each alert to alerts.json so the dashboard can read them later, and prints a formatted line to the terminal so you see detections in real time.
- Add the port scan detection rule below save_alert by pasting this code at the bottom of the file:
def detect_port_scan(packet):
# Only look at TCP SYN packets (connection attempts)
if not packet.haslayer(IP) or not packet.haslayer(TCP):
return None
if packet[TCP].flags != "S":
return None
# Record this connection attempt from the source IP
src = packet[IP].src
now = datetime.now().timestamp()
connection_tracker[src].append({"port": packet[TCP].dport, "time": now})
# Keep only connections from the last 10 seconds
recent = [c for c in connection_tracker[src] if now - c["time"] < 10]
connection_tracker[src] = recent
unique_ports = len(set(c["port"] for c in recent))
# Flag if more than 5 unique ports probed in 10 seconds
if unique_ports > 5:
connection_tracker[src] = []
return {
"timestamp": datetime.now().isoformat(),
"rule": "Port Scan Detected",
"technique": "T1046",
"technique_name": "Network Service Discovery",
"tactic": "Discovery",
"severity": "High",
"src_ip": src,
"dst_ip": packet[IP].dst,
"detail": f"{unique_ports} unique ports probed in 10s from {src}",
}
return None
How does port scan detection work?
A port scan sends connection attempts (SYN packets) to many ports on a target, looking for open services. This rule tracks how many unique ports each source IP touches within a 10-second window.
If the count exceeds 5, the rule fires a High severity alert mapped to T1046 (Network Service Discovery) in the MITRE ATT&CK framework. Normal browsing typically hits only ports 80 and 443, so 5+ unique ports in 10 seconds is a strong signal.
- Save detector.py.
- Verify the code loads without errors by running this in your admin Command Prompt:
python -c "from detector import detect_port_scan; print('Port scan rule loaded')"
You should see Port scan rule loaded printed with no errors.
Seeing a SyntaxError or ImportError?
- Check that you deleted all of the old code before pasting. Leftover lines from the previous version can cause syntax errors.
- Make sure you saved the file in VS Code before running the command.
- Confirm your virtual environment is active. You should see (venv) at the start of your Command Prompt line.
Why won't my detector code load?
Now add the remaining three detection rules. Each follows the same pattern: check the packet for a specific layer, look for a suspicious pattern, and return a structured alert dictionary if the pattern matches.
- Add the suspicious DNS query detection rule below detect_port_scan by pasting this code at the bottom of the file:
def detect_suspicious_dns(packet):
# Only look at DNS query packets
if not packet.haslayer(DNS):
return None
if not packet.haslayer(IP):
return None
if packet[DNS].qr != 0:
return None
if packet[DNS].qd is None:
return None
query_name = packet[DNS].qd.qname.decode(errors="ignore")
# Flag queries longer than 50 characters
if len(query_name) > 50:
return {
"timestamp": datetime.now().isoformat(),
"rule": "Suspicious Long DNS Query",
"technique": "T1071.004",
"technique_name": "Application Layer Protocol: DNS",
"tactic": "Command and Control",
"severity": "Medium",
"src_ip": packet[IP].src,
"dst_ip": packet[IP].dst,
"detail": f"Long query ({len(query_name)} chars): {query_name[:60]}",
}
return None
Why flag long DNS queries?
Attackers use DNS tunneling to exfiltrate data or communicate with command-and-control servers by encoding payloads into DNS query names. These encoded queries are much longer than normal ones. Legitimate queries are typically under 40 characters, so anything over 50 is worth investigating. This maps to T1071.004 (Application Layer Protocol: DNS).
- Add the suspicious port detection rule below detect_suspicious_dns by pasting this code at the bottom of the file:
def detect_unusual_port(packet):
# Only look at TCP SYN packets
if not packet.haslayer(IP) or not packet.haslayer(TCP):
return None
if packet[TCP].flags != "S":
return None
# Ports commonly used by attack tools and backdoors
suspicious_ports = [4444, 5555, 1234, 31337, 8443, 9001]
dst_port = packet[TCP].dport
if dst_port in suspicious_ports:
return {
"timestamp": datetime.now().isoformat(),
"rule": "Connection to Suspicious Port",
"technique": "T1071.001",
"technique_name": "Application Layer Protocol: Web Protocols",
"tactic": "Command and Control",
"severity": "Critical",
"src_ip": packet[IP].src,
"dst_ip": packet[IP].dst,
"detail": f"Outbound SYN to port {dst_port} on {packet[IP].dst}",
}
return None
What makes these ports suspicious?
Port 4444 is the default for Metasploit reverse shells. Port 31337 ("eleet") is a classic backdoor port. Port 9001 is the default for Tor. Any outbound SYN to these ports earns a Critical severity alert mapped to T1071.001.
- Save detector.py.
- Verify all three rules load correctly by running:
python -c "from detector import detect_port_scan, detect_suspicious_dns, detect_unusual_port; print('3 rules loaded')"
You should see 3 rules loaded with no errors.
Seeing an IndentationError?
- Make sure each function starts at the leftmost column (no leading spaces on the def line). Pasting can sometimes add extra indentation.
- Check that there is a blank line between the end of one function and the start of the next. Missing or extra lines can confuse Python's parser.
Help me fix my indentation in detector.py.
- Add the ARP monitoring rule and the wiring that connects all four rules to the packet sniffer. Paste this below detect_unusual_port:
def detect_arp_spoof(packet):
# Only look at ARP reply packets
if not packet.haslayer(ARP):
return None
if packet[ARP].op != 2:
return None
return {
"timestamp": datetime.now().isoformat(),
"rule": "ARP Reply Detected",
"technique": "T1557",
"technique_name": "Adversary-in-the-Middle",
"tactic": "Credential Access",
"severity": "Low",
"src_ip": packet[ARP].psrc,
"dst_ip": packet[ARP].pdst,
"detail": f"ARP reply: {packet[ARP].psrc} is at {packet[ARP].hwsrc}",
}
# Run every packet through each detection rule
RULES = [detect_port_scan, detect_suspicious_dns, detect_unusual_port, detect_arp_spoof]
def process_packet(packet):
for rule in RULES:
alert = rule(packet)
if alert:
save_alert(alert)
What does this code do?
- detect_arp_spoof watches for ARP reply packets. ARP spoofing (mapped to T1557) is how attackers intercept traffic on a local network. This rule is set to Low severity because ARP replies are normal. It logs them as baseline indicators.
- RULES is a list of all four detection functions. process_packet loops through this list for every captured packet, calling each rule and saving any alert it returns.
- Add the main function and entry point at the very bottom of the file:
def main():
print("=" * 60)
print(" Network Threat Detector")
print(" Monitoring live traffic... Press Ctrl+C to stop.")
print("=" * 60)
print()
# Clear old alerts on startup
if os.path.exists(ALERTS_FILE):
os.remove(ALERTS_FILE)
sniff(prn=process_packet, store=False)
if __name__ == "__main__":
main()
What does the main function do?
- main() prints a banner so you know the detector started, then deletes any leftover alerts.json from a previous run so you start with a clean slate.
- The if __name__ == "__main__" guard means main() only runs when you execute the file directly. Importing the module (like you did earlier) does not start the sniffer.
- Save detector.py.
- Test the full detector by running it in your admin Command Prompt:
python detector.py
You should see the banner:
============================================================
Network Threat Detector
Monitoring live traffic... Press Ctrl+C to stop.
============================================================
The detector is now listening. You may see some Low severity ARP alerts scroll by from normal network activity. That is expected.
- Press Ctrl+C to stop the detector for now. You will restart it after creating the test traffic generator.
Detector exits immediately or shows a permission error?
- Scapy requires administrator privileges to capture packets. Make sure you are running the Command Prompt as Administrator (right-click the Command Prompt icon and select Run as administrator).
- Confirm Npcap is installed by checking Settings > Apps for "Npcap" in your installed programs list.
Why can't my detector capture packets?
✔️ Awesome, I've got everything!
Great. Make sure you have saved detector.py before moving on.
ⓧ I'd like to double check the full code
Your detector.py is over 30 lines, so scroll up and confirm each section is present in this order:
- Imports and constants (ALERTS_FILE, connection_tracker).
- load_alerts() and save_alert() functions.
- detect_port_scan() function.
- detect_suspicious_dns() function.
- detect_unusual_port() function.
- detect_arp_spoof() function.
- RULES list and process_packet() function.
- main() function and if __name__ == "__main__" guard at the bottom.
What is MITRE ATT&CK?
MITRE ATT&CK is a publicly available knowledge base of adversary tactics and techniques based on real-world observations. Each technique has an ID (like T1046) that security teams use to categorize threats.
Mapping your detections to ATT&CK technique IDs is how professional SOC teams communicate about threats. It turns vague alerts into precise, actionable intelligence.
Create a test traffic generator
Your detection rules are ready, but you cannot sit around waiting for real attacks to test them. You need a way to send known-bad packets on demand so you can confirm each rule fires correctly.
- Create a new file called test_traffic.py in your threat-detector folder.
- Paste the following test traffic generator code into test_traffic.py:
import time
from scapy.all import IP, TCP, send
print("=" * 60)
print(" Test Traffic Generator")
print(" Make sure detector.py is running in another terminal.")
print("=" * 60)
print()
# Test 1: Port scan simulation (T1046)
print("[Test 1] Simulating port scan on localhost...")
for port in [22, 80, 443, 8080, 3306, 5432, 6379, 27017]:
send(IP(dst="127.0.0.1") / TCP(dport=port, flags="S"), verbose=False)
print(" Sent SYN packets to 8 ports on 127.0.0.1")
time.sleep(2)
# Test 2: Suspicious port connection (T1071.001)
print("[Test 2] Simulating connection to suspicious port 4444...")
send(IP(dst="127.0.0.1") / TCP(dport=4444, flags="S"), verbose=False)
print(" Sent SYN to port 4444 (common reverse shell port)")
time.sleep(1)
# Test 3: Another suspicious port (T1071.001)
print("[Test 3] Simulating connection to suspicious port 31337...")
send(IP(dst="127.0.0.1") / TCP(dport=31337, flags="S"), verbose=False)
print(" Sent SYN to port 31337 (classic backdoor port)")
print()
print("Done! Check detector.py output for alerts.")
What does this test script do?
- Test 1 sends SYN packets to 8 different ports on 127.0.0.1 (your own machine). This exceeds the 5-port threshold and triggers the port scan detection rule (T1046).
- Tests 2 and 3 send SYN packets to ports 4444 and 31337. Both are in the suspicious ports list, so each triggers a Critical alert (T1071.001).
- The time.sleep() pauses give the detector time to process each batch before the next arrives.
Why test on 127.0.0.1?
Npcap supports loopback traffic capture and injection. Sending packets to 127.0.0.1 means the test traffic never leaves your machine. The packets travel through the loopback interface, where the detector picks them up just like real network traffic.
- Save test_traffic.py.
Now run both processes together. You need two admin Command Prompt windows for this.
- In your existing admin Command Prompt, make sure your venv is active and start the detector by running:
python detector.py
- Open a second Command Prompt as Administrator.
- Navigate to your project folder and activate the virtual environment in this second terminal by running:
cd %USERPROFILE%\Desktop\threat-detector
venv\Scripts\activate
Before you run the test script, predict: how many alerts do you expect to see in the detector terminal? Which severity levels?
- Run the test traffic generator in the second terminal:
python test_traffic.py
- Switch back to your first terminal (where detector.py is running) and check the output.
You should see alerts with severity levels and ATT&CK technique IDs. A High severity port scan alert tagged T1046 and two Critical suspicious port alerts tagged T1071.001 should appear. You may also see some Low severity ARP alerts from normal network activity.
No alerts showing in the detector terminal?
- Make sure both terminals are running as Administrator. The test traffic generator also needs admin privileges to send raw packets.
- Confirm that detector.py was already running before you ran test_traffic.py. The detector must be listening first to catch the packets.
- Check that both terminals have the virtual environment activated. You should see (venv) at the start of each prompt.
Why am I not seeing alerts when I run the test traffic?
- Press Ctrl+C in the detector terminal to stop it.
- Confirm alerts.json was created in your threat-detector folder by running:
dir alerts.json
You should see alerts.json listed with a file size greater than zero. This file is what the dashboard will read in the next step.
✔️ Awesome, I've got everything!
Great. Double check that you have saved both detector.py and test_traffic.py.
ⓧ I'd like to double check test_traffic.py
Here is the complete test_traffic.py for comparison:
import time
from scapy.all import IP, TCP, send
print("=" * 60)
print(" Test Traffic Generator")
print(" Make sure detector.py is running in another terminal.")
print("=" * 60)
print()
# Test 1: Port scan simulation (T1046)
print("[Test 1] Simulating port scan on localhost...")
for port in [22, 80, 443, 8080, 3306, 5432, 6379, 27017]:
send(IP(dst="127.0.0.1") / TCP(dport=port, flags="S"), verbose=False)
print(" Sent SYN packets to 8 ports on 127.0.0.1")
time.sleep(2)
# Test 2: Suspicious port connection (T1071.001)
print("[Test 2] Simulating connection to suspicious port 4444...")
send(IP(dst="127.0.0.1") / TCP(dport=4444, flags="S"), verbose=False)
print(" Sent SYN to port 4444 (common reverse shell port)")
time.sleep(1)
# Test 3: Another suspicious port (T1071.001)
print("[Test 3] Simulating connection to suspicious port 31337...")
send(IP(dst="127.0.0.1") / TCP(dport=31337, flags="S"), verbose=False)
print(" Sent SYN to port 31337 (classic backdoor port)")
print()
print("Done! Check detector.py output for alerts.")
Your detector now catches four categories of suspicious activity, tags each finding with a MITRE ATT&CK technique, and logs everything to a JSON file. In the next step, you will turn that raw JSON data into a visual SOC dashboard that a security analyst would actually want to look at.
Build the SOC Dashboard
Your detector now catches port scans, suspicious DNS queries, suspicious port connections, and ARP anomalies. Every alert maps to a MITRE ATT&CK technique and saves to alerts.json.
Terminal output is not how real SOC analysts work. In this step, you will use Streamlit to build a visual dashboard that reads your alert data and presents severity metrics, a scrollable alert feed, and ATT&CK coverage charts.
In this step, get ready to:
- Create a Streamlit dashboard that reads alerts from alerts.json.
- Display severity metric cards, an alert feed table, and ATT&CK coverage charts.
- Run the full three-process system and view the live dashboard.
Create the Streamlit dashboard
Why Streamlit instead of Flask?
Streamlit produces a professional-looking dashboard with zero HTML, CSS, or JavaScript knowledge. All your effort goes toward cybersecurity concepts instead of web development boilerplate.
A Flask dashboard would need roughly three times the code for the same visual result. Streamlit also auto-reloads every time you save your file, so you see changes instantly.
- In VS Code, create a new file called dashboard.py in your threat-detector folder.
- Add the imports, page configuration, alert-loading function, and dashboard header by pasting this code:
import json
import os
import streamlit as st
import pandas as pd
ALERTS_FILE = "alerts.json"
st.set_page_config(
page_title="Network Threat Detector",
layout="wide",
)
def load_alerts():
if not os.path.exists(ALERTS_FILE):
return []
try:
with open(ALERTS_FILE, "r") as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
return []
st.title("Network Threat Detector Dashboard")
st.caption("Live alerts from network traffic mapped to MITRE ATT&CK")
if st.button("Refresh Alerts"):
st.rerun()
What does this code do?
- st.set_page_config() sets the browser tab title and uses a wide layout so the dashboard fills the screen.
- load_alerts() reads alerts.json and returns the alerts as a Python list. It handles missing or corrupted files gracefully by returning an empty list.
- st.button("Refresh Alerts") adds a clickable button. When clicked, st.rerun() reruns the entire script so the dashboard reloads the latest alerts from disk.
- Save dashboard.py.
- Launch the dashboard by running this command in your admin Command Prompt:
streamlit run dashboard.py
First-time email prompt
Streamlit asks for your email the first time it launches. Press Enter to skip this prompt. Your browser opens automatically to http://localhost:8501.
You should see the title "Network Threat Detector Dashboard", a subtitle, and a Refresh Alerts button. The page is mostly empty because you have not added the alert display logic yet.
Command not found?
- Make sure your virtual environment is active. You should see (venv) at the start of your command line. If not, run venv\Scripts\activate first.
Ask for help: Why can't my terminal find the streamlit command?
Keep Streamlit running in this terminal. It watches your file for changes and reloads automatically every time you save.
- Back in VS Code, add the alert loading and severity metric cards below the st.button block in dashboard.py:
alerts = load_alerts()
if not alerts:
st.info("No alerts yet. Make sure detector.py is running in another terminal.")
else:
col1, col2, col3, col4 = st.columns(4)
critical = len([a for a in alerts if a["severity"] == "Critical"])
high = len([a for a in alerts if a["severity"] == "High"])
medium = len([a for a in alerts if a["severity"] == "Medium"])
low = len([a for a in alerts if a["severity"] == "Low"])
col1.metric("Critical", critical)
col2.metric("High", high)
col3.metric("Medium", medium)
col4.metric("Low", low)
What does this code do?
- load_alerts() reads the alert data from alerts.json. If the file is empty or missing, the dashboard shows a helpful info message instead of crashing.
- st.columns(4) creates four side-by-side columns. Each column gets a st.metric() card showing the count for one severity level: Critical, High, Medium, and Low.
- These metric cards give a SOC analyst an at-a-glance severity breakdown without scrolling through individual alerts.
- Save dashboard.py. Streamlit auto-reloads in your browser.
If you have an alerts.json left over from your earlier testing, you will see four metric cards with counts. If the file does not exist, you will see a blue info message saying no alerts are available yet.
Dashboard not updating after save?
- Make sure you saved the file in VS Code (Ctrl+S). Streamlit only reloads when the file on disk changes.
- If Streamlit shows a prompt asking whether to rerun, click Always rerun so future saves reload automatically.
Ask for help: Why doesn't my Streamlit dashboard update when I save?
- Still in VS Code, add the alert feed table and bar charts below col4.metric("Low", low), still inside the else: block. Make sure every line is indented with four spaces to stay inside else:
st.subheader("Alert Feed")
df = pd.DataFrame(alerts)
display_cols = ["timestamp", "severity", "rule", "technique", "tactic", "src_ip", "detail"]
available = [c for c in display_cols if c in df.columns]
st.dataframe(df[available], use_container_width=True)
st.subheader("MITRE ATT&CK Coverage")
technique_counts = df["technique"].value_counts()
st.bar_chart(technique_counts)
st.subheader("Alerts by Severity")
severity_order = ["Critical", "High", "Medium", "Low"]
severity_counts = df["severity"].value_counts().reindex(severity_order, fill_value=0)
st.bar_chart(severity_counts)
What does this code do?
- pd.DataFrame(alerts) converts your list of alert dictionaries into a table. st.dataframe() renders it as a scrollable, sortable feed showing timestamps, severity, rule names, and ATT&CK technique IDs.
- The first bar chart plots how many times each ATT&CK technique was triggered, showing your detection coverage.
- The second bar chart groups alerts by severity level in order from Critical down to Low, giving a quick risk overview.
- Save dashboard.py. Streamlit auto-reloads in your browser.
If alerts exist, you will now see the alert feed table and two bar charts below the metric cards. If no alerts exist yet, the page still shows the info message.
Seeing an IndentationError?
- Every line in this chunk must be indented with four spaces because it sits inside the else: block. Check that lines like st.subheader("Alert Feed") start with exactly four spaces.
- Make sure this code appears after col4.metric("Low", low), not after the st.info(...) line.
Ask for help: Why am I getting an IndentationError in my Streamlit dashboard?
✔️ Awesome, I've got everything!
Great. Double check that you have saved dashboard.py and that Streamlit is still running in your terminal.
ⓧ I'd like to double check the full code
Scroll up and confirm your dashboard.py contains all three code chunks in this order:
- Chunk 1: Imports, ALERTS_FILE, st.set_page_config(), load_alerts() function, title, caption, and Refresh Alerts button.
- Chunk 2: alerts = load_alerts(), the if not alerts empty-state check, and four st.metric() cards inside the else: block.
- Chunk 3: st.subheader("Alert Feed"), the st.dataframe() call, and two st.bar_chart() calls, all indented inside the else: block.
Run the full system
Your dashboard is ready. Now you will run the complete three-process system: the detector capturing packets, the test traffic generator triggering alerts, and the dashboard displaying everything visually.
- Press Ctrl+C in the terminal where Streamlit is running to stop it.
- Open a new Command Prompt as Administrator by pressing the Windows key, typing cmd, and selecting Run as administrator.
- Navigate to your project folder and activate the virtual environment by running these commands:
cd Desktop\threat-detector
venv\Scripts\activate
- Start the detector in this admin terminal by running:
python detector.py
You should see the "Network Threat Detector" banner and the "Monitoring live traffic..." message. The detector is now watching every packet that crosses your network.
- Open a second Command Prompt as Administrator (same steps as above).
- Navigate to the project and activate the venv by running:
cd Desktop\threat-detector
venv\Scripts\activate
- Generate test attack traffic by running:
python test_traffic.py
Watch the first terminal. You should see alerts printing with severity levels and ATT&CK technique IDs as the test packets trigger your detection rules.
- Return to the original terminal (the one where you stopped Streamlit earlier).
- Launch the dashboard again by running:
streamlit run dashboard.py
Before you look at the dashboard, think about what you expect to see. How many Critical alerts should the test traffic produce? How many unique ATT&CK techniques should appear in the coverage chart?
- Open http://localhost:8501 in your browser.
- Click Refresh Alerts to load the latest data.
You should see four severity metric cards showing counts for Critical, High, Medium, and Low. The test traffic produces at least 2 Critical alerts (ports 4444 and 31337) and 1 High alert (the port scan).
Below the metrics, a scrollable alert feed table shows every alert with its timestamp, severity, rule name, ATT&CK technique ID, tactic, source IP, and detail. Two bar charts at the bottom show ATT&CK technique coverage and severity breakdown.
Why three separate processes?
The detector and dashboard run as separate processes connected by alerts.json. This mirrors real SOC tooling where collectors and viewers are decoupled.
Scapy's sniff() blocks the terminal while capturing, so it cannot share a process with Streamlit's own event loop. Separating them keeps each piece simple and testable on its own.
Dashboard shows no alerts?
- Make sure you ran detector.py before test_traffic.py. The detector must be listening when the test packets arrive.
- Confirm both the detector and test traffic terminals are running as Administrator. Scapy cannot capture or send packets without admin privileges on Windows.
- Click the Refresh Alerts button in the dashboard. The dashboard reads the file at load time and needs a rerun to pick up new alerts.
Ask for help: Why is my Streamlit dashboard not showing any alerts even though my detector is running?
Secret mission
Detect DNS Tunneling with Entropy Analysis
DNS tunneling is one of the stealthiest ways attackers exfiltrate data. Simple string matching will not catch it. In this mission, you will implement Shannon entropy analysis to detect the mathematical fingerprint of encoded data hiding inside DNS queries.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally on your machine, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- The threat-detector project folder (contains detector.py, test_traffic.py, dashboard.py, alerts.json, and the venv virtual environment with all installed packages).
- Npcap 1.88 (Windows packet capture driver).
Keep everything running
No action needed. Choose this if you are still actively building or want to extend your detector with new rules.
- Your detector, dashboard, and test scripts stay in your threat-detector folder on your Desktop.
- Npcap remains installed on your system. It is used by other security tools like Wireshark, so keeping it is usually a good idea.
- There are no ongoing costs.
Pause - I'll come back to this later
Close all running processes to free up system resources. Your project files remain on disk, ready to run again anytime.
- Press Ctrl+C in each terminal running detector.py, test_traffic.py, or the Streamlit dashboard to stop them.
- Close all Command Prompt windows.
- When you are ready to resume, open an admin Command Prompt, navigate to threat-detector, activate the virtual environment, and run your scripts again.
Delete - I don't want to use this again
Remove all project files and optionally uninstall Npcap. This frees disk space and removes the packet capture driver from your system.
- Stop any running terminals first by pressing Ctrl+C in each one.
- Delete the threat-detector project folder by running this command in a Command Prompt:
cd %USERPROFILE%\Desktop
rmdir /s /q threat-detector
- Type Y and press Enter if prompted to confirm.
- This deletes all Python files, the virtual environment, alerts.json, and the installed packages (Scapy and Streamlit) in one step.
To uninstall Npcap (optional, skip this if you use other tools like Wireshark that depend on it):
- Open Windows Settings.
- Select Apps, then Installed apps.
- Search for Npcap, click the three-dot menu next to it, and select Uninstall.
Nice Work!
Nice Work!
You just built a live network threat detection system from scratch. Your detector captures real traffic, analyzes it against custom rules, maps findings to the MITRE ATT&CK framework, and displays everything in a professional SOC dashboard.
You've learned how to:
- Built a live network packet sniffer using Scapy and Npcap on Windows to capture and inspect raw network traffic in real time.
- Created 4 threat detection rules that analyze packets and map findings to MITRE ATT&CK techniques including port scanning (T1046), suspicious DNS (T1071.004), suspicious ports (T1071.001), and ARP anomalies (T1557).
- Built a Streamlit SOC dashboard with severity metric cards, a scrollable alert feed, and ATT&CK coverage charts, plus a test traffic generator using Scapy to simulate attacks and verify your rules fire correctly.
- Secret Mission: Added entropy-based DNS tunneling detection (T1071.004) using Shannon entropy analysis to catch covert command-and-control channels.
Ready to quiz yourself?