I wanted to create the smallest Lua dissector for Wireshark for BGGP6! This year’s challenge theme is RECYCLE, which opens up all the previous challenges for this year, running until January 18th 2026. Another option is to create the smallest file of a given type that prints, returns, or otherwise displays “6”, which is what I am doing here.
I have previously written about using Wireshark as a lolbin and call os.execute() to run a program through Lua. Tiktok PoC Vid.
Lua dissectors are used to add additional protocol support without having to recompile Wireshark (as with C dissectors).
To install a new Lua dissector, place them in one of the following locations before opening Wireshark:
- Linux: 
~/.local/lib/wireshark/plugins - MacOS: 
~/.local/lib/wireshark/pluginsor~ /.config/wireshark/plugins - Windows: 
C:\Program Files\Wireshark\plugins\ 
Lua Dissectors on the Wireshark Wiki: https://wiki.wireshark.org/Contrib
Some dissectors I’ve written: https://github.com/netspooky/dissectors
The pcap used in this writeup is telnet-cooked.pcap from here: https://wiki.wireshark.org/samplecaptures#telnet
DissectorTable.get
To start, I went with what I know, and used DissectorTable.get to create a dissector that would run on every packet sent to TCP port 23.
Here is a simple dissector to do that in 230 bytes (without comments): 6-vanilla.lua:
myProtocol=Proto("bggp6","bggp6") -- Create the protocol
function myProtocol.dissector(buffer, pinfo, tree) -- Declare the dissector
 pinfo.cols.protocol="BGGP6"
 z=tree:add(myProtocol,buffer(),"BGGP6")
end
local dtable=DissectorTable.get("tcp.port") -- Get the table of TCP ports
dtable:set(23,myProtocol) -- Register your dissector on this port, calling :set() overrides other dissectors

I did some experiments to see how little I could get away with during the basic dissector declaration. I shortened the protocol name to “b6”. Protocol names must be at least two characters, and they must start with a character from a-z. I also started putting a 6 in as many spots as I could instead of a string.
139 bytes: 6-139.lua
s=Proto("b6",6)
function s.dissector(b, p, t)
 p.cols.protocol=6
 z=t:add(s,b(),6)
end
local e=DissectorTable.get("tcp.port")
e:set(23,s)
Result:

Even though it’s already pretty small, I could still shrink it a bit more. I got rid of the statement that adds the packet buffer to dissector tree (highlighted in the above image) and kept it so that it only overwrote the protocol in the packet table in the GUI with 6 instead of “BGGP6”. This was 108 characters.
s=Proto("b6",6)
function s.dissector(b, p, t) p.cols.protocol=6 end
DissectorTable.get("tcp.port"):set(23,s)
I removed some more unneeded things like the 3rd argument to the dissector function, and reconfigured it to hijack every IPv4 eth.type using the ethertype DissectorTable (see 13.3.2. DissectorTable). These changes resulted in a packet that was also 108 bytes.
s=Proto("b6",6)
function s.dissector(b,p) p.cols.protocol=6 end
DissectorTable.get("ethertype"):set(0x800,s)

Later on, I realized that you can also overwrite pInfo.cols.info instead of protocol to save an additional 4 bytes, bringing this down to 104 bytes. See next section for info!
s=Proto("b6",6)
function s.dissector(b,p) p.cols.info=6 end
DissectorTable.get("ethertype"):set(0x800,s)
register_heuristic
I wanted to created a dissector that would override every Ethernet frame, and not just those with the IPv4 ethertype.
A way to do this is to create a heuristic dissector using register_heuristic
This 112 byte dissector overwrites p.cols.protocol with 6, and returns 1. Returning 1 or True will tell the dissection engine that you claim the packet, and trigger only your dissector!
s=Proto("b6",6)
s.dissector=function(b,p) p.cols.protocol=6 return 1 end
s:register_heuristic("eth",s.dissector)
The dissector was still kind of long, so I turned it into a one liner at 86 bytes.
s=Proto("b6",6):register_heuristic("eth",function(b,p) p.cols.protocol=6 return 1 end)
It was here that I discovered you could overwrite p.cols.info to get down to 82 bytes.
s=Proto("b6",6):register_heuristic("eth",function(b,p) p.cols.info=6 return 1 end)

Searching for smaller functions to call
After getting to 82 bytes using register_heuristic, I started hunting for more global functions and builtins that could produce output. I started by looking for Wireshark builtins in the docs, since functions like print(6) are only available if you have debugging enabled. I wanted this to work on vanilla Wireshark, so I avoided that.
The first API I saw on the GUI function page was ProDlg.new, which would be 16 bytes, but it requires additional code to support it.
ProgDlg.new(6,6)
I found that new_dialog created a window, but needed a callback function as the 2nd argument which processes the data that is input into the window. Instead of declaring my own function, I just reused the print function as the callback.
21 bytes:
new_dialog(6,print,6)

I was hoping to find an even shorter standard Lua API to register as a callback, so I combed through all the standard Lua functions.
The only usable smaller functions are 4 characters, load, next and type. I used type since it seemed to be the safest, and got down to 20 bytes.
new_dialog(6,type,6)
Because of the argument requirement, I switched my focus to TextWindow.new() which despite being longer, can take just a single char argument.
Even though TextWindow.new is longer than new_dialog, the shorter amount of args makes it smaller. Even if i had a 1 char builtin or function, new_dialog would still only be 17 bytes due to the requirement of the arguments, so TextWindow.new() wins!
17 bytes:
TextWindow.new(6)
This is the smallest dissector I could manage that would output in Wireshark. It opens a single Text Window with the title “6”

Now that we know the limit, how can we make it more diabolical?
Listener.new
The register_heuristic approach only hooks Ethernet frames, but what if we wanted to hook every frame that came in?
We can use a Listener, which can get triggered prior to other dissectors being called. One caveat of Listeners is that they can’t modify any of the packet info, so we need to use the TextWindow.new(6) method to produce some output.
I took a similar approach to minifying a basic Listener, and got to 64 characters:
t=Listener.new(nil,"");t.packet=function() TextWindow.new(6) end
The last little trick is to turn this into a one liner by removing the last couple of spaces, bringing it down to 62 characters:
t=Listener.new(nil,"");t.packet=function()TextWindow.new(6)end
This oneliner opens up a new window on every new packet when sniffing, or every packet within a pcap when you open it directly like here :)

I found another funny thing that I will write up later. Like for Part 2!!!
BGGP6 Announcement