Python Device Hacking (Keyboard)

After spending a bit of time hacking at the gamepad I decided to take a deeper look in to how /dev devices worked in Python, the easiest device I could get my hands on of course was a keyboard.

First things first I needed to discover which device name represented my keyboard, to do this I used the virtual /proc filesystem at /proc/bus/input/devices :

I: Bus=0011 Vendor=0001 Product=0001 Version=ab41
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/devices/platform/i8042/serio0/input/input3
U: Uniq=
H: Handlers=sysrq kbd event2
B: PROP=0
B: EV=120013
B: KEY=4 2000000 3803078 f800d001 feffffdf ffefffff ffffffff fffffffe
B: MSC=10
B: LED=7

From the above output I can see my device is event2 within Handlers, which I know is the block device /dev/input/event2 .

Next I want to confirm that by running a simple piece of Python while holding down the A key on the keyboard:

>>> f = open('/dev/input/event2', 'r')
>>> while True:
...   print f.read(8)

Running the above snippet while holding the A key resulted in the below:

>>> f = open('/dev/input/event2', 'r')
>>> while True:
...   print f.read(8)
...
|?N*

|?N*

|?N *

|?N;

|?N
   ;

Knowing the block device is correct, and Python can read from it we can move on to understanding how the keyboard device works.

Like in the last blog post I convert the binary output in to easily readable Python list containing ASCII character numbers using a simple function:

>>> def k():
...   byte = []
...   f = open('/dev/input/event2', 'r')
...   while True:
...     for bit in f.read(1):
...       byte.append(ord(bit))
...       if len(byte) == 8:
...         print byte
...         byte = []

Calling this function I then press the letter A again on the keyboard and review the output:

