【wireshark】插件開發(五):C插件

1. Wireshark對C插件的支持

每一個解析器解碼本身的協議部分, 而後把封裝協議的解碼傳遞給後續協議。html

所以它可能老是從一個Frame解析器開始, Frame解析器解析捕獲文件本身的數據包細節(如:時間戳), 將數據交給一個解碼Ethernet頭部的Ethernet frame解析器, 而後將載荷交給下一個解析器(如:IP), 如此等等. 在每一步, 數據包的細節會被解碼並顯示.編程

能夠用兩種可能的方式實現協議解析. 一是寫一個解析器模塊, 編譯到主程序中, 這意味着它將永遠是可用的. 另外一種方式是實現一個插件(共享庫/DLL), 它註冊自身用於處理解析。windows

插件形式和內置形式的解析器之間的差異很小. 在Windows平臺, 經過列於libwireshark.def中的函數, 咱們能夠訪問有限的函數, 但它們幾乎已經夠用了.數組

比較大的好處是插件解析器的構建週期要遠小於內置. 所以以插件開始會使最初的開發工做變得簡單, 而最終代碼的佈署會和內置解析器同樣。緩存

另見 README.developer  文件doc/README.developer包含更多有關實現解析器(並且在某些狀況下, 比本文檔要新一些)的信息.sass

2. 編譯構建C解析器

首先須要決定解析器是要以built-in方式,仍是以plugin方式實現。plugin方式實現比較容易上手。網絡

解析器初始化:app

#include "config.h"
#include <epan/packet.h>

#define FOO_PORT 9877

static int proto_foo = -1;


void
proto_register_foo(void)
{
    proto_foo = proto_register_protocol (
        "FOO Protocol", /* name       */
        "FOO",      /* short name */
        "foo"       /* abbrev     */
        );
}

首先include一些必需的頭文件。proto_foo用來記錄咱們的協議,當將此解析器註冊到主程序時,它的值將會更新。把全部非外部使用的變量和函數聲明爲static是一個好的編程實踐,能夠避免名字空間污染。通常狀況下這不是問題,除非咱們的解析器很是大,分紅了多個文件。tcp

咱們#define了協議的UDP端口FOO_PORT。函數

如今咱們已經有了與主程序交互所需的基本東西了。接下來實現2個解析器構建函數(dissector setup functions)。

首先調用proto_register_protocol()函數來註冊協議。能夠給它3個名字用來未來在不一樣的地方顯示。好比full和short name用於「Preferences」和「Enabled protocols」對話框。abbrev name用於顯示過濾器。

接下來咱們須要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);
}

首先建立一個dissector handle,它和foo協議及執行實際解析工做的函數關聯。接下來將此handle與UDP端口號關聯,以便主程序在看到此端口上的UDP數據時調用咱們的解析器。

標準wireshark解析器習慣是把proto_register_foo()和proto_reg_handoff_foo()作爲解析器代碼的最後2個函數。

最後咱們來編寫一些解析器代碼。目前將它作爲基本的佔位符。

static void
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
    /* Clear out stuff in the info column */
    col_clear(pinfo->cinfo,COL_INFO);
}

此函數用於解析交給它的packets。packet數據放在名爲tvb的特殊緩存中。對此隨着咱們對協議細節瞭解的深刻將會變得很是熟悉。packet_info結構包含有關協議的通常數據,咱們應該在此更新信息。tree參數是細節解析發生的地方。

如今咱們進行最小化的實現。第1行咱們設置咱們協議的文本,以示用戶能夠看到協議被識別了。另外惟一作的事情是清除INFO列中的全部數據,若是它正在被顯示的話。

此時,咱們已經準備好基本的解析器,能夠進行編譯和安裝了。它什麼也不作,除了識別協議並標識它。

爲了編譯此解析器並建立插件,除了packet-foo.c中的源代碼,還有一堆必需的支持文件,它們是:

  • Makefile.am - This is the UNIX/Linux makefile template
  • CMakeLists.txt - 使用cmake編譯時所需的腳本
  • Makefile.common - This contains the file names of this plugin
  • Makefile.nmake - This contains the Wireshark plugin makefile for Windows
  • moduleinfo.h - This contains plugin version info
  • moduleinfo.nmake - This contains DLL version info for Windows
  • packet-foo.h, packet-foo.c - This is your dissector source
  • plugin.rc.in - This contains the DLL resource template for Windows

這些文件的例子能夠在plugins/gryphon中找到,把全部與gryphon相關的東西都改爲foo便可。plugin.rc.in不須要改動,windows編譯不須要的文件也不須要改動。

把以上文件準備好、修改好以後,cmd進入plugins/foo目錄,運行

nmake -f Makefile.nmake xxx

來進行編譯,就像編譯wireshark源碼同樣。編譯好以後生成foo.dll,將它拷貝到編譯好的wireshark的plugins目錄(可能會有中間目錄,視狀況)。

還能夠修改plugins目錄下面的Makefile.nmake文件,在PLUGIN_LIST中加入新插件的目錄名,這樣下次編譯wireshark時會一塊兒編譯你的插件。

 

若是是在Mac OSX系統中編譯插件(CMake方式),須要修改主目錄下的CMakeLists.txt,搜索plugin字符串,找到set(PLUGIN_SRC_DIRS下面的行,在路徑中加入plugins/foo(plugins目錄的Makefile.am文件可能不須要修改,其中SUBDIRS項中列出了各插件的源碼目錄) ;而後如同以前文章所述,進入build目錄,執行make –j 6 plugins,便可編譯插件們。

而後啓動wireshark,打開Dissector Tables窗口,能夠查到如下信息,說明wireshark已經正確加載咱們的插件。

 

打開foo.pcap,效果以下圖所示,此時沒有協議解析樹,只在報文列表中添加了協議名:

3. 完善C解析器

接下來能夠作一些複雜一點的解析工做。最簡單的事情是對載荷進行標記。

首先建立一個subtree用來放解析結果。這有助於在detailed display中更佳顯示。對解析器的調用有2種狀況。一種狀況用於獲取packet的摘要,另外一種狀況用於解析packet的細節。這兩種狀況由tree指針的不一樣來區別。若是tree指針爲NULL,用於獲取簡略信息。若是是非NULL,則須要解析協議的各個細部。記住這些後,讓咱們來加強咱們的解析器。

static void
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
    /* Clear out stuff in the info column */
    col_clear(pinfo->cinfo,COL_INFO);

    if (tree) { /* we are being asked for details */
        proto_item *ti = NULL;
        ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA);
    }
}

這裏所作的是把一個subtree加入到解析中。此subtree會保存此協議的全部細節,且不會在不須要時弄亂顯示。

咱們還能夠標記被此協議所消費的數據區域。在目前的狀況下,這統治是傳遞過來的全部數據,由於咱們假定此協議再也不封裝其餘協議。所以,咱們用proto_tree_add_item()往tree裏添加新的節點,標識它的協議名,用tvb緩衝區作爲數據,並消費此數據的0到最後1個字節(-1表示結束)。ENC_NA(not applicable)是編碼參數。

在這些改變以後,在detailed display中就會有此協議的標識,且選中它將會高亮此packet的剩餘內容。以下圖所示:

如今,讓咱們進行下一步,添加一些協議解析。這一步咱們須要建立2個表來幫助解析。這須要在proto_register_foo()函數中添加一些代碼。

在proto_register_foo()的前面添加了2個static數組。這些數組在proto_register_protocol()調用以後被註冊。

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"       /* abbrev     */
        );

    proto_register_field_array(proto_foo, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
}

變量hf_foo_pdu_type和ett_foo也須要在此文件的前面聲明。

static int hf_foo_pdu_type = -1;

static gint ett_foo = -1;

如今咱們能夠用一些細節來增長協議的顯示。

if (tree) { /* we are being asked for details */
  proto_item *ti = NULL;
  proto_tree *foo_tree = NULL;

  ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA);
  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);
}

如今解析開始看起來更加有趣了。咱們開始破解此協議的第1個比特。packet起始處的一個字節數據定義了foo協議的packet type。

proto_item_add_subtree()調用往協議樹中增長了一個子節點。此節點的展開是由ett_foo變量控制的。它會記住節點是否應該展開,在你在packet中移動的時候。全部後續的解析會添加到此樹中,就像在接下來的調用中看到的那樣。proto_tree_add_item向foo_tree添加了新項,並用hf_foo_pdu_type來控制此項的格式。pdu type是1個字節的數據,從0開始。咱們假定它是網絡字節序(也叫big endian),所以用ENC_BIG_ENDIAN。對於1個字節的數來講,沒用字節序之說,但這是好的編程實踐。

咱們來看static數組中的定義細節:

  • hf_foo_pdu_type - 此節點的索引
  • FOO PDU Type - 此項的標識
  • foo.type - 過濾用的字符串。它使咱們能夠在過濾器框中輸入foo.type=1的語句
  • FT_UINT8 - 指出此項是一個8bit的無符號整數。
  • BASE_DEC - 對於整型來講,它令其打印爲一個10進制數。還能夠是16進制(BASE_HEX)或8進制(BASE_OCT)。

咱們目前忽略結構中的其他成員。

若是此時編譯並安裝此插件,咱們會看到它開始顯示一些看起來有用的東西。

如今咱們來完成這個簡單協議的解析。咱們須要添加更多的變量在hf數組中,以及更多的函數調用。

static int hf_foo_flags = -1;
static int hf_foo_sequenceno = -1;
static int hf_foo_initialip = -1;
...

