DEV Community

Rençber AKMAN
Rençber AKMAN

Posted on

Stage 0.5 — Programming Fundamentals

From Zero to Cybersecurity Professional | Complete Roadmap Series

Series: Cybersecurity × OT/ICS Security — Full Roadmap

Stage: 0 — Computer Science Foundations

Module: 0.5 — Programming Fundamentals

Level: Beginner → Intermediate

Prerequisites: Stage 0.4 — Linux Fundamentals

Next Stage: Stage 01 — Network Fundamentals


Table of Contents

  1. Why Programming Is the Multiplier
  2. Python — The Language of Security
  3. Variables and Data Types
  4. Loops and Conditionals
  5. Functions
  6. Error Handling
  7. File Read and Write
  8. String Operations
  9. Basic Algorithms
  10. Security-Oriented Programming Mindset
  11. Hands-On Projects
  12. Further Reading and Resources

1. Why Programming Is the Multiplier

There is a clear ceiling in cybersecurity for people who cannot code. They can run tools built by others. They can follow documented exploitation steps. They can use frameworks someone else developed. But they cannot:

  • Build a custom exploit for a vulnerability that has no public PoC
  • Write a script that automates reconnaissance across thousands of targets
  • Modify existing tools to evade detection
  • Analyse malware source code or reverse engineer compiled binaries
  • Build detection logic that catches novel attack patterns
  • Create tools tailored to specific OT protocols that no commercial product supports

Programming removes that ceiling. It turns you from a tool user into a tool builder.

The security professional's programming reality:

You do not need to be a software engineer. You do not need to build production-grade applications with clean architecture and unit tests. You need to be able to:

  1. Read code and understand what it does
  2. Write scripts that automate your work
  3. Modify existing tools for your specific needs
  4. Understand how vulnerabilities manifest at the code level

These four abilities are achievable. This module builds the foundation for all of them.

Language choice — Python:

Every language has advocates. For security work, the choice is Python, and it is not close:

  • All major security frameworks are Python — Impacket, Scapy, Volatility, pwntools, sqlmap
  • Metasploit modules are Ruby but Python scripts are the glue that connects everything
  • OSCP, GICSP, and every professional certification assumes Python competence
  • The speed from idea to working script is the fastest in Python
  • Libraries for every security task exist — socket, struct, ctypes, requests, cryptography, scapy

For your OT/ICS path: Modbus, DNP3, and IEC 60870 all have Python libraries. Writing a custom Modbus scanner, a DNP3 fuzzer, or a script that reads PMU data — all Python. The intersection of industrial protocol knowledge and Python is exactly where the most interesting OT security work happens.


2. Python — The Language of Security

2.1 Installation and Environment

# Check Python version (Python 3.8+ required, 3.10+ recommended)
python3 --version

# On Kali/Ubuntu — Python 3 is pre-installed
# Verify pip is available
pip3 --version

# Create a virtual environment (best practice — isolates project dependencies)
python3 -m venv security_env
source security_env/bin/activate     # Activate on Linux/Mac
# security_env\Scripts\activate      # Activate on Windows

# Install packages
pip3 install requests scapy pwntools impacket
pip3 install -r requirements.txt     # Install from requirements file

# Deactivate virtual environment
deactivate
Enter fullscreen mode Exit fullscreen mode

2.2 Running Python

# Interactive interpreter (REPL — Read Eval Print Loop)
python3
>>> print("Hello")
>>> 2 + 2
>>> exit()

# Run a script
python3 script.py

# Run one-liner
python3 -c "import socket; print(socket.gethostbyname('google.com'))"

# Start a simple HTTP server (used constantly in security work)
python3 -m http.server 8080
python3 -m http.server 8080 --bind 0.0.0.0  # Listen on all interfaces
Enter fullscreen mode Exit fullscreen mode

2.3 Python Basics — Syntax Rules

# Comments start with #
# Python uses indentation (4 spaces) instead of brackets for code blocks

# Correct:
if True:
    print("indented correctly")

# Wrong — IndentationError:
if True:
print("not indented")  # This will crash

# No semicolons needed at end of lines
# No variable declaration keywords (no var, let, int)
# Python is dynamically typed — type is determined at runtime
Enter fullscreen mode Exit fullscreen mode

3. Variables and Data Types

3.1 Variables

# Variable assignment — no type declaration needed
name = "Alice"
age = 30
height = 1.75
is_admin = True

# Multiple assignment
x = y = z = 0
a, b, c = 1, 2, 3   # Tuple unpacking

# Variable naming conventions
user_name = "bob"          # snake_case — Python standard
MAX_RETRIES = 3            # UPPERCASE for constants (convention, not enforced)
_private = "internal"      # Underscore prefix = "private" by convention

# Check type
print(type(name))          # <class 'str'>
print(type(age))           # <class 'int'>
print(type(height))        # <class 'float'>
print(type(is_admin))      # <class 'bool'>
Enter fullscreen mode Exit fullscreen mode

3.2 Core Data Types

Integers and Floats

# Integers — whole numbers, no size limit in Python 3
port = 443
pid = 1337
max_uint32 = 0xFFFFFFFF      # Hex notation
binary_value = 0b11001100    # Binary notation
octal_value = 0o755          # Octal notation (file permissions!)

print(hex(255))              # '0xff'
print(bin(255))              # '0b11111111'
print(oct(255))              # '0o377'

# Integer arithmetic
result = 10 // 3             # Integer division: 3 (floor division)
remainder = 10 % 3           # Modulo: 1 (remainder)
power = 2 ** 10              # Exponentiation: 1024

# Floats — floating point numbers
pi = 3.14159
scientific = 1.5e-10         # Scientific notation

# Type conversion
port_str = "8080"
port_int = int(port_str)     # String to int
port_float = float(port_str) # String to float
port_back = str(port_int)    # Int to string

# Security use: working with raw bytes and integers
ip_int = int.from_bytes(b'\xc0\xa8\x01\x01', 'big')  # 192.168.1.1 as integer
print(ip_int)                # 3232235777
ip_bytes = ip_int.to_bytes(4, 'big')
print(ip_bytes)              # b'\xc0\xa8\x01\x01'
Enter fullscreen mode Exit fullscreen mode

Strings

# String literals — single, double, or triple quotes
name = 'Alice'
message = "Hello, World"
multiline = """This is
a multiline
string"""

# Raw strings — backslashes not treated as escape sequences
path = r"C:\Users\Alice\Documents"     # r prefix = raw string
regex = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"  # IP regex pattern

# f-strings — formatted strings (Python 3.6+, use these always)
host = "192.168.1.1"
port = 80
print(f"Connecting to {host}:{port}")
print(f"Port in hex: {port:#x}")       # Port in hex: 0x50

