Tactical Python for Security and IoT
Socket-Level Security Tools
Sockets The Building Blocks
At the lowest level of network programming in Python is the socket module. It provides a direct interface to the operating system's networking capabilities. Think of a socket as a digital endpoint for sending or receiving data. Just like you plug a cable into a physical socket in a wall, your program creates a software socket to connect to another program on a network.
Sockets come in two main flavors, corresponding to the two most common transport layer protocols: TCP and UDP. Choosing between them depends entirely on what you need to accomplish.
| Protocol | Type | Key Feature |
|---|---|---|
| TCP | Connection-Oriented | Reliable, ordered data delivery. A handshake establishes the connection before data is sent. |
| UDP | Connectionless | Fast, low-overhead data broadcast. No guarantee of delivery or order. |
For tasks like port scanning, where reliability is key, we almost always use TCP sockets. Creating one is straightforward.
import socket
# Create a socket object
# AF_INET specifies we're using IPv4
# SOCK_STREAM specifies it's a TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Always a good practice to close the socket when done
s.close()
With AF_INET, we're telling the socket to use the standard IPv4 address family. For a UDP socket, you would simply use socket.SOCK_DGRAM instead of SOCK_STREAM.
Building a Port Scanner
One of the first steps in network reconnaissance is discovering which services are running on a target machine. We do this by checking which ports are open. A port scanner is a tool that automates this process. While powerful tools like Nmap exist, building your own helps you understand the underlying mechanics and gives you a custom tool when off-the-shelf options are unavailable or too noisy.
The core of our scanner is the socket's connect_ex() method. Unlike the standard connect(), which raises an exception on failure, connect_ex() returns an error code. A return value of 0 indicates the connection was successful, meaning the port is open. Anything else means it's closed or unreachable.
We also need to set a timeout. Without one, our script could hang for a long time trying to connect to a port that isn't responding. socket.setdefaulttimeout() applies a global timeout to all new sockets.
import socket
target = '127.0.0.1' # Scan localhost
socket.setdefaulttimeout(1) # 1 second timeout
for port in range(1, 1025):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = s.connect_ex((target, port))
if result == 0:
print(f'Port {port} is open')
s.close()
Speeding It Up With Threads
The simple scanner works, but it's slow. It checks each port one by one, waiting for the timeout or a connection on each before moving to the next. Scanning all 65,535 ports would take ages. To fix this, we can use multi-threading.
Python's threading module lets us run multiple functions concurrently. We can create a worker function that scans a single port and then spawn hundreds of threads, each running that function for a different port. This allows us to check many ports in parallel, drastically reducing the total scan time.
import socket
import threading
from queue import Queue
target = '127.0.0.1'
queue = Queue()
open_ports = []
def port_scan(port):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
s.connect((target, port))
return True
except:
return False
def worker():
while not queue.empty():
port = queue.get()
if port_scan(port):
print(f'Port {port} is open')
open_ports.append(port)
for port in range(1, 1025):
queue.put(port)
threads = []
for _ in range(100): # Create 100 threads
thread = threading.Thread(target=worker)
threads.append(thread)
thread.start()
for thread in threads:
thread.join() # Wait for all threads to finish
print('Open ports are:', open_ports)
Identifying Services with Banners
Knowing a port is open is useful, but knowing what service is running on it is even better. Many services announce themselves by sending a "banner" when a client connects. This banner often contains the software name and version, which is invaluable for a security assessment. This technique is called s.
We can grab this banner by using the recv() method on our socket right after a successful connection. This method receives a specified number of bytes from the socket. We'll add this to our simple, single-threaded scanner for clarity.
import socket
target = '127.0.0.1'
socket.setdefaulttimeout(2)
for port in [21, 22, 25, 80]: # Common ports
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
# Try to receive 1024 bytes of data
banner = s.recv(1024)
print(f'Port {port} is open')
print(f' Banner: {banner.decode().strip()}')
s.close()
except Exception as e:
# Port is likely closed or banner not sent
print(f'Port {port} is closed or no banner. Error: {e}')
By combining socket programming, threading, and banner grabbing, you can build powerful and custom reconnaissance tools. These low-level skills are the foundation for understanding how more advanced network security tools operate under the hood.
What is the primary role of Python's socket module?
When building a port scanner, why is the connect_ex() method generally preferred over the connect() method?
With these fundamentals, you're now equipped to interact with networks at a much deeper level.