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本地失败的原因很有可能是请求包不符合要求。