Hopsum:利用路由器校验和机制实现网络侧信道计算 在实际网络通信中路由器的主要职责是根据IP报文头部的目标地址进行转发它通常不关心数据载荷的内容。但你是否想过能否利用路由器对报文必须进行的某些标准操作来实现一些非标准的功能例如让路由器在转发过程中无意间完成一次简单的算术运算。这听起来像是网络协议栈的一个“副作用”或“技巧”但它恰恰揭示了网络设备处理数据包时一些有趣且底层的机制。Hopsum 这个概念正是探索了这种可能性通过精心构造的、本应被丢弃的“过期”数据包诱导路由器在计算校验和Checksum的过程中间接地为我们执行计算。本文面向对网络协议、路由器工作原理以及底层数据包操作感兴趣的开发者、网络工程师和安全研究人员。我们将从 IPv4 和 ICMP 协议的基础讲起深入理解校验和的计算机制然后探讨 Hopsum 这一设想背后的原理、实现思路、潜在挑战以及其作为学习工具的价值。通过本文你将能理解如何将网络设备的固有行为转化为一种计算原语并掌握相关的协议分析和数据包构造技能。1. 理解基石IPv4、ICMP 与校验和要理解 Hopsum必须首先掌握三个核心概念IPv4 报文结构、ICMP 协议以及校验和算法。它们是整个设想得以成立的基础。1.1 IPv4 报文头部与生存时间TTLIPv4 报文头部包含多个字段其中与 Hopsum 设想最相关的是生存时间Time To Live, TTL和头部校验和Header Checksum。一个简化的 IPv4 头部关键字段如下0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -------------------------------- |Version| IHL |Type of Service| Total Length | -------------------------------- | Identification |Flags| Fragment Offset | -------------------------------- | Time to Live | Protocol | Header Checksum | -------------------------------- | Source Address | -------------------------------- | Destination Address | -------------------------------- | Options (if any) | --------------------------------TTLTime to Live这是一个8位字段初始值通常为64、128或255。每经过一个路由器即一跳该值减1。当 TTL 减至0时路由器会丢弃该数据包并通常向源地址发送一个ICMP Time Exceeded消息。这个“递减”操作是路由器对数据包内容进行的少数几种算术运算之一。头部校验和Header Checksum这是一个16位字段用于检测 IPv4 头部在传输过程中是否发生错误。关键点在于每当 IPv4 头部的任何字段发生变化例如 TTL 被减1路由器都必须重新计算并更新这个校验和。这个重新计算的过程就是 Hopsum 设想利用的核心环节。1.2 ICMP 协议与错误报告ICMPInternet Control Message Protocol是 IP 协议的辅助协议用于传递控制信息和错误报告。当路由器因为 TTL0 而丢弃一个数据包时它会生成一个ICMP Time Exceeded (Type 11, Code 0)报文并将其发送回原数据包的源地址。这个 ICMP 报文本身也包含一个头部和校验和。更重要的是根据 RFC 792ICMP 错误报文如 Time Exceeded的载荷部分必须包含触发该错误的原始 IP 数据包的头8个字节以及其数据部分的前64位即原始IP头至少8字节的原始传输层数据。这为“携带”信息提供了可能。1.3 校验和的计算原理IPv4 头部校验和与 ICMP 校验和的计算算法相同都是简单的16位二进制反码求和。算法步骤如下将需要计算校验和的字段对于IPv4是整个头部对于ICMP是整个报文视为一系列16位2字节的字word。如果总字节数为奇数则在末尾补一个值为0的字节使其成为偶数。将所有16位字进行二进制求和普通的加法进位会加到结果上。将求和结果中溢出的高位进位不断加到低位上直到没有进位为止。这个过程称为“回卷”Wrap Around。最后对得到的16位结果取反码即按位取反这个值就是校验和。在发送方计算出的校验和填入对应字段。接收方或中间的路由器进行同样的计算但这次是将包括校验和字段在内的所有16位字进行求和。如果数据包没有错误这个求和的结果应该等于0xFFFF二进制全1。如果不是则说明数据包在传输中出错。注意IPv6 头部取消了校验和将错误检测的责任完全交给了上层协议如TCP、UDP。因此Hopsum 的设想主要围绕 IPv4 网络展开。路由器在转发 IP 包时必须将 TTL 减1。由于 TTL 是 IPv4 头部的一部分它的改变意味着头部发生了变化因此路由器必须重新计算整个 IPv4 头部的校验和。这个“重新计算”是一个确定性的数学过程。Hopsum 的核心思想就是能否通过精心设计原始数据包的头部字段特别是那些在传输中会保持不变的部分使得当路由器为这个“过期”数据包生成 ICMP Time Exceeded 响应时在 ICMP 响应报文的某个部分如校验和或载荷中的某个字段体现出我们预设的“计算结果”2. Hopsum 设想的工作机制与实现思路Hopsum 不是一个已部署的软件而是一个概念验证Proof of Concept或思想实验。它探讨的是一种“计算范式”。下面我们拆解其工作机制和一种可能的实现思路。2.1 核心逻辑链假设我们想让沿途的路由器对两个数 A 和 B 进行加法运算并将结果“报告”回来。逻辑链如下构造触发包Trigger Packet我们构造一个特殊的 IPv4 数据包。将其 TTL 设置为 1确保它在到达第一个路由器后就会因 TTL 过期而被丢弃。编码操作数我们将想要相加的两个数字 A 和 B以某种形式编码到这个触发包的 IP 头部或载荷的特定位置。这些位置需要满足一个条件在路由器生成 ICMP Time Exceeded 报文时这些编码值会被原封不动地或经过某种可预测的变换后复制到 ICMP 报文的载荷部分。诱导路由器“计算”路由器收到 TTL1 的包将其 TTL 减为 0然后丢弃。接着它需要构造一个 ICMP Time Exceeded 报文。在构造过程中路由器会 a. 计算这个新 ICMP 报文的校验和。 b. 将触发包的头部包含我们编码的 A 和 B的前 8 个字节作为载荷的一部分放入 ICMP 报文。校验和作为“计算结果”ICMP 校验和的计算涵盖了整个 ICMP 报文包括其头部和载荷即包含我们编码的 A 和 B 的那部分数据。因此最终计算出的 ICMP 校验和值在某种程度上是原始数据A和B和固定ICMP头部的一个函数。如果我们设计的编码方式非常巧妙使得 ICMP 校验和的计算结果恰好等于A B或 A ⊕ B 等其它运算那么我们就成功地让路由器完成了一次算术运算并将结果“写”在了校验和字段。接收与解码我们作为发送方监听 ICMP 响应。当收到 ICMP Time Exceeded 报文时提取其校验和字段这个值就是我们期望的“运算结果”。2.2 一个简化的理论模型让我们用极度简化的伪代码来理解这个过程。假设 ICMP 校验和计算函数是Checksum(data)。我们构造触发包在载荷中固定位置放入两个16位数A和B。路由器构造 ICMP 响应其载荷部分Payload_ICMP包含了触发包的头部我们无法控制以及我们的载荷包含 A 和 B。路由器计算ICMP_Checksum Checksum(ICMP_Header Payload_ICMP)。我们的目标是通过精心选择 A 和 B使得ICMP_Checksum A B模 2^16 运算。这本质上是一个反推问题已知ICMP_Header和Payload_ICMP中除 A、B 外的大部分固定内容以及期望的校验和结果AB求解满足等式的 A 和 B。由于校验和计算是线性运算二进制反码加法在模 2^16-1 下是线性的理论上可以构造出这样的 A 和 B。2.3 实现挑战与关键点在实际操作中会遇到诸多挑战ICMP 载荷的不可控部分ICMP Time Exceeded 报文的载荷包含了原始 IP 包的头 8 个字节。这 8 个字节中包括版本、IHL、TOS、总长度、标识符、标志位和片偏移这些字段在我们构造触发包时是确定的但并非全部可以为了“计算”而随意设置例如总长度必须符合实际包长。校验和算法的非线性处理虽然二进制加法是线性的但最后的“取反码”操作是非线性的。不过在验证校验和时接收方是将所有字包括校验和相加看是否得全1。从构造的角度看我们可以将“取反”的步骤纳入方程求解。网络地址转换NAT与防火墙许多网络环境会过滤或篡改 ICMP 报文这可能使得响应无法返回或者返回的报文校验和被中间设备重新计算破坏了我们的“结果”。多跳与不确定性如果 TTL 设置大于1数据包可能会经过多个路由器。每个路由器都会修改 TTL 和 IP 校验和但只有最终那个 TTL 减到 0 的路由器才会发送 ICMP 响应。路径的不确定性使得我们难以预测是哪个路由器进行了“计算”。尽管有这些挑战Hopsum 的价值不在于成为一个实用的分布式计算平台而在于它深刻地揭示了网络协议栈中一些可被利用的确定性行为。3. 动手实验构造一个触发包并观察响应为了更具体地理解这个过程我们可以使用像ScapyPython或raw socket编程这样的工具来构造数据包并进行实验。以下是一个使用 Scapy 的概念性代码框架用于发送一个 TTL 很小的包并捕获可能的 ICMP 响应。3.1 环境准备首先确保你有一个可以发送原始数据包并接收 ICMP 响应的环境。通常这需要管理员/root权限。# 安装 Scapy (Python 3) pip install scapy警告数据包构造和发送应在你自己控制的网络环境如本地虚拟机、隔离的测试网络中进行。随意向互联网或他人网络发送异常数据包可能违反服务条款或被误认为攻击行为。3.2 使用 Scapy 发送探测包下面的 Python 脚本演示了如何构造一个 TTL 为 1 的 IP 包并发送到一个假设的下一跳地址例如你的默认网关。#!/usr/bin/env python3 from scapy.all import * import sys def send_ttl_probe(dst_ip, src_port12345, dst_port80): 发送一个TTL很小的TCP SYN包诱导网关返回ICMP Time Exceeded。 实际上任何协议如UDP、ICMP Echo都可以。 # 构造IP层设置TTL1目标地址为远端服务器源地址为本机 ip IP(dstdst_ip, ttl1) # 构造TCP层使用SYN标志随机源端口目标端口为80HTTP tcp TCP(sportsrc_port, dportdst_port, flagsS, seq1000) # 组合成数据包 packet ip / tcp print(f[*] 发送探测包到 {dst_ip}:{dst_port}, TTL{packet.ttl}) # 发送包并同时开启嗅探以等待ICMP响应 # timeout 设置等待响应的超时时间 answered, unanswered sr(packet, timeout2, verbose0) for sent, received in answered: if received.haslayer(ICMP): icmp_layer received.getlayer(ICMP) if icmp_layer.type 11 and icmp_layer.code 0: # Time Exceeded print(f[] 收到 ICMP Time Exceeded 来自 {received.src}) # 打印收到的ICMP包的校验和 print(f ICMP 校验和字段: 0x{icmp_layer.chksum:04x}) # 尝试提取原始包的头部信息 if received.haslayer(IPerror): original_ip received.getlayer(IPerror) print(f 原始触发包 TTL: {original_ip.ttl1}) # 路由器已将其减为0 # 这里可以进一步分析载荷寻找我们编码的A和B else: print(f[?] 收到其他类型 ICMP: type{icmp_layer.type}, code{icmp_layer.code}) else: print(f[?] 收到非ICMP响应: {received.summary()}) if __name__ __main__: if len(sys.argv) ! 2: print(f用法: {sys.argv[0]} 目标IP) sys.exit(1) target_ip sys.argv[1] send_ttl_probe(target_ip)3.3 运行与观察将脚本保存为hopsum_probe.py。在终端中运行需要 root 权限sudo python3 hopsum_probe.py 8.8.8.8这里用8.8.8.8举例你可以替换成你知道的、需要经过至少一跳才能到达的任意公网IP。对于TTL1它很可能在你的网关处就被丢弃。预期输出可能类似于[*] 发送探测包到 8.8.8.8:80, TTL1 [] 收到 ICMP Time Exceeded 来自 192.168.1.1 ICMP 校验和字段: 0x4d2f 原始触发包 TTL: 0这个脚本并没有实现完整的 Hopsum 计算它只是演示了如何触发 ICMP Time Exceeded 并获取其校验和。0x4d2f这个值就是你的网关路由器在构造那个特定 ICMP 报文时计算出的校验和。这个值是由 ICMP 头部类型、代码等和载荷包含我们发送的原始 IP 头前8字节和TCP头前8字节共同决定的。3.4 深入尝试编码信息要实现 Hopsum我们需要控制触发包的更多字段。例如我们可以将操作数 A 和 B 编码在 TCP 的序列号Sequence Number和确认号Acknowledgment Number字段中因为这两个字段是32位的且会被包含在 ICMP 载荷的前64位传输层数据里。def send_hopsum_encoded(dst_ip, A, B): 尝试将A和B编码到TCP头部观察ICMP校验和的变化。 这是一个概念性演示实际关系远比此复杂。 ip IP(dstdst_ip, ttl1) # 将A和B编码到TCP序列号和确认号取低16位作为示例 tcp TCP(sportRandShort(), dport80, flagsS, seqA, ackB) packet ip / tcp / Raw(bX*20) # 添加一些载荷确保长度 print(f[*] 发送编码包: A(seq低16位){A 0xFFFF}, B(ack低16位){B 0xFFFF}) answered, _ sr(packet, timeout2, verbose0) for sent, received in answered: if received.haslayer(ICMP) and received.getlayer(ICMP).type 11: icmp_chksum received.getlayer(ICMP).chksum print(f[] ICMP 校验和: 0x{icmp_chksum:04x} ({icmp_chksum})) # 理论上我们需要建立 icmp_chksum 与 A, B 的数学关系模型 # 这需要解一个关于校验和计算的方程运行这个脚本改变 A 和 B 的值观察icmp_chksum的变化。你会发现它确实会变但变化规律并非简单的AB。要建立准确的数学模型需要完整实现 RFC 1071 定义的校验和算法并考虑所有固定字段的影响。4. 校验和计算模型与方程构建要让路由器“计算”出我们想要的结果我们必须能够精确预测给定输入下ICMP 校验和会是什么。这就需要我们形式化校验和的计算过程。4.1 定义校验和计算函数我们实现一个标准的16位二进制反码求和函数def ones_complement_add(words): 对一系列16位整数列表进行二进制反码求和。 words: list of integers (0-65535) 返回: 16位校验和0-65535 total 0 for word in words: total word # 处理溢出将进位加回低位 total (total 0xFFFF) (total 16) # 取反码得到校验和值 checksum ~total 0xFFFF return checksum def calculate_icmp_checksum(icmp_type, icmp_code, icmp_rest_of_header, payload_words): 计算ICMP校验和。 icmp_type, icmp_code: 各8位 icmp_rest_of_header: 16位对于Time Exceeded通常是0 payload_words: list of 16-bit words representing the payload # 将类型和代码组合成一个16位字 type_code_word (icmp_type 8) | icmp_code # 校验和字段在计算时视为0 pseudo_header [type_code_word, icmp_rest_of_header] all_words pseudo_header payload_words return ones_complement_add(all_words)4.2 构建 Hopsum 方程假设我们构造的触发包其 IP 头前8字节和 TCP 头前8字节共16字节即8个16位字中有一部分是我们可以控制的变量如编码了 A 和 B其余是固定值或可预测值如IP版本、头长度、总长度等。设这8个字为W[0]到W[7]其中W[i]和W[j]是我们编码的 A 和 B。当路由器构造 ICMP Time Exceeded 报文时其载荷payload_words就包含了W[0]到W[7]。此外ICMP 头部还有固定的类型11、代码0和未用字段0。设C_target为我们希望路由器计算出的结果例如AB模 65536。那么我们需要解以下方程calculate_icmp_checksum(11, 0, 0, [W[0], W[1], ..., W[7]]) C_target其中W[0]...W[7]中的某些W是 A 和 B 的函数例如W[2] A,W[3] B其他W是常数。由于ones_complement_add是线性运算在模2^16-1的域上我们可以将这个方程重写为K a*A b*B ~C_target (模 2^16-1)这里K是所有固定字求和的结果包括ICMP头部固定部分和载荷中的固定字a和b是系数通常是 1 或 -1取决于 A 和 B 在字序列中的位置和字节序。~C_target是对目标值取反因为校验和是取反后存储的。通过求解这个线性方程我们可以找到满足条件的 A 和 B。这需要一些代数运算但用编程实现是可行的。4.3 求解与验证脚本下面是一个高度简化的示例演示如何为特定的目标校验和比如AB求解 A假设 B 固定或反之。def solve_for_A_given_B(C_target, B_fixed, fixed_words, A_position, B_position): 固定B求解A使得ICMP校验和等于C_target。 这是一个概念性框架忽略了字节序、IP头固定字段计算等复杂细节。 fixed_words: 一个列表在A和B位置用None占位。 A_position, B_position: A和B在fixed_words列表中的索引。 # 1. 计算常数部分K假设A0 BB_fixed test_words fixed_words[:] test_words[A_position] 0 test_words[B_position] B_fixed # 这里需要将test_words转换为实际的16位字列表包括ICMP头部 # 为简化假设有一个函数能计算除A外的所有贡献 # K contribution_of_all_fixed_parts(B_fixed) # 实际实现需要完整的包构造和校验和计算模拟 # 2. 由于校验和计算是线性的A的贡献是它本身如果它在载荷中作为一个独立的16位字 # 方程: K A ~C_target (模 65535) # 所以 A (~C_target - K) mod 65535 # 注意这是极度简化的模型真实情况要复杂得多。 # 以下为伪代码 K calculate_constant_contribution(B_fixed, fixed_words, B_position) A_solution (~C_target - K) 0xFFFF return A_solution # 实际应用需要模拟完整的IP和ICMP包结构计算每个字段的精确贡献。这个求解过程是 Hopsum 从设想变为可演示代码的关键。它要求我们对网络数据包的比特级布局有极其精确的理解。5. 常见问题、挑战与排查在实际尝试实现 Hopsum 时你会遇到一系列问题。下面列出常见问题及其排查思路。5.1 收不到 ICMP Time Exceeded 响应问题现象可能原因检查与排查方法处理建议发送探测包后无任何响应本地防火墙阻止了出站原始套接字使用tcpdump或 Wireshark 在发送主机上抓包确认包是否被发出。检查防火墙规则如iptables,firewalld确保允许出站原始包。中间路由器或目标主机防火墙丢弃了ICMP错误报文在网关或更近的网络节点抓包看ICMP响应是否生成但被过滤。许多云主机提供商默认禁用ICMP错误消息。尝试在可控的本地网络如家庭路由器后测试。TTL 设置过大包到达了目标主机目标主机可能不回复TCP SYN到未开放端口如80或回复RST。使用traceroute命令先确定到目标的第一跳地址将TTL设为1目标地址设为该网关地址。收到响应但不是 ICMP Time Exceeded目标端口不可达如果目标主机响应可能是ICMP Destination Unreachable (Port)。确保使用一个大概率未使用的远程端口或使用不存在的目标主机IP。5.2 计算出的校验和与预期不符问题现象可能原因检查与排查方法处理建议即使A、B相同每次收到的校验和也不同触发包中某些字段是随机的检查脚本中是否使用了随机源端口、随机IP ID、随机TCP序列号等。在构造触发包时将所有非实验变量固定为常量例如固定源端口、IP ID、TCP序列号基数等。校验和变化与A、B的变化无简单关系编码位置或字节序错误仔细核对RFC 791 (IP)和RFC 792 (ICMP)确认ICMP载荷中原始包数据的格式和字节序网络字节序。编写一个函数完全模拟路由器构造ICMP包并计算校验和的过程与抓取到的真实包进行逐位比对。忽略了IP头部的校验和路由器在转发时修改了TTL因此也修改了IP头的校验和。这个修改后的IP头会被包含在ICMP载荷里。你的计算模型必须考虑“原始IP头”是经过路由器TTL递减和校验和重算后的版本而不是你最初发送的版本。5.3 实验环境搭建建议使用虚拟网络在 VirtualBox 或 VMware 中创建两个虚拟机配置为仅主机Host-Only网络相互可达。在一台虚拟机上运行发送脚本在另一台或网关虚拟机上抓包分析。这是最安全、可控的方式。利用 Docker 网络创建自定义的 Docker 网络运行容器利用容器作为简单的路由器或终端进行测试。本地回环适配器在 Linux 上可以尝试向本地回环地址127.0.0.1发送TTL1的包但某些系统可能不会为本地产生的包生成ICMP Time Exceeded。明确目标初期目标不是实现完整的算术运算而是先实现“发送固定包能准确预测返回的ICMP校验和”。这是验证你对协议理解是否正确的基础步骤。6. Hopsum 的意义、局限与扩展思考6.1 作为学习工具的意义Hopsum 最大的价值在于教育性和启发性深入理解协议它强迫你深入研究 IPv4、ICMP 的报文格式、字节序、校验和算法等细节。理解网络设备行为让你从“黑盒”使用路由器转变为思考其内部处理逻辑。掌握数据包构造熟练使用Scapy、libpcap等工具进行底层网络编程。安全研究视角这种“滥用”协议预期行为的方式与某些网络侧信道攻击或隐蔽信道技术有思想上的关联有助于理解安全攻防。6.2 现实局限性效率极低完成一次“计算”需要往返一次网络跳数的时间毫秒级并且需要构造、发送、接收、解析多个数据包。与CPU的纳秒级运算速度相比效率相差数十亿倍。不可靠ICMP 报文可能丢失、被过滤、被篡改。结果单一只能进行非常有限类型的计算主要是与校验和算法相关的线性运算。无通用性严重依赖于特定的网络环境和协议版本IPv4。6.3 扩展思考与相关概念尽管 Hopsum 本身不实用但其思想可以引申到其他领域网络隐蔽信道Covert Channel利用协议字段如IP ID、TCP初始序列号、TTL来秘密传输信息是信息隐藏的一个研究方向。Hopsum 可以看作是一种极端隐蔽的信道。协议模糊测试Fuzzing通过发送非标准或畸形的数据包如奇异的TTL值、校验和来测试网络设备或协议栈实现的健壮性。路径诊断与拓扑发现traceroute工具本身就是利用 TTL 过期和 ICMP 响应的原理。Hopsum 可以视为对traceroute原理的另一种深度应用。IPv6 的挑战如前所述IPv6 没有头部校验和。那么在纯 IPv6 环境中是否还有其他必选字段的修改能产生类似的可利用点例如跳数限制Hop Limit的递减是必须的但 IPv6 的扩展头部结构更为复杂。6.4 最佳实践与安全警告仅在授权环境测试所有数据包构造和发送实验必须在你自己拥有完全控制权的网络环境中进行。明确学习目的理解原理为主不要试图将其用于干扰或探测未经授权的网络。注意法律与政策即使是在自家网络某些 ISP 可能对异常的 ICMP 流量有监控或限制。代码健壮性实验代码应包含充分的错误处理和日志记录避免因程序异常产生不可控的网络流量。Hopsum 是一个精巧的“思维体操”它展示了将网络基础设施转化为计算单元的某种理论可能性。通过尝试实现它你收获的将不是一种实用的计算工具而是对计算机网络底层运作机制深刻而直观的理解。这种理解是进行高性能网络编程、协议设计、网络安全分析和故障排查的坚实基础。下一步你可以尝试实现完整的校验和预测模型或者探索如何利用 TCP 序列号、IP ID 等字段实现更复杂的“隐写”或编码方案从而深化对网络协议可塑性的认识。