# String operations
s = "Hello, Security!"
print(len(s))                          # 16 — length
print(s.upper())                       # HELLO, SECURITY!
print(s.lower())                       # hello, security!
print(s.strip())                       # Remove whitespace from both ends
print(s.replace("Hello", "Goodbye"))
print(s.split(", "))                   # ['Hello', 'Security!']
print(s.startswith("Hello"))           # True
print(s.endswith("!"))                 # True
print("Security" in s)                 # True — membership test

# Indexing and slicing
s = "ABCDEFGH"
print(s[0])                            # A — first character
print(s[-1])                           # H — last character
print(s[2:5])                          # CDE — slice [start:end]
print(s[:3])                           # ABC — from beginning
print(s[5:])                           # FGH — to end
print(s[::-1])                         # HGFEDCBA — reverse

# Bytes — critical for network and binary work
data = b"GET / HTTP/1.1\r\n"          # b prefix = bytes literal
print(type(data))                      # <class 'bytes'>
print(data[0])                         # 71 — integer value of 'G'
print(data.hex())                      # 474554202f20485454502f312e310d0a

# Convert between strings and bytes
s = "Hello"
b = s.encode('utf-8')                  # str → bytes
s2 = b.decode('utf-8')                 # bytes → str

# Base64 encoding/decoding — used constantly in security
import base64
encoded = base64.b64encode(b"secret payload")   # b'c2VjcmV0IHBheWxvYWQ='
decoded = base64.b64decode(encoded)              # b'secret payload'

# Hex encoding/decoding
hex_string = b"shellcode".hex()        # '7368656c6c636f6465'
back = bytes.fromhex(hex_string)       # b'shellcode'
Enter fullscreen mode Exit fullscreen mode

Lists

# Lists — ordered, mutable, allow duplicates
ports = [80, 443, 8080, 8443]
hosts = ["192.168.1.1", "192.168.1.2", "10.0.0.1"]
mixed = [1, "two", 3.0, True, None]

# Indexing and slicing (same as strings)
print(ports[0])                        # 80
print(ports[-1])                       # 8443
print(ports[1:3])                      # [443, 8080]

# Modifying lists
ports.append(3389)                     # Add to end
ports.insert(0, 21)                    # Insert at index
ports.extend([22, 25])                 # Add multiple
ports.remove(21)                       # Remove by value
popped = ports.pop()                   # Remove and return last
del ports[0]                           # Remove by index

# List operations
print(len(ports))                      # Length
print(80 in ports)                     # Membership test
ports.sort()                           # Sort in place
sorted_ports = sorted(ports)           # Return sorted copy
ports.reverse()                        # Reverse in place

# List comprehension — Python's most powerful one-liner
squares = [x**2 for x in range(10)]
open_ports = [p for p in ports if p < 1024]
hosts_80 = [h for h in hosts if h.startswith("192")]

# Security use: IP range generation
subnet = [f"192.168.1.{i}" for i in range(1, 255)]
print(subnet[:5])                      # ['192.168.1.1', '192.168.1.2', ...]
Enter fullscreen mode Exit fullscreen mode

Dictionaries

# Dictionaries — key-value pairs, ordered (Python 3.7+), mutable
# The most important data structure for security scripts

# Create
host_info = {
    "ip": "192.168.1.100",
    "hostname": "server01",
    "open_ports": [22, 80, 443],
    "os": "Ubuntu 22.04",
    "is_reachable": True
}

# Access
print(host_info["ip"])                 # 192.168.1.100
print(host_info.get("hostname"))       # server01
print(host_info.get("mac", "unknown")) # "unknown" — default if key missing

# Modify
host_info["services"] = {"22": "SSH", "80": "HTTP"}
host_info["open_ports"].append(8080)
del host_info["is_reachable"]

# Iterate
for key, value in host_info.items():
    print(f"{key}: {value}")

for key in host_info.keys():
    print(key)

for value in host_info.values():
    print(value)

# Check membership
print("ip" in host_info)               # True
print("mac" in host_info)              # False

# Nested dictionaries — scan results
scan_results = {
    "192.168.1.1": {"ports": [22, 80], "os": "Linux"},
    "192.168.1.2": {"ports": [445, 3389], "os": "Windows"},
    "192.168.1.3": {"ports": [], "os": "Unknown"}
}

# Dictionary comprehension
port_dict = {port: "open" for port in [22, 80, 443]}
Enter fullscreen mode Exit fullscreen mode

Sets and Tuples

# Sets — unordered, unique elements, fast membership testing
unique_ips = {"192.168.1.1", "10.0.0.1", "192.168.1.1"}  # Duplicate removed
print(unique_ips)                      # {'192.168.1.1', '10.0.0.1'}

# Set operations — perfect for finding differences in scan results
scan1 = {22, 80, 443, 8080}
scan2 = {22, 80, 3389, 5985}

newly_opened = scan2 - scan1          # {3389, 5985} — in scan2 not scan1
closed = scan1 - scan2                # {443, 8080} — in scan1 not scan2
always_open = scan1 & scan2           # {22, 80} — intersection

# Tuples — ordered, immutable
coordinates = (192, 168, 1, 1)
host_port = ("192.168.1.1", 443)      # Common pattern for socket connections

# Unpacking
host, port = host_port
ip1, ip2, ip3, ip4 = coordinates
Enter fullscreen mode Exit fullscreen mode

3.3 None — The Null Value

# None represents absence of a value
result = None

# Check for None
if result is None:                     # Use "is None", not "== None"
    print("No result")

# Functions that don't return a value return None implicitly
def do_something():
    x = 1 + 1                          # No return statement

result = do_something()
print(result)                          # None
Enter fullscreen mode Exit fullscreen mode

4. Loops and Conditionals

4.1 Conditionals — if/elif/else

# Basic if/elif/else
port = 443

if port == 80:
    service = "HTTP"
elif port == 443:
    service = "HTTPS"
elif port == 22:
    service = "SSH"
elif port == 3389:
    service = "RDP"
else:
    service = "Unknown"

print(f"Port {port}: {service}")

# Comparison operators
# ==   equal
# !=   not equal
# >    greater than
# <    less than
# >=   greater or equal
# <=   less or equal
# is   identity (same object in memory)
# in   membership

# Logical operators
if port > 1023 and port < 65536:
    print("Unprivileged port")

if port == 80 or port == 8080:
    print("HTTP variant")

if not port == 443:
    print("Not HTTPS")

# One-line conditional (ternary)
label = "privileged" if port < 1024 else "unprivileged"

# Chaining comparisons (Pythonic)
if 1024 <= port <= 65535:
    print("User port range")

# Truthiness — what evaluates as False:
# False, None, 0, 0.0, "", [], {}, set()
# Everything else is True

ports = []
if not ports:                          # Empty list is falsy
    print("No open ports found")

result = None
if result:                             # None is falsy
    process(result)
Enter fullscreen mode Exit fullscreen mode

4.2 Loops

# for loop — iterate over any iterable
for port in [22, 80, 443, 3389]:
    print(f"Scanning port {port}...")