static void
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
    gint offset = 0;

    ...

    if (tree) { /* we are being asked for details */
        proto_item *ti = NULL;
        proto_tree *foo_tree = NULL;

        ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA);
        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;
    }
    ...
}

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 }
        },
        ...
    ...
}
...

再修改一些細節,好比flag的位顯示方式、foo協議樹子節點字符串,packet列表中Info列的顯示等等,最後效果以下:

4. 完整代碼

/* packet-foo.c
 * Routines for Foo protocol packet disassembly
 * By zzq
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/prefs.h>
//#include <epan/dissectors/packet-tcp.h>
#include "packet-foo.h"


#define FOO_PORT        9877
#define FOO_NAME        "Foo Protocol"
#define FOO_SHORT_NAME  "Foo"
#define FOO_ABBREV      "foo"


static int proto_foo = -1;

static int hf_foo_pdu_type = -1;
static int hf_foo_flags = -1;
static int hf_foo_seqno = -1;
static int hf_foo_ip = -1;
static gint ett_foo = -1;

static const value_string pkt_type_names[] = 
{
    {1, "Initilize"},
    {2, "Terminate"},
    {3, "Data"},
    {0, NULL}
};

#define FOO_START_FLAG  0x01
#define FOO_END_FLAG    0x02
#define FOO_PRIOR_FLAG  0x04

static int hf_foo_start_flag    = -1;
static int hf_foo_end_flag      = -1;
static int hf_foo_prior_flag    = -1;



void proto_register_foo(void);
void proto_reg_handoff_foo(void);
static int dissect_foo(tvbuff_t*, packet_info*, proto_tree*, void*);



void
proto_register_foo(void)
{
    static hf_register_info hf[] = 
    {
        {
            &hf_foo_pdu_type,
            {
                "Type", "foo.type",
                FT_UINT8, BASE_DEC,
                VALS(pkt_type_names), 0x0, 
                NULL, HFILL
            }
        },
        {
            &hf_foo_flags,
            {
                "Flags", "foo.flags",
                FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL
            }
        },
        {
            &hf_foo_start_flag,
            {
                "Start Flag", "foo.flags.start",
                FT_BOOLEAN, 8,
                NULL, FOO_START_FLAG, NULL, HFILL
            }
        },
        {
            &hf_foo_end_flag,
            {
                "End Flag", "foo.flags.end",
                FT_BOOLEAN, 8,
                NULL, FOO_END_FLAG, NULL, HFILL
            }
        },
        {
            &hf_foo_prior_flag,
            {
                "Priority Flag", "foo.flags.prior",
                FT_BOOLEAN, 8,
                NULL, FOO_PRIOR_FLAG, NULL, HFILL
            }
        },
        {
            &hf_foo_seqno,
            {
                "Sequence Number", "foo.seq",
                FT_UINT16, BASE_DEC,
                NULL, 0x0, NULL, HFILL
            }
        },
        {
            &hf_foo_ip,
            {
                "IP Address", "foo.ip",
                FT_IPv4, BASE_NONE,
                NULL, 0x0, NULL, HFILL
            }
        }
    };
    
    static gint *ett[] = { &ett_foo };

    
    proto_foo = proto_register_protocol (
            FOO_NAME,
            FOO_SHORT_NAME,
            FOO_ABBREV);
            
    proto_register_field_array(proto_foo, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
}



static int
dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data)
{
    guint8 packet_type = tvb_get_guint8(tvb, 0);
    
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
    /* Clear out stuff in the info column */
    col_clear(pinfo->cinfo,COL_INFO);
    col_add_fstr(pinfo->cinfo, COL_INFO, "Type %s",
        val_to_str(packet_type, pkt_type_names, "Unknown (0x%02x)"));
    
    /* proto details display */
    if(tree)
    {
        proto_item* ti = NULL;
        proto_tree* foo_tree = NULL;
        gint offset = 0;
        
        ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA);
        proto_item_append_text(ti, ", Type %s",
            val_to_str(packet_type, pkt_type_names, "Unknown (0x%02x)"));
        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);
        proto_tree_add_item(foo_tree, hf_foo_start_flag, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(foo_tree, hf_foo_end_flag, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(foo_tree, hf_foo_prior_flag, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        proto_tree_add_item(foo_tree, hf_foo_seqno, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;
        proto_tree_add_item(foo_tree, hf_foo_ip, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
    }

    return tvb_reported_length(tvb);
}

void
proto_reg_handoff_foo(void)
{
    static dissector_handle_t foo_handle;

    foo_handle = new_create_dissector_handle(dissect_foo, proto_foo);
    dissector_add_uint("udp.port", FOO_PORT, foo_handle);
}

5. 參考

Wireshark開發指南第9章「Add a basic dissector」

相關文章
相關標籤/搜索