Wireshark is one of the indispensable utilities for wiretapping a network when working with network protocols. The program already includes a certain number of dissectors
1 , which help to examine in detail the packages of basic protocols. But when working on a Nortel proprietary protocol, I was faced with a lack of a suitable dissector. And he needed like air. The solution was obvious - write your own. What I did.
Thus, having a little experience writing an “anatomist” plug-in under Wireshark, I decided to share my knowledge and experience with the community. Well, write for yourself, in case you need it in the future.
Dissection - lat. dissectio, from dissecare, dissect
Note 1: when writing this article, I assumed that the reader is familiar with the Wireshark utility and also knows how to use its basic features.Note 2: since I work under Linux, the plugin also wrote for this OS. Under Windows, my plugin should also be built. Differences, I think, will be only in make files.')
Note 3: since the protocol I was working on was proprietary and closed, and also because of the million non-disclosure agreements, in this article I consider my own, invented, protocol.Foo protocol
In a nutshell, I will describe an invented protocol, which I will call the plain name
Foo . Let this protocol be over the UDP protocol and have the following structure:
- 1 byte - protocol version
- 1 byte - packet type
- 1 byte - all sorts of flags
- 1 byte - some boolean variable
- 4 bytes - payload length
- From 0 to 200 bytes - payload
Setting up the environment
Before you start writing a plugin, you need to put something, create something, change something somewhere. Here is what Wireshark is asking to install:
- python (I think any 2nd version, but definitely not 3rd)
- cmake
- bison
- flex
A complete list of required libraries and utilities is
here .
After installing everything you need, download the Wireshark source for the version for which we will write the plugin. In my case, the source code for the stable branch 1.6:
$ cd $HOME $ svn co http://buildbot.wireshark.org/trunk-1.6/ wireshark
To make sure that the compilation works, as well as that all the necessary utilities and libraries are installed, run:
$ cd wireshark $ ./autogen.sh $ ./configure
If everything is in order, then go to the plugins folder and create there your folder with the name of the protocol:
$ cd $HOME/wireshark/plugins $ mkdir foo $ cd foo $ touch packet-foo.c
Frame
Here I would like to start by identifying the framework of the plug-in, the main part of which is enough for Wireshark to pick up the plug-in.
packet-foo.c
#ifdef HAVE_CONFIG_H # include "config.h" #endif #include <epan/packet.h> #define FOO_PORT 35000 /* UDP , FOO . */ static int proto_foo = -1; /* . */ void proto_register_foo(void) { proto_foo = proto_register_protocol ( "FOO Protocol", /* */ "FOO", /* */ "foo" /* */ ); }
At the very beginning, we call the
proto_register_protocol () function, which registers the protocol and issues its unique identifier. Functions we pass three different names of our protocol, which will be displayed in different parts of the program. (For example, the names “FOO Protocol” and “FOO” are used in the Wireshark Settings, and the abbreviation “foo” is used in the filter field.)
Next, we need to assign a function that will be called if necessary to decrypt the captured foo-package. This is done by calling the
create_dissector_handle () function.
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); }
Here we register the processor-dissector, which performs the dirty work of decomposing the protocol in composition and binds it to the
proto_foo protocol. Then we connect our handler to the UDP port on which we expect traffic to go. Thus, Wireshark will call “us” when some activity appears on the port we set (35000).
There is an agreement that the
proto_register_foo () and
proto_reg_handoff_foo () functions must be written at the end of our source file.
Now the most important thing is to write the code of the dissector itself.
static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO"); col_clear(pinfo->cinfo, COL_INFO); }
This function will be called for detailed work on the packet that is passed to it. Packet data is stored in a special buffer -
tvb . In
pinfo we put general information about the protocol. And in the
tree , major changes occur when the final protocol view is determined in the output of Wireshark.
In the first line, we set the value in the
Protocol column. In the second, we clear the
Info column. (Both columns are typically shown during packet capture.)
Everything, at the moment there is a minimum of code in order to be able to safely assemble our plugin and upload it to Wireshark.
Plugin build
To build (under Linux) we need to get some files in our directory: Makefile.am, Makefile.common and moduleinfo.h.
- Makefile.am - UNIX / Linux make file
- Makefile.common - common makefile for UNIX / Linux and Windows, contains plugin file names
- Makefile.nmake - make file for Windows
- moduleinfo.h - contains the plugin version
- moduleinfo.nmake - contains information about the version of the DLL for Windows
- packet-foo.c - our source code for the dissector
By the way, it is not necessary to bury in so many make files, since Wireshark provides a simpler way - to use cmake. Due to this, the number of changes in make files is minimized. To do this, create
CMakeLists.txt in the folder with our plugin with the following content:
set(DISSECTOR_SRC packet-foo.c ) set(PLUGIN_FILES plugin.c ${DISSECTOR_SRC} ) set(CLEAN_FILES ${PLUGIN_FILES} ) if (WERROR) set_source_files_properties( ${CLEAN_FILES} PROPERTIES COMPILE_FLAGS -Werror ) endif() include_directories(${CMAKE_CURRENT_SOURCE_DIR}) register_dissector_files(plugin.c plugin ${DISSECTOR_SRC} ) add_library(foo ${LINK_MODE_MODULE} ${PLUGIN_FILES} ) set_target_properties(foo PROPERTIES PREFIX "") set_target_properties(foo PROPERTIES LINK_FLAGS "${WS_LINK_FLAGS}") target_link_libraries(foo epan) install(TARGETS foo LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/@CPACK_PACKAGE_NAME@/plugins/${CPACK_PACKAGE_VERSION} NAMELINK_SKIP RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/@CPACK_PACKAGE_NAME@/plugins/${CPACK_PACKAGE_VERSION} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/@CPACK_PACKAGE_NAME@/plugins/${CPACK_PACKAGE_VERSION} )
Note: you can not memorize every time what the CMakeLists.txt file looks like, but copy it from the folder of any existing plug-in, for example, interlink. And then replace all interlink references in this file with foo.Then let us know about our plugin tools for automating the build process.
$ cd $HOME/wireshark/ $ vim CMakeLists.txt
Add the line "plugins / foo" given the indents:
....... if(ENABLE_PLUGINS) set(HAVE_PLUGINS 1) set(PLUGIN_DIR="${DATAFILE_DIR}/plugins/${CPACK_PACKAGE_VERSION}") set(PLUGIN_SRC_DIRS plugins/foo plugins/asn1 plugins/docsis .......
Now we create a directory in which files created during compilation and linking will be stored. Next run the build process itself. The following steps are performed from the
$ HOME / wireshark / directory
$ mkdir build $ cd build $ cmake .. $ make foo
If the compilation is successful, we will get the
foo.so library in the
$ HOME / wireshark / build / lib / folder.
Test run
Copy the
foo.so file to the
/usr/lib/wireshark/plugins/1.6.0/ directory and launch Wireshark. Go to the "Help -> About Wireshark", go to the
Plugins tab and in the list that appears we find our plugin -
foo.so.To make sure that the plugin recognizes our protocol, we start packet capture and start packets with our protocol over the network. (For example, I quickly wrote a program that sends the FOO protocol packet over the UDP network. You can find its code
at the end of the article .)
Working dissector
Now, when we have the framework of the plug-in, which is not doing anything useful for us, but at least it starts up, we will “write” our protocol to it. The simplest thing we can do is define the boundaries of our protocol.
To do this, first of all, we create a branch on our tree (
tree ), into which we will place the decoding results. The dissector is called in two different cases: in one case, when it is necessary to obtain summary information about the packet, and in another case, when it is necessary to output detailed information about the same packet. If the pointer to the
tree is NULL, then only the summary information is requested from us. Otherwise, we are asked to fill the
tree with detailed information that will be displayed to the Wireshark user. With this in mind, our dissector takes the form:
static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO"); col_clear(pinfo->cinfo, COL_INFO); if (tree) { proto_item *ti = NULL; ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE); } }
Here we have added a branch to a tree (
tree ) by calling
proto_tree_add_item () . This thread will contain all the details about our
foo protocol. We also mark the data area in the packet that our protocol uses. In our case, this is the entire area beyond the data boundary of the UDP packet, since we do not consider the case when our protocol contains another one.
Parameters of the
proto_tree_add_item () function:
- tree - all tree information about our package
- proto_foo is our protocol id
- tvb - data block (data transmitted by our protocol)
- 0 - specify the starting position at which the data in the tvb block is the data of our foo protocol
- -1 - the number of bytes occupied by our protocol ("-1" means "to the end of the data block")
- FALSE - the byte order is specified (TRUE - if the bytes are located in the network order.)
After these changes, Wireshark will begin to determine where the region of our protocol begins and ends and marks it as “FOO Protocol”.
The next step is to add details. This step will require the creation of several arrays and additional function calls that will help with decoding. The changes will affect the body of the
proto_register_foo () function shown earlier.
Add two static arrays to the beginning of
proto_register_foo () . Then we register these arrays after calling the
proto_register_protocol () function.
void proto_register_foo(void) { static hf_register_info hf[] = { { &hf_foo_hdr_version, { "FOO Header Version", "foo.hdr.version", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_foo_hdr_type, { "FOO Header Type", "foo.hdr.type", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } } }; static gint *ett[] = { &ett_foo }; proto_foo = proto_register_protocol ( "FOO Protocol", "FOO", "foo" ); proto_register_field_array(proto_foo, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); }
And immediately after the declaration of the
proto_foo global variable
, add 3 more declarations:
static gint ett_foo = -1; static int hf_foo_hdr_version = -1; static int hf_foo_hdr_type = -1;
Now we will improve the display of our protocol:
if (tree) { proto_item *ti = NULL; proto_tree *foo_tree = NULL; ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE); foo_tree = proto_item_add_subtree(ti, ett_foo); proto_tree_add_item(foo_tree, hf_foo_hdr_version, tvb, 0, 1, FALSE); proto_tree_add_item(foo_tree, hf_foo_hdr_type, tvb, 1, 1, FALSE); }
Now Wireshark's knowledge of our protocol is becoming more detailed. So far we have only recognized the first 2 bytes of our foo protocol, which are responsible for the protocol version and packet type, respectively.
The call to the
proto_item_add_subtree () function added an additional branch (
foo_tree ) with details about the foo protocol to the tree (
tree ) of information about the entire incoming packet. In
foo_tree, we will describe our protocol in detail.
The
ett_foo variable
is controlled by the “expansion” (expansion) of the protocol information tree in the output of the Wireshark program. This variable remembers whether our protocol should be deployed while we move through the packets.
proto_tree_add_item () this time uses the
hf_foo_hdr_version variable to output the value in the proper format.
hdr_version - 1 byte from
tvb , starting from position 0.
hdr_type - 1 byte from
tvb , starting from position 1.
If we look at the
hf_foo_hdr_version declaration in a static array, we will see a detailed field, where
- hf_foo_hdr_version - branch index
- FOO Header Version - field naming
- foo.hdr.version is the line used to filter packets (this type of line will be in the filter field)
- FT_UNIT8 indicates the type and size of the element that we read from the tvb data block . In this case, indicates that the "protocol version" field occupies 1 byte of unsigned integer type. (Other possible types can be found in the file wireshark / epan / ftypes / ftypes.h )
- BASE_DEC - for numeric types you specify, in which system to output numbers. (It can also be BASE_HEX or BASE_OCT. For non-numeric types, use BASE_NONE.)
How the rest is used - see below.
Now you can build the plugin again and run it with Wireshark. We will see that Wireshark's output has become more usable.
Well, let's finish the work on decoding our foo-protocol. To do this, we need to declare a few more global variables and make some additional calls:
... static int hf_foo_hdr_flags = -1; static int hf_foo_hdr_bool = -1; static int hf_foo_pl_len = -1; static int hf_foo_payload = -1; ... void proto_register_foo(void) { ... { &hf_foo_hdr_flags, { "FOO Header Flags", "foo.hdr.flags", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_foo_hdr_bool, { "FOO Header Boolean", "foo.hdr.bool", FT_BOOLEAN, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_foo_pl_len, { "FOO Payload Length", "foo.pl_len", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_foo_payload, { "FOO Payload", "foo.payload", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } } ... } static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { ... if (tree) { gint offset = 0; proto_item *ti = NULL; proto_tree *foo_tree = NULL; ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE); foo_tree = proto_item_add_subtree(ti, ett_foo); proto_tree_add_item(foo_tree, hf_foo_hdr_version, tvb, offset, 1, FALSE); offset += 1; proto_tree_add_item(foo_tree, hf_foo_hdr_type, tvb, offset, 1, FALSE); offset += 1; proto_tree_add_item(foo_tree, hf_foo_hdr_flags, tvb, offset, 1, FALSE); offset += 1; proto_tree_add_item(foo_tree, hf_foo_hdr_bool, tvb, offset, 1, FALSE); offset += 1; proto_tree_add_item(foo_tree, hf_foo_pl_len, tvb, offset, 4, TRUE); offset += 4; proto_tree_add_item(foo_tree, hf_foo_payload, tvb, offset, -1, FALSE); } }
So we decrypted all the bits of our invented protocol.
You can see the different types of elements that we used during the decryption: FT_BOOLEAN, FT_STRING and FT_UINT8. And also, in the case of non-numeric elements, used BASE_NONE.
Now Wireshark has a pretty good idea of ​​our protocol. And at this step it would be possible to stop if such detailing was always enough. We still have the
flags field, which will have to be decomposed manually. And also, if the protocol version is unknown, can you trust the output? Well, the most important point - the dissector will generate an exception if the packet comes without the
payload field and with a zero value of
pl_len . So, there is a place to improve the plugin. Let's continue ...
Add details to the output
There are no boundaries for perfection! So in our case. We start by naming the version and type of the incoming package, which will allow us to perceive information much faster when viewing the package. For this we need two arrays:
static const value_string packetversions[] = { { 1, "Version 1" }, { 0, NULL } }; static const value_string packettypes[] = { { 1, "Ping request" }, { 2, "Ping acknowledgment" }, { 3, "Print payload" }, { 0, NULL } };
Dependence in arrays is very simple - “value, name of meaning”. Thus, when viewing a package, we will see not the bare number of the type of package and remember what it means, but we will immediately see a description of the type. To access these arrays, we will use VALS macros, which are provided by Wireshark itself.
{ &hf_foo_hdr_version, { "FOO Header Version", "foo.hdr.version", FT_UINT8, BASE_DEC, VALS(packetversions), 0x0, NULL, HFILL } }, { &hf_foo_hdr_type, { "FOO Header Type", "foo.hdr.type", FT_UINT8, BASE_DEC, VALS(packettypes), 0x0, NULL, HFILL } }
With the version and type of package we decided. Now we will write down the flags in more detail.
#define FOO_FIRST_FLAG 0x01 #define FOO_SECOND_FLAG 0x02 #define FOO_ONEMORE_FLAG 0x04 static int hf_foo_flags_first = -1; static int hf_foo_flags_second = -1; static int hf_foo_flags_onemore = -1; void proto_register_foo(void) { ... { &hf_foo_flags_first, { "FOO first flag", "foo.hdr.flags.first", FT_BOOLEAN, FT_INT8, NULL, FOO_FIRST_FLAG, NULL, HFILL } }, { &hf_foo_flags_second, { "FOO second flag", "foo.hdr.flags.second", FT_BOOLEAN, FT_INT8, NULL, FOO_SECOND_FLAG, NULL, HFILL } }, { &hf_foo_flags_onemore, { "FOO onemore flag", "foo.hdr.flags.onemore", FT_BOOLEAN, FT_INT8, NULL, FOO_ONEMORE_FLAG, NULL, HFILL } } ... } static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { ... proto_tree_add_item(foo_tree, hf_foo_hdr_flags, tvb, offset, 1, FALSE); proto_tree_add_item(foo_tree, hf_foo_flags_first, tvb, offset, 1, FALSE); proto_tree_add_item(foo_tree, hf_foo_flags_second, tvb, offset, 1, FALSE); proto_tree_add_item(foo_tree, hf_foo_flags_onemore, tvb, offset, 1, FALSE); offset += 1; ... }
Since the flag is one bit of information with the values ​​“1” or “0”, we use the type
FT_BOOLEAN . We also set a mask for each flag in the sixth parameter (FOO_FIRST_FLAG, FOO_SECOND_FLAG, FOO_ONEMORE_FLAG) to determine which bit of a byte to interpret. Note that we use the same value for the variable
offset for all flags.
The conclusion is now much more readable. But we will not stop and make it even more informative. Note: Wireshark refers to packets as "FOO Protocol". We set this name when registering the protocol. Wireshark provides the ability to add additional information to this name. We will display in this field information about the type of protocol. To do this, we need to get the value of the type using
tvb_get_guint8 () 2 . The resulting value is displayed in two places: immediately after the name of the protocol and in the
Info column.
static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { guint8 packet_version = tvb_get_guint8(tvb, 0); guint8 packet_type = tvb_get_guint8(tvb, 1); guint32 packet_pl_len = 0; col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO"); col_clear(pinfo->cinfo, COL_INFO); if (tree) { gint offset = 0; proto_item *ti = NULL; proto_tree *foo_tree = NULL; ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, FALSE); foo_tree = proto_item_add_subtree(ti, ett_foo); proto_tree_add_item(foo_tree, hf_foo_hdr_version, tvb, offset, 1, FALSE); offset += 1; switch ( packet_version ) { case 1: col_add_fstr(pinfo->cinfo, COL_INFO, "Type: %s", val_to_str(packet_type, packettypes, "Unknown (0x%02x)")); proto_item_append_text(ti, ", Type: %s", val_to_str(packet_type, packettypes, "Unknown (0x%02x)")); proto_tree_add_item(foo_tree, hf_foo_hdr_type, tvb, offset, 1, FALSE); offset += 1; proto_tree_add_item(foo_tree, hf_foo_hdr_flags, tvb, offset, 1, FALSE); proto_tree_add_item(foo_tree, hf_foo_flags_first, tvb, offset, 1, FALSE); proto_tree_add_item(foo_tree, hf_foo_flags_second, tvb, offset, 1, FALSE); proto_tree_add_item(foo_tree, hf_foo_flags_onemore, tvb, offset, 1, FALSE); offset += 1; proto_tree_add_item(foo_tree, hf_foo_hdr_bool, tvb, offset, 1, FALSE); offset += 1; proto_tree_add_item(foo_tree, hf_foo_pl_len, tvb, offset, 4, TRUE); packet_pl_len = tvb_get_ntohl(tvb, offset); offset += 4; if ( packet_pl_len ) proto_tree_add_item(foo_tree, hf_foo_payload, tvb, offset, -1, FALSE); break; default: col_add_fstr(pinfo->cinfo, COL_INFO, "Unknown version of Foo protocol (0x%02x)", packet_version); } } }
We pass the obtained value to the macro
val_to_str () , which for the transferred value returns a description string, or a string specified by us, in case the description is not found.
We also fixed an error when improperly handling
payload when an exception was thrown.
And in addition added branching for different protocol versions.
Addition
Example of a working dissector for the Foo protocol

tvb_get_guint8 ()
In addition to the
tvb_get_guint8 () function, there are a number of others, which can be found in the file
wireshark / epan / tvbuff.hArchive
Archive with working source files. Includes:
- packet-foo.c - source code for the dissector plugin
- CMakeLists.txt - cmake file for plugin
- send-foo-packet.c - the source code of the program that sends the foo-packet
Program listing sending foo packet to port 35000 *
#include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #define FOO_PORT 35000 #define BUFFER_SIZE 210 struct _message { unsigned char pck_version; // = argv[1] unsigned char pck_type; // = 1 | 3 unsigned char pck_flags; // = rand unsigned char pck_boolean; // = rand unsigned int pck_payload_len; // = strlen(argv[2]) }; int main(int argc, char ** argv) { if ( argc != 3 ) { printf("Usage: %s <version> <ping|\"text\">\n", argv[0]); return 1; } struct sockaddr_in cli_addr; int s, cli_len = sizeof(cli_addr); char buf[BUFFER_SIZE]; struct _message msg; msg.pck_version = atoi(argv[1]); msg.pck_payload_len = 0; unsigned int randomData = open("/dev/urandom", O_RDONLY); unsigned int myRandomInteger; read(randomData, &myRandomInteger, sizeof(myRandomInteger)); msg.pck_flags |= myRandomInteger%8; read(randomData, &myRandomInteger, sizeof(myRandomInteger)); msg.pck_boolean = myRandomInteger%2; close(randomData); if ( (s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1 ) { perror("socket"); exit(1); } memset((char*)&cli_addr, 0, sizeof(cli_addr)); cli_addr.sin_family = AF_INET; cli_addr.sin_port = htons(FOO_PORT); cli_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); memset(buf, 0, BUFFER_SIZE); if ( ! strcmp(argv[2], "ping") ) { msg.pck_type = 1; } else { msg.pck_type = 3; msg.pck_payload_len = (strlen(argv[2])<200)?strlen(argv[2]):200; strncpy(buf+sizeof(struct _message), argv[2], (strlen(argv[2])<200)?strlen(argv[2]):199); } memcpy(buf, (char*)&msg, sizeof(struct _message)); if ( sendto(s, buf, sizeof(struct _message)+msg.pck_payload_len, 0, (struct sockaddr*)&cli_addr, cli_len) == -1 ) { perror("sendto"); exit(1); } exit(0); }
* program without foolproof