# range() — generate sequence of numbers
for i in range(10):                    # 0 to 9
    print(i)

for i in range(1, 11):                 # 1 to 10
    print(i)

for i in range(0, 256, 16):           # 0, 16, 32, ..., 240 (step 16)
    print(i)

for i in range(254, 0, -1):           # Count down
    print(i)

# Iterate with index — enumerate()
hosts = ["192.168.1.1", "192.168.1.2", "192.168.1.3"]
for index, host in enumerate(hosts):
    print(f"{index}: {host}")

# Iterate over dictionary
for key, value in scan_results.items():
    print(f"Host: {key}, Ports: {value}")

# while loop
attempts = 0
max_attempts = 3

while attempts < max_attempts:
    print(f"Attempt {attempts + 1}")
    attempts += 1

# Infinite loop with break
while True:
    data = receive_data()
    if data is None:
        break                          # Exit loop
    process(data)

# Loop control
for port in range(1, 1025):
    if port == 80:
        continue                       # Skip this iteration
    if port == 100:
        break                          # Exit loop entirely
    scan_port(port)

# else on loops — runs if loop completed without break
for port in range(1, 1025):
    if is_open(port):
        print(f"Found open port: {port}")
        break
else:
    print("No open ports found in range")   # Only runs if no break

# Nested loops
for host in ["192.168.1.1", "192.168.1.2"]:
    for port in [22, 80, 443]:
        print(f"Scanning {host}:{port}")
Enter fullscreen mode Exit fullscreen mode

4.3 Comprehensions — Compact Loops

# List comprehension
open_ports = [p for p in range(1, 65536) if check_port(p)]

# With transformation
port_strings = [str(p) for p in [22, 80, 443]]

# Dict comprehension
port_services = {22: "SSH", 80: "HTTP", 443: "HTTPS"}
reverse = {v: k for k, v in port_services.items()}  # {"SSH": 22, ...}

# Set comprehension
unique_subnets = {ip.rsplit('.', 1)[0] for ip in ip_list}

# Generator expression — like list comp but lazy (doesn't create full list in memory)
# Use for large datasets
total = sum(1 for line in open('access.log') if '404' in line)
Enter fullscreen mode Exit fullscreen mode

5. Functions

5.1 Defining and Calling Functions

# Basic function
def scan_port(host, port):
    """
    Check if a TCP port is open on a host.

    Args:
        host: Target IP address or hostname
        port: TCP port number to check

    Returns:
        True if port is open, False otherwise
    """
    import socket
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(1)
        result = sock.connect_ex((host, port))
        sock.close()
        return result == 0             # 0 = success = port open
    except Exception:
        return False

# Call the function
is_open = scan_port("192.168.1.1", 80)
print(is_open)
Enter fullscreen mode Exit fullscreen mode

5.2 Parameters and Arguments

# Default parameters
def connect(host, port=80, timeout=5, use_ssl=False):
    print(f"Connecting to {host}:{port} (timeout={timeout}, ssl={use_ssl})")

connect("192.168.1.1")                 # Uses defaults: port=80, timeout=5, ssl=False
connect("192.168.1.1", 443)            # port=443, others default
connect("192.168.1.1", use_ssl=True)   # Keyword argument — skip positional
connect("192.168.1.1", 443, 10, True)  # All positional

# *args — variable number of positional arguments
def scan_ports(host, *ports):
    for port in ports:
        print(f"Scanning {host}:{port}")

scan_ports("192.168.1.1", 22, 80, 443, 8080)

