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
- Why Programming Is the Multiplier
- Python — The Language of Security
- Variables and Data Types
- Loops and Conditionals
- Functions
- Error Handling
- File Read and Write
- String Operations
- Basic Algorithms
- Security-Oriented Programming Mindset
- Hands-On Projects
- 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:
- Read code and understand what it does
- Write scripts that automate your work
- Modify existing tools for your specific needs
- 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
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
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
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'>
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'
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'
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', ...]
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]}
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
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
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)
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}")
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)
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)
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
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)
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
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}")
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}")
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}")
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}")
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")
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
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)
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]
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}")
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)
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.
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),
}
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>")
# <script>alert('XSS')</script>
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'
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")
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"]))
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
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
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")
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())
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
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'
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
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
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
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
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
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)