【网络入侵检测】基于源码分析Suricata的IP分片重组
【作者主页】只道当时是寻常
【专栏介绍】Suricata入侵检测。专注网络、主机安全,欢迎关注与评论。
目录
目录
1.概要
2. 配置信息
2.1 名词介绍
2.2 defrag 配置
3. 代码实现
3.1 配置解析
3.1.1 defrag配置
3.1.2 主机系统策略
3.2 分片重组模块
3.2.1分片追踪器(DefragTracker )
3.2.2 获取分片追踪器(DefragGetTracker)
3.2.3 分片插入(DefragInsertFrag)
3.2.3.1 分片冲突策略
3.2.3.1.1 DEFRAG_POLICY_BSD
3.2.3.1.2 DEFRAG_POLICY_BSD_RIGHT
3.2.3.1.3 DEFRAG_POLICY_LINUX
3.2.3.1.4 DEFRAG_POLICY_WINDOWS
3.2.3.1.5 DEFRAG_POLICY_SOLARIS
3.2.3.2 获取主机系统策略
1.概要
👋 本文针对网络入侵检测中 Suricata 的 IP 分片重组模块展开源码分析。IP 分片重组是网络数据传输关键,对 NIDS 执行 DPI 至关重要。Suricata 作为开源高性能网络威胁检测引擎,通过分析其 IP 分片重组模块源码,解析该模块处理 IP 分片数据、监视重组数据包,并将重组数据传递给后续模块的过程,以确保检测准确完整。文中主要包含Suricata中分片配置、分片重组以及解决重组冲突信息。
2. 配置信息
2.1 名词介绍
IP 分片 | IP 分片是因不同网络链路 MTU 不同,当 IP 数据包大小超链路 MTU 时,将其拆成多个小数据包的机制。 |
IP 分片流 | 单个 IP 报文经分片处理后,由其形成的多个分片所构成的传输流,被称为 IP 分片流,其中各分片报文的 IP 包头 ID 标识保持一致。 |
分片重组引擎 | Suricata 中专门用于对 IP 分片报文实施内部重组的功能模块。 |
2.2 defrag 配置
在 Suricata 的配置文件中,defrag 关键字包含的配置信息用于配置分片重组引擎。其默认配置信息如下:
defrag:memcap: 32mb# memcap-policy: ignorehash-size: 65536trackers: 65535 # number of defragmented flows to followmax-frags: 65535 # number of fragments to keep (higher than trackers)prealloc: yestimeout: 60# Enable defrag per host settings
# host-config:
#
# - dmz:
# timeout: 30
# address: [192.168.1.0/24, 127.0.0.0/8, 1.1.1.0/24, 2.2.2.0/24, "1.1.1.1", "2.2.2.2", "::1"]
#
# - lan:
# timeout: 45
# address:
# - 192.168.0.0/24
# - 192.168.10.0/24
# - 172.16.14.0/24
-
memcap:设置分片重组引擎可申请的最大使用内存,这限制了碎片整理过程中内存的使用量。
-
memcap-policy:[drop-packet/pass-packet/reject/ignore(默认)],适用于IPS模式,例如在分片重组时空间申请失败等因素导致无法重组该分片时,对该分片采取的策略。
-
hash-size:设置哈希表中能存储的元素的上限。
-
trackers:设置预先申请的分片追踪器(DefragTracker )的数量,一般和 prealloc 配合使用,prealloc 为 yes,则申请执行数量的DefragTracker 存储在defragtracker_spare_q结构中,否则不申请。
-
max-frags:设置最大分片数量。
-
prealloc:见 "trackers" 字段解释。
-
timeout:设置分片重组超时时间,默认 60s。
-
host-config: 针对特定主机单独设置分片重组策略。
-
timeout:设置分片重组超时时间,默认 60s。
-
address:设置主机IP地址。
-
3. 代码实现
3.1 配置解析
3.1.1 defrag配置
DefragInit 函数是Suricata 解析配置文件中 Defrag 配置信息的入口函数,DefragPolicyLoadFromConfig 函数用于解析 defrag.host-config 相关的配置信息,DefragInitConfig 函数用于解析其它配置信息。
3.1.2 主机系统策略
在Suricata中 SCHInfoLoadFromConfig 函数主要用于加载配置文件中 host-os-policy 相关的配置信息。
host-os-policy 的配置格式如下所示,分号前面为操作系统类型,后面列表中包含主机的IP地址,不同的IP地址可使用逗号分割。Suricata依据用户配置的IP地址区分主机系统类型。
# Host specific policies for defragmentation and TCP stream
# reassembly. The host OS lookup is done using a radix tree, just
# like a routing table so the most specific entry matches.
host-os-policy:windows: [0.0.0.0/0]bsd: []bsd_right: []old_linux: []linux: [10.0.0.0/8, 192.168.1.100, "8762:2352:6241:7245:E000:0000:0000:0000"]old_solaris: []solaris: ["::1"]hpux10: []hpux11: []irix: []macos: []vista: []windows2k3: []
下面sc_hinfo_os_policy_map 结构用于将配置信息中主机类型转换成宏变量。
/** Enum map for the various OS flavours */
SCEnumCharMap sc_hinfo_os_policy_map[ ] = {{ "none", OS_POLICY_NONE },{ "bsd", OS_POLICY_BSD },{ "bsd-right", OS_POLICY_BSD_RIGHT },{ "old-linux", OS_POLICY_OLD_LINUX },{ "linux", OS_POLICY_LINUX },{ "old-solaris", OS_POLICY_OLD_SOLARIS },{ "solaris", OS_POLICY_SOLARIS },{ "hpux10", OS_POLICY_HPUX10 },{ "hpux11", OS_POLICY_HPUX11 },{ "irix", OS_POLICY_IRIX },{ "macos", OS_POLICY_MACOS },{ "windows", OS_POLICY_WINDOWS },{ "vista", OS_POLICY_VISTA },{ "windows2k3", OS_POLICY_WINDOWS2K3 },{ NULL, -1 },
};
3.2 分片重组模块
Defrag 函数作为分片重组引擎的入口函数,在网络数据包解码过程中发挥着关键作用。当解码进入网络层时,会根据数据包的 IP 版本调用 DecodeIPV4 或 DecodeIPV6 函数进行处理(有关具体的解码流程以及这两个函数的详细介绍,可查阅 【网络入侵检测】基于源码分析Suricata的解码模块)。在调用这两个解码函数期间,Defrag 函数可能会被触发。IPv4 报文触发的条件是:IP 包头中的偏移字段值大于零,或者更多分片标志位(MF)被设置为 1。只有满足这些条件,才会调用 Defrag 函数对分片数据包进行重组操作。IPv6不是本文重点,本文不再关注。
下面是 Suricata 源码中对于IPv4包头是否分片判断:
int DecodeIPV4(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p,const uint8_t *pkt, uint16_t len)
{... .../* If a fragment, pass off for re-assembly. */if (unlikely(IPV4_GET_IPOFFSET(p) > 0 || IPV4_GET_MF(p) == 1)) {Packet *rp = Defrag(tv, dtv, p);if (rp != NULL) {PacketEnqueueNoLock(&tv->decode_pq, rp);}p->flags |= PKT_IS_FRAGMENT;return TM_ECODE_OK;}... ...
}
下面是分片重组模块四个主要函数:
-
Defrag:分片重组入口函数。
-
DefragGetTracker:获取分片追踪器(DefragTracker )。
-
DefragInsertFrag:执行分片重组操作。
-
DefragTrackerRelease:执行 DefragTracker 结构引用计数减一操作,并不是释放该对象。
下面是分片重组模块整体设计:
3.2.1分片追踪器(DefragTracker )
在 Suricata 中 DefragTracker 这个结构体尤为重要,该结构体用于跟踪和管理 IP 分片重组的过程。它的主要作用是记录与特定 IP 分片流相关的信息,以便在接收到所有分片后能够正确地重组原始数据包。下面是该结构体的结构:
/*** A defragmentation tracker. Used to track fragments that make up a* single packet.*/
typedef struct DefragTracker_ {SCMutex lock; /**< Mutex for locking list operations on* this tracker. */uint16_t vlan_id[VLAN_MAX_LAYERS]; /**< VLAN ID tracker applies to. */uint32_t id; /**< IP ID for this tracker. 32 bits for IPv6, 16* for IPv4. */uint8_t proto; /**< IP protocol for this tracker. */uint8_t policy; /**< Reassembly policy this tracker will use. */uint8_t af; /**< Address family for this tracker, AF_INET or* AF_INET6. */uint8_t seen_last; /**< Has this tracker seen the last fragment? */uint8_t remove; /**< remove */Address src_addr; /**< Source address for this tracker. */Address dst_addr; /**< Destination address for this tracker. */int datalink; /**< datalink for reassembled packet, set by first fragment */SCTime_t timeout; /**< When this tracker will timeout. */uint32_t host_timeout; /**< Host timeout, statically assigned from the yaml *//** use cnt, reference counter */SC_ATOMIC_DECLARE(unsigned int, use_cnt);struct IP_FRAGMENTS fragment_tree;/** hash pointers, protected by hash row mutex/spin */struct DefragTracker_ *hnext;struct DefragTracker_ *hprev;/** list pointers, protected by tracker-queue mutex/spin */struct DefragTracker_ *lnext;struct DefragTracker_ *lprev;
} DefragTracker;
下面我们挑选几个主要的变量进行解释:
-
vlan_id:记录 VLAN ID,用于标识分片所属的 VLAN 网络。
-
id:IP 分片的标识符(IP ID),用于区分不同的分片流。
-
proto: IP 协议类型(如 TCP、UDP 等),用于确定分片所属的协议。
-
policy:分片重组策略,不同的主机系统在遇到重组冲突时遵循的重组策略是不同的。
-
af:地址族,标识是 IPv4 (AF_INET) 还是 IPv6 (AF_INET6)。
-
seen_last:标记是否已经接收到最后一个分片。
-
remove:标记该跟踪器是否需要被移除。
-
src_addr:记录分片流的源地址。
-
dst_addr:记录分片流的目的地址。
-
datalink:数据链路层类型,由第一个分片设置。
-
timeout:超时时间,用于确定何时丢弃未完成的分片流。
-
fragment_tree: 用于存储分片的红黑树,按分片偏移量排序。
-
hnext 和 hprev: 哈希表指针,用于在哈希表中链接多个跟踪器。
-
lnext 和 lprev: 链表指针,用于在跟踪器队列中链接多个跟踪器。
👋 综上所述,我们能够明确,每个不同的 IP 分片流都与唯一的 DefragTracker 结构相对应。DefragTracker 结构借助红黑树,按照偏移顺序对当前 IP 分片流中的各个数据包进行存储。后续的分片存储、数据包重组等操作,均依赖这一结构体展开。
3.2.2 获取分片追踪器(DefragGetTracker)
在 Defrag 函数对分片包完成统计操作后,会调用 DefragGetTracker 函数,以查找当前分片报文是否存在对应的 DefragTracker 结构。若存在,则返回该 DefragTracker 结构;若不存在,则创建一个新的 DefragTracker 结构,用于存储该分片包所在分片流的相关信息。具体流程如下图所示:
3.2.3 分片插入(DefragInsertFrag)
调用 DefragGetTracker 后已经获取当前分片关联的 DefragTracker 对象,接着根据当前分片的偏移值,将分片插入到分片队列中。下面是分片插入操作流程图:
3.2.3.1 分片冲突策略
分片在重组的过程中可能出现新的分片与旧的分片出现冲突或者覆盖的现象,如下图所示(参考自TCP IP协议 卷2 第十章):
通过观察上图可以看到分片5与分片1和分片2发生重叠,分片7和分片3发生重叠,分片6与分片4发生重叠。其中分片1、2、3、4先到达目标主机,分片5、6、7后到达主机。
👋 为什么分片在传输过程中出现重叠现象?
在正常的网络通信中,IP分片的“Fragment Offset”字段应确保各分片数据在重组时无重叠。然而,攻击者可以故意设置多个分片的偏移量和长度,使它们在重组时发生重叠。
例如,攻击者可能发送两个分片:
分片A:偏移量为0,长度为100字节
分片B:偏移量为50,长度为100字节
在这种情况下,分片B的前50字节将覆盖分片A的后50字节,导致数据重叠。
👋 发生分片重叠时应该怎么处理?
不同操作系统和网络设备在处理重叠分片时的行为可能不同:
某些系统可能保留第一个到达的分片数据,忽略后续重叠部分。
另一些系统可能使用后到达的分片数据覆盖先前的内容。
还有的系统可能在检测到重叠时直接丢弃整个数据包。
Suricata 支持多种分片重组策略来适配多种不同的操作系统,而在配置文件中 host-os-policy 配置可指定目标主机系统类型。
3.2.3.1.1 DEFRAG_POLICY_BSD
该策略遵循左侧优先法,即优先保留左侧偏移较小的分片数据。如果新分片与旧分片完全重合的情况下,优先保留旧分片。如果新分片左侧偏移与旧分片左侧偏移相等的情况下,优先裁剪新分片数据。
该部分逻辑代码实现如下所示:
/*** Insert a new IPv4/IPv6 fragment into a tracker.** \todo Allocate packet buffers from a pool.*/
static Packet *
DefragInsertFrag(ThreadVars *tv, DecodeThreadVars *dtv, DefragTracker *tracker, Packet *p)
{... ...case DEFRAG_POLICY_BSD:if (frag_offset < prev->offset + prev->data_len) {if (prev->offset <= frag_offset) { /* We prefer the data from the previous* fragment, so trim off the data in the new* fragment that exists in the previous* fragment. */uint16_t prev_end = prev->offset + prev->data_len;if (prev_end > frag_end) { /* Just skip. *//* TODO: Set overlap flag. */goto done;}ltrim = prev_end - frag_offset;if ((next != NULL) && (frag_end > next->offset)) {next->ltrim = frag_end - next->offset;}goto insert;}/* If the end of this fragment overlaps the start* of the previous fragment, then trim up the* start of previous fragment so this fragment is* used.** See:* DefragBsdSubsequentOverlapsStartOfOriginal.*/if (frag_offset <= prev->offset && frag_end > prev->offset + prev->ltrim) {uint16_t prev_ltrim = frag_end - prev->offset;if (prev_ltrim > prev->ltrim) {prev->ltrim = prev_ltrim;}}if ((next != NULL) && (frag_end > next->offset)) {next->ltrim = frag_end - next->offset;}goto insert;}break;... ...
}
3.2.3.1.2 DEFRAG_POLICY_BSD_RIGHT
该策略遵循右侧覆盖策略,即右边的分片覆盖前面的分片数据。
3.2.3.1.3 DEFRAG_POLICY_LINUX
该策略遵循左侧优先法,即优先保留左侧偏移较小的分片数据。如果新分片与旧分片完全重合的情况下,优先保留新分片。如果新分片左侧偏移与旧分片左侧偏移相等的情况下,优先裁剪旧分片数据。
该部分逻辑代码实现如下所示:
/*** Insert a new IPv4/IPv6 fragment into a tracker.** \todo Allocate packet buffers from a pool.*/
static Packet *
DefragInsertFrag(ThreadVars *tv, DecodeThreadVars *dtv, DefragTracker *tracker, Packet *p)
{... ...case DEFRAG_POLICY_LINUX:/* Check if new fragment overlaps the end of previous* fragment, if it does, trim the new fragment.** Old: AAAAAAAA AAAAAAAA AAAAAAAA* New: BBBBBBBB BBBBBBBB BBBBBBBB* Res: AAAAAAAA AAAAAAAA AAAAAAAA BBBBBBBB*/if (prev->offset + prev->ltrim < frag_offset + ltrim &&prev->offset + prev->data_len > frag_offset + ltrim) {ltrim += prev->offset + prev->data_len - frag_offset;}/* Check if new fragment overlaps the beginning of* previous fragment, if it does, tim the previous* fragment.** Old: AAAAAAAA AAAAAAAA* New: BBBBBBBB BBBBBBBB BBBBBBBB* Res: BBBBBBBB BBBBBBBB BBBBBBBB*/if (frag_offset + ltrim < prev->offset + prev->ltrim &&frag_end > prev->offset + prev->ltrim) {prev->ltrim += frag_end - (prev->offset + prev->ltrim);goto insert;}/* If the new fragment completely overlaps the* previous fragment, mark the previous to be* skipped. Re-assembly would succeed without doing* this, but this will prevent the bytes from being* copied just to be overwritten. */if (frag_offset + ltrim <= prev->offset + prev->ltrim &&frag_end >= prev->offset + prev->data_len) {prev->skip = 1;goto insert;}break;... ...
}
3.2.3.1.4 DEFRAG_POLICY_WINDOWS
该策略遵循"先到先得"原则,即尽量保全先到来的数据包分片。除非新分片包含且不完全重合于旧分片的情况下,保留新分片。如果新分片左侧偏移与旧分片左侧偏移相等的情况下,优先裁剪新分片数据。
该部分逻辑代码实现如下所示:
/*** Insert a new IPv4/IPv6 fragment into a tracker.** \todo Allocate packet buffers from a pool.*/
static Packet *
DefragInsertFrag(ThreadVars *tv, DecodeThreadVars *dtv, DefragTracker *tracker, Packet *p)
{... ...case DEFRAG_POLICY_WINDOWS:/* If new fragment fits inside a previous fragment, drop it. */if (frag_offset + ltrim >= prev->offset + ltrim &&frag_end <= prev->offset + prev->data_len) {goto done;}/* If new fragment starts before and ends after* previous fragment, drop the previous fragment. */ if (frag_offset + ltrim < prev->offset + ltrim &&frag_end > prev->offset + prev->data_len) {prev->skip = 1;goto insert;}/* Check if new fragment overlaps the end of previous* fragment, if it does, trim the new fragment.** Old: AAAAAAAA AAAAAAAA AAAAAAAA* New: BBBBBBBB BBBBBBBB BBBBBBBB* Res: AAAAAAAA AAAAAAAA AAAAAAAA BBBBBBBB*/if (frag_offset + ltrim > prev->offset + prev->ltrim &&frag_offset + ltrim < prev->offset + prev->data_len) { ltrim += prev->offset + prev->data_len - frag_offset; goto insert;}/* If new fragment starts at same offset as an* existing fragment, but ends after it, trim the new* fragment. */if (frag_offset + ltrim == prev->offset + ltrim &&frag_end > prev->offset + prev->data_len) {ltrim += prev->offset + prev->data_len - frag_offset;goto insert;}break;... ...
}
3.2.3.1.5 DEFRAG_POLICY_SOLARIS
该策略遵循左侧优先法,即优先保留左侧偏移较小的分片数据。如果新分片与旧分片完全重合的情况下,优先保留旧分片。如果新分片左侧偏移与旧分片左侧偏移相等的情况下,优先裁剪新分片数据。
该部分逻辑代码实现如下所示:
/*** Insert a new IPv4/IPv6 fragment into a tracker.** \todo Allocate packet buffers from a pool.*/
static Packet *
DefragInsertFrag(ThreadVars *tv, DecodeThreadVars *dtv, DefragTracker *tracker, Packet *p)
{... ...case DEFRAG_POLICY_SOLARIS:if (frag_offset < prev->offset + prev->data_len) {if (frag_offset >= prev->offset) {ltrim = prev->offset + prev->data_len - frag_offset;}if ((frag_offset < prev->offset) &&(frag_end >= prev->offset + prev->data_len)) {prev->skip = 1;}goto insert;}break;... ...
}
3.2.3.2 获取主机系统策略
在Suricata的配置文件中通过host-os-policy关键字已经配置好主机系统策略,而在初始化DefragTracker对象时,即调用DefragTrackerInit函数时,会通过当前分片的IP地址,结合host-os-policy配置来选择重组策略。
调用逻辑如下所示:
在 DefragGetOsPolicy 函数中,Suricata会将不同的系统策略归为7类。注意,如果未配置,默认重组策略为DEFRAG_POLICY_BSD。
系统策略 | 分片重组策略 |
DEFRAG_POLICY_BSD | OS_POLICY_BSD |
OS_POLICY_HPUX10 | |
OS_POLICY_IRIX | |
DEFRAG_POLICY_BSD_RIGHT | OS_POLICY_BSD_RIGHT |
DEFRAG_POLICY_LINUX | OS_POLICY_OLD_LINUX |
OS_POLICY_LINUX | |
DEFRAG_POLICY_FIRST | OS_POLICY_OLD_SOLARIS |
OS_POLICY_HPUX11 | |
OS_POLICY_MACOS | |
OS_POLICY_FIRST | |
DEFRAG_POLICY_SOLARIS | OS_POLICY_SOLARIS |
DEFRAG_POLICY_WINDOWS | OS_POLICY_WINDOWS |
OS_POLICY_VISTA | |
OS_POLICY_WINDOWS2K3 | |
DEFRAG_POLICY_LAST | OS_POLICY_LAST |
分片重组策略结构如下所示:
/** Fragment reassembly policies. */
enum defrag_policies {DEFRAG_POLICY_FIRST = 1,DEFRAG_POLICY_LAST,DEFRAG_POLICY_BSD,DEFRAG_POLICY_BSD_RIGHT,DEFRAG_POLICY_LINUX,DEFRAG_POLICY_WINDOWS,DEFRAG_POLICY_SOLARIS,DEFRAG_POLICY_DEFAULT = DEFRAG_POLICY_BSD,
};