Opening Framing: Write Once, Use Everywhere
By now, you've written scripts that analyze passwords, classify logins, and process logs. But what happens when you need the same logic in multiple scripts? Copy-paste creates maintenance nightmares—fix a bug in one place, forget another, and now your tools behave inconsistently.
Functions solve this. A function packages code into a reusable unit with
a name. Instead of rewriting password strength logic, you call
check_password_strength(password). Instead of duplicating
IP validation, you call is_valid_ip(address).
Professional security tools are built from functions. Nmap isn't one giant script—it's thousands of functions working together. This week, you learn to build and organize code like a professional.
Key insight: Functions enable code reuse, testing, and collaboration. A well-designed function is a tool you can trust and share.
1) Defining and Calling Functions
A function is defined with def, given a name, and called
with parentheses:
# Define a function
def greet_analyst():
print("Welcome to the Security Operations Center")
print("All systems operational")
# Call the function
greet_analyst()
# Call it again - reuse!
greet_analyst()
Function Anatomy:
def- keyword that starts a function definitiongreet_analyst- function name (use snake_case)()- parentheses for parameters (empty here):- colon ends the definition line- Indented block - the function body (code that runs when called)
# Security example: Alert function
def send_security_alert():
print("[ALERT] Security event detected!")
print("[ALERT] Notifying SOC team...")
print("[ALERT] Logging to SIEM...")
# Use whenever needed
send_security_alert()
Key insight: Defining a function doesn't run it—calling it does. You can define functions at the top of your script and call them later.
2) Parameters: Functions That Accept Input
Parameters let functions work with different data each time:
# Function with one parameter
def check_port(port_number):
if port_number < 1024:
print(f"Port {port_number}: Privileged port (requires root)")
else:
print(f"Port {port_number}: Unprivileged port")
# Call with different values
check_port(22)
check_port(443)
check_port(8080)
Multiple Parameters:
# Function with multiple parameters
def log_connection(src_ip, dst_port, protocol):
print(f"Connection: {src_ip} -> port {dst_port} ({protocol})")
# Call with arguments
log_connection("192.168.1.50", 443, "TCP")
log_connection("10.0.0.25", 53, "UDP")
Default Parameter Values:
# Parameters can have defaults
def scan_port(port, timeout=5, verbose=False):
if verbose:
print(f"Scanning port {port} with {timeout}s timeout...")
# Scanning logic here
print(f"Port {port}: checked")
# Call with defaults
scan_port(22)
# Override defaults
scan_port(80, timeout=10)
scan_port(443, verbose=True)
scan_port(8080, timeout=3, verbose=True)
Key insight: Parameters make functions flexible. The same function handles any IP, any port, any data—you just pass different arguments.
3) Return Values: Functions That Produce Output
Functions can calculate results and return them for use elsewhere:
# Function that returns a value
def calculate_risk_score(failed_attempts, unusual_time, foreign_ip):
score = 0
if failed_attempts > 3:
score += 3
if unusual_time:
score += 2
if foreign_ip:
score += 2
return score
# Use the returned value
risk = calculate_risk_score(5, True, False)
print(f"Risk score: {risk}")
# Use directly in conditions
if calculate_risk_score(10, True, True) >= 5:
print("HIGH RISK - Take action!")
Returning Multiple Values:
# Return multiple values as a tuple
def analyze_password(password):
length = len(password)
has_upper = any(c.isupper() for c in password)
has_digit = any(c.isdigit() for c in password)
is_strong = length >= 12 and has_upper and has_digit
return length, has_upper, has_digit, is_strong
# Unpack returned values
pwd_len, has_up, has_dig, strong = analyze_password("SecurePass123")
print(f"Length: {pwd_len}, Has upper: {has_up}, Strong: {strong}")
Return None (implicit):
# Functions without return statement return None
def print_alert(message):
print(f"[ALERT] {message}")
# No return statement
result = print_alert("Test")
print(result) # None
Key insight: Return transforms functions from "doers" into "calculators." A function that returns can be used in expressions, conditions, and assignments—making your code more flexible.
4) Scope: Where Variables Live
Variables inside functions are "local"—they exist only within that function. This prevents accidental interference between functions:
# Local scope demonstration
def process_ip():
ip = "192.168.1.100" # Local variable
print(f"Inside function: {ip}")
process_ip()
# print(ip) # ERROR: ip doesn't exist outside the function
Global vs. Local:
# Global variable (defined outside functions)
threshold = 5
def check_attempts(attempts):
# Can READ global variables
if attempts > threshold:
return "ALERT"
return "OK"
print(check_attempts(3)) # OK
print(check_attempts(10)) # ALERT
Why Scope Matters for Security:
# BAD: Global state can cause bugs
password = "" # Global - dangerous!
def set_password(new_pass):
global password # Modifying global
password = new_pass
def check_password(attempt):
return attempt == password
# This "works" but is fragile and hard to debug
set_password("secret123")
print(check_password("secret123")) # True
# BETTER: Keep data local, pass as parameters
def verify_password(stored_hash, attempt):
# All data comes from parameters - clear and testable
return hash_password(attempt) == stored_hash
Key insight: Avoid global variables in security code. Functions should receive all needed data through parameters and return results explicitly. This makes code predictable and auditable.
5) Building a Function Library
Professional code organizes related functions together. Here's a mini-library of security utility functions:
# security_utils.py - A collection of security functions
def is_private_ip(ip_address):
"""Check if IP is in private range (RFC 1918)."""
if ip_address.startswith("10."):
return True
if ip_address.startswith("172."):
second_octet = int(ip_address.split(".")[1])
if 16 <= second_octet <= 31:
return True
if ip_address.startswith("192.168."):
return True
return False
def classify_port(port):
"""Classify port by range and common services."""
if port < 0 or port > 65535:
return "INVALID"
if port < 1024:
return "PRIVILEGED"
if port < 49152:
return "REGISTERED"
return "DYNAMIC"
def check_password_strength(password):
"""Return password strength score (0-5)."""
score = 0
if len(password) >= 8:
score += 1
if len(password) >= 12:
score += 1
if any(c.isupper() for c in password):
score += 1
if any(c.islower() for c in password):
score += 1
if any(c.isdigit() for c in password):
score += 1
return score
def format_alert(severity, message, source_ip=None):
"""Format a security alert message."""
alert = f"[{severity.upper()}] {message}"
if source_ip:
alert += f" (Source: {source_ip})"
return alert
Using Your Library:
# In another script or interactive session
# from security_utils import is_private_ip, classify_port
# Test your functions
print(is_private_ip("192.168.1.50")) # True
print(is_private_ip("8.8.8.8")) # False
print(classify_port(22)) # PRIVILEGED
print(classify_port(8080)) # REGISTERED
print(check_password_strength("Abc123!@#")) # 4
print(format_alert("high", "Brute force detected", "10.0.0.5"))
Key insight: Building a personal library of security functions makes you more efficient over time. Every function you write and test is a tool you never have to build again.
Real-World Context: Functions in Security Tools
Every professional security tool is built from functions:
Volatility (Memory Forensics): Volatility organizes
analysis capabilities as plugins, each implemented as functions. When
you run vol.py -f memdump.raw pslist, you're calling the
pslist function that extracts process information.
Scapy (Packet Manipulation): Scapy provides functions
like sniff(), send(), and sr1()
that handle complex networking operations. You call
sniff(filter="tcp port 80", count=10)—a function with parameters.
YARA (Malware Detection): YARA rules are essentially functions that return True (match) or False (no match). Each rule encapsulates detection logic you can reuse across millions of files.
MITRE ATT&CK Reference: Technique T1059.006 (Python) notes that attackers often use Python's extensive function libraries for reconnaissance, exfiltration, and C2. Understanding functions helps you understand how attackers structure their tools.
Key insight: Functions are the building blocks of professional tools. Learning to write good functions is learning to build tools others can use.
Guided Lab: Security Utility Library
Let's build a collection of reusable security functions and demonstrate their use in a practical scenario.