Module 1 Capstone: I Built the Tools. Now I See What Attackers See.
Digital Insights

Module 1 Capstone: I Built the Tools. Now I See What Attackers See.

Mar 29, 2026
35 min read
Kuldeep Singh

Day 10: Module 1 Capstone (Continuing). I spent today building three real offensive security tools from scratch — a multi-threaded port scanner, a network mapper with OS fingerprinting, and a banner grabber with live CVE correlation. No libraries doing the hard work. Pure Python, raw sockets, and ten days of accumulated context about what attackers actually do with this information.

📍 Journey Navigation Prev: Day 9 — Building a Detection System That Thinks | Next: Day 11 — The Engine of Detection: Building a DPI Packet Sniffer | Day 1 | Day 3 | Day 5 | | Day 6 | Day 7 | Day 8 | Day 9 | Day 10 |

Status: Day 10 of 90-day AI Security journey — Module 1 Capstone (Continuing) Projects Shipped: Port Scanner · Network Mapper · Banner Grabber GitHub: applied-ai-security-projects


This Is What Ten Days Feels Like

Ten days ago I started this journey knowing Python well and networking poorly. I knew what a port was. I did not know what information a port could leak. I knew what an IP address was. I did not know how an attacker turns an IP address into a complete picture of a target's infrastructure in under ten minutes.

Today — a Saturday — I pushed three tools to GitHub that answer that question concretely.

I want to be honest about what today looked like: I woke up, opened the laptop, and didn't close it until everything was shipped. No meetings, no interruptions, just an entire day of building. The kind of Saturday that doesn't feel like a sacrifice because the work is genuinely interesting. Module 1 has been 10 days of labs and theory. Today was the day all of it became code.

Not tutorials. Not wrappers around existing libraries. Built from scratch: raw TCP sockets, manual protocol handshakes, multi-threaded execution engines, CVE correlation. The kind of tools you understand completely because you wrote every line.

This post is the Module 1 capstone — the projects, the architecture decisions, the lessons that only come from building the thing yourself, and what I now see differently about every system I'll ever build as an engineer.

The GitHub repo is live: kuldeepstechwork/applied-ai-security-projects


The Context Behind the Capstone

Before the tools, the reason they matter.

Over the last nine days, every lab was reconnaissance in some form. Day 3 was sniffing packets. Day 4 was nmap. Day 5 was exploiting services whose banners told us exactly what they were running. Day 6 was ARP poisoning to intercept credentials. Day 7 was DHCP spoofing to redirect traffic. Day 8 was reading the evidence trail left behind. Day 9 was writing detection rules to catch it all.

The pattern across every lab was the same: the attacker always starts by figuring out what's there. Before exploitation comes enumeration. Before exploitation comes service identification. Before everything else: reconnaissance.

And the reconnaissance tools that every attacker actually uses — nmap, masscan, metasploit's auxiliary scanners — all do fundamentally simple things at the socket level. They connect. They read what comes back. They infer from what they receive.

I didn't want to just use those tools. I wanted to build them. Because when you build a port scanner yourself, you understand every evasion technique used against port scanners. When you build a banner grabber yourself, you understand exactly why services leak information they shouldn't. When you build a network mapper yourself, you understand why OS fingerprinting works and how to defeat it.

That's the capstone mindset: build the attack tool to understand both the attack and the defense.


Project 1 — Port Scanner: Multi-Threaded, Protocol-Aware, Production-Grade

What It Does

The tool takes a target IP, a port specification (22,80,443 or 8000-8090 or top100), and a thread count. It scans all specified ports concurrently, identifies open ones, then does something most beginner scanners skip: it sends protocol-correct handshake bytes and reads the actual service response.

The result is a color-coded table showing open ports, identified services, and the actual banner — the raw text a service sent back when contacted with the right greeting.

The Architecture

main()
  ↓
parse_port_spec("22,80,8000-8090") → [22, 80, 8000, 8001, ..., 8090]
  ↓
ThreadPoolExecutor(max_workers=N)
  ↓
scan_port(host, port) → PortResult (or None if closed)
  ↓
grab_banner(host, port) → service name + raw banner text
  ↓
print_results() + optional JSON export

The core insight is the queue.Queue for thread-safe work distribution. Each worker thread pulls a port from the queue, scans it, stores results in a thread-safe list, then pulls the next port. No shared mutable state beyond the results collector.

