From 5ffce43b21a1fa1e02545730b78d840f5af9392a Mon Sep 17 00:00:00 2001 From: wuko233 Date: Sun, 19 Apr 2026 22:24:25 +0800 Subject: [PATCH] =?UTF-8?q?[cmd]=20=E4=BC=98=E5=8C=96=E5=8F=AF=E7=96=91?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=92=8C=E8=BF=9B=E7=A8=8B=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=EF=BC=8C=E6=94=AF=E6=8C=81=E9=80=90=E4=B8=AA?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4;=20=E6=94=B9=E8=BF=9B=E7=99=BD=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=9B=B4=E6=96=B0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/safe/safe.go | 206 ++++++++++++++++++++++++++++-------- internal/storage/storage.go | 72 ++++++++++++- 2 files changed, 228 insertions(+), 50 deletions(-) diff --git a/cmd/safe/safe.go b/cmd/safe/safe.go index 6326af5..a90c737 100644 --- a/cmd/safe/safe.go +++ b/cmd/safe/safe.go @@ -3,7 +3,6 @@ package safe import ( "fmt" "os" - "path/filepath" "sysmonitord/internal/config" "sysmonitord/internal/scanner/file" "sysmonitord/internal/scanner/process" @@ -58,21 +57,19 @@ func interactiveSafe(cfg *config.Config) { dubiousFiles, err := storage.LoadDubiousFiles(dataDir, cfg.Storage.DubiousFileListFile) if err != nil { fmt.Printf("无法读取可疑文件列表: %v\n", err) - return + dubiousFiles = nil } if len(dubiousFiles) == 0 { fmt.Println("没有可疑文件需要处理。") - return } dubiousProcesses, err := storage.LoadDubiousProcesses(dataDir, cfg.Storage.DubiousProcessListFile) if err != nil { fmt.Printf("无法读取可疑进程列表: %v\n", err) - return + dubiousProcesses = nil } if len(dubiousProcesses) == 0 { fmt.Println("没有可疑进程需要处理。") - return } fmt.Println("\n╔══════════════════════════════════════════════╗") @@ -93,10 +90,11 @@ func interactiveSafe(cfg *config.Config) { fmt.Println("╚══════════════════════════════════════════════╝") fmt.Println("\n请选择操作:") - fmt.Println("[1] 将以上可疑文件全部确认为安全 (移至白名单)") - fmt.Println("[2] 将以上可疑进程全部确认为安全 (移至白名单)") - fmt.Println("[3] 全部确认安全 (文件和进程)") - // Todo: 支持逐个确认 + fmt.Println("[1] 逐个确认可疑文件") + fmt.Println("[2] 逐个确认可疑进程") + fmt.Println("[3] 将可疑文件全部确认为安全") + fmt.Println("[4] 将可疑进程全部确认为安全") + fmt.Println("[5] 全部确认安全 (文件和进程)") fmt.Println("[ESC] 退出不处理") fmt.Print("请输入选项: ") @@ -108,30 +106,54 @@ func interactiveSafe(cfg *config.Config) { switch input { case "1": - fmt.Println("正在处理...") - if err := confirmFilesAsSafe(cfg, dubiousFiles); err != nil { - fmt.Printf("处理失败: %v\n", err) + conformed, unconfirmed := confirmFiles(dubiousFiles) + if len(conformed) > 0 { + fmt.Println("正在处理可疑文件...") + if err := confirmFilesAsSafe(cfg, conformed); err != nil { + fmt.Printf("确认文件安全失败: %v\n", err) + } else { + fmt.Printf("已将 %d 个文件移入白名单,%d 个文件仍为可疑\n", len(conformed), len(unconfirmed)) + } } else { - fmt.Println("已将可疑文件移入白名单。") + fmt.Println("没有文件被确认安全,所有文件仍为可疑。") } case "2": - fmt.Println("正在处理...") - if err := confirmProcessesAsSafe(cfg, dubiousProcesses); err != nil { - fmt.Printf("处理失败: %v\n", err) + conformed, unconfirmed := confirmProcesses(dubiousProcesses) + if len(conformed) > 0 { + fmt.Println("正在处理可疑进程...") + if err := confirmProcessesAsSafe(cfg, conformed); err != nil { + fmt.Printf("确认进程安全失败: %v\n", err) + } else { + fmt.Printf("已将 %d 个进程移入白名单,%d 个进程仍为可疑\n", len(conformed), len(unconfirmed)) + } } else { - fmt.Println("已将可疑进程移入白名单。") + fmt.Println("没有进程被确认安全,所有进程仍为可疑。") } case "3": - fmt.Println("正在处理...") + fmt.Println("正在将所有可疑文件确认为安全...") if err := confirmFilesAsSafe(cfg, dubiousFiles); err != nil { - fmt.Printf("处理失败: %v\n", err) + fmt.Printf("确认文件安全失败: %v\n", err) } else { - fmt.Println("已将可疑文件移入白名单。") + fmt.Printf("已将 %d 个文件移入白名单\n", len(dubiousFiles)) + } + case "4": + fmt.Println("正在将所有可疑进程确认为安全...") + if err := confirmProcessesAsSafe(cfg, dubiousProcesses); err != nil { + fmt.Printf("确认进程安全失败: %v\n", err) + } else { + fmt.Printf("已将 %d 个进程移入白名单\n", len(dubiousProcesses)) + } + case "5": + fmt.Println("正在将所有可疑文件和进程确认为安全...") + if err := confirmFilesAsSafe(cfg, dubiousFiles); err != nil { + fmt.Printf("确认文件安全失败: %v\n", err) + } else { + fmt.Printf("已将 %d 个文件移入白名单\n", len(dubiousFiles)) } if err := confirmProcessesAsSafe(cfg, dubiousProcesses); err != nil { - fmt.Printf("处理失败: %v\n", err) + fmt.Printf("确认进程安全失败: %v\n", err) } else { - fmt.Println("已将可疑进程移入白名单。") + fmt.Printf("已将 %d 个进程移入白名单\n", len(dubiousProcesses)) } case "ESC": fmt.Println("已取消操作。") @@ -142,15 +164,11 @@ func interactiveSafe(cfg *config.Config) { } func confirmProcessesAsSafe(cfg *config.Config, processes []storage.DubiousProcessInfo) error { - dataDir := cfg.Storage.DataDir - whiteListPath := filepath.Join(dataDir, cfg.Storage.ProcessSystemFile) - // dubiousFile := filepath.Join(dataDir, cfg.Storage.DubiousProcessListFile) - - f, err := os.OpenFile(whiteListPath, os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return fmt.Errorf("无法打开白名单文件: %v", err) + if len(processes) == 0 { + return nil } - defer f.Close() + + dataDir := cfg.Storage.DataDir var toWhitelist []process.ProcessInfo for _, proc := range processes { @@ -162,30 +180,23 @@ func confirmProcessesAsSafe(cfg *config.Config, processes []storage.DubiousProce } if err := storage.AppendProcessToWhitelist(toWhitelist, dataDir, cfg.Storage.ProcessSystemFile); err != nil { - return fmt.Errorf("更新白名单失败: %v", err) + return fmt.Errorf("更新进程白名单失败: %v", err) } logger.Log.Debug("已将可疑进程移入白名单", zap.Int("count", len(toWhitelist))) - // Todo: 逐个删除条目 - if err := storage.RemoveDubiousProcesses(dataDir, cfg.Storage.DubiousProcessListFile, []storage.DubiousProcessInfo{}); err != nil { + if err := storage.RemoveDubiousProcesses(dataDir, cfg.Storage.DubiousProcessListFile, processes); err != nil { return fmt.Errorf("删除可疑进程列表失败: %v", err) } - return nil - } func confirmFilesAsSafe(cfg *config.Config, files []storage.DubiousFileInfo) error { - dataDir := cfg.Storage.DataDir - whiteListPath := filepath.Join(dataDir, cfg.Storage.FileSystemFile) - // dubiousFile := filepath.Join(dataDir, cfg.Storage.DubiousFileListFile) - - f, err := os.OpenFile(whiteListPath, os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return fmt.Errorf("无法打开白名单文件: %v", err) + if len(files) == 0 { + return nil } - defer f.Close() + + dataDir := cfg.Storage.DataDir var toWhitelist []file.FileInfo for _, f := range files { @@ -201,10 +212,113 @@ func confirmFilesAsSafe(cfg *config.Config, files []storage.DubiousFileInfo) err logger.Log.Debug("已将可疑文件移入白名单", zap.Int("count", len(toWhitelist))) - // Todo: 逐个删除条目 - if err := storage.RemoveDubiousFiles(dataDir, cfg.Storage.DubiousFileListFile, []storage.DubiousFileInfo{}); err != nil { + if err := storage.RemoveDubiousFiles(dataDir, cfg.Storage.DubiousFileListFile, files); err != nil { return fmt.Errorf("删除可疑文件列表失败: %v", err) } - return nil + +} + +func confirmFiles(files []storage.DubiousFileInfo) (confirmed []storage.DubiousFileInfo, unconfirmed []storage.DubiousFileInfo) { + if len(files) == 0 { + return nil, nil + } + + fmt.Println("\n╔══════════════════════════════════════════════╗") + fmt.Println("║ 逐个确认可疑文件 ║") + fmt.Println("╠══════════════════════════════════════════════╣") + fmt.Println("║ [y] 确认安全 [n] 仍为可疑 [q] 退出 ║") + fmt.Println("╚══════════════════════════════════════════════╝") + + for i, file := range files { + fmt.Printf("\n[%d/%d] 文件路径: %s\n", i+1, len(files), file.Path) + fmt.Printf(" 哈希值: %s\n", file.Hash) + fmt.Printf(" 发现时间: %s\n", file.DiscoveredAt) + fmt.Print(" 选择 [y/n/q]: ") + + input, err := readKeyWithESC() + if err != nil { + fmt.Printf("读取输入失败: %v,跳过此文件\n", err) + unconfirmed = append(unconfirmed, file) + continue + } + + switch input { + case "y", "Y": + fmt.Println("已确认安全") + confirmed = append(confirmed, file) + case "n", "N": + fmt.Println("仍为可疑") + unconfirmed = append(unconfirmed, file) + case "q", "Q": + fmt.Println("已退出确认") + for j := i; j < len(files); j++ { + unconfirmed = append(unconfirmed, files[j]) + } + return confirmed, unconfirmed + default: + fmt.Println("无效输入,默认仍为可疑") + unconfirmed = append(unconfirmed, file) + } + } + + fmt.Printf("\n╔══════════════════════════════════════════════╗\n") + fmt.Printf("║ 文件确认完成 ║\n") + fmt.Printf("║ ✓ 确认安全: %d 个 ║\n", len(confirmed)) + fmt.Printf("║ ✗ 保留可疑: %d 个 ║\n", len(unconfirmed)) + fmt.Printf("╚══════════════════════════════════════════════╝\n") + + return confirmed, unconfirmed +} + +func confirmProcesses(processes []storage.DubiousProcessInfo) (confirmed []storage.DubiousProcessInfo, unconfirmed []storage.DubiousProcessInfo) { + if len(processes) == 0 { + return nil, nil + } + + fmt.Println("\n╔══════════════════════════════════════════════╗") + fmt.Println("║ 逐个确认可疑进程 ║") + fmt.Println("╠══════════════════════════════════════════════╣") + fmt.Println("║ [y] 确认安全 [n] 仍为可疑 [q] 退出 ║") + fmt.Println("╚══════════════════════════════════════════════╝") + + for i, proc := range processes { + fmt.Printf("\n[%d/%d] 进程名称: %s\n", i+1, len(processes), proc.Name) + fmt.Printf(" 路径: %s\n", proc.Path) + fmt.Printf(" 哈希值: %s\n", proc.FileHash) + fmt.Print(" 选择 [y/n/q]: ") + + input, err := readKeyWithESC() + if err != nil { + fmt.Printf("读取输入失败: %v,跳过此进程\n", err) + unconfirmed = append(unconfirmed, proc) + continue + } + + switch input { + case "y", "Y": + fmt.Println("已确认安全") + confirmed = append(confirmed, proc) + case "n", "N": + fmt.Println("仍为可疑") + unconfirmed = append(unconfirmed, proc) + case "q", "Q": + fmt.Println("已退出确认") + for j := i; j < len(processes); j++ { + unconfirmed = append(unconfirmed, processes[j]) + } + return confirmed, unconfirmed + default: + fmt.Println("无效输入,默认仍为可疑") + unconfirmed = append(unconfirmed, proc) + } + } + + fmt.Printf("\n╔══════════════════════════════════════════════╗\n") + fmt.Printf("║ 进程确认完成 ║\n") + fmt.Printf("║ ✓ 确认安全: %d 个 ║\n", len(confirmed)) + fmt.Printf("║ ✗ 保留可疑: %d 个 ║\n", len(unconfirmed)) + fmt.Printf("╚══════════════════════════════════════════════╝\n") + + return confirmed, unconfirmed } diff --git a/internal/storage/storage.go b/internal/storage/storage.go index ca9d495..6f6dd3e 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -180,8 +180,28 @@ func AppendProcessToWhitelist(procs []process.ProcessInfo, dataDir string, proce return writer.Flush() } -func RemoveDubiousProcesses(dataDir string, dubiousProcessFile string, toKeep []DubiousProcessInfo) error { +func RemoveDubiousProcesses(dataDir string, dubiousProcessFile string, toRemove []DubiousProcessInfo) error { filePath := filepath.Join(dataDir, dubiousProcessFile) + + allProcs, err := LoadDubiousProcesses(dataDir, dubiousProcessFile) + if err != nil { + return fmt.Errorf("[storage]无法加载可疑进程记录文件%s: %w", filePath, err) + } + + toRemoveMap := make(map[string]bool) + for _, proc := range toRemove { + key := fmt.Sprintf("%s:%s", proc.Name, proc.Path) + toRemoveMap[key] = true + } + + var toKeep []DubiousProcessInfo + for _, proc := range allProcs { + key := fmt.Sprintf("%s:%d", proc.Name, proc.PID) + if !toRemoveMap[key] { + toKeep = append(toKeep, proc) + } + } + if len(toKeep) == 0 { return os.Remove(filePath) } @@ -194,6 +214,11 @@ func RemoveDubiousProcesses(dataDir string, dubiousProcessFile string, toKeep [] writer := bufio.NewWriter(f) + header := fmt.Sprintf("# 可疑进程记录 - 最后更新: %s\n", time.Now().Format("2006-01-02 15:04:05")) + if _, err := writer.WriteString(header); err != nil { + return err + } + for _, proc := range toKeep { line := fmt.Sprintf("%s:%s:%s\n", proc.Name, proc.Path, proc.FileHash) if _, err := writer.WriteString(line); err != nil { @@ -201,7 +226,15 @@ func RemoveDubiousProcesses(dataDir string, dubiousProcessFile string, toKeep [] } } - return writer.Flush() + if err := writer.Flush(); err != nil { + return err + } + + logger.Log.Debug("[storage]已从可疑进程列表删除条目", + zap.Int("deleted", len(toRemove)), + zap.Int("remaining", len(toKeep))) + + return nil } func SaveDubiousFiles(files DubiousFileInfo, dataDir string, dubiousFileName string) error { @@ -243,8 +276,26 @@ func AppendFileToWhitelist(files []file.FileInfo, dataDir string, fileSystemFile return writer.Flush() } -func RemoveDubiousFiles(dataDir string, dubiousFileName string, toKeep []DubiousFileInfo) error { +func RemoveDubiousFiles(dataDir string, dubiousFileName string, toRemove []DubiousFileInfo) error { filePath := filepath.Join(dataDir, dubiousFileName) + + allFiles, err := LoadDubiousFiles(dataDir, dubiousFileName) + if err != nil { + return fmt.Errorf("[storage]无法加载可疑文件记录文件%s: %w", filePath, err) + } + + toRemoveMap := make(map[string]bool) + for _, file := range toRemove { + toRemoveMap[file.Path] = true + } + + var toKeep []DubiousFileInfo + for _, file := range allFiles { + if !toRemoveMap[file.Path] { + toKeep = append(toKeep, file) + } + } + if len(toKeep) == 0 { return os.Remove(filePath) } @@ -257,6 +308,11 @@ func RemoveDubiousFiles(dataDir string, dubiousFileName string, toKeep []Dubious writer := bufio.NewWriter(f) + header := fmt.Sprintf("# 可疑文件记录 - 最后更新: %s\n", time.Now().Format("2006-01-02 15:04:05")) + if _, err := writer.WriteString(header); err != nil { + return err + } + for _, file := range toKeep { line := fmt.Sprintf("%s:%s:%s\n", file.Path, file.Hash, file.DiscoveredAt) if _, err := writer.WriteString(line); err != nil { @@ -264,7 +320,15 @@ func RemoveDubiousFiles(dataDir string, dubiousFileName string, toKeep []Dubious } } - return writer.Flush() + if err := writer.Flush(); err != nil { + return err + } + + logger.Log.Debug("[storage]已从可疑文件列表删除条目", + zap.Int("deleted", len(toRemove)), + zap.Int("remaining", len(toKeep))) + + return nil } func LoadDubiousFiles(dataDir string, dubiousFileName string) ([]DubiousFileInfo, error) {