【免费下载链接】paqet
Ferries Packets Across Forbidden Boundaries 👀
paqet 是一个基于原始数据包(raw packet)的加密代理工具,它绕过操作系统 TCP/IP 协议栈,自行构造并注入 TCP 数据包来承载加密流量。由于这些"假 TCP 包"没有任何真实的连接状态,paqet 的流量伪装技巧就成了对抗指纹识别的关键:本文带你从零理解TCP Flag 循环(TCP flag cycling)、序列号伪造(sequence number spoofing)与时间戳伪造(timestamp spoofing)的完整原理,以及为什么接收端可以"看破却放行"。🧩
为什么原始包流量需要流量伪装?
普通应用发包要经过内核 TCP/IP 协议栈,内核会为每个连接维护完整的状态(握手、序号、窗口)。而 paqet 的思路完全不同:
- 发送侧:用 gopacket 手工拼出以太网帧 + IP 头 + TCP 头,直接通过 pcap 注入网卡;
- 接收侧:用 pcap 从网卡驱动处"抄送"每一个入站数据包,用 BPF 过滤出目标端口的包。
这就带来一个副作用:包里的TCP 标志位、序列号、确认号、时间戳选项对协议栈毫无意义——paqet 的接收端只关心 TCP payload(见 internal/socket/recv_handle.go 中Read方法,仅提取d.tcp.Payload)。
但这些字段恰恰是流量指纹。如果所有包永远都是PSH+ACK、序号从 1 开始递增、时间戳恒定——特征太单一,很容易被深度包检测(DPI)规则盯上。paqet 的解法分三层:
- TCP Flag 循环:让标志位组合周期性变化,模拟多种"连接阶段"的混合流量;
- 序列号伪造:让 Seq/Ack 落在大数值空间并缓慢增长,像一条"成熟连接";
- 时间戳伪造:让 Timestamps 选项以毫秒级节奏推进,并附带逼真的 RTT 回显。
三层叠加后,抓包看到的是一段"看似普通、又说不清细节"的 TCP 流,而且没有任何 SYN 握手可供防火墙做连接跟踪(conntrack)——这正是 README.md 中提到的绕过握手型过滤的核心。
paqet 数据包在链路上长什么样?
发送端为每个数据单元拼出一个完整 IP 包(internal/socket/send_handle.go 中的 IPv4/IPv6 头构造),固定特征如下:
| 字段 | 取值 | 说明 |
|---|---|---|
| TTL | 64 | 典型的 Linux 主机默认值 |
| TOS | 184 | 固定低优先级 |
| Window | 65535 | 满窗口 |
| 源 MAC | 网关 MAC | 直接以路由器为下一跳 |
| 源端口 | 配置端口(客户端自动随机) | 与监听端口对应 |
真正的"伪装戏法"全部集中在 TCP 头,核心代码就是buildTCPHeader(internal/socket/send_handle.go)。
TCP Flag 循环:最灵活的伪装开关 🔁
配置 local_flag 与 remote_flag
在配置文件的network.tcp下,可以定义两组标志位数组(example/client.yaml.example):
network: tcp: local_flag: ["PA"] # 本端发出的包使用的标志位 remote_flag: ["PA"] # 要求对端回包时使用的标志位- 不配置时,默认值为
["PA"](PSH+ACK),即最常见的"正常数据"形态,逻辑见 internal/conf/tcp.go; - local_flag:本端发包时使用的标志位列表。客户端侧就是客户端→服务端的包;
- remote_flag:客户端在建立连接时会通过一条
PTCPF控制消息推给服务端(internal/client/timed_conn.go),服务端为每个客户端单独保存一份覆盖表(internal/server/tcpf.go 与 internal/socket/socket.go)。也就是说,同一个服务端对不同客户端的回包可以用不同标志位,而服务端自己的默认回包形态由 server 配置的local_flag决定(example/server.yaml.example)。
标志位字符速查表
每个组合是若干字符的字符串,逐字符解析为 TCP 标志位(internal/conf/tcp.go):
| 字符 | 标志位 | 典型含义 |
|---|---|---|
F | FIN | 请求断开连接 |
S | SYN | 发起连接(握手) |
R | RST | 重置连接 |
P | PSH | 推送数据 |
A | ACK | 确认 |
U | URG | 紧急指针 |
E | ECE | 拥塞通知 |
C | CWR | 拥塞避免确认 |
N | NS | 保留位 |
循环是怎么工作的?
把数组配置成多个组合,发送端就会逐包轮流取用,取完一轮从头再来:
network: tcp: local_flag: ["PA", "S", "A"]实现是一个原子计数器 + 模运算的泛型迭代器(internal/pkg/iterator/iterator.go),无锁、线程安全。第 1 个包PA、第 2 个包S、第 3 个包A、第 4 个包又回到PA……
更妙的是:标志位组合还会改变整个 TCP 头的形态——
- 组合中含
S时,走"握手形态"分支:带上 MSS(1460)、SACK、Timestamps、Window Scale(8) 全套握手选项(internal/socket/send_handle.go); - 其他组合走"数据形态"分支:只带两个 NOP + Timestamps 选项(internal/socket/send_handle.go)。
因此混合标志位列表后,链路流量会呈现出**"数据流 + 伪握手流"交织**的形态,就像多条不同生命周期的普通 TCP 连接重叠在一起——单看任何几个包都"很常见",这正是 TCP Flag 循环的价值。💡
序列号伪造:让假包像一条真连接 🔢
真实 TCP 的 ISN(初始序号)是大随机数,序号随数据单调递增。paqet 用"时间 + 全局包计数器"来模拟这两个特征,分两种形态:
握手形态(SYN 分支)——模拟刚建立的连接:
Seq = 1 + (counter & 0x7) # 落在 1~8 的小数值,像新连接 Ack = 0 # 若组合同时含 A,则 Ack = Seq + 1数据形态(非 SYN 分支)——模拟成熟连接:
Seq = 启动时刻(ms) + (counter << 7) # 大数值空间,每包增长 128 Ack = Seq - (counter & 0x3FF) + 1400 # 在 Seq 附近 ±1023 范围内抖动三个设计细节(internal/socket/send_handle.go):
- Seq 基线取启动毫秒时间戳(internal/socket/send_handle.go),天然是个 13 位左右的大数,与真实 ISN 的数值量级一致;
- Ack 恒在 Seq 附近波动,并带
+1400偏移(约一个 MSS 的数据量级),模仿"对端也在发数据"的双向会话观感,counter & 0x3FF的低 10 位让偏移逐包缓慢漂移; - 无需连续性。paqet 把每个原始包当作独立数据报(datagram)处理,接收端从不校验序号,所以"伪造"的唯一目的就是对旁路观察者"看起来合理"。
时间戳伪造:模拟毫秒时钟与 RTT ⏱️
TCP 的 Timestamps 选项(RFC 7323)包含两个字段:tsVal(本机时钟)与tsEcr(回显对端时钟)。paqet 在两种形态下都会写入该选项,伪造策略如下:
- tsVal= 启动毫秒时间戳 +
counter >> 3,即每发 8 个包才递增 1。普通主机毫秒级发几个包是很常见的节奏,这样推进的时钟既"不跳变"也不"过快"; - tsEcr(数据形态)= tsVal − (
counter % 200+ 50),即回显值始终落在 50~249 的过去,随计数器缓慢滑动——等价于伪造了一个 50~250ms 量级的往返延迟画像,这是大多数骨干/城网环境的常见 RTT 区间; - 握手形态则回显 0,模拟"对端刚发出、尚未可回显"的新连接。
配合固定选项:MSS 1460(internal/socket/send_handle.go 中0x05b4)、Window Scale 8、SACK permitted,一个含S标志的包与正常浏览器的 SYN 包在结构上几乎无法区分。🕵️
接收端为什么"看破却放行"?
理解伪造的前提是理解 paqet 的接收端到底在做什么。RecvHandle.Read(internal/socket/recv_handle.go)的流程是:
- BPF 过滤
tcp and dst port <端口>,只留下发给本端的数据包; - 解码以太网/IP/TCP 层,只取源地址和
tcp.Payload; - 标志位、序号、确认号、时间戳——一个都不读。
所以整套伪造不是为了让对端"相信",而是为了让链路上的第三方观察者(DPI 设备、旁路监控)看到符合统计规律的普通 TCP 流量。这也解释了为什么服务器侧必须配置iptables NOTRACK等规则(见 README.md 的 Critical Firewall Configuration 一节):内核不知道这些"连接"存在,不能让内核代为回 RST 破坏伪装后的会话状态。
如何验证你的伪装效果?
两步走,从"应用视角"和"链路视角"分别确认:
- 应用视角:用
paqet ping -c config.yaml确认通道可用; - 链路视角:
- 内置诊断工具
paqet dump -c config.yaml(需要 server 配置,实现见 cmd/dump/dump.go)可以打印到达的每个数据包及十六进制 payload,快速确认流量确实走的是原始包通道; - 在服务器或中间节点用 Wireshark 抓包,重点观察三个指标是否随时间"漂移":
- 标志位组合是否按你配置的列表周期轮换;
- Seq 是否为大数值且缓慢增长、Ack 是否在 Seq 附近波动;
- Timestamps 选项是否以"每 8 包 +1"的节奏推进,tsEcr 是否落在 50~250 区间。
- 内置诊断工具
如果三者都呈现自然漂移,说明 TCP Flag 循环与序列号/时间戳伪造均已生效。✅
伪装调参清单(Checklist)
| 目标 | 建议配置 | 说明 |
|---|---|---|
| 最贴近普通数据流量 | ["PA"] | 默认值,形态单一但最"像业务" |
| 模拟混合连接 | ["PA", "S", "A"] | 数据流与伪握手流交织,指纹更复杂 |
| 双端差异化 | 客户端分别设置local_flag/remote_flag | 客户端可强制服务端回包形态,逐客户端生效 |
| 控制包上限 | 每列表 ≤ 64 项 | 超出会被配置校验拒绝(internal/conf/tcp.go) |
最后提醒:伪装提升的是指纹抗性而非匿名性——paqet 自身已用 KCP 提供加密传输(transport.kcp.key必须双端一致),密钥、端口规划与服务器 iptables 规则同样重要。将 internal/conf/tcp.go、internal/socket/send_handle.go 两处源码与本文对照阅读,可以完整复现 paqet 流量伪装技巧的每一处设计决策。
【免费下载链接】paqet
Ferries Packets Across Forbidden Boundaries 👀
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考