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

386 lines
11 KiB
Go

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