Skip to content
CSY103 Week 05 Beginner

Practice building reusable functions before moving to reading resources.

Programming Fundamentals

Track your progress through this week's content

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 definition
  • greet_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)

  1. How does is_valid_ip() call within is_private_ip()?
  2. What does if __name__ == "__main__": do?
  3. How does assess_connection_risk() use other functions?
  4. What would you add to this library?

Week 5 Outcome Check

By the end of this week, you should be able to:

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

Start TryHackMe Python Basics →

📝 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

Start PicoCTF General Skills →

💡 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.

Lab: IOC Validation Library

Goal: Build a library of functions for validating Indicators of Compromise (IOCs).

Linux/Windows Path (same for both)

  1. Create ioc_validator.py
  2. 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 check
    • is_valid_url(url) - check for http/https prefix
    • classify_ioc(ioc_string) - return type: "MD5", "SHA256", "IP", "Domain", "URL", or "Unknown"
  3. Include docstrings for each function
  4. Add a test section with if __name__ == "__main__":
  5. Test with at least 3 examples per function

Deliverable (submit):

Checkpoint Questions

  1. What keyword is used to define a function in Python?
  2. What is the difference between a parameter and an argument?
  3. What does a function return if it has no return statement?
  4. Why should you avoid global variables in security code?
  5. What is a docstring and why is it useful?
  6. 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:

A strong reflection will connect functions to code quality, collaboration, and the professional practice of security tool development.

Verified Resources & Videos

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.

← Previous: Week 04 Next: Week 06 →

Week 05 Quiz

Test your understanding of the weekly concepts.

Format: 10 multiple-choice questions. Passing score: 70%. Time: Untimed.

Take Quiz