The Protocol Probe Database

This is the part that makes the scanner useful instead of just functional. When you connect to port 22, you don't just say "hello" — you wait for the SSH banner. When you connect to port 3306 (MySQL), you need to send nothing and just read the greeting. When you connect to port 6379 (Redis), you send PING\r\n and expect +PONG. When you connect to port 25 (SMTP), you get a 220 greeting, then send EHLO test\r\n to enumerate capabilities.

SERVICE_PROBES = {
    22:   {"probe": b"",           "name": "SSH",       "pattern": r"SSH-(\d+\.\d+)"},
    21:   {"probe": b"",           "name": "FTP",       "pattern": r"(\d{3})\s+(.+)"},
    25:   {"probe": b"EHLO x\r\n", "name": "SMTP",      "pattern": r"220\s+(\S+)"},
    80:   {"probe": b"HEAD / HTTP/1.0\r\n\r\n", "name": "HTTP", "pattern": r"Server:\s*(.+)"},
    443:  {"probe": b"",           "name": "HTTPS",     "pattern": None},
    3306: {"probe": b"",           "name": "MySQL",     "pattern": r"(\d+\.\d+\.\d+)"},
    6379: {"probe": b"PING\r\n",   "name": "Redis",     "pattern": r"\+PONG"},
    5432: {"probe": b"",           "name": "PostgreSQL","pattern": r"PostgreSQL"},
    27017:{"probe": b"\x3a\x00\x00\x00\x01\x00\x00\x00...", "name": "MongoDB", "pattern": None},
}

The distinction matters enormously in a real scan. A generic scanner tells you port 6379 is open. Mine tells you it's Redis 7.2.1 — which, combined with the CVE database in Project 3, immediately surfaces CVE-2023-28425 (a Lua sandbox escape in Redis 7.0.x).

The top100 Flag

Every pentest starts with the same decision: scan all 65,535 ports (slow, thorough) or scan the high-probability ports first (fast, covers 99% of real infrastructure).

The top100 flag embeds the same port prioritization nmap uses — ranked by frequency of appearance in real-world internet scans. Port 80, 443, 22, 21, 3306, 5432, 6379, 8080, 8443, 27017 will cover the vast majority of what you'll actually find.

TOP_100_PORTS = [
    80, 443, 22, 21, 25, 3306, 5432, 6379, 8080, 8443,
    27017, 6380, 11211, 9200, 5601, 4848, 8888, 9090, 3000,
    # ... 80 more
]

What a Defender Learns From Building This

When I wrote the banner grabber, I immediately understood why services should not send version information in their banners. Redis by default returns +PONG. MySQL by default returns its full version string in the connection handshake. Apache HTTP Server by default includes the version and OS in every response header.

Every piece of information in a banner is a gift to an attacker. It narrows the CVE search from thousands of possibilities to the exact version running. The fix is trivially easy:

Apache: ServerTokens Prod → returns Apache instead of Apache/2.4.51 (Ubuntu) Redis: requirepass + rename-command INFO "" → removes version enumeration entirely MySQL: No version string suppression in the handshake (architectural limitation — use network-level controls) SSH: The banner is mandatory per RFC 4253, but you can set it to the minimum: SSH-2.0-OpenSSH without the version

I configured all of these in my lab environment the moment I saw what my own scanner returned. That's the feedback loop: build the attack tool, immediately apply the defense it reveals.


Project 2 — Network Mapper: Subnet Discovery With OS Fingerprinting

What It Does

Give it a subnet (192.168.100.0/24). It discovers every live host, identifies their operating systems, resolves their hostnames via reverse DNS, reads their MAC addresses from the ARP cache (no extra traffic), and outputs a structured table — or JSON for piping into other tools.

The Discovery Problem: Why ICMP Isn't Enough

Every beginner network scanner sends ICMP echo requests (ping) and assumes hosts that don't respond are offline. This is wrong. Many servers, particularly Windows hosts and hardened Linux servers, block ICMP by firewall policy. They are fully up, running services, and completely invisible to a ping sweep.

My solution: ICMP first, TCP fallback for non-responders.

def is_host_alive(ip: str) -> bool:
    # Try ICMP first (fastest)
    if ping_host(ip):
        return True

    # TCP fallback — check common ports
    for port in [80, 443, 22, 445, 3389]:
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.settimeout(0.5)
                if s.connect_ex((ip, port)) == 0:
                    return True
        except:
            continue

    return False

