【作者主頁】只道當時是尋常
【專欄介紹】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,
};