Ok, więc mój program działał przez noc i nadal działa, więc wysyłam kod. Jest trochę niezręcznie, ale działa. Napiszę też o tym, jak to zrobiłem, ponieważ będzie to przydatne dla osób, które nie mają mojej klawiatury. Program potrzebuje najnowszej wersji libpcap i wireshark. Debugfs musi zostać zamontowany (mount -t debugfs none_debugs / sys / kernel / debug) i załadowany moduł usbmon (modprobe -v usbmon).
To jest program działający w tle:
#!/usr/bin/python
# This program should be run as the logged in user. The user must have
# permissions to execute tshark as root.
from pexpect import spawn
from pexpect import TIMEOUT
from subprocess import PIPE
from subprocess import Popen
# Configuration variables
## Device ID from lsusb output
deviceID = "0458:0708"
## Output filter for tshark
filter = "usb.endpoint_number == 0x82 && usb.data != 00:00:00:00"
## Tshark command to execute
tsharkCmd = "/home/stribika/bin/tshark-wrapper"
## Keypress - command mapping
### Key: USB Application data in hex ":" between bytes "\r\n" at the end.
### Value: The command to execute. See subprocess.Popen.
commands = {
"00:00:20:00\r\n":[
"qdbus", "org.freedesktop.ScreenSaver", "/ScreenSaver",
"org.freedesktop.ScreenSaver.Lock"
],
"00:00:40:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Prev"
],
"00:00:10:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Next"
],
"02:00:00:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Pause"
],
"04:00:00:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Stop"
],
"00:04:00:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Mute"
],
"20:00:00:00\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "1"
],
"40:00:00:00\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "2"
],
"00:00:80:00\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "3"
],
"00:00:00:08\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "4"
],
"00:00:00:20\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "5"
],
"00:00:00:10\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "6"
],
}
# USB interface names change across reboots. This determines what is the correct
# interface called this week. If this turns out to be the case with endpoint
# numbers lsusb can tell that too.
lsusbCmd = [ "lsusb", "-d", deviceID ]
sedCmd = [
"sed", "-r",
"s/^Bus ([0-9]{3}) Device [0-9]{3}: ID " + deviceID + ".*$/\\1/;s/^0+/usbmon/"
]
lsusb = Popen(lsusbCmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
sed = Popen(sedCmd, stdin = lsusb.stdout, stdout = PIPE, stderr = PIPE)
usbIface = sed.stdout.readline().rstrip()
# Arguments for Tshark
## -i is the interface (usbmon[0-9]+)
## -R is the output filter
tsharkArgs = [
"-T", "fields", "-e", "usb.data",
"-i", usbIface,
"-R", filter
]
# Start capturing
## pexpect is needed to disable buffering. (Nothing else actally disables it
## don't belive the lies about Popen's bufsize=0)
tshark = spawn(tsharkCmd, tsharkArgs, timeout = 3600)
line = "----"
# Read keypresses while tshark is running and execute the proper command.
while line != "":
try:
line = tshark.readline()
Popen(commands[line], stdin = PIPE, stdout = PIPE, stderr = PIPE)
# We do not care about timeout.
except TIMEOUT:
pass
Jak widać, istnieje duża tablica poleceń zindeksowana danymi aplikacji z pakietów USB. Wartościami są wydane polecenia. Używam DBus do robienia tego, co należy zrobić, ale możesz użyć xvkbd do wygenerowania prawdziwych zdarzeń naciśnięcia klawisza (znalazłem xvkbd bardzo wolno, aby wysłać prostą kombinację klawiszy). tshark-wrapper to proste opakowanie dookoła tshark, które wykonuje tshark jako root i wyłącza stderr.
#!/bin/sh
sudo tshark "$@" 2> /dev/null
Jest problem. Użytkownik potrzebuje uprawnień do wykonania tshark jako root bez hasła. To naprawdę bardzo zła rzecz. Ryzyko można zmniejszyć, umieszczając więcej argumentów w opakowaniu, a mniej w skrypcie Pythona i umożliwiając użytkownikom wykonanie opakowania jako root.
Teraz o procesie robienia tego z innymi klawiaturami. Nie wiem prawie nic o USB i wciąż nie było to takie trudne. Większość mojego czasu spędziłem na zastanawianiu się, jak zrobić odczyt niebuforowany z potoku. Z wyjścia lsusb wiedziałem, że moja klawiatura jest na drugim interfejsie USB. Zacząłem więc przechwytywać za pomocą wireshark na usbmon2. Mysz i inne oprogramowanie sprzętowe generują dużo hałasu, więc odłącz je od zasilania lub przynajmniej nie poruszaj myszą.
Pierwszą rzeczą, którą zauważyłem, było to, że dodatkowe klucze mają identyfikator punktu końcowego 0x82, a normalne klucze mają identyfikator punktu końcowego 0x81. Na początku było kilka pakietów z 0x80. To dobrze, że można go łatwo przefiltrować:
usb.endpoint_number == 0x82
Normalne naciśnięcie klawisza:
Dodatkowe naciśnięcie klawisza:
Łatwo było zauważyć, że naciśnięcie klawisza generuje 4 pakiety USB: 2 dla prasy, 2 dla wydania. W każdej parze pierwszy pakiet był wysyłany przez klawiaturę do komputera, a drugi był na odwrót. Wydawało się, że ACK-y z TCP. „ACK” to URB-SUBMIT, a normalny pakiet to URB-COMPLETE. Postanowiłem więc odfiltrować „ACK” i wyświetlać tylko normalne pakiety:
usb.urb_type == "C\x01\x82\x03\x02"
„ACK” USB:
Teraz były tylko 2 pakiety na naciśnięcie klawisza. Każda sekunda miała zerowe pole wartości aplikacji, a wszystko inne miało inne wartości. Więc odfiltrowałem zera i użyłem innych wartości do identyfikacji kluczy.
usb.data != 00:00:00:00
Dodatkowe wydanie klucza:
Moja klawiatura to Slimstar 220 (mam nadzieję, że nie kwalifikuje się to jako spam. Jeśli to zrobię, usunę ją.) Jeśli masz takie same szanse, to niezmodyfikowany program będzie działał. W przeciwnym razie myślę, że przynajmniej wartość aplikacji będzie inna.
Jeśli ktoś ma ochotę napisać prawdziwy sterownik na podstawie tych danych, proszę dać mi znać. Nie podoba mi się mój brzydki hack.
Aktualizacja: Mam nadzieję, że kod jest teraz odporny na ponowne uruchomienie.