If a host blocks ICMP but has port 443 open, the TCP fallback catches it. In real networks, TCP-fallback discovery consistently finds 15-30% more hosts than ICMP-only scanning.

OS Fingerprinting via TTL

The Time-To-Live field in an IP packet is set by the sending operating system and decremented by each router hop. Different OS families use different initial TTL values:

Received TTLHops EstimatedLikely OS
60–640–4Linux / macOS
125–1280–4Windows
250–2550–4Cisco / Network Gear
127~1Windows (1 hop away)
63~1Linux (1 hop away)

This is not a guess — it's a statistical inference based on decades of observed behavior. The inference is: received_TTL rounded up to the nearest common initial value (64, 128, or 255), then hops = initial - received.

def fingerprint_os(ttl: int) -> str:
    if ttl <= 64:
        return "Linux/macOS"
    elif ttl <= 128:
        return "Windows"
    elif ttl <= 255:
        return "Network Device (Cisco/Juniper)"
    return "Unknown"

It's imprecise. A hardened Linux server configured with net.ipv4.ip_default_ttl = 128 will fingerprint as Windows. But as a first-pass triage tool — available from a single packet with zero additional traffic — the signal-to-noise ratio is high enough to be genuinely useful.

MAC Address Without ARP Scanning

ARP scanning — sending broadcast ARP requests to enumerate MAC addresses — is noisy and easily detected. My mapper reads the ARP cache that already exists on the scanning machine instead.

def get_mac_from_arp_cache(ip: str) -> str:
    try:
        with open("/proc/net/arp", "r") as f:
            for line in f.readlines()[1:]:
                fields = line.split()
                if fields[0] == ip and fields[2] == "0x2":
                    return fields[3]
    except:
        pass
    return "Unknown"

The ARP cache is populated automatically by any TCP connection to the host. If you already scanned it with the port scanner, the MAC is in the cache. Zero additional traffic. Zero additional detection risk.

Parallelism: Threading the Right Way

The mapper scans all hosts simultaneously, not sequentially. For a /24 subnet (256 hosts), sequential scanning takes 256 × timeout_per_host. With 100 threads scanning simultaneously, it takes ~1/100th as long.

with ThreadPoolExecutor(max_workers=100) as executor:
    futures = {executor.submit(probe_host, str(ip)): str(ip)
               for ip in ipaddress.ip_network(subnet).hosts()}

    for future in as_completed(futures):
        result = future.result()
        if result:
            live_hosts.append(result)

as_completed() processes results as they arrive rather than waiting for all futures to finish. The first host to respond appears in output immediately. This matters for interactive use — you see results as they happen, not all at once at the end.

What the Output Shows

A real scan of my lab network (192.168.100.0/24) produces:

HOST DISCOVERY RESULTS — 192.168.100.0/24
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
IP Address        Hostname           OS Guess      MAC Address
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
192.168.100.1     router.local       Linux/macOS   00:0c:29:aa:11:22
192.168.100.10    kali.local         Linux/macOS   00:0c:29:bb:33:44
192.168.100.20    client.local       Windows       00:0c:29:cc:55:66
192.168.100.30    webserver.local    Linux/macOS   00:0c:29:dd:77:88
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Scan complete: 4 live hosts found in 3.2s

In six seconds, starting from only a subnet, an attacker now knows: which hosts exist, their likely operating systems (useful for selecting OS-specific exploits), their hostnames (often expose environment names like prod-db-01), and their MAC addresses (useful for ARP spoofing and network mapping).


Project 3 — Banner Grabber: Service Fingerprinting With Live CVE Correlation

Why This Is the Most Dangerous Tool in the Set

The port scanner tells you a port is open. The network mapper tells you the host is running Linux. The banner grabber tells you it's running Apache 2.4.49 — which has CVE-2021-41773, a path traversal and remote code execution vulnerability with a CVSS score of 9.8.

That's the difference between knowing a door exists and knowing it's unlocked.

Protocol Coverage

The probe database covers 20+ protocols with service-specific handshakes:

