PS/2键盘记录器数据捕获:如何绕过X系统直接保存组合键原始信号


阅读 2 次

硬件层数据捕获的困境

在开发PS/2接口的硬件键盘记录器时,我们发现当设备将记录的键位数据回传到计算机时,普通字符输入可以正常保存到文本编辑器,但系统会拦截ALT+TAB这类系统级组合键。这导致记录的会话数据不完整。

Linux下的解决方案

通过直接读取/dev/input设备可以绕过X窗口系统的键盘事件处理:


#include <fcntl.h>
#include <linux/input.h>

int main() {
    int fd = open("/dev/input/event3", O_RDONLY);
    struct input_event ev;
    
    while(1) {
        read(fd, &ev, sizeof(ev));
        if(ev.type == EV_KEY) {
            // 将原始键值写入文件
            FILE *log = fopen("keylog.bin", "ab");
            fwrite(&ev, sizeof(ev), 1, log);
            fclose(log);
        }
    }
    return 0;
}

Windows平台的实现方案

使用Raw Input API可以获取底层键盘输入:


#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, 
                        WPARAM wParam, LPARAM lParam) {
    if(message == WM_INPUT) {
        UINT dwSize;
        GetRawInputData((HRAWINPUT)lParam, RID_INPUT, 
                        NULL, &dwSize, sizeof(RAWINPUTHEADER));
        
        LPBYTE lpb = malloc(dwSize);
        GetRawInputData((HRAWINPUT)lParam, RID_INPUT,
                        lpb, &dwSize, sizeof(RAWINPUTHEADER));
        
        // 写入二进制日志文件
        HANDLE hFile = CreateFile("keys.bin", GENERIC_WRITE, 
                                0, NULL, OPEN_ALWAYS, 
                                FILE_ATTRIBUTE_NORMAL, NULL);
        WriteFile(hFile, lpb, dwSize, NULL, NULL);
        CloseHandle(hFile);
        free(lpb);
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

数据解析的关键点

记录原始信号后,需要按照PS/2协议解析数据包:

  • 0xE0前缀表示扩展键
  • 断码(释放键)会在通码前加0xF0
  • 每个按键都有独立的扫描码

实际应用中的优化建议

建议采用环形缓冲区存储数据,并添加时间戳:


struct KeyEvent {
    uint32_t timestamp;
    uint8_t scancode;
    bool is_pressed;
};

#define BUF_SIZE 1024
struct KeyEvent ring_buffer[BUF_SIZE];
uint16_t head = 0, tail = 0;