Lua dissectors are the secret sauce for making Wireshark understand protocols it wasn’t born knowing.
Let’s say you’ve got a custom network protocol, and Wireshark is just showing you a wall of bytes. You want to see it parsed into meaningful fields. That’s where Lua scripting comes in.
Here’s a look at a simple protocol: a custom "chat" protocol. Imagine it has a fixed-size header (4 bytes: 2 for type, 2 for length) followed by a variable-length payload.
-- chat_dissector.lua
local chat = Proto.new("chat", "Simple Chat Protocol")
-- Fields in the header
local chat_type = ProtoField.uint16("chat.type", "Type", base.HEX)
local chat_len = ProtoField.uint16("chat.len", "Length", base.DEC)
-- Register the fields with the protocol
chat.fields = { chat_type, chat_len }
-- Define the dissector function
local function dissect_chat(buffer, offset, pkt, root_table)
-- Try to read the header
local type_val = buffer.uint_le(offset, 2)
local len_val = buffer.uint_le(offset + 2, 2)
-- Add the header fields to the packet tree
local subtree = root_table.add(chat, buffer(offset, 4))
subtree.cols.type = chat_type.value(type_val)
subtree.cols.len = chat_len.value(len_val)
-- If the length is greater than 0, add the payload
if len_val > 0 then
local payload_tree = subtree.add(chat.fields.payload, buffer(offset + 4, len_val))
-- For this simple example, we'll just display the raw payload bytes.
-- In a real protocol, you'd parse this further based on the type.
payload_tree.text = "Payload (" .. len_val .. " bytes)"
end
return 4 + len_val -- Return the total number of bytes consumed
end
-- Register the dissector with Wireshark
-- We'll say it applies to UDP port 12345
local udp_table = DissectorTable.get("udp.port")
udp_table:add(12345, chat)
-- Register the dissector function itself
Dissector.register("chat", dissect_chat)
To use this, you’d save it as chat_dissector.lua and place it in your Wireshark plugins directory (e.g., /usr/local/lib/wireshark/plugins/ on Linux, or C:\Program Files\Wireshark\plugins\ on Windows). Then, you need to tell Wireshark to load it.
You can do this via Edit -> Preferences -> Protocols -> Lua. Click "Edit…" and add a new entry pointing to your chat_dissector.lua file.
Now, if you capture traffic on UDP port 12345 that matches this structure, Wireshark will show it. Let’s simulate a packet:
Imagine a packet with the following bytes: 01 00 05 00 48 65 6c 6c 6f
01 00(little-endian01) is thechat.type.05 00(little-endian05) is thechat.len.48 65 6c 6c 6fare the ASCII bytes for "Hello".
In Wireshark, this would appear in the packet details pane like this:
Simple Chat Protocol
Type: 0x01
Length: 5
Payload (5 bytes)
[Raw bytes of "Hello"]
The Proto.new("chat", "Simple Chat Protocol") line creates a new protocol entry in Wireshark’s database. ProtoField.uint16 defines the fields within our protocol, specifying their name, display name, and base format. The dissect_chat function is the core logic. It takes the packet buffer, the current offset, and the pkt object (which represents the packet being dissected). It reads the header fields, adds them to the packet tree (root_table.add), and then conditionally adds the payload if len_val indicates there’s data.
The crucial part for making Wireshark automatically use your dissector for specific traffic is DissectorTable.get("udp.port"):add(12345, chat). This tells Wireshark, "Hey, if you see a UDP packet on port 12345, hand it over to the chat dissector." You can do this for TCP ports, specific protocol IDs (like in IP or Ethernet headers), or even based on the data within a previous dissector’s output.
The most surprising thing is how much logic you can pack into these Lua scripts without impacting Wireshark’s performance significantly. You can implement complex state machines, call external libraries (though less common for basic dissection), and even generate pseudo-code for fields that aren’t directly present in the packet but can be derived. This allows you to reverse-engineer and understand almost any proprietary or custom protocol that traverses your network.
Once you’ve got custom dissectors working for UDP port 12345, the next logical step is handling variations, like different header lengths or encrypted payloads that require decryption before dissection.