Skip to content

log_pcap() 拼包函数 ​

📦 r0capture 的精华之一:把 SSL hook 出来的明文,手工拼成一个合法的 pcap 数据包。

📎 源码在线查看:r0capture.py#L147-L255(log_pcap)

为什么要手工拼 ​

r0capture 抓的是 SSL hook 出来的已解密明文,不是网卡上的原始加密包。明文不在任何真实网络数据包里,所以必须手工把明文 data 包装成「假的 IPv4+TCP 包」,再按 pcap 文件格式逐字节写进去。

pcap 文件结构 ​

整个文件 = 1个全局头(24字节, 在 ssl_log 里写) + N条记录
每条记录 = 记录头(16字节) + 一个完整的 IPv4 包
IPv4 包 = IPv4头(20字节) + TCP头(20字节) + 载荷(明文 data)
每个包总长 = 16 + 20 + 20 + len(data) = 40 + len(data)   ← 40 = IP头+TCP头

函数签名 ​

python
def log_pcap(pcap_file, ssl_session_id, function, src_addr, src_port,
             dst_addr, dst_port, data):

seq/ack 模拟 ​

真实 TCP 用序列号保证顺序。这里造的是假包,得自己编一套自洽的 seq/ack:

python
if ssl_session_id not in ssl_sessions:
    ssl_sessions[ssl_session_id] = (random.randint(0, 0xFFFFFFFF),
                                    random.randint(0, 0xFFFFFFFF))
client_sent, server_sent = ssl_sessions[ssl_session_id]

if function == "SSL_read":
    seq, ack = (server_sent, client_sent)   # 服务端发
else:
    seq, ack = (client_sent, server_sent)   # 客户端发
  • 首次见该会话:随机生成两个起始 seq(client_sent / server_sent)
  • SSL_write(客户端发):seq 用客户端已发量,ack 用服务端已发量
  • SSL_read(服务端发):seq/ack 对调
  • 写完一个包后,对应方向的「已发字节数」累加 len(data),下次同会话的包 seq 接着往后排

声明式写头 ​

用「元组列表 + 循环」把协议字段按顺序列成表,比逐行写清晰:

python
for writes in (
        # --- pcap 记录头 16字节, 本机字节序 ---
        ("=I", int(t)),                          # Timestamp seconds
        ("=I", int((t * 1000000) % 1000000)),    # Timestamp microseconds
        ("=I", 40 + len(data)),                  # Number of octets saved
        ("=i", 40 + len(data)),                  # Actual length
        # --- IPv4 头 20字节, 大端序 ---
        (">B", 0x45),                            # Version+IHL: 0100 0101
        (">B", 0),                               # ToS/DSCP
        (">H", 40 + len(data)),                  # Total Length
        (">H", 0),                               # Identification
        (">H", 0x4000),                          # Flags=DF + Fragment Offset
        (">B", 0xFF),                            # TTL=255
        (">B", 6),                               # Protocol=TCP
        (">H", 0),                               # Header Checksum=0
        (">I", src_addr),                        # Source Address
        (">I", dst_addr),                        # Destination Address
        # --- TCP 头 20字节, 大端序 ---
        (">H", src_port),                        # Source Port
        (">H", dst_port),                        # Destination Port
        (">I", seq),                             # Sequence Number
        (">I", ack),                             # Acknowledgment Number
        (">H", 0x5018),                          # Data Offset(5)+Flags(ACK+PSH)
        (">H", 0xFFFF),                          # Window Size
        (">H", 0),                               # Checksum=0
        (">H", 0)):                              # Urgent Pointer
    pcap_file.write(struct.pack(writes[0], writes[1]))
pcap_file.write(data)   # 最后写载荷明文

字节序 ​

区域字节序struct 首字符原因
pcap 记录头本机序(小端)=全局头 magic 声明了 0xa1b2c3d4(本机序)
IPv4 头大端(网络序)>网络协议规定
TCP 头大端(网络序)>网络协议规定

关键字段含义 ​

IPv4 ​

  • 0x45 = Version(4) + IHL(5,即 5×4=20 字节头)
  • 0x4000 = DF(Don't Fragment)位置 1
  • 6 = Protocol = TCP(1=ICMP, 17=UDP)
  • Checksum 填 0:Wireshark 不校验造的包也能解析

TCP ​

  • 0x5018:高 4 位 5 = Data Offset(5×4=20 字节头),低 12 位 0x018 = ACK+PSH 标志
  • 0xFFFF Window:填满避免 Wireshark 报窗口警告

相关文档 ​

基于 VitePress 构建 · 教学用途