We received a pcap file containing USB Request Blocks (URBs) with no other information. A quick look at the exchanged frames with Wireshark revealed that most of the data was sent to the host from a specific device (26.3, HID device from “bInterfaceClass”, keyboard from “bInterfaceProtocol” from the official documentation) on an interrupt endpoint.

The first idea was of course: is the key typed on the keyboard? Every interrupt packet from the 26.3 device was carrying a keycode, and all these packets had the same URB id: 0xffff88003b7d8fc0. Exploring packets structure made it easy to localize these keycodes: the offset 0x42 of these interrupt packets. We just had to script keycodes extracting using a correspondance table, then!

We created a Python script using the dpkt library to parse the pcap file and extract the keycodes:

import binascii
import dpkt
import struct
import sys

# Start the pcap file parsing
f = open(sys.argv[1], 'rb')
pcap = dpkt.pcap.Reader(f)

# Create a partial mapping from keycodes to ASCII chars
keys = {}
keys.update({
    i + 0x4: chr(i + ord('a'))
    for i in range(26)
})
keys.update({
    i + 0x1e: chr(i + ord('1'))
    for i in range(9)
})
keys[0x27] = '0'
keys.update({
    0x28: '\n',
    0x2c: ' ',
    0x2d: '-',

    0x2e: '+',
    0x2f: '[',
    0x30: ']',
})

# Then iterate over each USB frame
for ts, buf in pcap:
    # We are interested only in packets that has the expected URB id, and
    # packets carrying keycodes embed exactly 8 bytes.
    urb_id = ''.join(reversed(buf[:8]))
    if binascii.hexlify(urb_id) != 'ffff88003b7d8fc0':
        continue
    data_length, = struct.unpack('<I', buf[0x24:0x28])
    if data_length != 8:
        continue
    key_code = ord(buf[0x42])
    if not key_code:
        continue
    sys.stdout.write(keys[key_code])

The output of this script was the following “keyboard stream”:

rxterm -geometry 12x1+0+0
echo k
rxterm -geometry 12x1+75+0
echo e
rxterm -geometry 12x1+150+0
echo y
rxterm -geometry 12x1+225+0
echo [
rxterm -geometry 12x1+300+0
echo c
rxterm -geometry 12x1+375+0
echo 4
rxterm -geometry 12x1+450+0
echo 8
rxterm -geometry 12x1+525+0
echo b
rxterm -geometry 12x1+600+0
echo a
rxterm -geometry 12x1+675+0
echo 9
rxterm -geometry 12x1+0+40
echo 9
rxterm -geometry 12x1+75+40
echo 3
rxterm -geometry 12x1+150+40
echo d
rxterm -geometry 12x1+225+40
echo 3
rxterm -geometry 12x1+300+40
echo 5
rxterm -geometry 12x1+450+40
echo c
rxterm -geometry 12x1+375+40
echo 3
rxterm -geometry 12x1+525+40
echo a
rxterm -geometry 12x1+600+40
echo ]

Alright, the indented result should be to display the key re-ordering first the characters with terminal positions. We then had just to format a script to actually open multiple terms in the same time at the right place and containing the associated character:

python2 extract_keyboard.py dongle.pcap |
    sed 's/rxterm \(.*\)/xterm \1 -e "\\/g' |
    sed 's/echo \(.*\)/echo -n \1; read" \&/g' > display_key.sh

And finally, running the display_key.sh script gave us the key: key[c48ba993d353ca]