Be the first to review “Building Hacking Tools from Scratch: Python Projects for Security Professionals” Cancel reply

by Carter Hayes
Security tools are the extension of a professional’s judgment and skill. Knowing how to use scanners and frameworks is useful, but the ability to build your own tools from scratch separates capable practitioners from those who can only run software written by others. Building Hacking Tools from Scratch: Python Projects for Security Professionals is a hands-on guide that teaches you how to create your own security utilities using Python.
Carter Hayes takes a project-based approach. Each chapter introduces a real security need, then walks you through designing and implementing a Python tool to meet that need. You will build network scanners, port scanners, subdomain enumerators, password strength checkers, hash crackers, simple keyloggers for authorized testing, and basic packet sniffers. Along the way, you will learn not just the code, but the reasoning behind each design decision.
The book assumes a basic understanding of Python but does not require you to be an expert programmer. Concepts are explained clearly, and the code is broken down into manageable pieces. You will learn how to work with sockets, parse network protocols, handle concurrency, read and write files, interact with APIs, and structure your code so it can grow beyond a single script. There is also guidance on how to package your tools, add command-line interfaces, and document them for others.
Not sure yet? Here is the full table of contents and the first chapter, exactly as it appears in the book.
18 chapters in the complete book.
Building effective security tools requires more than just understanding vulnerabilities and attack vectors. You need to master the technical foundation that transforms security knowledge into working code. Python has become the de facto language for security professionals, not because it’s the fastest or most low-level option, but because it strikes the perfect balance between power and productivity. When you’re testing a new exploit technique at two in the morning or need to rapidly prototype a custom scanning tool before a client meeting, Python lets you focus on the security logic rather than fighting with language syntax.
This chapter builds the Python foundation you’ll rely on throughout this book. We’re not starting from absolute zero. I’m assuming you understand basic programming concepts like variables, loops, and functions. Instead, we’ll focus specifically on the Python skills that matter for security work: manipulating bytes and understanding encodings, working directly with network sockets, managing project dependencies properly, and structuring code so you can actually debug it when something goes wrong at three in the morning.
The reality of security engineering is that you’re constantly working at the boundary between high-level application logic and low-level protocol details. You might be parsing HTTP headers one moment and crafting raw TCP packets the next. Python excels in this environment because it provides both high-level abstractions through rich libraries and low-level access when you need to work with raw bytes or implement custom protocol handlers. Let’s build that versatility into your skill set from the beginning.
The Bytes and Strings Divide
One of the first places security-focused Python code differs from typical application development is in how you handle text and binary data. In modern Python 3, the distinction between strings and bytes is explicit and enforced. Strings represent Unicode text: the kind you display to users or write in log files. Bytes represent raw binary data: the kind that flows across networks or gets written to files.
Security tools constantly straddle this boundary. When you send data across a network socket, you’re always sending bytes. When you receive data, you’re receiving bytes. But the content might represent ASCII text, UTF-8 encoded JSON, binary protocol headers, or encrypted payloads. Understanding when and how to convert between these formats prevents an entire class of subtle bugs.
Consider a simple example: you’re building a tool to scan for open ports. When you connect to an HTTP server, you need to send a properly formatted request. That request looks like text, but the socket requires bytes. The standard approach uses the encode method to convert strings to bytes with a specific encoding, typically UTF-8 for modern applications or ASCII for older protocols.
When you send an HTTP request, you might write something like this: “GET / HTTP/1.1\r\nHost: example.com\r\n\r\n”. This looks like a string in your code, but before it can travel across a network socket, it needs conversion to bytes. The encode method handles this: request_bytes = request_string.encode(‘utf-8’). The reverse operation uses the decode method: response_string = response_bytes.decode(‘utf-8’).
Here’s where security work gets interesting. Not all data you encounter will be valid UTF-8 or even valid text. You might receive binary protocols, malformed data, or deliberately crafted attack payloads designed to crash parsers. The decode method will raise an exception if it encounters invalid encoding, which could crash your tool at a critical moment. Robust security code often uses the errors parameter: response_string = response_bytes.decode(‘utf-8′, errors=’ignore’) or errors=’replace’ to substitute invalid sequences rather than failing.
Working with raw bytes also means thinking in hexadecimal and understanding byte representation. When you’re analyzing network traffic or crafting exploit payloads, you’ll frequently work with specific byte sequences. Python provides multiple ways to create bytes objects. You can use byte literals like b’\x41\x42\x43′, which represents the bytes for ASCII characters ABC, or you can construct bytes from a list of integers: bytes([65, 66, 67]) produces the same result since 65 is the decimal value for hex 0x41, which is ASCII ‘A’.
The bytes and bytearray types both represent sequences of bytes, but bytearray is mutable while bytes is immutable. When you’re building packets or payloads incrementally, bytearray offers better performance since you can modify it in place rather than creating new bytes objects with each change. For reading data from sockets or files, bytes is typically sufficient and preferred for its immutability guarantees.
Socket Programming for Security Tools
Network programming forms the foundation of most security tools. Whether you’re building a port scanner, a custom HTTP client, or a remote access tool, you’ll work with sockets. Python’s socket module provides a straightforward interface to BSD sockets, the standard networking API on Unix-like systems and Windows.
A socket represents one endpoint of a network connection. The fundamental pattern involves creating a socket, connecting it to a remote address and port, sending and receiving data, then closing the connection. For TCP connections, which guarantee reliable, ordered delivery of data, the process looks conceptually simple but contains important details that matter for security tools.
Creating a TCP socket starts with importing the socket module and calling socket.socket with appropriate parameters. The socket.AF_INET constant specifies IPv4 addressing, while socket.SOCK_STREAM indicates a TCP connection rather than UDP. Once you have a socket object, you connect it to a target using a tuple of hostname or IP address and port number.
Here’s what the basic pattern looks like in practice. You import socket, create a socket object with socket.socket(socket.AF_INET, socket.SOCK_STREAM), then call the connect method with a tuple like (‘example.com’, 80). Once connected, you can send data using the send or sendall methods, sendall is generally preferred because it handles the case where not all data gets sent in a single call, retrying until everything is transmitted. Receiving data uses the recv method with a buffer size parameter indicating the maximum bytes to receive at once.
Sockets for security work require careful handling of several edge cases. Network operations can fail in numerous ways: the remote host might be down, firewalls might block connections, or the remote server might simply not respond. Without proper timeout handling, your tool might hang indefinitely waiting for a response that will never come. Setting socket timeouts prevents this: after creating your socket, call socket.settimeout(5.0) to limit operations to five seconds.
Another critical consideration is that TCP is a streaming protocol without inherent message boundaries. When you call recv, you might receive part of a message, multiple messages, or any arbitrary chunk of the data stream. Your code needs to handle reassembling complete messages. For line-based protocols like HTTP or SMTP, you can read until you encounter a delimiter like \r\n. For binary protocols, you often need to parse length headers to know how many bytes to expect.
Security tools also frequently need to work with UDP sockets, which use socket.SOCK_DGRAM. UDP provides no connection guarantees: packets can be lost, duplicated, or arrive out of order. For reconnaissance tools or DoS testing, UDP’s connectionless nature can be advantageous. Instead of connecting, you use sendto with a destination address for each packet, and recvfrom to receive packets along with the sender’s address.
Raw sockets take this even further, letting you craft packets at the IP layer or below. This requires administrative privileges but enables building custom protocol implementations or packet injection tools. Creating a raw socket uses socket.SOCK_RAW, and you need to construct packet headers manually. Libraries like scapy abstract much of this complexity, but understanding the underlying socket operations helps when debugging or optimizing.
One often-overlooked aspect of socket programming for security tools is proper resource cleanup. Sockets are operating system resources with limits on how many can be open simultaneously. Always close sockets when done, ideally using a try-finally block or the with statement to ensure cleanup even if exceptions occur.
Virtual Environments and Dependency Management
Professional security tool development means managing dependencies properly. Different tools require different library versions, and you don’t want your packet manipulation tool’s requirements conflicting with your web vulnerability scanner’s dependencies. Python’s virtual environment system solves this by creating isolated Python environments for each project.
Virtual environments contain their own Python interpreter copy and site-packages directory where libraries get installed. This isolation means each project controls its exact dependency versions without affecting other projects or the system Python installation. Creating a virtual environment uses the built-in venv module: python3 -m venv security_env creates a new environment in the current directory.
Activating the environment changes your shell to use that environment’s Python and pip. On Linux or macOS, you run source security_env/bin/activate. On Windows, the command is security_env\Scripts\activate. Your command prompt changes to show the active environment name. Now when you install packages with pip, they install only in this environment.
Dependency management extends beyond just installation. Professional projects use requirements.txt files to document exact dependency versions. After installing the packages your tool needs, run pip freeze > requirements.txt to capture the complete dependency list with version numbers. Anyone else working on your project can then recreate the exact environment with pip install -r requirements.txt.
This version pinning matters more for security tools than typical applications because security libraries often have breaking changes between versions. A tool that works perfectly with scapy 2.4.5 might fail with 2.5.0 if APIs changed. Pinning versions ensures reproducible behavior.
For more complex projects, consider tools like pipenv or poetry that combine virtual environment management with dependency resolution. These tools can handle development dependencies separately from runtime dependencies and provide deterministic builds through lock files. Pipenv, for instance, maintains a Pipfile for high-level dependencies and Pipfile.lock with exact versions and hashes, providing security against dependency tampering.
Essential Security Libraries
Python’s ecosystem includes specialized libraries that accelerate security tool development. Understanding their capabilities and when to use each one turns you from someone writing everything from scratch into a productive security engineer who builds on solid foundations.
The requests library has become the standard for HTTP operations, replacing Python’s lower-level urllib. Security tools constantly interact with web services: checking for vulnerabilities, exploiting APIs, or exfiltrating data. Requests provides an intuitive API: requests.get(‘https://example.com’) returns a response object with status code, headers, and content properties. For more complex operations, you can send custom headers, handle cookies, use authentication, and configure SSL verification.
That last point (SSL verification) illustrates why requests excels for security work. By default, the library verifies SSL certificates, but you can disable this for testing against hosts with self-signed certificates by passing verify=False. This flexibility lets you write tools that work in both production and testing environments without code changes, just configuration.
Scapy represents the opposite end of the spectrum from requests’ high-level convenience. It provides low-level packet crafting and manipulation, letting you build custom protocol implementations or analyze traffic at the packet level. With scapy, you can construct packets layer by layer (IP layer, TCP layer, payload) then send them on the network or capture responses. This power enables building custom scanning tools that evade detection by implementing unusual protocol variants.
Using scapy looks different from typical Python. You import layer classes like IP and TCP, then combine them with the division operator: packet = IP(dst=”192.168.1.1″)/TCP(dport=80). This syntax creates a packet with an IP header pointing to the destination and a TCP header for port 80. The send function transmits it, while sr functions (send and receive) wait for responses.
For security tools that need remote system access, paramiko implements SSH protocol support in pure Python. This enables building automated penetration testing tools, remote command execution frameworks, or file transfer utilities. Paramiko handles authentication, encrypted channels, and remote command execution. Creating an SSH connection involves instantiating an SSHClient object, setting host key policy, connecting with credentials, then executing commands or opening SFTP sessions for file operations.
These libraries share a common characteristic: they abstract complex protocols while still providing access to low-level details when needed. Security work constantly requires both high-level automation and low-level protocol manipulation, and this flexibility matters.
Code Structure and Maintainability
Security tools that start as quick scripts often evolve into critical infrastructure. Writing maintainable code from the beginning saves enormous debugging time later. Python provides strong support for structured, modular code, and security work benefits particularly from careful organization.
The foundation of maintainability is proper function and class design. Each function should do one thing well with a clear purpose. When building a port scanner, separate the connection attempt from result recording from output formatting. This separation makes each piece independently testable and reusable.
Error handling deserves special attention in security tools because they encounter more failure cases than typical applications. Remote hosts might be unreachable, firewalls might drop packets, or servers might return unexpected responses. Wrapping network operations in try-except blocks with specific exception handling prevents your tool from crashing while providing useful error information.
Python’s exception hierarchy for network operations includes several useful types. socket.timeout occurs when operations exceed configured timeouts. socket.gaierror indicates DNS resolution failures. ConnectionRefusedError means the target host actively rejected the connection. Catching these specifically rather than using a bare except clause lets you handle different failure modes appropriately.
Logging provides visibility into tool operation, especially for long-running scans or automated security checks. Python’s logging module offers flexible, configurable output. Rather than scattering print statements through your code, use logging.info, logging.warning, and logging.error to emit structured log messages. Configure log levels to control verbosity. DEBUG for development, INFO for normal operation, WARNING for production.
Type hints, introduced in modern Python, dramatically improve code maintainability for complex security tools. Adding type annotations to function signatures documents expected types and enables static analysis tools to catch type mismatches before runtime. For a function that sends data over a socket, you might declare: def send_data(sock: socket.socket, data: bytes) -> int. This tells both human readers and type checkers that sock should be a socket object, data should be bytes, and the function returns an integer.
Project structure matters as codebases grow. Start by organizing related functionality into modules: separate files for networking operations, protocol parsing, output formatting, and main logic. Group these modules into packages, directories with init.py files: when you have multiple related modules. A well-structured security tool might have packages for scanning, exploitation, reporting, and utilities.
Setting Up Safe Testing Environments
The most sophisticated security tool becomes a liability if you accidentally test it against production systems or violate ethical boundaries. Professional security work requires isolated testing environments where you can safely develop and validate tools without risk.
The foundation of safe testing is network isolation. Set up virtual machines or containers that can communicate with each other but not with production networks. VMware, VirtualBox, and Docker all support creating isolated networks. Configure your testing VMs with a custom network segment, perhaps 10.0.99.0/24, that exists only within the virtualization platform.
Your security tools should include safeguards against accidental misuse. Implement explicit target validation that requires confirmation before executing operations. Use allowlists that specify valid testing targets rather than blocklists that try to prevent hitting production systems. A blocklist approach fails dangerously when you forget to add something; an allowlist fails safely by refusing to run unless explicitly configured.
Configuration files provide one way to enforce safe testing. Rather than hardcoding target IP addresses or domains, read them from a configuration file that must be explicitly created and populated. This extra step prevents simply running a tool with default values that might target unintended systems. The configuration can also specify operational parameters like scan rates, timeout values, and concurrency limits that affect both safety and detectability.
Environment variables offer another layer of safety control. Set a SECURITY_TESTING_MODE environment variable that must be present for tools to run. Check for this at startup and refuse to execute if it’s missing. This prevents accidental execution outside designated testing environments.
Documenting testing scope and authorization is equally important. Every security tool should include clear documentation of its intended use, required permissions, and legal considerations. Keep authorization records for any systems you test, even in your own lab environment. This habit becomes critical when conducting professional security assessments where authorization documentation protects both you and your clients.
Consider implementing rate limiting even in testing environments. Tools that generate high volumes of traffic can overwhelm test systems just as easily as production ones. Adding configurable delays between requests or limiting concurrent connections helps ensure your testing environment remains stable and representative of real-world conditions.
Logging becomes a safety feature when it creates audit trails of tool execution. Record when tools run, what targets they scanned, what credentials were used, and what operations were performed. This audit trail helps identify if a tool was misused or misconfigured and provides valuable information for debugging unexpected behaviors.
Practical Patterns for Security Code
Certain coding patterns appear repeatedly in security tool development. Recognizing and applying them makes your code more robust and your development faster.
Retry logic with exponential backoff handles transient network failures gracefully. When a connection attempt fails, rather than immediately giving up or retrying indefinitely, wait briefly then try again with progressively longer delays. This pattern prevents overwhelming recovering systems while still completing operations when possible. Implement this with a loop that tracks attempts, uses time.sleep with increasing delays, and eventually exceeds a maximum retry count.
Context managers, Python’s with statement, provide clean resource management. Network sockets, file handles, and database connections all benefit from automatic cleanup even when exceptions occur. While Python’s standard library provides context managers for common resources, you can write custom ones for security-specific patterns. A custom context manager might open an SSH connection, yield it for use, then ensure cleanup happens even if the code using it encounters errors.
The producer-consumer pattern helps when you need to separate data generation from processing. A port scanner might have one thread generating target addresses while multiple worker threads test connections. Python’s queue module provides thread-safe queues perfect for this. The producer adds items to the queue while consumers pull items and process them, automatically handling synchronization.
Generator functions provide memory-efficient iteration for large datasets. Rather than building a list of all targets before starting to scan, a generator yields targets one at a time. This matters when scanning large address spaces: you don’t need to hold millions of IP addresses in memory simultaneously. Define a generator with def and use yield instead of return to produce values lazily.
Command-line argument parsing deserves attention because it’s the primary user interface for most security tools. Python’s argparse module provides sophisticated option parsing with minimal code. Define arguments with types, defaults, and help text, and argparse handles validation and generates usage documentation automatically. Well-designed command-line interfaces distinguish professional tools from quick scripts.
Configuration files complement command-line arguments for more complex settings. The configparser module handles INI-style configuration files, while PyYAML provides YAML support for more structured data. Separating frequently changed options like target lists into configuration files while keeping operational flags as command-line arguments creates flexible, maintainable tools.
These patterns don’t just make code cleaner: they make security tools more reliable under the unpredictable conditions they encounter. Network timeouts, unexpected responses, and edge cases are the norm rather than the exception. Code structured around proven patterns handles these situations gracefully rather than crashing at critical moments.
As you work through the remaining chapters, you’ll see these fundamental concepts applied repeatedly. Socket programming underpins network scanning tools. Bytes handling appears in exploit development. Virtual environments keep tool development organized. The libraries introduced here (requests, scapy, paramiko) become the building blocks for sophisticated security tools. Most importantly, the emphasis on safety, structure, and maintainability continues throughout. Security tool development is engineering work, and engineering requires solid foundations. You now have those foundations in place.
… and that is just the beginning. The complete book continues from here, instant download, DRM-free, yours to keep.






Reviews
There are no reviews yet.