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).
Ubuntu 20.04
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
.
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
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
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?
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.
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')
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']