Logger

Learning about USB

As soon as we download the archive we can see that there is a PCAPNG file aka a “Packet Caputre Next Generation” file inside. The captured traffic that we have to analyse is USB traffic from a keyboard. You can use the Hierarchy tool in Statistics > Protocol Hierarchy to see that only USB traffic was captured. Here is a screenshot of a few packets. After inspecting the individual packets for some time and googling about how to inspect USB traffic in Wireshark I found out that…

  • The host address is basically the computer.
  • There are maybe a few USB devices since there are a few addresses as you can see
  • Each “action” like sending a keystroke consists of two packets. One going from the device sending the keystroke and one from the host to confirm it. (At least that’s how I understand it). For keystrokes you would only be interested in packets going from the USB device to the computer, you should filter accordingly.
  • The packets iscreated according to the URB format (USB Request Blocks). The URB is the largest part in the packets we have here. It has various information like destination address and the length of the packet. It is not very interesting to us right now since the importand date like keystrokes is not in the URB. It is in the HID Data section which is explained later.
  • There are a few types of USB packets, as you can see there are packets with strings like GET DESCRIPTION in the URB, those packets can request information from the device. Another type of packet can enable/set a configuration (SET CONFIGURATION). The packets we are interested in are the URB_INTERRUPT packets since they have keystrokes or mouse movement data inside of them.

Some sources I looked at online: Stackoverflow answer on how to filter output USB HID Usage Table PDF Some theory to skim through Wireshark USB Page A CTF Writeup Hacktricks

Creating Filters

First lets filter according to our needs. Inspecting the GET DESCRIPTION Response from all the different addresses. We discover that address 1.13.1 is representing a gaming mouse:

DEVICE DESCRIPTOR
    bLength: 18
    bDescriptorType: 0x01 (DEVICE)
    bcdUSB: 0x0110
    bDeviceClass: Device (0x00)
    bDeviceSubClass: 0
    bDeviceProtocol: 0 (Use class code info from Interface Descriptors)
    bMaxPacketSize0: 8
    idVendor: [Maxxter] (0x18f8)
    idProduct: Optical Gaming Mouse [Xtrem] (0x0f97)
    bcdDevice: 0x0100
    iManufacturer: 0
    iProduct: 1
    iSerialNumber: 0
    bNumConfigurations: 1

And address 1.16.1 is representing a Keyboard:

DEVICE DESCRIPTOR
    bLength: 18
    bDescriptorType: 0x01 (DEVICE)
    bcdUSB: 0x0110
    bDeviceClass: Device (0x00)
    bDeviceSubClass: 0
    bDeviceProtocol: 0 (Use class code info from Interface Descriptors)
    bMaxPacketSize0: 8
    idVendor: Holtek Semiconductor, Inc. (0x04d9)
    idProduct: Keyboard LKS02 (0x1702)
    bcdDevice: 0x0406
    iManufacturer: 1
    iProduct: 2
    iSerialNumber: 0
    bNumConfigurations: 1

Since we obviosly are interested in the keystrokes from the keyboard we should inspect packets that come from the address of this keyboard. We can see that all the URB_INTERRUPT packets coming from the device (meaning there are keystrokes inside them) have the length of 35. Let’s filter by packet length.

frame.len == 35

The keystroke itself is in the HID Data field, you can see this field you can see it selected in the bottom half of the picture. If you now apply HID Data as a column, you will notice that every second packet has 00 00 00 00 00 00 00 00 inside HID Data. I think this is supposed to indicate that the key was released. We are not interested in those packets so lets filter those packets out. This is our final filter:

frame.len == 35 && !(usbhid.data == 00:00:00:00:00:00:00:00)

Basically this means: frame.len == 35 - The length of the fram should be equal to 35 && AND !(usbhid.data == 00:00:00:00:00:00:00:00) the usbhid.data should NOT be equal to all 00

I found the usbhid.data attribute by right-clicking the column we created.

Extracting Keystrokes

