如何重組拆分的數據包
有些協議有時必須將一個大型數據包拆分到多個其他數據包中。在這種情況下,只有在獲得所有數據之后才能正確執行解剖。第一個數據包沒有足夠的數據,并且后續的數據包沒有預期的格式。要剖析這些數據包,您需要等待所有部分都已到達,然后開始剖析。
以下各節將指導您完成兩個常見案例。有關所有可能的函數、結構和參數的說明,請參見epan/reAssembly e.h。
如何重組拆分的UDP數據包
作為示例,讓我們研究一個協議,該協議位于UDP之上,該協議拆分了自己的數據流。如果數據包大于給定的大小,它將被分成多個塊,并以某種方式在其協議中發出信號。
為了處理此類流,我們需要觸發幾件事。我們需要知道此數據包是多數據包序列的一部分。我們需要知道序列中有多少個數據包。我們還需要知道何時擁有所有數據包。
對于此示例,我們假設有一個簡單的協議內信令機制來提供詳細信息。一個標志字節,用于指示多數據包序列以及最后一個數據包的存在,后跟該序列的ID和數據包序列號。
msg_pkt ::= SEQUENCE {
.....
flags ::= SEQUENCE {
fragment BOOLEAN,
last_fragment BOOLEAN,
.....
}
msg_id INTEGER(0..65535),
frag_id INTEGER(0..65535),
.....
}
*Reassembling fragments - Part 1. *
#include <epan/reassemble.h>
...
save_fragmented = pinfo->fragmented;
flags = tvb_get_guint8(tvb, offset); offset++;
if (flags & FL_FRAGMENT) { /* fragmented */
tvbuff_t* new_tvb = NULL;
fragment_data *frag_msg = NULL;
guint16 msg_seqid = tvb_get_ntohs(tvb, offset); offset += 2;
guint16 msg_num = tvb_get_ntohs(tvb, offset); offset += 2;
pinfo->fragmented = TRUE;
frag_msg = fragment_add_seq_check(msg_reassembly_table,
tvb, offset, pinfo,
msg_seqid, NULL, /* ID for fragments belonging together */
msg_num, /* fragment sequence number */
tvb_captured_length_remaining(tvb, offset), /* fragment length - to the end */
flags & FL_FRAG_LAST); /* More fragments? */
我們先保存此數據包的碎片狀態,以便稍后進行恢復。接下來是一些特定于協議的內容,用于從流中挖掘片段數據(如果存在的話)。確定存在后,我們讓函數 fragment_add_seq_check()執行其工作。我們需要為此提供一定數量的參數:
- 該
msg_reassembly_table表用于簿記,稍后進行描述。 - 我們正在分析的tvb緩沖區。
- 部分數據包開始的偏移量。
- 提供的數據包信息。
- 片段流的序列號。可能有幾條碎片在飛行中,這被用來鎖定要重新組裝的相關碎片。
- 用于識別片段的可選附加數據。可以將
NULL大多數解剖器設置為(如示例中所示)。 - msg_num是序列中的數據包編號。
- 由于我們需要其余的分組數據,因此此處的長度指定為tvb的其余部分。
- 最后是一個參數,指示是否這是最后一個片段。在這種情況下,這可能是一個標志,或者協議中可能有一個計數器。
*Reassembling fragments part 2. *
new_tvb = process_reassembled_data(tvb, offset, pinfo,
"Reassembled Message", frag_msg, &msg_frag_items,
NULL, msg_tree);
if (frag_msg) { /* Reassembled */
col_append_str(pinfo->cinfo, COL_INFO,
" (Message Reassembled)");
} else { /* Not last packet of reassembled Short Message */
col_append_fstr(pinfo->cinfo, COL_INFO,
" (Message fragment %u)", msg_num);
}
if (new_tvb) { /* take it all */
next_tvb = new_tvb;
} else { /* make a new subset */
next_tvb = tvb_new_subset_remaining(tvb, offset);
}
}
else { /* Not fragmented */
next_tvb = tvb_new_subset_remaining(tvb, offset);
}
.....
pinfo->fragmented = save_fragmented;
將片段數據傳遞給重組處理程序后,我們現在可以檢查是否有完整的消息。如果有足夠的信息,此例程將返回新重組的數據緩沖區。
之后,我們向顯示屏添加一些信息性消息,以表明這是序列的一部分。然后可以對緩沖區和解剖進行一些操作。通常情況下,除非片段已重新組裝,否則您可能就不會再進一步??剖析了,因為找不到的東西很多。如果您愿意,有時序列中的第一個數據包可以部分解碼。
現在,我們將神秘的數據傳遞到fragment_add_seq_check()。
*Reassembling fragments - Initialisation. *
static reassembly_table reassembly_table;
static void
proto_register_msg(void)
{
reassembly_table_register(&msg_reassemble_table,
&addresses_ports_reassembly_table_functions);
}
首先reassembly_table,在協議初始化例程中聲明并初始化結構。第二個參數指定應用于識別片段的功能。addresses_ports_reassembly_table_functions為了通過給定的序列號(msg_seqid),數據包的源地址和目標地址以及端口來識別片段,我們將使用 。
之后,fragment_items分配一個結構,并用一系列ett項目,hf數據項目和一個字符串標簽填充該結構。ett和hf值應包含在相關表中,就像協議可以使用的所有其他變量一樣。需要將hf變量放在結構中,如下所示。當然,可能需要調整名稱。
*Reassembling fragments - Data. *
...
static int hf_msg_fragments = -1;
static int hf_msg_fragment = -1;
static int hf_msg_fragment_overlap = -1;
static int hf_msg_fragment_overlap_conflicts = -1;
static int hf_msg_fragment_multiple_tails = -1;
static int hf_msg_fragment_too_long_fragment = -1;
static int hf_msg_fragment_error = -1;
static int hf_msg_fragment_count = -1;
static int hf_msg_reassembled_in = -1;
static int hf_msg_reassembled_length = -1;
...
static gint ett_msg_fragment = -1;
static gint ett_msg_fragments = -1;
...
static const fragment_items msg_frag_items = {
/* Fragment subtrees */
&ett_msg_fragment,
&ett_msg_fragments,
/* Fragment fields */
&hf_msg_fragments,
&hf_msg_fragment,
&hf_msg_fragment_overlap,
&hf_msg_fragment_overlap_conflicts,
&hf_msg_fragment_multiple_tails,
&hf_msg_fragment_too_long_fragment,
&hf_msg_fragment_error,
&hf_msg_fragment_count,
/* Reassembled in field */
&hf_msg_reassembled_in,
/* Reassembled length field */
&hf_msg_reassembled_length,
/* Tag */
"Message fragments"
};
...
static hf_register_info hf[] =
{
...
{&hf_msg_fragments,
{"Message fragments", "msg.fragments",
FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL } },
{&hf_msg_fragment,
{"Message fragment", "msg.fragment",
FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
{&hf_msg_fragment_overlap,
{"Message fragment overlap", "msg.fragment.overlap",
FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
{&hf_msg_fragment_overlap_conflicts,
{"Message fragment overlapping with conflicting data",
"msg.fragment.overlap.conflicts",
FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
{&hf_msg_fragment_multiple_tails,
{"Message has multiple tail fragments",
"msg.fragment.multiple_tails",
FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
{&hf_msg_fragment_too_long_fragment,
{"Message fragment too long", "msg.fragment.too_long_fragment",
FT_BOOLEAN, 0, NULL, 0x00, NULL, HFILL } },
{&hf_msg_fragment_error,
{"Message defragmentation error", "msg.fragment.error",
FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
{&hf_msg_fragment_count,
{"Message fragment count", "msg.fragment.count",
FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } },
{&hf_msg_reassembled_in,
{"Reassembled in", "msg.reassembled.in",
FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
{&hf_msg_reassembled_length,
{"Reassembled length", "msg.reassembled.length",
FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } },
...
static gint *ett[] =
{
...
&ett_msg_fragment,
&ett_msg_fragments
...
這些hf變量在重組例程內部內部使用,以建立有用的鏈接,并將數據添加到解析中。它產生從一個分組到另一個分組的鏈接,例如具有到完全重組的分組的鏈接的部分分組。同樣,也有指向重組后單個數據包的后向指針。其他變量用于標記錯誤。
如何重組拆分的TCP數據包
解析器獲取一個tvbuff_t指針,該指針保存TCP數據包的payload。此有效負載包含應用程序層協議的標頭和數據。
剖析應用程序層協議時,不能假定每個TCP數據包僅包含一個應用程序層消息。一個應用層消息可以分為幾個TCP數據包。
您還不能假定TCP數據包僅包含一個應用程序層消息,并且消息頭位于TCP payload 的開頭。一個TCP數據包中可以傳輸多個消息,因此消息可以在任意位置開始。
這聽起來很復雜,但是有一個簡單的解決方案。 tcp_dissect_pdus()為您完成所有這些tcp數據包的重組。該功能在epan / dissectors / packet-tcp.h中實現。
*Reassembling TCP fragments. *
#include "config.h"
#include <epan/packet.h>
#include <epan/prefs.h>
#include "packet-tcp.h"
...
#define FRAME_HEADER_LEN 8
/* This method dissects fully reassembled messages */
static int
dissect_foo_message(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree _U_, void *data _U_)
{
/* TODO: implement your dissecting code */
return tvb_captured_length(tvb);
}
/* determine PDU length of protocol foo */
static guint
get_foo_message_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
{
/* TODO: change this to your needs */
return (guint)tvb_get_ntohl(tvb, offset+4); /* e.g. length is at offset 4 */
}
/* The main dissecting routine */
static int
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
tcp_dissect_pdus(tvb, pinfo, tree, TRUE, FRAME_HEADER_LEN,
get_foo_message_len, dissect_foo_message, data);
return tvb_captured_length(tvb);
}
...
如您所見,這真的很簡單。只需在主解析例程中調用tcp_dissect_pdu(),并將消息解析代碼移到另一個函數中即可。只要重新組裝了消息,就會調用此函數。
參數tvb、pinfo、tree和data只是移交給tcp_dissect_pdu()。第四個參數是指示是否應該重組數據的標志。這也可以根據解剖偏好進行設置。參數5指示至少有多少數據可用于能夠確定FOO消息的長度。參數6是指向返回此長度的方法的函數指針。當至少有前面參數中給出的字節數可用時,就會調用它。參數7是指向實際消息分析器的函數指針。參數8是從父分析器傳入的數據。
在可以確定消息長度之前需要更多數據的協議可以返回零。其他小于固定長度的值將導致異常。
Wireshark中文使用教程(開發版)