USB PCAP Forensics: Barcode Scanner (NSEC CTF 2021 Writeup, Part 1/3)

Émilio Gonzalez
6 min readMay 24, 2021


Part 2

During the annual NSec Capture The Flag (CTF), I (partly) solved a really original set of challenges made by Joey Dubé: Goldsmiths’ Guild. Three of those challenges involved understanding the behavior of different USB device as seen by a USB packet sniffer.

Challenge Introduction:

Magician, please sit down. The Kingdom has a money problem and the King requires your assistance for a delicate operation. The Goldsmiths are suspected of creating a new, physical currency based on Goat Pieces, the new crypto currency which is becoming popular among citizens.

This has a risk of devaluating the power of the King’s treasury and ruin our economy. We have a series of field operation that will require you stealth and technical prowess on the physical security devices. One such device is the card reader on the guild door. We need you to go set a sniffer that can grab Access Cards Captures. We have some technical elements for you to decrypt.

Flag 1: What is the model number of the device reading the access cards? Format: flag-card-[MODEL]

Flag 2: What’s the odd username used to breach into the guild?

Flag 3: What’s the name of the attack used to breach into the guild? Format: flag-card-[attack]

The packet capture file (PCAP) can be downloaded here.

Exploring the data

The first thing I like to do when I open a PCAP is to understand how much data is exchanged, how many machines are involved and for how long. For larger PCAP, Wireshark has nice features in the “Statistics” menu, but since this PCAP is small (34ko), simply scrolling down to the last packet does the trick:

Don’t forget to create a separate profile (bottom right)for USB packets, as interesting columns and settings will differ from network-based PCAP

Here we see that we are dealing with ~700 packets that were captured in less than 30 seconds.

The first flag asks the vendor of the USB device that was plugged in. Logically, it makes sense for this information to be found at the beginning of the PCAP. We see two sets of what I guess is the “USB device handshake”:

Two identical “handshakes”

Here, if we look at packet #2 and #8 (GET DESCRIPTOR Response DEVICE), we can somewhat understand what happens:

The first packet registers the computer’s internal USB Hub. Then, the actual plugged device appears, and Wireshark, fortunately, knows to what entity the Vendor and Product Id belong to. We are dealing with a USB barcode scanner. Interesting.

Flag 1: flag-card_EVHK0012

Note: These Vendor IDs are reserved by the USB Implementation Forum.

Let’s look at the next packet from the barcode scanner (GET DESCRIPTOR Response CONFIGURATION):

Interesting. What does HID mean? Why does it mention “Keyboard”? HID stands for “Human Interface Device”. It’s a group of USB devices meant to be used by humans (such as keyboard and mouse). The specification can be found here. In this document, barcode scanners are also mentionned:

Page 11

From my understanding, the HID specification ensures a framework so that USB devices that are the same type (keyboard, mouse) share the same protocol so we don’t have to have a different driver for every mouse model that exists. Cool. So why is the protocol “keyboard”? It turns out, barcode readers actually behave like keyboards from the perspective of the machine that it’s plugged in.

Extracting and understanding the data

So we want to see the data that was scanned by the barcode scanner, which means see what characters were “typed” by the device. The rest of the PCAP consists of “URB_INTERRUPT in” packets. Let’s filter on packets sent by the scanner by right-clicking the source of a packet:

Easy way to filter when you don’t remember the exact filter name

Now to extract the data, there is a way with a long command line using TShark, but when we want to extract a low volume of data per packet, simply creating a column with the data and use Wireshark’s “Export Packets Dissection As CSV…” works very well:

Create a new display column easily with this one weird trick
Export as CSV

Now we load the data in a spreadsheet editor and take a look! I chose LibreOffice, but Excel or Google spreadsheet will work fine, too.

Note: When importing, you might need to specify the type of the “HID Class” column to “text” to avoid your spreadsheet editor to automatically parse it as a number (or worse — a date).

Now, we could read the spec and parse every field of the messages, but it’s a CTF and we don’t really have time for this. So let’s do it the heuristic way: finding patterns by scrolling up and down the data:

By using this very scientific technique, we clearly see that only 2 bytes contain variable information: the 2nd byte (which is either 0x00 or 0x02) and the 4th byte (which has a lot of different values).

At this point, I’m pretty sure that the 2nd byte represents the “shift” key on the keyboard and that the 4th byte represents the character that was typed. Let’s extract those two bytes using my favourite CTF tool, Cyberchef:

The “fork” operation splits the input into multiple inputs, so each subsequent operation is done on the subset of the input. The “Merge delimiter” dictate what is to be appended between each input when they are merged together in the output box.

Because I like Python, I decided to extract the data as a list of tuples.

Recovering what was scanned

To know which key represented which character, I googled “USB HID Keyboard Key Codes” and found this nice Gist. Great, we now have every ingredient needed to recover the data, let’s write the code:

# The data from Cyberchef
chars = [("00", "16"), ("00", "0c"), ... ("00", "04"), ("00", "28")]

# Association between code and char
map = {
"04": "a",
"05": "b",
"06": "c",
"07": "d",
"08": "e",
"09": "f",
"0A": "g",
"0B": "h",
"0C": "i",
"0D": "j",
"0E": "k",
"0F": "l",
"10": "m",
"11": "n",
"12": "o",
"13": "p",
"14": "q",
"15": "r",
"16": "s",
"17": "t",
"18": "u",
"19": "v",
"1A": "w",
"1B": "x",
"1C": "y",
"1D": "z",
"1E": "1",
"1F": "2",
"20": "3",
"21": "4",
"22": "5",
"23": "6",
"24": "7",
"25": "8",
"26": "9",
"27": "0",
"2C": " ",
"28": "\n",
"2D": "-",
"34": "'",
"36": ",",
"2E": "=",
"33": ";",

phrase = ""
for char in chars:
if char[1].upper() in map:
# If LShift is not pressed
if char[0] == "00":
phrase += map[char[1].upper()]
# If LShift is pressed
actual_char = map[char[1].upper()]
upper_map = {
"-": "_",
"0": ")",
"9": "(",
# For debug purposes
if char not in upper_map:
print(f"skip {char}")
# Add a char to be printed
actual_char = upper_map[char]
phrase += actual_char.upper()
# For debug purposes
print(f"skip {char}")

We run the code, and we see the flag :)

Flag 2: flag-card_faraday_backdoor

Flag 3: flag-card_sqlinjection

Very cool challenge! I learned about USB HID and that barcode scanners behave like keyboards to the machine it’s plugged in.

Part 2 is here!



Émilio Gonzalez

Blue team analyst in Quebec, Canada. Passionate about cybersecurity and urbanism. Twitter: @res260