网络安全编程——制作一个TCP代理Python实现

news/2025/11/30 11:06:27/文章来源:https://www.cnblogs.com/tlnshuju/p/19288660

网络安全编程——制作一个TCP代理Python实现

2025-11-30 10:57  tlnshuju  阅读(0)  评论(0)    收藏  举报

今天给大家带来的是TCP代理的功能实现;
我们用Python构建过不少简单的TCP代理,比如接下来的这个,我们常常用它来分析未知的协议,篡改应用的网络流量,或者为fuzzer创建测试用例。

文章目录

    • 介绍
    • 第一版代码:
      • hexdump()函数
      • receive_from()函数
      • 修改 *_handler()函数
      • proxy_handler()函数—(最重要)
      • server_loop()函数
      • main()函数
    • 完整改进代码:
      • 效果展示:
        • 测试 A(HTTP 流量)
    • 总结


介绍

在工具箱里常备TCP代理的理由有很多:

  • 你也许会用它在主机之间转发流量,又或者用它检测一些网络软件。
  • 在企业环境里进行渗透测试时,你可能无法使用Wireshark,也无法在Windows上加载驱动嗅探本地回环流量;
  • 而网段的阻隔让你无法直接在目标机器上使用手头的工具。

代理的代码主要分为四部分

  • hexdump函数:把本地设备和远程设备之间的通信过程显示到屏幕上(hexdump函数);
  • receive_from函数:从本地设备或远程设备的入口socket接收数据(receive_from函数);
  • proxy_handler函数:控制远程设备和本地设备之间的流量方向(proxy_handler函数);
  • server_loop函数:最后,还需要创建一个监听socket,并把它传给我们的proxy_handler(server_loop函数)

话不多说,直接让我们开始今天的代码编写;

第一版代码:

我们首先打开一个新文件,将其命名为proxy.py

hexdump()函数

import sys
import threading
import socket
# 旧版本的
# Hex_filter = ''.join((len(repr(chr(3))) == 3) and chr(i) or '.' for i in range(256))
# 替换成新的python3
hex_filter = ''.join(chr(i) if 32 <= i <= 126 else '.' for i in range(256))
def hexdump(src, step=16, show=True):
# 修正:bytes安全转换为可见字符串
if isinstance(src, bytes):
src = ''.join([chr(x) if 32 <= x <= 126 else '.' for x in src])
result = []
for i in range(0, len(src), step):
word = src[i:i + step]
hexa = ' '.join(f'{ord(c):02x}' for c in word)
hexwidth = step * 3
result.append(f'{i:04x} {hexa:<{hexwidth}} {word}')
if show:
for line in result:
print(line)
else:
return result

我知道大家对这段代码有很多疑问,不影响,接下来为大家一一解释:

  • hex_filter作用:我们创建了一个hex_filtter字符串
    • 在所有可打印字符的位置上,保持原有的字符不变;
    • 在所有不可打印字符的位置上,用一个句点.替代

(如果大家看不懂这个多元表达式,那么这段代码呢?)

# 生成0-255的ASCII码序列
ascii_codes = range(256)
filter_chars = []
# 遍历每个ASCII码,添加对应字符(或'.')
for i in ascii_codes:if 32 <= i <= 126:char = chr(i)  # 可打印字符else:char = '.'     # 不可打印字符用'.'代替filter_chars.append(char)
# 拼接列表为字符串
hex_filter = ''.join(filter_chars)
  • hexdump()函数:然后定义了一个hexdump函数,它能接收bytesstring类型的输入,并将其转换为十六进制格式输出到屏幕上;
    • 然后每step长度(16位),截取一段数据进行打印(可打印的就保留,否则以.代替)
  • hexa作用:转换为十六进制字符串(每个字符用2位十六进制表示,用空格分隔),如字符’A’→’41’
    • hexwidth作用: 确保对齐,step个字符对应step*3长度:每个十六进制2位+1个空格
    • result.append()作用:拼接当前行(偏移量+十六进制+可打印字符)

下面图片可以让大家直观的感受到hexdump函数相应的作用:

在这里插入图片描述

两者作用其实是等效的;(效果如图所示;)

举例:对于0到255之间的每个整数,如果其对应的字符表示长度等于3,我们就直接用这个字符(chr(i));否则,就用一个句点(.)表示。

在这里插入图片描述

具体举例:
假如我们输入print(hexdump("python rocks I Love Hvv\n\r"))

在这里插入图片描述

receive_from()函数

接下来,我们编写从代理两端接收数据的函数:

def receive_from(connections):
# connections表示已经创立的一个连接,例如client_socket或者server_socket
buffer = b''
connections.settimeout(5)
try:
while True:
data = connections.recv(4096)
if not data:
break
buffer += data
except Exception as e:
print(f"接受数据失败:{e}")
pass
return buffer

代码解释:

  • connections:表示已经创立的一个连接,例如client_socket或者server_socket
  • 随后的步骤就是接收数据,并保存到buffer缓冲区中;

作用
(1)要想接收本地或远程数据,必须先传入一个socket对象。创建一个空的bytes变量buffer,用来存储socket对象返回的数据。我们设定的超时时间默认为5秒;
(2)然后创建一个循环,不断把返回的数据写进buffer,直到数据读完或者连接超时为止。
(3)最后,把buffer返回给调用方,这个调用方可能是本地设备localhost,也可能是远程设备remote

修改 *_handler()函数

有时,你可能想在代理转发数据包之前,修改一下回复的数据包或请求的数据包。我们添加一对函数(request_handlerresponse_handler)来处理这种情况:

def request_handler(buffer):
return buffer
def respond_handler(buffer):
return buffer

作用:在这些函数里,可以修改数据包内容,进行模糊测试,挖权限校验漏洞,做你想做的任何事。

proxy_handler()函数—(最重要)

现在,我们插入如下代码,潜入proxy_handler函数:这个函数实现了整个代理的大部分逻辑

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 = respond_handler(remote_buffer)
if len(remote_buffer):
print(f"[==>] received %d bytes data from remote" % len(remote_buffer))
client_socket.send(remote_buffer)
while True:
local_buffer = receive_from(client_socket)
if len(local_buffer):
print(f"[==>] received %d bytes data from local." % len(local_buffer))
hexdump(local_buffer)
local_buffer = request_handler(local_buffer)
remote_socket.send(local_buffer)
print(f"[==>] send to remote.")
# 修正:每次循环重新接收服务端数据
remote_buffer = receive_from(remote_socket)
if len(remote_buffer):
print(f"[==>] received %d bytes data from remote." % len(remote_buffer))
hexdump(remote_buffer)
remote_buffer = respond_handler(remote_buffer)
client_socket.send(remote_buffer)
print(f"[==>] send to local.")
if not len(remote_buffer) and not len(local_buffer):
client_socket.close()
remote_socket.close()
print(f"[*] No more data. Closing connections.")
break

核心代码解释:

  • if len(local_buffer)if len(remote_buffer):
    • 双向转发循环:
    • 从客户端接收数据(receive_from(client_socket))→ 经request_handler处理 → 转发到远程服务器(remote_socket.send());
    • 远程服务器接收数据(receive_from(remote_socket))→ 经response_handler处理 → 转发到客户端(client_socket.send());
    • 当两端均无数据时,关闭套接字,结束本次连接。

具体实现过程:

(1)首先,连接远程主机。接着,进入主循环之前,先确认一下是否需要先从服务器那边接收一段数据。有的服务器会要求你做这样的操作(比如 FTP 服务器,会先发给你一条欢迎消息,你收到后才能发送数据给它)。
(2)之后对通信两端分别调用 receive_from 函数,它会从已连接的 socket 对象中收取数据。
(3)我们把收到的数据都输出到屏幕上,检查里面有没有什么有趣的东西。然后,把数据交给 response_handler 函数,等它处理数据后再转发给本地客户端。
(4)剩下的代理代码就很简单了:开启一个循环,不断地从本地客户端读取数据,处理数据,转发给远程服务器,从远程服务器读取数据,处理数据,转发给本地客户端,直到再也读不到任何数据为止;


server_loop()函数

我们再来编写server_loop函数,用来创建和管理连接:

def server_loop(local_host, local_port, remote_host, remote_port, receive_first):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
server_socket.bind((local_host, local_port))
except Exception as e:
print(f"problem on bind: {e!r}")
print(f"[!!] Failed to listen on {local_host}:{local_port}")
print(f"Check for other listening sockets or correct permissions.")
sys.exit()
print(f"Listening on {local_host}:{local_port}")
server_socket.listen(5)
while True:
client_socket, address = server_socket.accept()
print(f"Received incoming connection from {address[0]}:{address[1]}")
proxy_thread = threading.Thread(
target=proxy_handler,
args=(client_socket, remote_host, remote_port, receive_first)
)
proxy_thread.start()

这段代码的作用相当于TCP服务端的作用,具体可以看这篇文章;

代码解释:

  • server_loop函数创建了一个socket,将它绑定到本地主机并开始监听。
  • 在主循环里,每出现一个新连接,我们就新开一个线程,将新连接交给proxy_handler函数,由它来给数据流的两端收发数据;

main()函数

最后就只剩main函数了:

def main():
if len(sys.argv[1:]) != 5:
print("Usage: ./proxy.py [localhost] [localport]", end='')
print("[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)
if __name__ == '__main__':
main()

代码功能解释:

  • main函数是程序的 “启动器”,主要做三件事:

    • 参数校验:检查命令行参数数量是否为 5 个,若不符合则打印使用说明并退出。
    • 参数解析:将命令行传入的参数转换为代理运行所需的配置(本地 / 远程地址、端口、是否先从远程收数据)。
    • 启动代理服务:调用server_loop函数,正式启动代理的监听和数据转发逻辑。
  • 参数与函数调用关系:命令行需传入5 个参数,对应代理的核心配置:

参数位置含义代码中变量后续调用的作用
sys.argv[1]代理本地监听的IP地址local_host传给server_loop,用于绑定本地端口,接受客户端连接。
sys.argv[2]代理本地监听的端口local_port同上,指定本地监听的端口号(需转换为整数)。
sys.argv[3]远程服务器的IP地址remote_host传给server_loop,后续由proxy_handle连接该地址,实现数据转发。
sys.argv[4]远程服务器的端口remote_port同上,指定远程服务器的端口号(需转换为整数)。
sys.argv[5]是否先从远程服务器接收数据receive_first传给server_loop,由proxy_handle判断是否在转发前先收取远程的“初始化数据”(如FTP欢迎消息)。

流程图如下:
在这里插入图片描述

完整改进代码:

好了,接下来我直接给出完整的代码:

import sys
import threading
import socket
hex_filter = ''.join([chr(i) if 32 <= i <= 126 else '.' for i in range(256)])
def hexdump(src, step=16, show=True):
# 修正:bytes安全转换为可见字符串
if isinstance(src, bytes):
src = ''.join([chr(x) if 32 <= x <= 126 else '.' for x in src])
result = []
for i in range(0, len(src), step):
word = src[i:i + step]
hexa = ' '.join(f'{ord(c):02x}' for c in word)
hexwidth = step * 3
result.append(f'{i:04x} {hexa:<{hexwidth}} {word}')
if show:
for line in result:
print(line)
else:
return result
def receive_from(connection):  # 修正:参数名 connections → connection
buffer = b''
connection.settimeout(5)
try:
while True:
data = connection.recv(4096)
if not data:
break
buffer += data
except Exception as e:
print(f"接受数据失败: {e}")
pass
return buffer
def request_handler(buffer):
return buffer
def respond_handler(buffer):
return buffer
def proxy_handler(client_socket, remote_host, remote_port, receive_first):
# 修正:变量名 remotet_socket → remote_socket
remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote_socket.connect((remote_host, remote_port))
if receive_first:
# 修正:错误传参 receive_first → remote_socket
remote_buffer = receive_from(remote_socket)
hexdump(remote_buffer)
remote_buffer = respond_handler(remote_buffer)
if len(remote_buffer):
print(f"[==>] received %d bytes data from remote" % len(remote_buffer))
client_socket.send(remote_buffer)
while True:
local_buffer = receive_from(client_socket)
if len(local_buffer):
print(f"[==>] received %d bytes data from local." % len(local_buffer))
hexdump(local_buffer)
local_buffer = request_handler(local_buffer)
remote_socket.send(local_buffer)
print(f"[==>] send to remote.")
# 修正:每次循环重新接收服务端数据
remote_buffer = receive_from(remote_socket)
if len(remote_buffer):
print(f"[==>] received %d bytes data from remote." % len(remote_buffer))
hexdump(remote_buffer)
remote_buffer = respond_handler(remote_buffer)
client_socket.send(remote_buffer)
print(f"[==>] send to local.")
if not len(remote_buffer) and not len(local_buffer):
client_socket.close()
remote_socket.close()
print(f"[*] No more data. Closing connections.")
break
# 修正:函数参数补上 receive_first
def server_loop(local_host, local_port, remote_host, remote_port, receive_first):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
server_socket.bind((local_host, local_port))
except Exception as e:
print(f"problem on bind: {e!r}")
print(f"[!!] Failed to listen on {local_host}:{local_port}")
print(f"Check for other listening sockets or correct permissions.")
sys.exit()
print(f"Listening on {local_host}:{local_port}")
server_socket.listen(5)
while True:
client_socket, address = server_socket.accept()
print(f"Received incoming connection from {address[0]}:{address[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]", end='')
print(" [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)
if __name__ == '__main__':
main()

效果展示:

测试 A(HTTP 流量)

(1)打开三个powershell窗口(切换到proxy文件的目录):

(2)在 窗口 A(远端) 启动 HTTP 服务(被代理端)

python -m http.server 8000

在这里插入图片描述
(3)在 窗口 B(代理) 启动代理(监听本地9000 -> 远端127.0.0.1:8000)

python proxy.py 127.0.0.1 9000 127.0.0.1 8000 False

在这里插入图片描述

(4)在 窗口 C(客户端) 用 curl 或浏览器访问代理

curl http://127.0.0.1:9000/

在这里插入图片描述

而后窗口B也有了反应:

在这里插入图片描述

窗口A也同理:

在这里插入图片描述

总结

可以看到这次的代码量很大,但**总体框架60%**左右还是与TCP服务端有关,所以希望大家好好啃透这几个代码,不要急于求成;

而是达到你能够自己编写出来,而不参照任何资料,这样你就掌握了80%的能力要求,而这也将会对你的面试有很大的作用!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/981689.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GitGitHub学习路线笔记

Git&GitHub学习路线笔记 学习来源: 学习来源:程序员鱼皮 (本笔记仅作学习交流使用) git学习 基础知识及路线 Git 是目前最主流的 分布式版本控制系统需要什么了解概念 安装配置环境 学会操作 动手敲命令下载git ht…

2025监听耳机设备权威测评榜单最新发布

摘要:随着国内音乐制作、音频工程及专业内容创作领域的蓬勃发展,监听耳机作为音频工作流程中不可或缺的参考工具,其市场需求持续增长。行业数据显示,全球专业音频耳机市场容量已达36亿美元,其中精准还原、高可靠性…

2025 年 11 月 GEO 优化服务商权威推荐榜:精准定位与高效引流技术深度解析,助力企业本地搜索排名飙升

2025 年 11 月 GEO 优化服务商权威推荐榜:精准定位与高效引流技术深度解析,助力企业本地搜索排名飙升 在人工智能技术快速发展的今天,企业获取潜在客户的方式正在发生革命性变化。GEO优化(AI搜索优化)作为新兴的数…

2025年评价高的家电零配件旋压加工厂家推荐及选择指南

2025年评价高的家电零配件旋压加工厂家推荐及选择指南行业背景与市场趋势随着家电行业的持续发展和消费升级,家电零配件的精密制造需求日益增长。旋压加工作为一种先进的金属成型工艺,因其高效率、低成本和高精度等特…

2025 年 11 月数字人厂家权威推荐榜:智能交互与超写实形象,虚拟主播、数字员工及虚拟偶像定制服务深度解析

2025 年 11 月数字人厂家权威推荐榜:智能交互与超写实形象,虚拟主播、数字员工及虚拟偶像定制服务深度解析 随着人工智能技术的快速发展,数字人产业正迎来爆发式增长。从虚拟主播到数字员工,从超写实形象到智能交互…

2025年11月小红书代理商推荐榜:权威评测与选择指南

随着社交电商的快速发展,小红书已成为品牌营销不可或缺的阵地。许多企业主、市场负责人及创业者面临共同挑战:如何在海量代理商中筛选出专业可靠的合作伙伴?他们通常需要兼顾投放效果、成本控制与长期品牌价值,而市…

2025年11月小红书代理商推荐榜单:权威评测与选择指南

作为品牌主或营销负责人,您在2025年11月这个时间点寻求小红书代理商,很可能面临着双十一大促后的复盘与新周期规划的需求。随着小红书平台商业生态的日益成熟,用户对内容真实性与营销效果的要求水涨船高。选择一家靠…

2025年知名的压缩垃圾车厂家最新TOP实力排行

2025年知名的压缩垃圾车厂家TOP实力排行行业背景与市场趋势随着城市化进程加速和环保政策日趋严格,环卫装备行业迎来了前所未有的发展机遇。压缩垃圾车作为城市垃圾收运系统的核心装备,其市场需求持续增长。据行业数…

2025 AI培训价值革命:苏州市咖豆网络引领企业级AI落地新范式

在人工智能从"技术概念"迈向"业务刚需"的关键转折点,企业对AI培训的需求已发生根本性转变——从"学技术"升级为"解决业务问题"。IDC最新数据显示,2025年89.6%的企业培训后仍…

2025年口碑好的空调304金属波纹管/冷板金属波纹管最新TOP品牌厂家排行

2025年口碑好的空调304金属波纹管/冷板金属波纹管TOP品牌厂家排行行业背景与市场趋势随着全球空调行业的持续发展和能效标准的不断提高,304金属波纹管和冷板金属波纹管作为关键零部件,其市场需求呈现稳定增长态势。2…

2025年深圳优秀的植保机与低温快充锂电池品牌推荐

在2025年,深圳的锂电池市场呈现出旺盛的发展势头,尤其在植保机与快充锂电池领域。随着农业技术的提升,越来越多的企业开始意识到高效电池的重要性,以确保设备在关键时刻提供充足的动力。同时,深圳的制造商们也在推…

Linux服务器无ROOT权限编译安装WRFV4流程记录

未完待续直接从WRF的github仓库克隆,记得下载子模块,官方教程有命令。 根据online tutorial编译安装依赖,我是把 gcc740 mpich3 libz hdf5 netcdf-c netcdf-fortran pnetcdf libpng jasper全都编译好了 configure(3…

2025年气动阀十大优质厂家推荐,气动阀制造企业全解析

在工业自动化与流体控制领域,气动阀作为核心执行元件,直接影响系统稳定性与生产效率。面对市场上参差不齐的气动阀供应商,如何挑选兼具品质、技术与服务的合作伙伴?以下依据技术实力、定制能力、口碑表现,为你推荐…

2025年北京装修改造公司实力榜单:佳时特装修改造好不好?

装修是家庭生活的二次投胎,从预算把控到施工质量,每一步都藏着业主的焦虑。尤其在老房密集的北京,改造陷阱更是让不少家庭踩坑。2025年北京装修市场竞争激烈,如何找到实力过硬、口碑靠谱的改造专家?以下结合业主真…

2025年知名的CP库均化设备厂家推荐及采购参考

2025年知名的CP库均化设备厂家推荐及采购参考行业背景与市场趋势随着全球水泥工业的持续发展和技术升级,均化设备作为水泥生产线中的关键环节,其重要性日益凸显。均化设备的质量直接影响水泥产品的均匀性和稳定性,进…

20232429 2025-2026-1 《网络与系统攻防技术》实验X实验报告

1.实验内容 (1)简单应用SET工具建立冒名网站 (2)ettercap DNS spoof (3)结合应用两种技术,用DNS spoof引导特定访问到冒名网站。 2.实验过程 2.1 简单应用SET工具建立冒名网站 2.1.1建立冒名网站1在root用户下输…

信竞回忆录

六年级 第一次接触信竞。 当时是罗俊辉在教我们,罗俊辉教的真的不评价,一直考试考试,我当时经常垫底,主要是一进去就和比我大一届和大两届的一起训练。虽然考试经常垫底,但是感觉每次训练都很开心,当时真的觉得信…

2025年质量好的骨料散装设备厂家最新实力排行

2025年质量好的骨料散装设备厂家实力排行行业背景与市场趋势随着全球基础设施建设的持续扩张和绿色建筑理念的普及,骨料散装设备行业正迎来新一轮发展机遇。2025年,骨料散装设备市场预计将保持6.8%的年均增长率,其中…

「学习笔记」SQL 注入

SQL 注入 SQL 注入,通过构造一条精巧的语句,来查询到想要得到的信息。 注入点 注入点就是可以实行注入的地方,通常是一个访问数据库的连接。 字符型注入与数字型注入判断 用 and 1=2 来判断,能正常显示的是字符型注…

SQLi-Labs WP

SQLi-Labs WP less 1 WP 题目中让我们给 id 输入一个数值,我们 GET 一个 id=1.随后我们查询它的类型是否为数字型,用 ?id=1 and 1=2 来判断,然而页面依旧正常,可见这个是字符型。 然后我们查询它的闭合方式。 当我…