BANNER_PROBES = {
    21:    SSH_WAIT,
    22:    SSH_WAIT,
    23:    TELNET_WAIT,
    25:    b"EHLO security-test\r\n",
    80:    b"HEAD / HTTP/1.1\r\nHost: {target}\r\nConnection: close\r\n\r\n",
    110:   b"",           # POP3 sends greeting first
    143:   b"",           # IMAP sends greeting first
    389:   LDAP_PROBE,    # LDAP anonymous bind
    443:   TLS_WRAP,
    445:   SMB_NEGOTIATE, # SMB negotiate protocol
    3306:  b"",           # MySQL sends handshake first
    5432:  b"",           # PostgreSQL sends auth request first
    6379:  b"PING\r\n",
    8080:  b"HEAD / HTTP/1.1\r\nHost: {target}\r\nConnection: close\r\n\r\n",
    11211: b"version\r\n", # Memcached
    27017: MONGODB_PROBE,
}

Each probe is designed to elicit the maximum version information from the service while remaining non-destructive and non-authenticating.

TLS Certificate Analysis

For HTTPS services (port 443 and others), the TLS certificate itself is an information goldmine:

def analyze_tls_cert(host: str, port: int) -> CertInfo:
    context = ssl.create_default_context()
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE

    with socket.create_connection((host, port), timeout=3) as sock:
        with context.wrap_socket(sock, server_hostname=host) as ssock:
            cert = ssock.getpeercert()

    return CertInfo(
        subject=cert.get("subject"),
        issuer=cert.get("issuer"),
        not_before=cert.get("notBefore"),
        not_after=cert.get("notAfter"),
        san=cert.get("subjectAltName", []),
    )

What you learn from a certificate:

  • Common Name / SAN: Often reveals internal hostnames, environment names (staging-api.internal), or the full list of domains served by this host
  • Issuer: Let's Encrypt vs internal CA vs commercial CA — tells you the organization's cert management sophistication
  • Expiry: An expired certificate is a misconfiguration signal; an org that lets certs expire often has other hygiene issues
  • SAN entries: A wildcard cert for *.corp.example.com reveals the internal domain naming convention

CVE Correlation: From Version to Vulnerability

This is the feature I'm most proud of. After identifying a service version, the tool queries a curated CVE database and returns matching vulnerabilities sorted by CVSS severity.

CVE_DATABASE = {
    "apache": {
        "2.4.49": [
            CVE("CVE-2021-41773", 9.8, "CRITICAL",
                "Path traversal and RCE — unauthenticated attacker can read/execute files"),
        ],
        "2.4.50": [
            CVE("CVE-2021-42013", 9.8, "CRITICAL",
                "Incomplete fix for CVE-2021-41773 — same path traversal persists"),
        ],
    },
    "openssh": {
        "8.5": [
            CVE("CVE-2021-28041", 7.4, "HIGH",
                "Double-free memory corruption in ssh-agent"),
        ],
    },
    "redis": {
        "7.0.0": [
            CVE("CVE-2023-28425", 5.5, "MEDIUM",
                "Lua sandbox escape via integer overflow"),
        ],
    },
    "nginx": {
        "1.20.1": [
            CVE("CVE-2021-23017", 7.7, "HIGH",
                "1-byte memory overwrite in DNS resolver"),
        ],
    },
}

The output when scanning a vulnerable host:

SERVICE FINGERPRINT RESULTS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Port  Service   Version    Banner
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
22    SSH       8.5p1      SSH-2.0-OpenSSH_8.5p1 Ubuntu
80    HTTP      Apache/2.4.49  Server: Apache/2.4.49 (Ubuntu)
6379  Redis     7.0.0      +PONG

[!] VULNERABILITIES FOUND:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[CRITICAL 9.8] CVE-2021-41773 → Apache/2.4.49
  Path traversal + RCE — unauthenticated, no auth required

[HIGH 7.4]     CVE-2021-28041 → OpenSSH/8.5
  Double-free in ssh-agent
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠  2 vulnerabilities found. Critical issues require immediate attention.

Thirty seconds from starting the scan to knowing you have a CVSS 9.8 unauthenticated RCE on the target. That is the attack capability this tool represents.

What a Defender Does With This Information

Run this against your own infrastructure. Not an attacker's target — yours.

Every version number it finds is a question: is this version patched? Every CVE it returns is a priority: how exposed is this service? Can it be reached from untrusted networks?

This tool is a vulnerability scanner. The only difference between running it against a target you're attacking and running it against infrastructure you're defending is authorization. The code is identical.

That's the core insight of offensive security tooling: the attacker's reconnaissance tool is the defender's vulnerability assessment tool. Understanding one makes you better at the other.