Here are some important infos on how to extract keystrokes out of those packets:

  • The keystroke itself is in the HID Data string in Hexadecimal format. What the Hex codes represent can be found in an HID usage table. I used this PDF. The codes for keyboards can be found on page 88. There are 3 types of Hexcodes to be aware of.
    • Generally codes in the third position represent letters or symbols. You can look those up in an HID usage table. Example: 00 00 15 00 00 00 00 00 would be a small r (assuming CAPS LOCK is off)
    • 39 in the third position stands for CAPS LOCK Example: 00 00 39 00 00 00 00 00 would activate/deactivate CAPS LOCK
    • 20 in the first position stands for SHIFT Example: 20 00 09 00 00 00 00 00 would be a capital F

Now, I could use a Python script or something similar to translate those Hexcodes into ASCII but I decided not to and simply transcribed them. Unfortunately the laziness caused me to make a big mistake. I mixed up the position in HID Data. Since 20 in the first position means SHIFT and 20 in the thid position means 3 I missed all the threes because I interpreted them all as SHIFT. This way the flag came out wrong…

After trying for a while I googled, found the correct flag, looked up the code for 3 and I understood my mistake immediately. Here is my final and correct transcription, the number on the left is the packet number in Wireshark, right after that is the Hexcode and the symbol it equals to.

49: 39 = CAPS LOCK
55: 0b = H
59: 17 = T
63: 05 = B
67: 20 + 00 = SHIFT
69: 20 + 2F = {
71: 20 + 00 = SHIFT
75: 39 = caps lock
81: 0C = i
85: 20 + 00 = SHIFT
87: 20 + 2D = _
89: 20 + 00 = SHIFT
93: 39 = CAPS LOCK
99: 06 = C
103: 21 = 4
107: 11 = N
111: 20 + 00 = SHIFT
113: 20 + 2D = _
115: 20 + 00 = SHIFT
119: 22 = 5
123: 20 = 3
127: 20 = 3
131: 20 + 00 = SHIFT
133: 20 + 2D = _
135: 20 + 00 = SHIFT
139: 39 = caps lock
145: 1c = y
149: 39 = CAPS LOCK
155: 12 = O
159: 18 = U
163: 39 = caps lock
169: 15 = r
173: 20 + 00 = SHIFT
175: 20 + 2D = _
177: 20 + 00 = SHIFT
181: 39 = CAPS LOCK
187: 0E = K
191: 20 = 3
195: 1C = Y
199: 1F = 2
203: 39 = caps lock
209: 20 + 00 = SHIFT
211: 20 + 30 = }
213: 20 + 00 = SHIFT

If you write it all down you will get the flag but before doing that let’s do it the “proper” way with a Python script.

Using Python

There a few tools online to do this. I used this one on GitHub which was mentioned on Hacktricks. To use it we need to extract the Hexcodes. On the GitHub page it is explained how to do that. Here is the command.

tshark -r ./usb.pcap -Y 'usb.capdata && usb.data_len == 8' -T fields -e usb.capdata

Tshark is basically Wireshark as a command line tool. As you can see this command filters using usb.capdata and usb.data_len. Since in our case we use usbhid.data we have to modify it a bit. Here is my version.

tshark -r ./keystrokes.pcapng -Y 'frame.len == 35' -T fields -e usbhid.data

As you can see I use the same filter as before to get all packets coming from our keboard to the computer. We don’t have to filter out the empty usbhid.data packets since the python script will take case of it. At the end we extract the usbhid.data field. Here is how the ouput looks like:

0000390000000000
0000000000000000
00000b0000000000
0000000000000000
0000170000000000
0000000000000000
0000050000000000
0000000000000000
.
.
.

On the Github page it is mentioned that we have to have : inside the Hex strings. We can add those with Sed and save the output as hex. Here is the final command:

tshark -r ./keystrokes.pcapng -Y 'usbhid.data && frame.len == 35' -T fields -e usbhid.data | sed 's/../:&/g2' > hex

Now clone the python script and run it.

git clone https://github.com/carlospolop-forks/ctf-usb-keyboard-parser.git

cd ctf-usb-keyboard-parser

python3 usbkeyboard.py /root/hex
⇪htb{⇪i_⇪c4n_533_⇪y⇪ou⇪r_⇪k3y2⇪}

You can see the symbols which indicate a CAPS LOCK. After rewriting the flag with the in mind we get the final flag.