从 lwIP-2.0.0 开始,可以自定义 TCP 报文段的初始序列号。
初始序列号
TCP 报文段首部有一个序列号字段(见下图),它是一个32位的计数器,从 0 到 4294967295,它的值为当前报文段中第一个数据的字节序号。在建立连接之初,通信双方都会各自选择一个序列号,称之为初始序列号(Initial Sequence Number:ISN),在建立连接时,通信双方通过 SYN 报文交换彼此的初始序列号。
初始序列号的生成方式可能会因实现而异,但是大多数实现都会选择一个随机数或者根据某些特定的算法生成。TCP 报文段的初始序列号是由 TCP 协议栈函数生成的。在 lwIP 中,协议栈为每个新的 TCP 连接生成一个 TCP 初始序列号,tcp.c 中的函数 tcp_next_iss
用于这个目的。
lwIP 1.4.1 版本初始序列号实现
在1.4.1版本中,这个函数长这样:
/**
* Calculates a new initial sequence number for new connections.
* @return u32_t pseudo random sequence number
*/
u32_t tcp_next_iss(void)
{
static u32_t iss = 6510;
iss += tcp_ticks;
return iss;
}
这个算法很简单,一个固定值(6510)加上系统启动到当前的时间。这是一个常见的在 TCP 协议中为新连接生成初始序列号的方法。但是这个算法产生的值是可以预测的,以至于 lwIP 的 TCP 连接可能成为 TCP 欺骗攻击的目标。
对初始序列号的准确预测是 IP 欺骗、数据注入和会话劫持能成功的先决条件。举一个TCP Reset 攻击例子:
如果能准确预测初始序列号,就可以假冒客户端向服务器发送 RST 包,要求复位连接。由于 RST 包并不需要向应用层提交,服务器端的 TCP 协议栈只要接到该包就立即终止此次 TCP 连接,这将让真正的客户端连不上服务器,导致了拒绝服务攻击。
根据研究结果表明,Windows 2000、Windows XP SP1 的初始序列号都是可以预测的,从 Windows XP SP2 开始,初始序列号才能难以预测。
lwIP 2.1.3 版本初始序列号实现
RFC6528 规定了 TCP 发送方在建立连接时如何选择初始序列号,这个文档描述了如何避免在因特网中针对TCP连接的大量 off-path
攻击。但是,RFC6528 中的算法需要高分辨率计时器和加密散列函数。这远远超出了 lwIP 本身的范围。所以 lwIP 增加了 LWIP_HOOK_TCP_ISN
,这是一个钩子函数,可以让开发者根据自己的硬件平台实现自己的初始序列号生成算法。该钩子函数提供了很大的灵活性,既可以用简单的随机数生成初始序列号,也可以实现完整的 RFC 6528 规定的算法。在 lwIP-2.1.3,新的 tcp_next_iss
函数为:
u32_t tcp_next_iss(struct tcp_pcb *pcb)
{
#ifdef LWIP_HOOK_TCP_ISN
LWIP_ASSERT("tcp_next_iss: invalid pcb", pcb != NULL);
return LWIP_HOOK_TCP_ISN(&pcb->local_ip, pcb->local_port, &pcb->remote_ip, pcb->remote_port);
#else /* LWIP_HOOK_TCP_ISN */
static u32_t iss = 6510;
LWIP_ASSERT("tcp_next_iss: invalid pcb", pcb != NULL);
LWIP_UNUSED_ARG(pcb);
iss += tcp_ticks;
return iss;
如果定义了宏 LWIP_HOOK_TCP_ISN
,则需要这个钩子函数来生成初始序列号,你需要在外部实现这个函数。如果没有定义宏 LWIP_HOOK_TCP_ISN
,则还是使用 lwip-1.4.1 版本中简单算法。总之,新的 lwIP 版本给出了生成初始序列号的另一种途径,不同的硬件可以选择不同的算法。
读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)