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.
Step 1: Create the Library
Create sec_utils.py:
# Security Utility Library
# A collection of reusable security functions
def is_valid_ip(ip_string):
"""Validate IPv4 address format."""
parts = ip_string.split(".")
if len(parts) != 4:
return False
for part in parts:
try:
num = int(part)
if num < 0 or num > 255:
return False
except ValueError:
return False
return True
def is_private_ip(ip_address):
"""Check if IP is in RFC 1918 private range."""
if not is_valid_ip(ip_address):
return False
if ip_address.startswith("10."):
return True
if ip_address.startswith("172."):
parts = ip_address.split(".")
if 16 <= int(parts[1]) <= 31:
return True
if ip_address.startswith("192.168."):
return True
return False
def get_port_service(port):
"""Return common service name for port."""
services = {
21: "FTP",
22: "SSH",
23: "Telnet",
25: "SMTP",
53: "DNS",
80: "HTTP",
443: "HTTPS",
3389: "RDP",
3306: "MySQL",
5432: "PostgreSQL"
}
return services.get(port, "Unknown")
def assess_connection_risk(src_ip, dst_port, is_encrypted=True):
"""Assess risk level of a network connection."""
risk_score = 0
risk_factors = []
# External source is higher risk
if not is_private_ip(src_ip):
risk_score += 2
risk_factors.append("External source")
# Unencrypted services
if not is_encrypted:
risk_score += 2
risk_factors.append("Unencrypted")
# High-risk ports
high_risk_ports = [23, 21, 3389] # Telnet, FTP, RDP
if dst_port in high_risk_ports:
risk_score += 3
risk_factors.append(f"High-risk service ({get_port_service(dst_port)})")
# Determine level
if risk_score >= 5:
level = "HIGH"
elif risk_score >= 3:
level = "MEDIUM"
else:
level = "LOW"
return level, risk_score, risk_factors
# Test the library when run directly
if __name__ == "__main__":
print("Testing Security Utility Library")
print("=" * 40)
# Test IP validation
test_ips = ["192.168.1.1", "10.0.0.1", "8.8.8.8", "999.1.1.1", "abc.def.ghi.jkl"]
print("\nIP Validation Tests:")
for ip in test_ips:
valid = is_valid_ip(ip)
private = is_private_ip(ip) if valid else "N/A"
print(f" {ip}: Valid={valid}, Private={private}")
# Test port services
print("\nPort Service Tests:")
for port in [22, 80, 443, 3389, 9999]:
print(f" Port {port}: {get_port_service(port)}")
# Test risk assessment
print("\nRisk Assessment Tests:")
connections = [
("192.168.1.50", 443, True),
("203.0.113.50", 22, True),
("8.8.8.8", 23, False),
]
for src, port, enc in connections:
level, score, factors = assess_connection_risk(src, port, enc)
print(f" {src}:{port} -> {level} (score: {score})")
for f in factors:
print(f" - {f}")
Step 2: Run and Test
Run python3 sec_utils.py to execute the tests.
Step 3: Reflection (mandatory)
- How does
is_valid_ip()call withinis_private_ip()? - What does
if __name__ == "__main__":do? - How does
assess_connection_risk()use other functions? - What would you add to this library?
Week 5 Outcome Check
By the end of this week, you should be able to:
- Define functions with proper syntax
- Use parameters including default values
- Return single and multiple values
- Understand local vs. global scope
- Build and use a library of reusable functions
- Write docstrings to document functions
Next week: Data Structures—where we learn to organize collections of security data with lists and dictionaries.
🎯 Hands-On Labs (Free & Essential)
Practice building reusable functions before moving to reading resources.
🎮 TryHackMe: Python Basics (Functions)
What you'll do: Define functions with parameters and return values.
Why it matters: Reusable functions turn scripts into reliable tools.
Time estimate: 1-1.5 hours
📝 Lab Exercise: Helper Function Library
Task: Write 3 helper functions for parsing IPs, validating ports, and normalizing usernames.
Deliverable: A small `utils.py` module with docstrings and a short demo.
Why it matters: Security work benefits from consistent utility functions.
Time estimate: 60-90 minutes
🏁 PicoCTF Practice: General Skills (Functions)
What you'll do: Solve beginner challenges that require modular code.
Why it matters: Modular code is easier to test and reuse.
Time estimate: 1-2 hours
💡 Lab Tip: Give functions clear, descriptive names — they become your security toolkit.
🛡️ Secure Coding: Defensive Functions
Functions are security boundaries. A safe function has clear inputs, well-defined outputs, and predictable failure modes.
Defensive function checklist:
- Validate inputs at the boundary
- Avoid hidden global state
- Return explicit errors, not silent failures
- Document assumptions in docstrings
📚 Building on CSY101 Week-13: Treat each function as a threat model boundary.
Resources
Complete the required resources to build your foundation.
- Python Tutorial - Defining Functions · 30-45 min · 50 XP · Resource ID: csy103_w5_r1 (Required)
- Real Python - Defining Your Own Functions · 45-60 min · 50 XP · Resource ID: csy103_w5_r2 (Required)
- Automate the Boring Stuff - Chapter 3: Functions · 30-45 min · 25 XP · Resource ID: csy103_w5_r3 (Optional)
Lab: IOC Validation Library
Goal: Build a library of functions for validating Indicators of Compromise (IOCs).
Linux/Windows Path (same for both)
- Create
ioc_validator.py - Implement these functions:
is_valid_md5(hash_string)- check if valid MD5 (32 hex chars)is_valid_sha256(hash_string)- check if valid SHA256 (64 hex chars)is_valid_domain(domain)- basic domain format checkis_valid_url(url)- check for http/https prefixclassify_ioc(ioc_string)- return type: "MD5", "SHA256", "IP", "Domain", "URL", or "Unknown"
- Include docstrings for each function
- Add a test section with
if __name__ == "__main__": - Test with at least 3 examples per function
Deliverable (submit):
- Your
ioc_validator.pylibrary - Screenshot showing all tests passing
- One paragraph: How would this library help in incident response?
Checkpoint Questions
- What keyword is used to define a function in Python?
- What is the difference between a parameter and an argument?
- What does a function return if it has no return statement?
- Why should you avoid global variables in security code?
- What is a docstring and why is it useful?
- How can functions make your code more testable?
Weekly Reflection
Reflection Prompt (200-300 words):
This week you learned functions—the mechanism that transforms repetitive code into reusable tools. Functions are fundamental to professional software development and security tool creation.
Reflect on these questions:
- How do functions improve code maintainability in security tools?
- Think about a script you wrote earlier in this course. How would you refactor it using functions?
- Why is it important for security functions to be "pure" (no global state, predictable output)?
- How might you share your security utility library with teammates or the community?
A strong reflection will connect functions to code quality, collaboration, and the professional practice of security tool development.
Verified Resources & Videos
- Python Function Best Practices: Python Docs - Functional Programming HOWTO
- Security Tool Reference: Volatility Foundation
- Security perspective (MITRE ATT&CK): MITRE ATT&CK — Python (T1059.006)
Functions are your building blocks. From this week forward, think in terms of "what function do I need?" rather than "what code do I write?" Next week: organizing data with lists and dictionaries.