python黑帽子二TCP代理

文章最后更新时间为:2018年08月19日 14:05:23

注:代码参考自《python黑帽子 黑客与渗透测试编程之道》,原书为python2版本,这里修改为python3版本。所有的代码整理在https://github.com/saucer-man/python-Black-hat

TCP代理随处可见,Wireshark,burpsuite还有各种翻墙软件,这次的目标就是实现一个简单的tcp代理服务器。

1. 简单说明

本次的实现共分为七个函数,按照调用的顺序我们来一一简单介绍一下:

  • main():

    主入口函数,我们启动函数,通过命令行将参数传递给main()函数,如果参数符合要求,那么main()函数会调用server_loop()函数来处理下一步的请求。
  • server_loop():

    这个函数类似于tcp服务端函数,通过建立本地服务端来作为流量的中转站。监听本地端口,当受到客户端的连接请求时,创建新线程并调用proxy_handler()函数来处理接下来的事情。
  • proxy_handler():
这是代表整个代码主体逻辑的函数,接收server_loop()函数的调用,首先连接远程主机,然后接收和发送来自客户端和远程主机之间的数据。例如:
截断本地发给远程主机的请求,然后用request_handler()函数处理请求数据,再将处理之后的数据发给远程主机。截断远程主机发给本地的请求,然后用response_handler()函数处理请求数据,再将处理之后的数据发给本地。
  • request_handler()、response_handler()

    数据处理函数,可自行定义。
  • receive_from()

    此函数相当于proxy_handler()的一个功能组件,接收一个socket参数,并return接收到的此socket发送的数据(bytes型)
  • hexdump():

    此函数很简单,就是将bytes类型的数据转换成十六进制型的,再打印出来,方便分析。类似于Wireshark。

需要传递的参数:

  • local_host、local_port:
    用来做tcp代理的本地ip和端口。例如burpsuite的127.0.0.1、8080
  • remote_host、remote_port:
    远程主机的ip和地址,这个代理服务器将把所有的本地请求转发到此远程主机。
  • receive_first:
    True or False,来确定是否先接收远程主机发来的数据。

鸡肋之处:
首先性能很差,这点是毋庸置疑的。。

其次是功能很少,因为规定了远程主机的地址和端口,就不能像浏览器那样代理全部的网站。比如我设置远程主机为www.baidu.com,那么我请求google的请求包发给百度也没用啊。。

2. 代码

import sys
import socket
import threading

def server_loop(local_host, local_port, remote_host, remote_port, receive_first):
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        server.bind((local_host, local_port))
    except:
        print("[!!] Fail to listen on %s: %d" % (local_host, local_port))
        print("[!!] Check for other listening sockets correct permissions.")
        sys.exit(0)
    print("[*] Listening on %s: %d" %(local_host, local_port))
    server.listen(5)
    while True:
        client_socket, addr = server.accept()

        # 打印出本地连接信息
        print("[>>==] Received incoming connection from %s: %d" %(addr[0], addr[1]))

        # 开启一个线程与远程主机通信
        proxy_thread = threading.Thread(target=proxy_handler, args=(client_socket, remote_host, remote_port, receive_first))

        proxy_thread.start()

def proxy_handler(client_socket, remote_host, remote_port, receive_first):
    #连接远程主机
    remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    remote_socket.connect((remote_host, remote_port))

    # 如果必要从远程主机接收数据
    if receive_first:
        remote_buffer =  receive_from(remote_socket)
        hexdump(remote_buffer)

        # 发送给我们的响应处理
        remote_buffer = response_handler(remote_buffer)

        # 如果我们有数据传递给本地客户端,发送它
        if len(remote_buffer):
            print("[<<==] Sending %d bytes to localhost."%len(remote_buffer))
            client_socket.send(remote_buffer)

    # 现在我们从本地循环读取数据。发送给远程主机和本地主机
    while True:
        # 从本地读取数据
        local_buffer = receive_from(client_socket)
        if len(local_buffer):
            print("[>>==] Received %d bytes from localhost."%len(local_buffer))

            hexdump(local_buffer)
            # 发送给我们的本地请求
            local_buffer = request_handler(local_buffer)
            # 向远程主机发送数据
            remote_socket.send(local_buffer)
            print("[==>>] Sent to remote.")

        # 接收响应的数据
        remote_buffer = receive_from(remote_socket)
        if len(remote_buffer):
            print("[==<<] Received %d bytes from remote. " % len(remote_buffer))
            hexdump(remote_buffer)
            # 发送到响应处理函数
            remote_buffer = response_handler(remote_buffer)
            # 将响应发送给本地socket
            client_socket.send(remote_buffer)
            print("[<<==] Sent to localhost")

        # 如果两边都没有数据,关闭连接
        if not len(local_buffer) or not len(remote_buffer):
            client_socket.close()
            remote_socket.close()
            print("[*] No more data. Closing connections.")
            break


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([hex(x)[2:].upper().zfill(digits) for x in s])
        text = ''.join([chr(x) if 0x20 <= x < 0x7F else '.' for x in s])
        result.append("{0:04X}".format(i) + ' ' * 3 + hexa.ljust(length * (digits + 1)) + ' ' * 3 + "{0}".format(text))

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

def receive_from(connection):
    buffer= ""

    # 我们设置了两秒的超时,这取决于目标的情况,肯需要调整
    connection.settimeout(2)

    try:
        # 持续从缓存中读取数据,直到没有数据或超时
        while True:
            data = connection.recv(4096)
            if not data:
                break
            data = bytes.decode(data)
            buffer += data
            buffer = str.encode(buffer)       
    except:
        pass
    return buffer

# 对目标是远程主机的请求进行修改
def request_handler(buffer):
    # 执行包修改
    return buffer

# 对目标是本地主机的响应进行修改
def response_handler(buffer):
    # 执行包修改
    return buffer

def main():
     if len(sys.argv[1:])!=5:
         print("Usage: ./proxy.py [localhost][localport][remotehost][remoteport][receive_first]")
         sys.exit(0)
     # 设置本地监听参数
     local_host = sys.argv[1]
     local_port = int(sys.argv[2])

     # 设置远程目标
     remote_host = sys.argv[3]
     remote_port = int(sys.argv[4])

     # 告诉代理在发送给远程主机之前连接和接收数据
     receive_first = sys.argv[5]

     if 'True' in receive_first:
         receive_first = True
     else:
         receive_first = False

     # 现在我们设置好我们的监听socket
     server_loop(local_host, local_port, remote_host, remote_port, receive_first)

main()

3. 运行结果

首先我是在本地win10上运行的,首先运行服务器:

python proxy.py 127.0.0.1 8888 www.baidu.com 80 True

然后去浏览器开本地代理刷百度,刷新失败,包还是收到了:

为什么直接error了,我是用浏览器刷新的,或许请求包不对。(不确定)

接下来再去试试ubuntu。

这次我没有使用浏览器请求数据,而是使用了curl命令。

首先开启监听:

python proxy.py 127.0.0.1 80 www.baidu.com 80 True

再用curl通过本地ip和端口请求www.baidu.com:

curl -x 127.0.0.1:80 www.baidu.com

这次很完美,成功获取首页。这里也印证了上面在win10本地失败的原因很有可能是请求包不符合要求。

1 + 2 =
快来做第一个评论的人吧~