# **kwargs — variable keyword arguments
def log_event(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

log_event(timestamp="2026-01-01", source_ip="10.0.0.1", event_type="login", user="root")

# Return multiple values (actually returns a tuple)
def get_host_info(ip):
    hostname = resolve_hostname(ip)
    open_ports = scan_all_ports(ip)
    os_guess = os_fingerprint(ip)
    return hostname, open_ports, os_guess   # Returns tuple

hostname, ports, os = get_host_info("192.168.1.1")  # Unpack
Enter fullscreen mode Exit fullscreen mode

5.3 Scope

# Local vs Global scope
counter = 0                            # Global variable

def increment():
    global counter                     # Declare we're using the global
    counter += 1

def bad_increment():
    counter += 1                       # UnboundLocalError — Python thinks counter is local

# Best practice: avoid global state, pass values as arguments
def increment_counter(count):
    return count + 1                   # Pure function — no side effects

counter = increment_counter(counter)
Enter fullscreen mode Exit fullscreen mode

5.4 Lambda Functions

# Lambda — anonymous one-line function
square = lambda x: x ** 2
print(square(5))                       # 25

# Common use: sorting with custom key
hosts = [
    {"ip": "192.168.1.10", "ports": 3},
    {"ip": "192.168.1.1", "ports": 10},
    {"ip": "192.168.1.5", "ports": 1}
]
sorted_hosts = sorted(hosts, key=lambda h: h["ports"], reverse=True)
# Sorted by number of open ports, descending
Enter fullscreen mode Exit fullscreen mode

5.5 Useful Built-in Functions

# Essential built-ins every security scripter must know

print(len([1, 2, 3]))                  # 3
print(range(10))                       # range(0, 10)
print(list(range(10)))                 # [0, 1, 2, ..., 9]
print(type("hello"))                   # <class 'str'>
print(isinstance("hello", str))        # True

# Input/Output
user_input = input("Enter target IP: ")

# Map and filter
ips = ["192.168.1.1", "10.0.0.1", "172.16.0.1"]
lengths = list(map(len, ips))          # [11, 8, 11]
private = list(filter(lambda ip: ip.startswith("192"), ips))  # Filter

# Zip — combine two iterables
hosts = ["web01", "db01", "mail01"]
ips = ["192.168.1.10", "192.168.1.20", "192.168.1.30"]
combined = list(zip(hosts, ips))       # [("web01", "192.168.1.10"), ...]
host_map = dict(zip(hosts, ips))       # {"web01": "192.168.1.10", ...}

# Min, Max, Sum
ports = [22, 80, 443, 8080]
print(min(ports))                      # 22
print(max(ports))                      # 8080
print(sum(ports))                      # 8625
print(sorted(ports))                   # [22, 80, 443, 8080]

# Any and All — check conditions across iterables
results = [True, True, False, True]
print(any(results))                    # True — at least one True
print(all(results))                    # False — not all True

# Enumerate and zip used together
for i, (host, ip) in enumerate(zip(hosts, ips)):
    print(f"{i}: {host}{ip}")
Enter fullscreen mode Exit fullscreen mode

6. Error Handling

6.1 Why Error Handling Matters in Security Scripts

Security scripts run against external targets — networks that are unreliable, hosts that go offline, services that behave unexpectedly. Without proper error handling, your scanner crashes on the first timeout, your exploit stops at the first failed connection, your log parser dies on the first malformed line.

Professional security code handles failures gracefully, logs what went wrong, and continues operating.

6.2 try/except/else/finally

import socket

# Basic try/except
try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(("192.168.1.1", 80))
    print("Connected!")
except socket.timeout:
    print("Connection timed out")
except ConnectionRefusedError:
    print("Port is closed")
except socket.gaierror as e:
    print(f"DNS resolution failed: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")
finally:
    sock.close()                       # Always runs, even if exception occurred

# try/except/else
try:
    result = int("443")
except ValueError:
    print("Not a valid port number")
else:
    # Only runs if NO exception occurred
    print(f"Valid port: {result}")

# Catching multiple exceptions
try:
    connect_to_target()
except (ConnectionRefusedError, socket.timeout, OSError) as e:
    print(f"Connection failed: {e}")
Enter fullscreen mode Exit fullscreen mode

6.3 Common Exceptions in Security Scripts

# Network errors
import socket, requests

try:
    response = requests.get("http://target.com", timeout=5)
    response.raise_for_status()        # Raises HTTPError for 4xx/5xx
except requests.exceptions.Timeout:
    print("Request timed out")
except requests.exceptions.ConnectionError:
    print("Cannot connect — host down or port closed")
except requests.exceptions.HTTPError as e:
    print(f"HTTP error: {e.response.status_code}")
except requests.exceptions.RequestException as e:
    print(f"Request failed: {e}")

# File errors
try:
    with open("targets.txt", "r") as f:
        targets = f.readlines()
except FileNotFoundError:
    print("targets.txt not found")
    targets = []
except PermissionError:
    print("No permission to read targets.txt")
    targets = []

# Type and value errors
try:
    port = int(user_input)
    if not 1 <= port <= 65535:
        raise ValueError(f"Port {port} out of valid range")
except ValueError as e:
    print(f"Invalid port: {e}")
Enter fullscreen mode Exit fullscreen mode

6.4 Raising Exceptions

def validate_ip(ip):
    """Validate IP address format."""
    parts = ip.split(".")
    if len(parts) != 4:
        raise ValueError(f"Invalid IP: {ip}")
    for part in parts:
        if not part.isdigit() or not 0 <= int(part) <= 255:
            raise ValueError(f"Invalid IP octet: {part} in {ip}")
    return True

# Custom exceptions — for complex tools
class ScanError(Exception):
    """Base exception for scanner errors."""
    pass

class TargetUnreachable(ScanError):
    """Target host is not responding."""
    pass

class AuthenticationFailed(ScanError):
    """Authentication to target failed."""
    pass

# Using custom exceptions
def connect_to_service(host, port, credentials):
    if not ping(host):
        raise TargetUnreachable(f"Host {host} is not responding")
    if not authenticate(credentials):
        raise AuthenticationFailed(f"Invalid credentials for {host}")

try:
    connect_to_service("192.168.1.1", 22, ("root", "password"))
except TargetUnreachable as e:
    print(f"Cannot reach target: {e}")
except AuthenticationFailed as e:
    print(f"Auth failed: {e}")
Enter fullscreen mode Exit fullscreen mode

6.5 Context Managers — The Pythonic Way

# "with" statement ensures resources are properly released even on error
# This is the correct way to handle files and network connections

# File handling with context manager
with open("output.txt", "w") as f:
    f.write("scan results")
# File is automatically closed when block exits, even if exception occurs

# Socket with context manager
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.settimeout(1)
    sock.connect(("192.168.1.1", 80))
    sock.send(b"GET / HTTP/1.0\r\n\r\n")
    data = sock.recv(4096)
# Socket automatically closed

# Custom context manager
from contextlib import contextmanager

@contextmanager
def timed_scan(host):
    import time
    print(f"Starting scan of {host}")
    start = time.time()
    try:
        yield
    finally:
        elapsed = time.time() - start
        print(f"Scan of {host} completed in {elapsed:.2f}s")

with timed_scan("192.168.1.1"):
    perform_scan("192.168.1.1")
Enter fullscreen mode Exit fullscreen mode

7. File Read and Write

7.1 Reading Files

# Reading entire file
with open("targets.txt", "r") as f:
    content = f.read()                 # Entire file as string

with open("targets.txt", "r") as f:
    lines = f.readlines()              # List of lines (includes \n)

# Reading line by line — memory efficient for large files
with open("access.log", "r") as f:
    for line in f:                     # f is an iterator
        if "404" in line:
            print(line.strip())        # strip() removes trailing \n

# Clean line reading
with open("targets.txt", "r") as f:
    targets = [line.strip() for line in f if line.strip()]  # Skip empty lines

# Reading binary files — for forensics, malware analysis
with open("sample.exe", "rb") as f:   # "rb" = read binary
    header = f.read(4)                 # Read first 4 bytes (magic bytes)
    print(header.hex())                # MZ header: 4d5a → PE file

# Checking magic bytes (file type detection)
magic_bytes = {
    b'\x4d\x5a': "Windows PE (EXE/DLL)",
    b'\x7fELF': "Linux ELF binary",
    b'\x89PNG': "PNG image",
    b'%PDF': "PDF document",
    b'PK\x03\x04': "ZIP archive (also DOCX, XLSX, APKJ)"
}

with open("unknown_file", "rb") as f:
    header = f.read(4)
    for magic, filetype in magic_bytes.items():
        if header.startswith(magic):
            print(f"File type: {filetype}")
            break
Enter fullscreen mode Exit fullscreen mode

7.2 Writing Files

# Write (overwrite if exists)
with open("scan_results.txt", "w") as f:
    f.write("Scan Results\n")
    f.write("=" * 40 + "\n")
    for host, ports in results.items():
        f.write(f"{host}: {', '.join(map(str, ports))}\n")

# Append to file
with open("scan_log.txt", "a") as f:
    f.write(f"[{timestamp}] Scanned {host}\n")

# Writing binary
with open("payload.bin", "wb") as f:
    f.write(b"\x90" * 100)            # 100 NOP instructions
    f.write(shellcode)

# Writing multiple lines efficiently
lines = [f"192.168.1.{i}\n" for i in range(1, 255)]
with open("subnet.txt", "w") as f:
    f.writelines(lines)
Enter fullscreen mode Exit fullscreen mode

7.3 JSON — The Standard Data Exchange Format

import json

# Load JSON from file
with open("scan_results.json", "r") as f:
    data = json.load(f)

# Load JSON from string
json_str = '{"host": "192.168.1.1", "ports": [22, 80, 443]}'
data = json.loads(json_str)
print(data["host"])                    # 192.168.1.1

# Write JSON to file
results = {
    "192.168.1.1": {"ports": [22, 80], "os": "Linux"},
    "192.168.1.2": {"ports": [3389], "os": "Windows"}
}
with open("results.json", "w") as f:
    json.dump(results, f, indent=4)    # indent=4 for pretty printing

# Convert to JSON string
json_str = json.dumps(results, indent=2)
print(json_str)

# Handling JSON from APIs and security tools
import requests
response = requests.get("https://api.shodan.io/shodan/host/8.8.8.8")
data = response.json()
print(data["org"])                     # Google LLC
print(data["ports"])                   # [53, 443]
Enter fullscreen mode Exit fullscreen mode

7.4 CSV — Log Analysis

import csv

# Read CSV
with open("hosts.csv", "r") as f:
    reader = csv.DictReader(f)         # Use header row as keys
    for row in reader:
        print(row["ip"], row["hostname"], row["status"])

# Write CSV
hosts = [
    {"ip": "192.168.1.1", "hostname": "router", "status": "up"},
    {"ip": "192.168.1.2", "hostname": "server", "status": "up"}
]
with open("output.csv", "w", newline='') as f:
    fieldnames = ["ip", "hostname", "status"]
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerows(hosts)

# Parse Apache/Nginx access logs
import re
log_pattern = r'(\S+) \S+ \S+ \[(.+?)\] "(\S+) (\S+) \S+" (\d+) (\d+)'
with open("/var/log/nginx/access.log", "r") as f:
    for line in f:
        match = re.match(log_pattern, line)
        if match:
            ip, time, method, path, status, size = match.groups()
            if status == "404":
                print(f"404: {ip} requested {path}")
Enter fullscreen mode Exit fullscreen mode

7.5 Working with Paths

import os
from pathlib import Path                # Modern, recommended

# pathlib (Python 3.4+, always use this)
p = Path("/etc/ssh/sshd_config")

print(p.exists())                      # True/False
print(p.is_file())                     # True
print(p.is_dir())                      # False
print(p.parent)                        # /etc/ssh
print(p.name)                          # sshd_config
print(p.stem)                          # sshd_config (no extension here)
print(p.suffix)                        # '' (no extension)
print(p.stat().st_size)                # File size in bytes
print(p.stat().st_mtime)               # Modification timestamp

# Build paths
home = Path.home()                     # /home/kali or /root
config = home / ".ssh" / "config"     # /home/kali/.ssh/config

# List directory
for f in Path("/etc").iterdir():
    if f.is_file():
        print(f.name)

# Recursive glob
for py_file in Path("/home").rglob("*.py"):
    print(py_file)

for sh_file in Path("/etc").glob("*.conf"):
    print(sh_file)

# os module (older but still used)
print(os.getcwd())                     # Current directory
os.makedirs("/tmp/results", exist_ok=True)  # Create directory tree
os.environ.get("HOME")                 # Get environment variable
os.listdir("/tmp")                     # List directory
os.path.join("/etc", "ssh", "sshd_config")  # Build path (os.path way)
Enter fullscreen mode Exit fullscreen mode

8. String Operations

8.1 Regular Expressions — The Security Analyst's Power Tool

Regular expressions (regex) are patterns for matching, extracting, and manipulating text. They are indispensable for log analysis, data extraction, and input validation.

import re

# Basic matching
text = "Failed password for root from 192.168.1.100 port 51234"

# re.search — find first match anywhere in string
match = re.search(r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", text)
if match:
    print(match.group(1))              # 192.168.1.100

# re.findall — find all matches
text = "Hosts: 192.168.1.1, 10.0.0.1, 172.16.0.5 are down"
ips = re.findall(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", text)
print(ips)                             # ['192.168.1.1', '10.0.0.1', '172.16.0.5']

# re.match — match at beginning of string
if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", "192.168.1.1"):
    print("Valid IP format")

# re.sub — replace
clean = re.sub(r"password=\S+", "password=REDACTED", "db_password=s3cr3t123")
print(clean)                           # db_password=REDACTED

# re.split — split on pattern
parts = re.split(r"[\s,;]+", "192.168.1.1, 10.0.0.1; 172.16.0.1")
print(parts)                           # ['192.168.1.1', '10.0.0.1', '172.16.0.1']

# Groups — extract parts of a pattern
log_line = "May 29 10:00:01 server sshd[1234]: Accepted publickey for john from 10.10.14.5"
pattern = r"Accepted \w+ for (\w+) from ([\d.]+)"
match = re.search(pattern, log_line)
if match:
    user = match.group(1)              # john
    ip = match.group(2)               # 10.10.14.5
    print(f"User {user} logged in from {ip}")

# Named groups — cleaner code
pattern = r"Accepted \w+ for (?P<user>\w+) from (?P<ip>[\d.]+)"
match = re.search(pattern, log_line)
if match:
    print(match.group("user"))         # john
    print(match.group("ip"))           # 10.10.14.5

# Compile pattern for reuse (performance)
ip_pattern = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
# Now use: ip_pattern.search(), ip_pattern.findall(), etc.
Enter fullscreen mode Exit fullscreen mode

8.2 Essential Regex Patterns for Security Work

import re

patterns = {
    # Network
    "ipv4": r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b",
    "ipv6": r"(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}",
    "mac": r"(?:[0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}",
    "url": r"https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+[/\w .?=%&-]*",
    "domain": r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}",

    # Credentials
    "email": r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
    "base64": r"(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?",

    # Hash patterns
    "md5": r"\b[a-fA-F0-9]{32}\b",
    "sha1": r"\b[a-fA-F0-9]{40}\b",
    "sha256": r"\b[a-fA-F0-9]{64}\b",
    "ntlm": r"\b[a-fA-F0-9]{32}\b",    # Same as MD5 format

    # Credit card (for DLP testing)
    "credit_card": r"\b(?:\d{4}[-\s]?){3}\d{4}\b",

    # AWS
    "aws_key": r"AKIA[0-9A-Z]{16}",
    "aws_secret": r"(?<![A-Z0-9])[A-Za-z0-9/+]{40}(?![A-Za-z0-9/+])",
}

# Usage example: extract IOCs from text
def extract_iocs(text):
    return {
        "ips": re.findall(patterns["ipv4"], text),
        "domains": re.findall(patterns["domain"], text),
        "emails": re.findall(patterns["email"], text),
        "md5": re.findall(patterns["md5"], text),
        "sha256": re.findall(patterns["sha256"], text),
        "urls": re.findall(patterns["url"], text),
    }
Enter fullscreen mode Exit fullscreen mode

8.3 String Encoding and Decoding for Security Work

import base64, binascii, urllib.parse, html

# Base64
encoded = base64.b64encode(b"secret payload").decode()   # String output
decoded = base64.b64decode(encoded)                       # Bytes output

# URL encoding/decoding
url_encoded = urllib.parse.quote("SELECT * FROM users WHERE name='admin'")
# SELECT%20%2A%20FROM%20users%20WHERE%20name%3D%27admin%27
decoded_url = urllib.parse.unquote(url_encoded)

# URL encoding for POST data
from urllib.parse import urlencode
params = {"username": "admin' OR '1'='1", "password": "anything"}
encoded_params = urlencode(params)

# HTML encoding/decoding
html_encoded = html.escape("<script>alert('XSS')</script>")
# &lt;script&gt;alert(&#x27;XSS&#x27;)&lt;/script&gt;
decoded_html = html.unescape(html_encoded)

# Hex encoding
hex_str = "hello".encode().hex()               # '68656c6c6f'
back = bytes.fromhex(hex_str).decode()         # 'hello'

# ROT13 (Caesar cipher, used in old CTFs)
import codecs
rot13 = codecs.encode("Hello World", "rot13")  # Uryyb Jbeyq
original = codecs.decode(rot13, "rot13")        # Hello World

# XOR — common in malware obfuscation
def xor_decode(data, key):
    return bytes(b ^ key for b in data)

obfuscated = bytes([0x48 ^ 0xAA, 0x65 ^ 0xAA, 0x6C ^ 0xAA])  # XOR with 0xAA
decoded = xor_decode(obfuscated, 0xAA)
print(decoded)                                  # b'Hel'
Enter fullscreen mode Exit fullscreen mode

9. Basic Algorithms

9.1 Why Algorithms Matter in Security

Algorithm knowledge in security is not academic. It directly applies to:

  • Understanding time complexity of brute force attacks (how long will this actually take?)
  • Writing efficient log parsers that handle millions of lines
  • Implementing search in threat intelligence lookups
  • Sorting and ranking vulnerabilities by severity
  • Detecting patterns in network traffic

9.2 Searching

# Linear search — O(n) — check every element
def linear_search(lst, target):
    for i, item in enumerate(lst):
        if item == target:
            return i
    return -1

# For security: searching for a specific IP in a blocklist
blocklist = ["10.0.0.1", "192.168.1.100", "172.16.0.5"]
result = linear_search(blocklist, "192.168.1.100")

# Better for membership: use a set — O(1) lookup
blocklist_set = set(blocklist)
if "192.168.1.100" in blocklist_set:   # Instant lookup regardless of list size
    print("IP is blocked")

# This matters at scale:
# Linear search through 1,000,000 IPs: up to 1,000,000 comparisons
# Set lookup: 1 comparison regardless of size
# When processing firewall logs with millions of entries, this is the difference
# between a script that takes 10 seconds and one that takes 2 hours

# Binary search — O(log n) — requires sorted list
def binary_search(lst, target):
    left, right = 0, len(lst) - 1
    while left <= right:
        mid = (left + right) // 2
        if lst[mid] == target:
            return mid
        elif lst[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

# Built-in binary search
import bisect
sorted_ports = sorted([443, 80, 22, 3389, 8080])
index = bisect.bisect_left(sorted_ports, 443)
if sorted_ports[index] == 443:
    print("Port 443 found")
Enter fullscreen mode Exit fullscreen mode

9.3 Sorting

# Python's built-in sort is Timsort — O(n log n) — extremely efficient
ports = [8080, 22, 443, 80, 3389, 21]
ports.sort()                           # In-place sort
sorted_ports = sorted(ports)           # Returns new sorted list

# Sort scan results by severity
vulnerabilities = [
    {"cve": "CVE-2021-44228", "cvss": 10.0, "name": "Log4Shell"},
    {"cve": "CVE-2022-0847", "cvss": 7.8, "name": "Dirty Pipe"},
    {"cve": "CVE-2023-0179", "cvss": 7.8, "name": "Linux Kernel Privesc"},
    {"cve": "CVE-2021-4034", "cvss": 7.8, "name": "PwnKit"},
]
# Sort by CVSS score descending (critical first)
sorted_vulns = sorted(vulnerabilities, key=lambda v: v["cvss"], reverse=True)
for vuln in sorted_vulns:
    print(f"[{vuln['cvss']}] {vuln['cve']}: {vuln['name']}")

# Sort with multiple keys
hosts.sort(key=lambda h: (h["subnet"], h["host_part"]))
Enter fullscreen mode Exit fullscreen mode

9.4 Practical Algorithms for Security

# Frequency analysis — which IPs are making the most requests?
from collections import Counter

with open("/var/log/nginx/access.log", "r") as f:
    ips = [line.split()[0] for line in f if line.strip()]

ip_counts = Counter(ips)
print("Top 10 IPs by request count:")
for ip, count in ip_counts.most_common(10):
    print(f"  {ip}: {count} requests")

# Sliding window — detect port scan (many ports hit in short time)
from collections import defaultdict
from datetime import datetime

connection_log = []  # List of (timestamp, ip, port) tuples

def detect_port_scan(connections, window_seconds=60, threshold=20):
    """Detect if any IP hit more than threshold ports in window_seconds."""
    ip_ports = defaultdict(set)

    for timestamp, ip, port in connections:
        ip_ports[ip].add(port)

    scanners = {ip: ports for ip, ports in ip_ports.items() 
                if len(ports) >= threshold}
    return scanners

# Deduplication while preserving order
def deduplicate(lst):
    seen = set()
    result = []
    for item in lst:
        if item not in seen:
            seen.add(item)
            result.append(item)
    return result

ips_with_duplicates = ["10.0.0.1", "192.168.1.1", "10.0.0.1", "172.16.0.1", "192.168.1.1"]
unique_ips = deduplicate(ips_with_duplicates)
print(unique_ips)                      # ['10.0.0.1', '192.168.1.1', '172.16.0.1']

# Chunking — process large lists in batches
def chunks(lst, size):
    """Split list into chunks of given size."""
    for i in range(0, len(lst), size):
        yield lst[i:i + size]

all_targets = [f"192.168.1.{i}" for i in range(1, 255)]
for batch in chunks(all_targets, 25):
    scan_batch(batch)                  # Scan 25 hosts at a time
Enter fullscreen mode Exit fullscreen mode

9.5 Time Complexity — Why It Matters for Bruteforce

# Understanding time complexity determines if an attack is practical

# Character sets
lowercase = 26
uppercase = 26
digits = 10
special = 32
full = lowercase + uppercase + digits + special  # 94

# Password space calculation
def password_space(charset_size, length):
    return charset_size ** length

# How many passwords in a 8-character lowercase password space?
print(password_space(lowercase, 8))   # 208,827,064,576 (~208 billion)

# At hashcat speed for NTLM (360 billion/second on RTX 4090):
def crack_time_seconds(charset_size, length, hashes_per_second):
    space = password_space(charset_size, length)
    return space / hashes_per_second

speed = 360_000_000_000  # NTLM on RTX 4090
time = crack_time_seconds(lowercase, 8, speed)
print(f"8 lowercase chars, NTLM: {time:.3f} seconds")   # < 1 second!

time = crack_time_seconds(full, 8, speed)
print(f"8 full charset, NTLM: {time:.1f} seconds")      # ~7 seconds

time = crack_time_seconds(full, 12, speed)
hours = time / 3600
years = hours / 8760
print(f"12 full charset, NTLM: {years:.0f} years")      # ~millions of years

# This is exactly why password length matters more than complexity
# And why bcrypt/Argon2 are used (they slow the hashing down by factor of 100,000+)
bcrypt_speed = 100_000  # bcrypt cost 12
time = crack_time_seconds(lowercase, 8, bcrypt_speed)
years = time / (365.25 * 24 * 3600)
print(f"8 lowercase chars, bcrypt: {years:.0f} years")  # Thousands of years
Enter fullscreen mode Exit fullscreen mode

10. Security-Oriented Programming Mindset

10.1 Think Like an Attacker When You Read Code

Every time you read code — whether it is someone else's application, a CTF challenge, or a piece of malware — ask these questions:

Where does user input come from?
input(), command-line arguments, environment variables, file reads, network data, database results — any of these can be attacker-controlled.

Where does that input go?
If input goes into a SQL query without sanitisation → SQL injection.
If input goes into a shell command → command injection.
If input goes into a file path → path traversal.
If input goes into a buffer without length check → buffer overflow.

What are the trust boundaries?
Which parts of the code run with elevated privilege? What can unprivileged input affect?

10.2 Writing Secure Code — The Basics

# INSECURE: Command injection vulnerability
import subprocess

def ping_host(hostname):
    # NEVER DO THIS — if hostname = "google.com; rm -rf /"
    os.system(f"ping -c 1 {hostname}")

# SECURE: Use subprocess with list arguments
def ping_host_safe(hostname):
    # Input validation first
    import re
    if not re.match(r'^[a-zA-Z0-9.-]+$', hostname):
        raise ValueError(f"Invalid hostname: {hostname}")
    # subprocess with list — no shell interpretation
    result = subprocess.run(
        ["ping", "-c", "1", hostname],
        capture_output=True,
        text=True,
        timeout=5
    )
    return result.returncode == 0

# INSECURE: Path traversal vulnerability
def read_file(filename):
    # If filename = "../../../../etc/passwd"
    with open(f"/var/www/files/{filename}") as f:
        return f.read()

# SECURE: Validate and resolve path
from pathlib import Path

def read_file_safe(filename):
    base_dir = Path("/var/www/files").resolve()
    file_path = (base_dir / filename).resolve()
    # Check that resolved path is still inside base directory
    if not str(file_path).startswith(str(base_dir)):
        raise ValueError("Path traversal attempt detected")
    return file_path.read_text()

# INSECURE: Hardcoded credentials
DATABASE_PASSWORD = "supersecret123"   # Never do this

# SECURE: Use environment variables
import os
DATABASE_PASSWORD = os.environ.get("DB_PASSWORD")
if not DATABASE_PASSWORD:
    raise RuntimeError("DB_PASSWORD environment variable not set")
Enter fullscreen mode Exit fullscreen mode

10.3 Writing Reliable Security Scripts

import argparse
import logging
import sys
from datetime import datetime

# Professional script structure

def setup_logging(verbose=False):
    level = logging.DEBUG if verbose else logging.INFO
    logging.basicConfig(
        level=level,
        format='%(asctime)s [%(levelname)s] %(message)s',
        handlers=[
            logging.FileHandler(f"scan_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"),
            logging.StreamHandler(sys.stdout)
        ]
    )

def parse_arguments():
    parser = argparse.ArgumentParser(
        description="Port scanner — educational example",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="Example: python3 scanner.py -t 192.168.1.1 -p 22,80,443"
    )
    parser.add_argument("-t", "--target", required=True, help="Target host or IP")
    parser.add_argument("-p", "--ports", default="80,443", help="Comma-separated ports")
    parser.add_argument("-T", "--timeout", type=float, default=1.0, help="Timeout in seconds")
    parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
    parser.add_argument("-o", "--output", help="Output file (JSON)")
    return parser.parse_args()

def main():
    args = parse_arguments()
    setup_logging(args.verbose)
    logger = logging.getLogger(__name__)

    logger.info(f"Starting scan of {args.target}")

    try:
        ports = [int(p.strip()) for p in args.ports.split(",")]
    except ValueError as e:
        logger.error(f"Invalid port specification: {e}")
        sys.exit(1)

    results = {}
    for port in ports:
        try:
            is_open = scan_port(args.target, port, args.timeout)
            results[port] = "open" if is_open else "closed"
            logger.info(f"Port {port}: {'OPEN' if is_open else 'closed'}")
        except Exception as e:
            logger.error(f"Error scanning port {port}: {e}")
            results[port] = "error"

    if args.output:
        import json
        with open(args.output, "w") as f:
            json.dump({"target": args.target, "results": results}, f, indent=2)
        logger.info(f"Results written to {args.output}")

    return 0 if any(v == "open" for v in results.values()) else 1

if __name__ == "__main__":
    sys.exit(main())
Enter fullscreen mode Exit fullscreen mode

11. Hands-On Projects

These projects build directly on everything in this module. Complete them in order. Do not look up solutions until you have spent genuine time trying.

Project 1: Port Scanner (1.5 hours)

Build a TCP port scanner from scratch.

#!/usr/bin/env python3
"""
Port Scanner — Stage 0.5 Project 1
Build this yourself using the concepts from this module.
"""

import socket
import sys
from concurrent.futures import ThreadPoolExecutor

def scan_port(host, port, timeout=1.0):
    """Return True if port is open."""
    # Your implementation here
    pass

def scan_range(host, start_port, end_port, threads=100):
    """Scan a range of ports using threads."""
    open_ports = []
    # Use ThreadPoolExecutor for concurrent scanning
    # Your implementation here
    return sorted(open_ports)

def main():
    # Use argparse for argument handling
    # Accept: target host, port range, thread count, timeout
    # Output: list of open ports with service names (use socket.getservbyport)
    pass

if __name__ == "__main__":
    main()

# Required features:
# 1. Validate IP/hostname input
# 2. Handle connection refused, timeouts separately
# 3. Multi-threaded scanning
# 4. Show progress
# 5. Output results in a clean format
# 6. Optionally save to JSON
Enter fullscreen mode Exit fullscreen mode

Project 2: Log Analyser (1 hour)

Parse an SSH auth log and produce a security report.

#!/usr/bin/env python3
"""
SSH Log Analyser — Stage 0.5 Project 2
Analyse /var/log/auth.log and produce security insights.
"""

# Your script should:
# 1. Parse auth.log line by line
# 2. Extract: timestamp, event type, username, source IP
# 3. Count: failed attempts by IP, failed attempts by username
# 4. Identify: IPs with > 10 failed attempts (potential brute force)
# 5. Find: successful logins after multiple failures (potential successful brute force)
# 6. Output: formatted report with counts and top attackers
# 7. Optionally: write report to file

# Sample output format:
# === SSH Security Report ===
# Period: 2026-05-29 00:00:00 to 2026-05-29 23:59:59
# Total events: 15,432
# Failed login attempts: 14,891
# Successful logins: 541
#
# Top 5 attacking IPs:
#   1. 45.12.34.56: 2,341 attempts
#   2. 89.45.67.89: 1,876 attempts
#   ...
#
# Suspicious: IPs with successful login after 10+ failures:
#   - 10.0.0.5: 47 failures, then 1 success for user 'admin'
Enter fullscreen mode Exit fullscreen mode

Project 3: Subdomain Enumerator (45 minutes)

#!/usr/bin/env python3
"""
Subdomain Enumerator — Stage 0.5 Project 3
Discover subdomains using a wordlist and DNS resolution.
"""

import socket
from concurrent.futures import ThreadPoolExecutor

def resolve_subdomain(subdomain, domain):
    """Try to resolve subdomain.domain. Return IP if exists, None if not."""
    fqdn = f"{subdomain}.{domain}"
    try:
        ip = socket.gethostbyname(fqdn)
        return fqdn, ip
    except socket.gaierror:
        return None

def enumerate_subdomains(domain, wordlist_path, threads=50):
    """Enumerate subdomains using a wordlist."""
    # Read wordlist
    # Use ThreadPoolExecutor to resolve concurrently
    # Return list of (fqdn, ip) tuples for discovered subdomains
    pass

# Test with: python3 subdomain_enum.py -d example.com -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
# Wordlists are in /usr/share/seclists/ on Kali
Enter fullscreen mode Exit fullscreen mode

Project 4: IOC Extractor (30 minutes)

#!/usr/bin/env python3
"""
IOC Extractor — Stage 0.5 Project 4
Extract Indicators of Compromise from text (emails, logs, reports).
"""

import re
import json
import sys

# Extract all of:
# - IPv4 addresses
# - IPv6 addresses  
# - Domain names
# - URLs (http and https)
# - Email addresses
# - MD5 hashes
# - SHA1 hashes
# - SHA256 hashes
# - File paths (Windows and Linux)

def extract_iocs(text):
    """Extract all IOC types from text. Return dict of IOC type → list."""
    # Your regex-based implementation here
    pass

# Usage: cat suspicious_email.txt | python3 ioc_extractor.py
# Or: python3 ioc_extractor.py -f malware_report.txt -o iocs.json
Enter fullscreen mode Exit fullscreen mode

Project 5: Modbus Scanner (OT/ICS Specific — 2 hours)

#!/usr/bin/env python3
"""
Modbus TCP Scanner — Stage 0.5 Project 5
Discover and fingerprint Modbus TCP devices on a network.
This is directly relevant to OT/ICS security assessment.

Install: pip3 install pymodbus
"""

from pymodbus.client import ModbusTcpClient
import ipaddress

def probe_modbus(host, port=502, timeout=2):
    """
    Probe a host for Modbus TCP.
    Try to read device identification (function code 43/14).
    Return device info dict or None.
    """
    try:
        client = ModbusTcpClient(host, port=port, timeout=timeout)
        if not client.connect():
            return None

        # Try reading holding registers (function code 03)
        # Registers 0-9, unit ID 1
        result = client.read_holding_registers(0, 10, slave=1)

        info = {"host": host, "port": port, "responsive": True}

        if not result.isError():
            info["registers"] = result.registers

        client.close()
        return info
    except Exception as e:
        return None

def scan_subnet(subnet, threads=50):
    """Scan an entire subnet for Modbus devices."""
    network = ipaddress.ip_network(subnet, strict=False)
    found_devices = []

    # Your threaded implementation here

    return found_devices

# This scanner demonstrates:
# 1. Industrial protocol implementation in Python
# 2. Network scanning with proper error handling
# 3. Concurrency for performance
# 4. Structured output suitable for reporting
Enter fullscreen mode Exit fullscreen mode

12. Further Reading and Resources

Books

  • "Automate the Boring Stuff with Python" — Al Sweigart (free at automatetheboringstuff.com). Practical Python without the academic fluff.
  • "Black Hat Python" — Justin Seitz. Security-specific Python — network sniffer, trojans, forensics tools. Directly applicable.
  • "Violent Python" — TJ O'Connor. Older but foundational — shows how to build security tools from scratch.
  • "Learning Python" — Mark Lutz. The comprehensive reference if you want deep language knowledge.

Online Resources

  • Python official docs — docs.python.org/3 — the most accurate reference
  • Real Python (realpython.com) — high quality tutorials
  • PyMOTW (pymotw.com) — Python Module of the Week — deep dives into standard library
  • regex101.com — test regex patterns interactively with explanation

Practice

  • Exercism.io (Python track) — structured exercises with mentor feedback
  • HackerRank (Python domain) — algorithmic challenges
  • PicoCTF — CTF challenges where Python scripting solves many problems
  • CryptoHack (cryptohack.org) — cryptography challenges solved in Python

Essential Security Libraries

# Install the core security Python toolkit
pip3 install \
    scapy \          # Packet crafting and analysis
    requests \       # HTTP library
    pwntools \       # CTF/exploit development
    impacket \       # Windows network protocols
    cryptography \   # Cryptographic operations
    paramiko \       # SSH client/server
    pymodbus \       # Modbus industrial protocol
    python-nmap \    # Nmap wrapper
    shodan \         # Shodan API
    volatility3      # Memory forensics
Enter fullscreen mode Exit fullscreen mode

Stage 00 Complete — What You Now Have

With this module, Stage 00 — Foundations is complete. Here is what you have built:

Stage 00 — Complete
├── ✅ Hardware Fundamentals     — CPU rings, RAM attacks, firmware, physical security
├── ✅ OS Fundamentals           — Kernel, user/kernel mode, processes, file systems, syscalls
├── ✅ Windows Fundamentals      — Registry, services, UAC, Defender, PowerShell, Event Log
├── ✅ Linux Fundamentals        — Permissions, users, services, cron, SSH, logs
└── ✅ Programming Fundamentals  — Python, variables, loops, functions, files, regex, algorithms
Enter fullscreen mode Exit fullscreen mode

What this foundation enables:

  • You understand why the stack smashing vulnerability works — because you know how memory layout works, what stack frames are, and what the instruction pointer does.
  • You understand why Pass-the-Hash works — because you know NTLM authentication, the Windows token system, and how credentials are stored in LSASS.
  • You understand why SUID exploitation works — because you know the Linux permission model, the difference between real and effective UID, and how the kernel enforces privilege boundaries.
  • You can write scripts that automate your work — because you have Python fundamentals and understand file I/O, error handling, and basic algorithms.

Next: Stage 01 — Network Fundamentals

The internet runs on protocols. Every attack crosses a network. Every defence monitors network traffic. Stage 01 builds the networking knowledge that everything from enumeration to exploitation to detection depends on.


Next Stage: Stage 01 — Network Fundamentals

Previous Module: Stage 0.4 — Linux Fundamentals

Stage Index: Stage 00 README


This document is part of the Cybersecurity × OT/ICS Security Full Roadmap series. All techniques are presented for educational purposes, authorised security research, and defensive security practice. Always obtain proper authorisation before testing any system.

Top comments (0)