What Building These Tools Changed About How I See Code

I've been writing backend code for 6.5 years. APIs, database layers, service integrations. I thought I understood software.

Building these three tools in one intensive day rewired several assumptions.

Every Service Is a Confessor

When I wrote the protocol probe database, I had to research what each service sends when you connect to it. The answer, for almost every service, was: more than it should.

MySQL 8.0 sends its exact version string in the first 68 bytes of the connection handshake — before authentication, before you've done anything, to every TCP client that connects. Redis sends its version in the INFO command response. SSH sends a banner with the daemon version and the OS it was compiled on.

This information is operational convenience for administrators. It is a reconnaissance gift for attackers. The argument for including it is that legitimate clients need to know what protocol version to negotiate. The argument against is that version information narrows CVE searches dramatically.

As a developer, I now think about this for every API I build: what does my service reveal before authentication? What information does an error message leak? What does my HTTP response headers disclose about my stack?

The answer should be: as little as possible.

Threading Is a Security Primitive

Multi-threading in my scanner isn't an optimization — it's the difference between a tool that completes a scan in 3 seconds (100 threads on a /24) and one that takes 20 minutes (sequential with 0.5s timeout per host).

This maps directly to attack economics. A scanner that takes 20 minutes is detectable: IDS rules can catch a slow, sustained stream of SYN packets. A scanner that takes 3 seconds may complete before the detection threshold triggers. Speed is evasion.

It also maps to defensive thinking: rate limiting at the connection level is more effective than rate limiting at the port level. Limiting connections per second from a single source IP catches scanners regardless of which ports they're hitting.

The Vulnerability Window Is Real and Measurable

When I saw CVE-2021-41773 appear in the output against Apache 2.4.49, I looked it up. It was disclosed October 4, 2021. Apache 2.4.50 was released October 7, 2021. The patch gap: 3 days.

But the real number is the exposure window in production — how long between disclosure and patching across real deployments. For this CVE, Shodan showed hundreds of thousands of vulnerable Apache instances weeks after the patch was available.

The tool I built can find those instances in seconds. This is the reality of unpatched infrastructure. The exploit is public. The scanner is trivial to write. The vulnerable surface is enormous. The only protection is patching, and the only thing that drives patching velocity is understanding the tool that finds what you haven't patched.


The Full Attack Chain: Connecting All Three Tools

Here's what a complete reconnaissance workflow looks like with these three tools chained together:

# Step 1: Discover live hosts on the network
python3 network_mapper.py 192.168.100.0/24 --json > hosts.json

# Output: 192.168.100.30 (Linux/macOS, webserver.local)

# Step 2: Scan ports on discovered host
python3 port_scanner.py 192.168.100.30 --ports top100 --threads 100

# Output: 22/SSH, 80/HTTP, 8080/HTTP, 3306/MySQL open

# Step 3: Grab banners and correlate CVEs
python3 banner_grabber.py 192.168.100.30 --ports 22,80,8080,3306

# Output: Apache 2.4.49 (CRITICAL CVE), MySQL 5.7.35 (HIGH CVE)

Total time for a complete reconnaissance sweep: under 60 seconds. The output tells an attacker:

  • What's running (port scanner)
  • What OS it's running on (network mapper)
  • What specific version of each service (banner grabber)
  • Which known vulnerabilities exist (CVE correlation)

This is the reconnaissance phase of every penetration test, automated and documented. From here, an attacker knows exactly where to look for entry points. A defender knows exactly what to patch first.


What Day 9's Detection System Would Catch

The meta-exercise: I built these offensive tools on Day 10. Day 9 I built an IDS. How would yesterday's Suricata rules respond to today's tools?

Port Scanner against IDS:

  • Rule 4000001 (Port Scan Detected) — fires immediately on the burst of SYN packets
  • Rule 4000002 (Low-Slow Scan) — fires if scanner uses T1 timing, but takes 120 seconds
  • The scanner is caught. The question is whether anyone is watching the alerts.

Banner Grabber against IDS:

  • The banner grabber establishes TCP connections and sends protocol-correct data
  • No rule fires on PING\r\n to port 6379 — it looks like a legitimate Redis health check
  • No rule fires on HEAD / HTTP/1.1 to port 80 — it looks like a legitimate HTTP client
  • The banner grabber largely evades my Day 9 rules. Protocol-correct probes are indistinguishable from legitimate traffic at the packet level.