>>> k()
[72, 1, 225, 78, 39, 228, 8, 0]
[4, 0, 4, 0, 30, 0, 0, 0]
[72, 1, 225, 78, 53, 228, 8, 0]
[1, 0, 30, 0, 1, 0, 0, 0]
[72, 1, 225, 78, 57, 228, 8, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[72, 1, 225, 78, 188, 251, 9, 0]
[4, 0, 4, 0, 30, 0, 0, 0]
[72, 1, 225, 78, 199, 251, 9, 0]
[1, 0, 30, 0, 0, 0, 0, 0]
[72, 1, 225, 78, 202, 251, 9, 0]
[0, 0, 0, 0, 0, 0, 0, 0]

This time I notice something a bit more interesting, something I’m sure that would of helped in the gamepad code. I notice on key press I get the following:

[1, 0, 30, 0, 1, 0, 0, 0]

and on key release I receive:

[1, 0, 30, 0, 0, 0, 0, 0]

This is great as I realize element 3 (30) is the letter, but more importantly the fifth element states if the key is pressed or release (1 of press and 0 for release).

Next was to determine what is returned when you hold down the key, so again I run the k() function and hold the key. This time I found the below:

>>> k()
[204, 2, 225, 78, 126, 197, 3, 0]
[4, 0, 4, 0, 30, 0, 0, 0]
[204, 2, 225, 78, 141, 197, 3, 0]
[1, 0, 30, 0, 1, 0, 0, 0]
[204, 2, 225, 78, 145, 197, 3, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[204, 2, 225, 78, 202, 116, 7, 0]
[4, 0, 4, 0, 30, 0, 0, 0]
[204, 2, 225, 78, 217, 116, 7, 0]
[1, 0, 30, 0, 2, 0, 0, 0]
[204, 2, 225, 78, 221, 116, 7, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[204, 2, 225, 78, 237, 237, 7, 0]
[4, 0, 4, 0, 30, 0, 0, 0]
[204, 2, 225, 78, 252, 237, 7, 0]
[1, 0, 30, 0, 2, 0, 0, 0]
[204, 2, 225, 78, 0, 238, 7, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[204, 2, 225, 78, 129, 104, 8, 0]
[4, 0, 4, 0, 30, 0, 0, 0]
[204, 2, 225, 78, 144, 104, 8, 0]
[1, 0, 30, 0, 2, 0, 0, 0]
[204, 2, 225, 78, 148, 104, 8, 0]
[0, 0, 0, 0, 0, 0, 0, 0]
[204, 2, 225, 78, 144, 231, 8, 0]
[4, 0, 4, 0, 30, 0, 0, 0]
[204, 2, 225, 78, 159, 231, 8, 0]
[1, 0, 30, 0, 2, 0, 0, 0]
[204, 2, 225, 78, 163, 231, 8, 0]
[0, 0, 0, 0, 0, 0, 0, 0]

With a ken eye you will notice a line almost identical to the above two, but with one slight difference (a value to repeat!):

[1, 0, 30, 0, 2, 0, 0, 0]

At this point I’m pretty impressed and all that is left is to write a bit of Python to interrupt the keys and repeats. Luckily for me I found a piece of Python that already had a Keyboard Map written, slight editing it creates me a working keyboardMap dictionary:

keyboardMap = {
        0: "KEY_RESERVED",
        1: "KEY_ESC",
        2: "KEY_1",
        3: "KEY_2",
        4: "KEY_3",
        5: "KEY_4",
        6: "KEY_5",
        7: "KEY_6",
        8: "KEY_7",
        9: "KEY_8",
        10: "KEY_9",
        11: "KEY_0",
        12: "KEY_MINUS",
        13: "KEY_EQUAL",
        14: "KEY_BACKSPACE",
        15: "KEY_TAB",
        16: "KEY_Q",
        17: "KEY_W",
        18: "KEY_E",
        19: "KEY_R",
        20: "KEY_T",
        21: "KEY_Y",
        22: "KEY_U",
        23: "KEY_I",
        24: "KEY_O",
        25: "KEY_P",
        26: "KEY_LEFTBRACE",
        27: "KEY_RIGHTBRACE",
        28: "KEY_ENTER",
        29: "KEY_LEFTCTRL",
        30: "KEY_A",
        31: "KEY_S",
        32: "KEY_D",
        33: "KEY_F",
        34: "KEY_G",
        35: "KEY_H",
        36: "KEY_J",
        37: "KEY_K",
        38: "KEY_L",
        39: "KEY_SEMICOLON",
        40: "KEY_APOSTROPHE",
        41: "KEY_GRAVE",
        42: "KEY_LEFTSHIFT",
        43: "KEY_BACKSLASH",
        44: "KEY_Z",
        45: "KEY_X",
        46: "KEY_C",
        47: "KEY_V",
        48: "KEY_B",
        49: "KEY_N",
        50: "KEY_M",
        51: "KEY_COMMA",
        52: "KEY_DOT",
        53: "KEY_SLASH",
        54: "KEY_RIGHTSHIFT",
        55: "KEY_KPASTERISK",
        56: "KEY_LEFTALT",
        57: "KEY_SPACE",
        58: "KEY_CAPSLOCK"
}

You will notice above, just like I said earlier the element with result 30 was the key for the letter A , you will notice the keymap maps 30 to KEY_A.

All that is left is that bit of Python I mentioned:

def k():
  byte = []
  f = open('/dev/input/event2', 'r')
  while True:
    for bit in f.read(1):
      byte.append(ord(bit))
      if len(byte) == 8:
        if byte[2] in keyboardMap:
          if byte == [1, 0, byte[2], 0, 1, 0, 0, 0]:
            print keyboardMap[byte[2]]
          elif byte == [1, 0, byte[2], 0, 2, 0, 0, 0]:
            print keyboardMap[byte[2]]
        byte = []

Now I can connect to my Linux device over SSH and run this bit of code, and when the keyboard on the Linux box is pressed I get the following output:

>>> k()
KEY_F
KEY_L
KEY_I
KEY_P
KEY_MINUS
KEY_E
KEY_D
KEY_E
KEY_S
KEY_I
KEY_G
KEY_N
KEY_DOT
KEY_C
KEY_O
KEY_M