Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Wireshark protocol decoder for FIDO(U2F) and FIDO2(WebAuthn) over USB HID
cbor = Dissector.get("cbor")
iso7816 = Dissector.get("iso7816")
ctap_proto = Proto("ctaphid","ctap hid")
-- Field Extractor
direction_fe ="usb.endpoint_address.direction")
udp_srcport_fe ="udp.srcport")
function payload_dissector(pinfo, subtree, state)
local t = ByteArray.tvb(state.payload_buffer:subset(0, state.total_len), "payload")
local isIN = 0
local usb_dir = direction_fe()
if usb_dir ~= nil then
isIN = usb_dir.value
if tostring(udp_srcport_fe()) == "8111" then
isIN = 1
isIN = 0
-- pinfo.cols.protocol = tostring(udp_srcport_fe())
if state.ctaphid_cmd == 0x10 then
local subtree = subtree:add(t(0),"FIDO2 Payload")
local ctap_cmd = t(0,1):uint();
local text = ({[0]=CTAP_COMMAND_CODE, [1]=CTAP_RESPONSE_CODE})[isIN][ctap_cmd]
pinfo.cols.protocol = "CTAP " .. text
subtree:add(t(0,1),string.format('CTAP CMD/Status: %s (0x%02x)', text, ctap_cmd))
if t(1):len() > 0 then
cbor:call(t(1):tvb(), pinfo, subtree)
elseif state.ctaphid_cmd == 0x03 then
local subtree = subtree:add(t(0),"FIDO/U2F Payload")
iso7816:call(t, pinfo, subtree)
local subtree = subtree:add(t(0),"Other Payload")
cached_info = {}
total_len = 0
ctaphid_cmd = 0
payload_buffer = nil
-- create a function to dissect it
function ctap_proto.dissector(buffer,pinfo,tree)
pinfo.cols.protocol = "CTAPHID"
local subtree = tree:add(ctap_proto,buffer(),"CTAPHID")
local data = nil
-- subtree:add(buffer,"length=" .. string.format('0x%08x',buffer:len()) .. " " .. direction_fe().value)
local cid = buffer(0,4):uint()
subtree:add(buffer(0,4),string.format('CID: 0x%08x',cid))
local cmd = buffer(4,1):uint()
if, 0x80) == 0x80 then
ctaphid_cmd =, 0x7f)
subtree:add(buffer(4,1),string.format('CMD: %s (0x%02x)',CTAPHID_COMMAND_CODE[ctaphid_cmd],ctaphid_cmd))
total_len = buffer(5,2):uint()
subtree:add(buffer(5,2),string.format('Length: 0x%04x',total_len))
payload_buffer =
data = buffer(7)
subtree:add(buffer(4,1),string.format('SEQ: 0x%02x',cmd))
data = buffer(5)
local state = cached_info[pinfo.number]
if state ~= nil then
if state.ctaphid_cmd ~= nil then
payload_dissector(pinfo, subtree, state)
-- first interation
state = {}
if payload_buffer:len() >= total_len then
-- cache info for later display
state.ctaphid_cmd = ctaphid_cmd
state.payload_buffer = payload_buffer
state.total_len = total_len
payload_dissector(pinfo, subtree, state)
cached_info[pinfo.number] = state
usb_table = DissectorTable.get("usb.product")
usb_table:add(0x10500407,ctap_proto) -- VID/PID of Yubikey
udp_table = DissectorTable.get("udp.port")
udp_table:add(8111,ctap_proto) -- Solokeys simulation
udp_table:add(7112,ctap_proto) -- Solokeys simulation
Copy link

scaszoo commented Nov 19, 2019

Wasn't working on my system. Cause was the VID/PID of my Yubikey wasn't matching.
Get your VID/PID from list or by capturing the traffic when connecting your Yubikey to your usb port and filtering in wireshark for usb.idVendor and extract idVendor and idProduct

Copy link

mesink commented Sep 19, 2022

This seems to work intermittently only on Mac for me, but consistently on Windows. I'm fairly new to wireshark, so still looking, but any ideas?

Copy link

scaszoo commented Sep 19, 2022

@DanBujak I am not sure what your question is . . .

Copy link

mesink commented Sep 19, 2022

Hey, thanks for the quick reply! When using a Mac, I need to reload the file several times to get it to work. Even then sometimes it doesn't work. On Windows it works consistently. The question is if you have any ideas as to why it would be less reliable on a Mac or things I can try.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment