數據解析的工作原理
每個解析器對其協議的一部分進行解碼,然后將解碼交給封裝協議的后續解析器。
每個解析都從幀解析開始,它解析捕獲文件本身的細節(例如時間戳)。它從那里將數據傳遞到最低級別的數據解析器,例如以太網報頭的以太網解析器。然后將有效負載傳遞到下一個分析器(例如IP),依此類推。在每個階段,都會解碼并顯示數據包的詳細信息。
分析器既可以內置到Wireshark中,也可以編寫為自注冊插件(共享庫或DLL)。無論是插件還是內置的分析器都沒有什么不同。您可以通過聲明為WS_DLL_PUBLIC的函數公開的ABI進行有限的函數訪問。
將解析器編寫為插件的最大好處是重建插件比編輯內置解析器后重建Wireshark要快得多。因此,從插件開始通常會使初始開發更快,而作為內置解析器,完成的代碼可能更有意義。
添加基本分析器
讓我們逐步添加一個基本的分析器。我們將從編造的“foo”協議開始。它由以下基本項目組成。
- 數據包類型-8位。可能的值:1-初始化,2-終止,3-數據。
- 以8位存儲的一組標志。0x01-開始數據包,0x02-結束數據包,0x04-優先級數據包。
- 序列號-16位。
- IPv4地址。
設置解析器
您需要做出的第一個決定是這個分析器是內置分析器并包含在主程序中,還是包含在插件中。
插件最初更容易編寫,所以讓我們從它開始。只要稍加小心,這個插件就可以轉換成一個內置的分析器。
分析器初始化:
#include "config.h"
#include <epan/packet.h>
#define FOO_PORT 1234
static int proto_foo = -1;
void
proto_register_foo(void)
{
proto_foo = proto_register_protocol (
"FOO Protocol", /* name */
"FOO", /* short name */
"foo" /* filter_name */
);
}
讓我們一次進行一下。首先,我們有一些樣板包含文件。首先,這些將是非常穩定的。
然后,#define用于承載foo通信的UDP端口。
接下來,我們有proto_foo一個存儲協議句柄并初始化為的int -1。當解剖器在主程序中注冊時,將設置此句柄。最好將所有未導出的變量和函數設為靜態,以最大程度地減少名稱空間污染。除非您的分析器變得太大以至于它可以跨越多個文件,否則通常這不是問題。
現在,我們已經具備了與主程序進行交互的基礎知識,我們將從兩個協議解剖器設置功能開始:proto_register_XXX和 proto_reg_handoff_XXX。
每個協議都必須具有形式為“ proto_register_XXX”的注冊功能。此功能用于在Wireshark中注冊協議。調用寄存器例程的代碼會自動生成,并在Wireshark啟動時被調用。在此示例中,函數名為 proto_register_foo。
proto_register_foo調用proto_register_protocol(),這需要一個name, short name和filter_name。名稱和簡稱在“首選項”和“已啟用協議”對話框以及文檔的生成的字段名稱列表中使用。在 filter_name用作顯示器用濾光器的名稱。proto_register_protocol() 返回協議句柄,該協議句柄可用于引用協議并獲取協議的解析器的句柄。
接下來,我們需要一個切換例程.
Dissector Handoff
void
proto_reg_handoff_foo(void)
{
static dissector_handle_t foo_handle;
foo_handle = create_dissector_handle(dissect_foo, proto_foo);
dissector_add_uint("udp.port", FOO_PORT, foo_handle);
}
切換例程將協議處理程序與協議的業務相關聯。它由兩個主要步驟組成:第一步是創建分析器句柄,它是與協議和調用來執行實際分析的函數相關聯的句柄。第二步是注冊分析器句柄,以便與協議相關聯的流量調用分析器。
在本例中,proto_reg_handoff_foo()調用create_dissector_handle()來獲取foo協議的分析器句柄。然后,它使用dissector_add_uint()將UDP端口foo_port(1234)上的流量與foo協議相關聯,以便Wireshark在端口1234上接收到UDP流量時調用dissect_foo()。
Wireshark的解析器約定是將proto_register_foo()和proto_reg_handoff_foo()作為解析器源代碼中的最后兩個函數。
下一步是編寫解析函數dissectfoo()。我們將從一個基本的占位符開始。
Dissection
static int
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_)
{
col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
/* Clear the info column */
col_clear(pinfo->cinfo,COL_INFO);
return tvb_captured_length(tvb);
}
調用dissectfoo()來解析提供給它的數據包。分組數據保存在這里稱為TVB的特殊緩沖器中。PACKET_INFO結構包含有關協議的常規數據,我們可以在此處更新信息。樹參數是進行詳細分析的位置。請注意,UFollowing樹和數據向編譯器發出參數未使用的信號,因此編譯器不會打印警告。
現在我們會盡最小努力逃脫懲罰。Colsetstr()用于將Wireshark的Protocol列設置為“foo”,這樣每個人都可以看到它正在被識別。我們唯一要做的另一件事就是清除INFO列中的任何數據(如果它正在顯示)。
在這一點上,我們已經準備好了一個基本的解析器,可以編譯和安裝。分析器除了識別協議并對其進行標記外,不做任何事情。以下是分析器的完整代碼:
Complete packet-foo.c :
#include "config.h"
#include <epan/packet.h>
#define FOO_PORT 1234
static int proto_foo = -1;
static int
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_)
{
col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
/* Clear the info column */
col_clear(pinfo->cinfo,COL_INFO);
return tvb_captured_length(tvb);
}
void
proto_register_foo(void)
{
proto_foo = proto_register_protocol (
"FOO Protocol", /* name */
"FOO", /* short_name */
"foo" /* filter_name */
);
}
void
proto_reg_handoff_foo(void)
{
static dissector_handle_t foo_handle;
foo_handle = create_dissector_handle(dissect_foo, proto_foo);
dissector_add_uint("udp.port", FOO_PORT, foo_handle);
}
要編譯此解析器并創建插件,除了Packet-foo.c中的解析器源代碼外,還需要一些支持文件:
CMakeLists.txt-包含此插件的CMake文件和版本信息。
Packet-foo.c-您的分析器來源。
Plugin.rc.in-包含Windows的DLL資源模板。(可選)。
您可以在gryphon插件目錄中找到這些文件的一個很好的示例。必須使用正確的插件名稱和版本信息以及要編譯的相關文件修改CMakeLists.txt。在主頂級源目錄中,將CMakeListsCustom.txt.Example復制到CMakeListsCustom.txt,并將插件的路徑添加到CUSTOM_PLUGIN_SRC_DIR中的列表中。
將解析器編譯為DLL或共享庫,然后按照第3.6節“運行生成的Wireshark”中的詳細說明從build目錄運行Wireshark,或者將插件二進制文件復制到Wireshark安裝的插件目錄中并運行。
剖析協議的詳細信息
現在我們已經啟動并運行了基本的解析器,讓我們對它做點什么吧。首先要做的最簡單的事情就是標記有效載荷。這將允許我們設置一些我們需要的部件。
我們要做的第一件事是構建一個子樹來解碼我們的結果。這有助于在詳細的展示中保持事物的美觀
Plugin Packet Dissection.
static int
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
/* Clear out stuff in the info column */
col_clear(pinfo->cinfo,COL_INFO);
proto_item *ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA);
return tvb_captured_length(tvb);
}
我們在這里所做的是在分析中添加一個子樹。該子樹將保留該協議的所有詳細信息,因此在不需要時不會使顯示混亂。
我們還標記了此協議消耗的數據區域。在這種情況下,這是所有傳遞的數據,因為該協議沒有封裝另一個協議。因此,我們使用添加新的樹節點proto_tree_add_item(),將其添加到傳入的樹中,用協議對其進行標記,將傳入的tvb緩沖區用作數據,并消耗從0到此數據的末尾(-1)。ENC_NA(“不適用”)被指定為“ encoding”參數。
更改之后,協議的詳細顯示中應有一個標簽,選擇該標簽將突出顯示數據包的其余內容。
現在,我們進入下一步并添加一些協議解析。對于這一步,我們將需要構造一些表格以幫助解析。這需要對proto_register_foo()前面顯示的功能進行一些補充。
在的開頭添加了兩個靜態分配的數組 proto_register_foo()。然后在調用之后注冊數組 proto_register_protocol()。
**Registering data structures**
void
proto_register_foo(void)
{
static hf_register_info hf[] = {
{ &hf_foo_pdu_type,
{ "FOO PDU Type", "foo.type",
FT_UINT8, BASE_DEC,
NULL, 0x0,
NULL, HFILL }
}
};
/* Setup protocol subtree array */
static gint *ett[] = {
&ett_foo
};
proto_foo = proto_register_protocol (
"FOO Protocol", /* name */
"FOO", /* short_name */
"foo" /* filter_name*/
);
proto_register_field_array(proto_foo, hf, array_length(hf));
proto_register_subtree_array(ett, array_length(ett));
}
變量hf_foo_pdu_typeand ett_foo還需要在文件頂部附近的某個位置聲明。
Dissector data structure globals
static int hf_foo_pdu_type = -1;
static gint ett_foo = -1;
現在,我們可以使用一些數據包詳細信息來增強協議顯示。
Dissector starting to dissect the packets
proto_item *ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA);
proto_tree *foo_tree = proto_item_add_subtree(ti, ett_foo);
proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, 0, 1, ENC_BIG_ENDIAN);
如前所述,foo協議以8位開頭,該位packet type 可以具有三個可能的值:1-初始化,2-終止,3-數據。這是我們如何添加數據包詳細信息:
該proto_item_add_subtree()調用已在協議樹中添加了一個子節點,在此將進行詳細的剖析。該節點的擴展由ett_foo 變量控制。這會記住在數據包之間移動時是否應擴展節點。如您從下一個調用中看到的,所有后續解剖將添加到該樹中。要在通話proto_tree_add_item()中foo_tree,利用這段時間hf_foo_pdu_type來控制項目的格式。pdu類型是數據的一個字節,從0開始。我們假設它是按網絡順序(也稱為big endian)進行的,因此這就是為什么使用的原因ENC_BIG_ENDIAN。對于1字節的數量,沒有順序問題,但是最好使它與可能存在的任何多字節字段相同,并且正如我們將在下一節中看到的那樣,此特定協議使用網絡順序。
如果我們詳細查看hf_foo_pdu_type靜態數組中的聲明,我們可以看到定義的詳細信息。
static hf_register_info hf[] = {
{ &hf_foo_pdu_type,
{ "FOO PDU Type", "foo.type",
FT_UINT8, BASE_DEC,
NULL, 0x0,
NULL, HFILL }
}
};
hf_foo_pdu_type - 節點的索引。
FOO PDU Type - 物品的標簽。
foo.type - T該項目的縮寫名稱,用于顯示過濾器(例如foo.type=1)。
FT_UINT8 - 8位無符號整數。這與上面的調用相吻合,在該調用中,我們告訴它僅查看一個字節。
BASE_DEC -對于整數類型,這告訴它以十進制數字形式打印。如果更合理,則可以是十六進制(BASE_HEX)或八進制(BASE_OCT)。
現在,我們將忽略結構的其余部分。
如果您安裝了該插件并進行了試用,您會發現一些有用的東西。
現在,讓我們結束對簡單協議的剖析。我們需要向hfarray添加更多的變量,以及更多的過程調用。
Wrapping up the packet dissection.
...
static int hf_foo_flags = -1;
static int hf_foo_sequenceno = -1;
static int hf_foo_initialip = -1;
...
static int
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
gint offset = 0;
...
proto_item *ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA);
proto_tree *foo_tree = proto_item_add_subtree(ti, ett_foo);
proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, offset, 1, ENC_BIG_ENDIAN);
offset += 1;
proto_tree_add_item(foo_tree, hf_foo_flags, tvb, offset, 1, ENC_BIG_ENDIAN);
offset += 1;
proto_tree_add_item(foo_tree, hf_foo_sequenceno, tvb, offset, 2, ENC_BIG_ENDIAN);
offset += 2;
proto_tree_add_item(foo_tree, hf_foo_initialip, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
...
return tvb_captured_length(tvb);
}
void
proto_register_foo(void) {
...
...
{ &hf_foo_flags,
{ "FOO PDU Flags", "foo.flags",
FT_UINT8, BASE_HEX,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_foo_sequenceno,
{ "FOO PDU Sequence Number", "foo.seqn",
FT_UINT16, BASE_DEC,
NULL, 0x0,
NULL, HFILL }
},
{ &hf_foo_initialip,
{ "FOO PDU Initial IP", "foo.initialip",
FT_IPv4, BASE_NONE,
NULL, 0x0,
NULL, HFILL }
},
...
...
}
...
這將剖析這個簡單假設協議的所有比特。我們在MIX中引入了一個新的變量OFFSET,以幫助跟蹤我們在數據包解析中的位置。有了這些額外的位,現在就可以剖析整個協議了。
Wireshark中文使用教程(開發版)
推薦文章: