介绍
本文分成两个部分。
第一部分给出了linux键盘驱动的工作原理,并且讨论了建立一个基于
内核的键盘纪录器的方法。这部分内容对那些想写一个基于内核的键盘纪录器,或者写一个
自己键盘驱动的朋友会有帮助。
第二部分详细描述了vlogger的每个细节,vlogger是一个强大的基于内核的linux键盘纪录器,
以及如何来使用它。这向技术可以运用在蜜罐系统中,也可以做成一些很有意思的hacker game,
主要用来分析和采集hacker的攻击手法。我们都知道,一些大家熟知的键盘纪录器,如iob,
uberkey,unixkeylogger等,它们是基于用户层的。这里介绍的是基于内核层的键盘纪录器。
最早期的基于内核的键盘纪录器是linspy,它发表在phrack杂志第50期。而现代的kkeylogger(
后面我们将用kkeylogger来表示基于内核的键盘纪录器)广泛采用的手法是中断sys_read或者
sys_write系统调用来对用户的击键进行记录。
显然,这种方法是很不稳定的并且会明显的降低系统的速度,因为我们中断的恰恰是系统使用最
频繁的两个系统调用sys_read,sys_write;sys_read在每个进程需要读写设备的时候都会用到。
在vlogger里,我用了一个更好的方法,就是劫持tty buffer进程函数,下面会介绍到。
我假定读者熟悉linux的可加载模块的原理和运作过程,如果不熟悉,推荐大家首先阅读我以前写
过的linux kernel simple hacking,或者linux tty hijack,(在有下载),
参阅《linux驱动程序设计》来获得相关的理论基础知识。
--[ 2 - linux键盘驱动的工作原理
首先让我们通过以下的结构图来了解一下用户从终端的击键是如何工作的:
_____________ _________ _________
/ \ put_queue| |receive_buf| |tty_read
/handle_scancode\-------->|tty_queue|---------->|tty_ldisc|------->
\ / | | |buffer |
\_____________/ |_________| |_________|
_________ ____________
| |sys_read| |
--->|/dev/ttyX|------->|user process|
| | | |
|_________| |____________|
Figure 1
首先,当你输入一个键盘值的时候,键盘将会发送相应的scancodes给键盘驱动。一个独立的
击键可以产生一个六个scancodes的队列。
键盘驱动中的handle_scancode()函数解析scancodes流并通过kdb_translate()函数里的
转换表(translation-table)将击键事件和键的释放事件(key release events)转换成连
续的keycode。
比如,'a'的keycode是30。击键’a'的时候便会产生keycode 30。释放a键的时候会产生
keycode 158(128+30)。
然后,这些keycode通过对keymap的查询被转换成相应key符号。这步是一个相当
复杂的过程。
以上操作之后,获得的字符被送入raw tty队列--tty_flip_buffer。
receive_buf()函数周期性的从tty_flip_buffer中获得字符,然后把这些字符送入
tty read队列。
当用户进程需要得到用户的输入的时候,它会在进程的标准输入(stdin)调用read()函数。
sys_read()函数调用定义在相应的tty设备(如/dev/tty0)的file_operations结构
中指向tty_read的read()函数来读取字符并且返回给用户进程。
/*e4gle add
file_operations是文件操作结构,定义了文件操作行为的成员,结构如下,很容易理解:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);<----这是本文提到的read函数
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
我们直到unix系统中设备也是文件,所以tty设备我们也可以进行文件操作。
*/
键盘驱动器可以有如下4种模式:
- scancode(RAW模式):应用程序取得输入的scancode。这种模式通常
用于应用程序实现自己的键盘驱动器,比如X11程序。
- keycode(MEDIUMRAW模式):应用程序取得key的击键和释放行为(通过
keycode来鉴别这两种行为)信息。
- ASCII(XLATE模式):应用程序取得keymap定义的字符,该字符是
8位编码的。
- Unicode(UNICODE模式):此模式唯一和ASCII模式不同之处就是UNICODE模式
允许用户将自己的10进制值编写成UTF8的unicode字符,如十进制的数可以编写成
Ascii_0到Ascii_9,或者用户16进制的值可以用Hex_0到Hex_9来代表。一个keymap
可以产生出一系列UTF8的序列。
以上这些驱动器的工作模式决定了应用程序所取得的键盘输入的数据类型。大家如果需要详细了解scancode,
keycode和keymaps的相关信息,参看read[3]。