The Clever Use of Postdissectors to Analyze Layer 2 Protocols

The Clever Use of Postdissectors to Analyze Layer 2 Protocols

In a recent blog about dissecting unusual protocols when troubleshooting OT security issues, we used the Cisco Nexus protocol as a use case to show how Wireshark plugins can help during the reverse engineering process.

In this post, we’re focusing on another interesting Layer 2 protocol related to a peculiar set of features that can be leveraged to instruct our tool to properly dissect the targeted communication scheme.

During our journey we had to analyze an unknown protocol used between Ruggedcom switches and their provisioning tools to ensure proper configuration. This protocol is known as RUGGEDCOM Discovery Protocol (RCDP).

Due to the popularity of such devices in the industrial sector, we decided to dive in and analyze the Layer 2 proprietary protocol used by the Ruggedcom ROS devices. In particular, we focused not on its inner structure (we don’t want to spoil the funny part, or do we?!) but on how we can instruct Wireshark to properly detect it and begin the dissection process.

Why Postpone the Dissection?

The setup we used for our analysis was:

  • Ruggedcom Switch RS910
  • RCDP discovery tool

Through the discovery tool we were able to trigger some key functionalities, and as a consequence, collect Pcaps for our deep dive analysis. This is what the protocol looked like over the wire:

Protocol display

As you can see, this time Wireshark doesn’t have complete knowledge of the communication taking place.

The last decoded frame shows the well-known LLC frame, normally seen as a link between the MAC (Layer 2) layer and the Network layer.

Our goal was to fully understand the Data content.

I won’t drill into the reverse engineering process we went through to fully dissect the RCDP protocol here. Instead, I’ll take you through how we analyzed the unknown protocol and gathered the indicators needed to develop the plugin.

By looking inside the LLC frame, we uncovered initial evidence that leads us to assume that the tool was not completely inaccurate on the dissection of this specific layer.

RCDP Protocol ID
RCDP Protocol ID

The seems to be quite a reliable indicator for tagging the protocol. But one question remained: how we can access the Data field without overlapping with the current dissection?

The Use of Postdissectors

An interesting concept that can help us a lot is related to so-called postdissectors. We can leverage this type of plugin to start our dissection after all the “normal” dissectors have done their jobs. This allows us to append the results at the bottom of the dissection tree.

TIP: In case you find yourself stuck trying to understand some complex mechanisms, remember that Wireshark has its own version of the well-known stackoverflow, called: ask.wireshark. Here’s an example.

The easiest way to call a postdissector is to use the register_postdissector() function at the bottom of the script, rather than the standard Dissector.Table.get().

-- initialize protocol fieldsrcdp_proto = Proto("RCDP", "Siemens RCDP")-- RCDP PROTOCOL IDlocal RCDP_PID = 0x01e6-- main functionfunction rcdp_proto.dissector(buffer, pinfo, tree)length=buffer:len()if length == 0 then return endend-- Register RCDP postdissectorregister_postdissector(rcdp_proto)

Next, we need to instruct the current script to bind itself every time it spots the custom LLC Protocol ID (0x01e6). In order to do this, we have to initialize two variables that are going to reference the Protocol ID and Data section of the upper frame.

TIP: Wireshark’s filters are also quite useful in the context of your dissector!

With the reference we can easily add the condition needed to add tagging for the RCDP protocol.

At this stage we just have to register our buffer to the LLC Data section. We can then start to add our dissection as usual.

-- initialize protocol fieldsrcdp_proto = Proto("RCDP", "Siemens RCDP")-- RCDP PROTOCOL IDlocal RCDP_PID = 0x01e6-- reference LLC framelocal llc_pid ="")local data_data ="")-- main functionfunction rcdp_proto.dissector(buffer, pinfo, tree)length=buffer:len()if length == 0 then return end-- get current Protocol ID value and check if it's the RCDP onelocal llc_pid_ex = llc_pid()if llc_pid_ex == nil or llc_pid_ex.value ~= RCDP_PIDthen returnendpinfo.cols.protocol = set buffer at the right locationlocal buf = data_data().range()local buf_len = buf:len()local subtree = tree:add(rcdp_proto, buf(0, buf:len()), "RCDP Protocol Data")end-- register RCDP postdissectorregister_postdissector(rcdp_proto)

Instructing Wireshark to Dissect Our Protocol

Using this approach, we can properly instruct Wireshark to dissect our protocol right after the standard parsing that we saw previously. This is how a post-dissector works, and shows how we can apply its logic in a real use case, such as with the RCDP protocol.

At this point, we can start digging inside the inner protocol structure, while also playing with the Ruggedcom Explorer utility. As an example, let’s try to extract some key asset details.

To do this, we need to trigger the autodiscovery function within the diagnostic tool and start analyzing the traffic generated.

TIP: Tools’ UI can be a good friend when you need to deepen your understanding of an unknown protocol.

As soon as the tool is able to discover the switch set up in the Nozomi Networks Lab, it can also gather some interesting details about it.

After gaining some understanding of the retrieved communication, we can easily improve the dissector to allow us to extract the visualized product information in a structured way. The end result is a complete parsing of the entire communication.

Autodiscovery completed
Autodiscovery completed

Nozomi Networks Labs: Supporting the Global Research Community

In this second blog on protocol dissectors, we discussed how to leverage the concept of postdissector in plugins and instruct Wireshark to bind our dissection to the protocol used between Ruggedcom switches and its monitoring tool, known as RUGGEDCOM Discovery Protocol (RCDP).

We were able to use the custom Protocol ID exposed within the LLC frame, and start the dissection in the right offset location.

To reach our goal of fully understanding the Data content, we used some key functions for triggering the postdissector and calling upper-level frames, including:

  • register_postdissector(v1)
  • ExtractedField.value
  • ExtractedField.range()

We hope that the global security community can use our suggestions and techniques to further their own analysis and research projects. Stay tuned for our next Nozomi Networks Labs blog. In the meantime, check out our new OT/IoT Security Report July 2021, and the related on-demand webinar below, for insights on ransomware, ICS and IoT vulnerabilities and much more.