What would actually catch it:

  • A behavioral rule detecting multiple distinct ports being probed from a single source
  • An anomaly detection system flagging unusual combinations of service connections
  • Rate limiting at the load balancer level

This is exactly the gap I analyzed on Day 9: signature rules catch the obvious. Behavioral analysis catches the subtle. Human analysts catch what both miss.

The lesson compounds: building the attack tool reveals exactly which detection gaps exist. Building the detection system reveals exactly which attack patterns slip through.


Module 1: The Ten-Day Honest Assessment

Ten days. Here's what actually changed.

What I know now that I didn't know ten days ago:

The networking layer is not infrastructure plumbing that you trust to work correctly. It is a battlefield. ARP is unauthenticated. DHCP is unauthenticated. DNS is unauthenticated at the local level. Every protocol assumption your application makes is an assumption an attacker can violate.

Reconnaissance is not a preliminary step before the real attack. Reconnaissance is the attack in aggregate — it's how an attacker maps the exact path from network access to data exfiltration, identifies exactly which components are unpatched, and chooses the highest-probability entry point. The quality of your reconnaissance determines the efficiency of everything that follows.

Detection coverage is not binary. You don't have it or not have it. You have it for specific patterns, at specific sensor placements, at specific thresholds. Every gap in coverage is a free movement corridor for an attacker who knows where the sensors are.

What I'm unimpressed by in myself:

My CVE database is curated, not live. A real vulnerability scanner queries the NVD API and has current data. Mine has a static dictionary of 15 CVEs I chose manually. The architecture is right; the data coverage is intentionally limited.

My OS fingerprinting only uses TTL. Real tools use TCP window size, TCP options order, IPID behavior, and timestamp behavior — nmap's OS detection uses 13 distinct probes and matches against a database of 2,600+ OS signatures. Mine is a first approximation.

My banner grabber doesn't handle encrypted protocols end-to-end. I analyze TLS certificates, but I can't read the application data inside an HTTPS session. This is a fundamental limitation — it's why many modern C2 frameworks use HTTPS to blend with legitimate traffic.

These limitations are documented in the README. I know exactly what they are and why they exist. That's what building the tool from scratch gives you: a precise understanding of what it can and cannot do, which is exactly what you need to use it responsibly and build on it effectively.


Sunday: Rest, Then Finishing Module 1 Projects

If you're reading this on a Sunday morning — that's intentional. I post every day on LinkedIn and WhatsApp, and this one lands in your feed while I'm away from the keyboard.

Sunday is the one day I deliberately don't code. Not because the work isn't there — it always is — but because this pace only works if recovery is as deliberate as the grind. Ten days of intensive labs, builds, and daily writing takes a toll. Sunday resets it.

Tomorrow, we continue with the remaining projects in Module 1. The capstone is more than just these three tools; there are a few more critical components I want to build to ensure the foundation of this journey is rock-solid.

Module 1 has been the foundation: understanding how networks work, how attackers move through them, how defenders detect them, and how to build the tools that perform each role.

By the time this module is fully complete, I'll have a complete suite of network security tools built from the ground up. The portfolio builds. The depth compounds. The picture gets clearer.


What's Actually in the GitHub Repo

If you want to look at the code: applied-ai-security-projects

Module 01, Week 01 has the full project structure:

  • projects/01_port_scanner/ — the multi-threaded scanner
  • projects/02_network_mapper/ — subnet discovery + OS fingerprinting
  • projects/03_banner_grabber/ — protocol fingerprinting + CVE correlation
  • labs/ — the 22 labs these projects are built on top of
  • forensics/ — the log analysis and detection rule labs from Days 8-9

Each project has a README documenting the architecture, what it does, what its limitations are, and what legitimate use cases it serves. Security tools without that documentation are just malware with a Python wrapper.


Day 10 of 90 | Module 1: 🟡 In Progress | Port Scanner: ✅ Shipped | Network Mapper: ✅ Shipped | Banner Grabber: ✅ Shipped | CVE Correlation: ✅ Working | GitHub Portfolio: ✅ Live | Tomorrow: Module 1 — Remaining Projects 🟡


CybersecurityPythonOffensive SecurityToolsPort ScannerNetwork ReconCapstone
Continue Reading

More Engineering Insights

Browse All Technical Posts