📜 ⬆️ ⬇️

Writing a plug-in dissector for Wireshark

image 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:

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:
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.
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:
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, whereHow 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.h

Archive
Archive with working source files. Includes:
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 

Source: https://habr.com/ru/post/121990/


All Articles