Search code examples
pythonkeyboardusbwiresharktshark

Reading a (Wireshark) LiveCapture of USB keystrokes into python on Ubuntu 20.04?


Context

The bluetooth of my keyboard is unstable, it's a known problem of the device. The manufacturer says the micro-USB does not transmit keypresses and that it is for charging only. However, I inspected the USB data with Wireshark and detected it does transmit the keystrokes over its micro-USB connection. So I am trying to give the keyboard a second life through the micro-USB connection (and hopefully help people with the same issue).

System

Ubuntu 20.04

Approach

I've identified the USB port of my keyboard device in Wireshark, and recorded the stream of data across that port. That data is saved into a file called abcd.pcapng (I pressed the buttons abcd during the recording). Next, I wrote a basic python script that uses tshark to convert abcd.pcapng file into its original keypresses abcd.

Code

This is the Python code that converts the abcd.pcapng file into the letters abcd:

# This script extracts the keypresses from a pcapng file.
import os

pcapng_filename = "abcd.pcapng"
keypress_ids_filename = "keypress_ids.txt"

# create the output for
command_pcapng_to_keypress_ids = (
    f"tshark -r '{pcapng_filename}' -T fields -e usb.capdata > {keypress_ids_filename}"
)
print(
    f"Running the following bash command to convert the pcapng file to 00xx00000 nrs:\n{command_pcapng_to_keypress_ids}"
)
os.system(command_pcapng_to_keypress_ids)

# read keypress id file
switcher = {
    "04": "a",  # or A
    "05": "b",  # or B
    "06": "c",  # or C
    "07": "d",  # or D
    "08": "e",  # or E
    "09": "f",  # or F
    "0A": "g",  # or G
    "0B": "h",  # or H
    "0C": "i",  # or I
    "0D": "j",  # or J
    "0E": "k",  # or K
    "0F": "l",  # or L
    "10": "m",  # or M
    "11": "n",  # or N
    "12": "o",  # or O
    "13": "p",  # or P
    "14": "q",  # or Q
    "15": "r",  # or R
    "16": "s",  # or S
    "17": "t",  # or T
    "18": "u",  # or U
    "19": "v",  # or V
    "1A": "w",  # or W
    "1B": "x",  # or X
    "1C": "y",  # or Y
    "1D": "x",  # or Z
    "1E": "1",  # or !
    "1F": "2",  # or @
    "20": "3",  # or #
    "21": "4",  # or $
    "22": "5",  # or %
    "23": "6",  # or ^
    "24": "7",  # or &
    "25": "8",  # or *
    "26": "9",  # or (
    "27": "0",  # or )
    "2D": "-",  # or _
    "2E": "+",  # or =
    "2F": "[",  # or {
    "30": "]",  # or }
    "31": '"',  # or |
    "33": ";",  # or :
    "34": "'",  # or "
    "35": "`",  # or ~
    "36": ",",  # or <
    "37": ".",  # or >
    "38": "/",  # or ?
}


def readFile(filename):
    fileOpen = open(filename)
    return fileOpen


file = readFile(keypress_ids_filename)
print(f"file={file}")

# parse the 0000050000000000 etc codes and convert them into keystrokes
for line in file:
    if len(line) == 17:
        two_chars = line[4:6]
        try:
            print(
                f"line={line[0:16]}, relevant characters indicating keypress ID: {two_chars} convert keypres ID to letter: {switcher[two_chars]}"
            )
        except:
            pass

Output

The output of that script for the specified file is:

Running the following bash command to convert the pcapng file to 00xx00000 nrs:
tshark -r 'abcd.pcapng' -T fields -e usb.capdata > keypress_ids.txt
file=<_io.TextIOWrapper name='keypress_ids.txt' mode='r' encoding='UTF-8'>
line=0000040000000000, relevant characters indicating keypress ID: 04 convert keypres ID to letter: a
line=0000050000000000, relevant characters indicating keypress ID: 05 convert keypres ID to letter: b
line=0000060000000000, relevant characters indicating keypress ID: 06 convert keypres ID to letter: c
line=0000070000000000, relevant characters indicating keypress ID: 07 convert keypres ID to letter: d

Question

How can I adjust the code to get the USB data directly as a continuous stream, instead of first having to start- and stop recording the USB data followed by having to create the output abcd.pcapng file?

For example, is there a Wireshark-api or tshark function that starts listening until the/some script is stopped?


Solution

  • Solution

    The following script called live_capture_keystrokes.py captures the Leftover Capture Data which contains the signals of the keystrokes, they are parsed live and continuously by the Python code.

    I think it is important to activate usb monitoring each time you reboot the computer (if you want to run this script successfully). To do that, you can run:

    sudo modprobe usbmon
    

    Ideally you would run that without sudo, that would also prevent one to run the python file with sudo, but I did not yet figure out how to run modprobe usbmon without sudo.

    Code

    This live_capture_keystrokes.py script needs to be ran with sudo if modprobe usbmon is ran with sudo. To run the python script in sudo, one can type:

    sudo su
    # sudo apt install python3
    # pip install pyshark
    # python3 live_capture_keystrokes.py
    

    And the content of live_capture_keystrokes.py is:

    import pyshark
    # Get keystrokes data
    print("\n----- Capturing keystrokes from usbmon0 --------------------")
    capture = pyshark.LiveCapture(interface='usbmon0', output_file='output.pcap')
    
    # Source: https://www.programcreek.com/python/example/92561/pyshark.LiveCapture 
    for i, packet in enumerate(capture.sniff_continuously()):
        try:
            data= packet[1].usb_capdata.split(":")
            print(data)
        except:
            pass
    capture.clear()
    capture.close()
    print(f'DONE')
    

    Output

    The output is not yet parsed back to keystrokes, I will add that later. However, this is the output, each time you press a key on your usb keyboard, it prints the accompanying list directly to screen.

    a['00', '00', '04', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    b['00', '00', '05', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    c['00', '00', '06', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    d['00', '00', '07', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    e['00', '00', '08', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    f['00', '00', '09', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    g['00', '00', '0a', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    h['00', '00', '0b', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    i['00', '00', '0c', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    j['00', '00', '0d', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    k['00', '00', '0e', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    l['00', '00', '0f', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    m['00', '00', '10', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    n['00', '00', '11', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    o['00', '00', '12', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    p['00', '00', '13', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    q['00', '00', '14', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    r['00', '00', '15', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    s['00', '00', '16', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    t['00', '00', '17', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    u['00', '00', '18', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    v['00', '00', '19', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    w['00', '00', '1a', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    x['00', '00', '1b', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    y['00', '00', '1c', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    z['00', '00', '1d', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    0['00', '00', '62', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    1['00', '00', '59', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    2['00', '00', '5a', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    3['00', '00', '5b', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    4['00', '00', '5c', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    5['00', '00', '5d', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    6['00', '00', '5e', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    7['00', '00', '5f', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    8['00', '00', '60', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    9['00', '00', '61', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    1['00', '00', '1e', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    2['00', '00', '1f', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    3['00', '00', '20', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    4['00', '00', '21', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    5['00', '00', '22', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    6['00', '00', '23', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    7['00', '00', '24', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    8['00', '00', '25', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    9['00', '00', '26', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']
    0['00', '00', '27', '00', '00', '00', '00', '00']
    ['00', '00', '00', '00', '00', '00', '00', '00']