Linux-Kernel键盘监控程序实验

Linux 输入子系统是 Linux 内核用于管理各种输入设备的。从上到下由三层实现,分别为:输入子系统事件处理层、输入子系统核心层和输入子系统设备驱动层。

设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。

/dev/input 目录下显示的是已经注册在内核中的设备编程接口,用户通过 open 这些设备文件来打开不同的输入设备进行硬件操作。

我们需要监控键盘,那么可以通过读取驱动程序得到键盘的活动。

用 C 语言编写一个 linux 键盘记录器

  1. 执行 cat /proc/bus/input/devices 查看键盘活动的对应驱动,内容较多,我们可以使用 grep 筛选一下。-A 5 表示显示后面 5 行,-C 1 表示显示前后 1 行。

image-20221102160451907

  1. 现在我们知道,我们需要读取的驱动文件是 /dev/input/event4,那么 open 之,然后 read 之。读取时这个驱动每次都会给我们返回一个 struct input_event ,我们分析这个结构体就知道键盘活动了。
struct input_event event;
/* __u16 type;
__u16 code;
__s32 value;
*/
if (read(fd, &event, sizeof(event)) == sizeof(event)) {
if (event.type == EV_KEY) {
if (event.code == 58 && event.value == 1) { // 按下 caps 键
caps_pressed ^= 1;
}
if ((event.code == 42 || event.code == 54) && event.value == 1) { // 按下 shift 键
shift_pressed = 1;
}
if ((event.code == 42 || event.code == 54) && event.value == 0) { // 松开 shift 键
shift_pressed = 0;
}
// 得到 event.code 对应的键
char *s = scancode_to_ascii[event.code];
if (shift_pressed) {
s = shift_scancode_to_ascii[event.code];
} else if (caps_pressed) {
s = caps_scancode_to_ascii[event.code];
}
// 将 log 写入文件
if (event.value == 0 || event.value == 1) {
char log[1024];
sprintf(log, "key %s %s\n", s, event.value ? "Pressed" : "Released");
write(fd2, log, strlen(log) + 1);
}
}
}
  1. 测试代码。将代码编译后使用 root 权限执行,因为需要保证能够读取驱动文件。最后生成的 log.txt 也需要使用 root 权限查看。

image-20221102193725160

我们可以看到,在大写和 shift 情况下,均能够正常工作。在执行 sudo ./keylogger 时我使用 ctrl + C 结束程序但是这两个按键没有出现在我们的 log.txt 中的原因是我使用了外接键盘,需要监听的驱动程序不同。

用 python 语言编写一个 linux 键盘记录器

参考链接,linux 下的键盘记录程序。这里都讲的很清楚了,有几个坑。

  1. 在安装模块的时候,需要执行 sudo pip install evdev,因为后面我们的程序是在 sudo 权限下执行,如果安装在了用户空间会报找不到模块的错误。个人理解,反正在执行的时候我报错了。
  2. 作者是使用 python2 写的代码,不过现在已经到了 python3 时代了。没有什么很大的需要调整的,只需要把 print 函数加个括号。dev.fn 需要修改为 dev.path
  3. re.search('eyboard', dev.name) 我们可以修改为 re.search('keyboard', dev.name),如果没有修改,在我的机器上找到的 Keyboard,从而没有办法得到结果。可能是外接键盘的锅。
  4. 注意,作者用了个 select 函数,这个是为了 I/O 多路复用,避免程序阻塞,造成性能浪费。由内核监控 dev,当 dev 的状态发生变化时,内核通知我们的程序来处理。多路复用(selectpollepoll)在处理多个 I/O 操作时非常有用,假设我们有 100I/O 操作,如果都阻塞线程,那么对性能是一种极大的浪费。

执行结果如下:

image-20221102202156843

使用 keylogger 命令

  1. 安装这个工具。
git clone https://github.com/kernc/logkeys.git  
cd logkeys
./autogen.sh
cd build
../configure
make
sudo make install
  1. 使用 logkeys 来监控键盘,通过 -s 选项开启,通过 -k 选项关闭。

image-20221102204534352

我们可以看到,日志文件记录下来我们系统的所有的键盘行为。

  1. 通过 sudo make uninstall 将这个工具从我们的系统中移除。

思考与总结

  1. event x 可以读,那么 event x 事件也可以写,这样就可以实现模拟敲击键盘。
void write_device(const char *dev) {
int fd = open(dev, O_WRONLY);
assert(fd != -1);
struct input_event event;
event.type = EV_KEY;
event.code = 2;
for (int i = 0; i < 10; i++) {
event.value = 1;
write(fd, &event, sizeof(event));
event.value = 0;
write(fd, &event, sizeof(event));
}
close(fd);
}

不断模拟按键和松开键,得到的结果如下,event.code = 2 对应的按键就是 1

image-20221102215225336

  1. 阅读 logkeys 源码,分析实现原理与过程。

从下面的截图我们可以看出,核心方案也是先通过命令,得到和键盘相关的驱动。然后读取驱动文件,得到 struct input_event 结构体。然后处理结构体,将键盘监控信息写入文件。

image-20221102213105190

image-20221102210807238

参考链接

Linux:监控键盘_C代码

linux 下的键盘记录程序(python版)

logkeys-github

我的代码