diff --git a/Common/i18n/messages/plugins.go b/Common/i18n/messages/plugins.go index deb8ff9..2accd71 100644 --- a/Common/i18n/messages/plugins.go +++ b/Common/i18n/messages/plugins.go @@ -650,4 +650,36 @@ var PluginMessages = map[string]map[string]string{ LangZH: "LDAP认证失败: %v", LangEN: "LDAP authentication failed: %v", }, + + // ========================= Memcached插件消息 ========================= + "memcached_unauth_access": { + LangZH: "Memcached服务 %s 未授权访问成功", + LangEN: "Memcached service %s unauthorized access successful", + }, + "memcached_service_identified": { + LangZH: "Memcached服务识别成功: %s - %s", + LangEN: "Memcached service identified: %s - %s", + }, + "memcached_connection_failed": { + LangZH: "Memcached连接失败: %v", + LangEN: "Memcached connection failed: %v", + }, + + // ========================= Modbus插件消息 ========================= + "modbus_unauth_access": { + LangZH: "Modbus服务 %s 无认证访问成功", + LangEN: "Modbus service %s unauthorized access successful", + }, + "modbus_device_info": { + LangZH: "设备信息: %s", + LangEN: "Device info: %s", + }, + "modbus_service_identified": { + LangZH: "Modbus服务识别成功: %s - %s", + LangEN: "Modbus service identified: %s - %s", + }, + "modbus_connection_failed": { + LangZH: "Modbus连接失败: %v", + LangEN: "Modbus connection failed: %v", + }, } \ No newline at end of file diff --git a/Core/Registry.go b/Core/Registry.go index 57b346d..90dcd84 100644 --- a/Core/Registry.go +++ b/Core/Registry.go @@ -12,6 +12,8 @@ import ( _ "github.com/shadow1ng/fscan/plugins/services/imap" _ "github.com/shadow1ng/fscan/plugins/services/kafka" _ "github.com/shadow1ng/fscan/plugins/services/ldap" + _ "github.com/shadow1ng/fscan/plugins/services/memcached" + _ "github.com/shadow1ng/fscan/plugins/services/modbus" _ "github.com/shadow1ng/fscan/plugins/services/mysql" _ "github.com/shadow1ng/fscan/plugins/services/redis" _ "github.com/shadow1ng/fscan/plugins/services/ssh" diff --git a/Plugins/services/memcached/connector.go b/Plugins/services/memcached/connector.go new file mode 100644 index 0000000..3971955 --- /dev/null +++ b/Plugins/services/memcached/connector.go @@ -0,0 +1,109 @@ +package memcached + +import ( + "context" + "fmt" + "net" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" +) + +// MemcachedConnection Memcached连接包装器 +type MemcachedConnection struct { + client net.Conn + target string +} + +// MemcachedConnector Memcached连接器实现 +type MemcachedConnector struct { + host string + port string +} + +// NewMemcachedConnector 创建Memcached连接器 +func NewMemcachedConnector() *MemcachedConnector { + return &MemcachedConnector{} +} + +// Connect 连接到Memcached服务 +func (c *MemcachedConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) { + c.host = info.Host + c.port = info.Ports + + target := fmt.Sprintf("%s:%s", c.host, c.port) + timeout := time.Duration(common.Timeout) * time.Second + + // 建立TCP连接 + client, err := common.WrapperTcpWithTimeout("tcp", target, timeout) + if err != nil { + return nil, fmt.Errorf("Memcached连接失败: %v", err) + } + + return &MemcachedConnection{ + client: client, + target: target, + }, nil +} + +// Authenticate 认证 - Memcached通常无认证,检查未授权访问 +func (c *MemcachedConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error { + memcachedConn, ok := conn.(*MemcachedConnection) + if !ok { + return fmt.Errorf("无效的连接类型") + } + + // 在goroutine中进行操作,支持Context取消 + resultChan := make(chan error, 1) + + go func() { + // 设置操作超时 + timeout := time.Duration(common.Timeout) * time.Second + if err := memcachedConn.client.SetDeadline(time.Now().Add(timeout)); err != nil { + resultChan <- fmt.Errorf("设置超时失败: %v", err) + return + } + + // 发送stats命令测试连接 + if _, err := memcachedConn.client.Write([]byte("stats\n")); err != nil { + resultChan <- fmt.Errorf("发送命令失败: %v", err) + return + } + + // 读取响应 + buffer := make([]byte, 1024) + n, err := memcachedConn.client.Read(buffer) + if err != nil { + resultChan <- fmt.Errorf("读取响应失败: %v", err) + return + } + + // 检查响应是否包含Memcached统计信息 + response := string(buffer[:n]) + if strings.Contains(response, "STAT") { + // 未授权访问成功 + resultChan <- nil + return + } + + resultChan <- fmt.Errorf("Memcached服务无响应或不可访问") + }() + + // 等待操作结果或Context取消 + select { + case err := <-resultChan: + return err + case <-ctx.Done(): + return fmt.Errorf("Memcached操作超时: %v", ctx.Err()) + } +} + +// Close 关闭连接 +func (c *MemcachedConnector) Close(conn interface{}) error { + if memcachedConn, ok := conn.(*MemcachedConnection); ok && memcachedConn.client != nil { + return memcachedConn.client.Close() + } + return nil +} \ No newline at end of file diff --git a/Plugins/services/memcached/exploiter.go b/Plugins/services/memcached/exploiter.go new file mode 100644 index 0000000..96246a3 --- /dev/null +++ b/Plugins/services/memcached/exploiter.go @@ -0,0 +1,36 @@ +package memcached + +import ( + "context" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" +) + +// MemcachedExploiter Memcached利用器实现 - 最小化版本,不提供利用功能 +type MemcachedExploiter struct { + *base.BaseExploiter +} + +// NewMemcachedExploiter 创建Memcached利用器 +func NewMemcachedExploiter() *MemcachedExploiter { + exploiter := &MemcachedExploiter{ + BaseExploiter: base.NewBaseExploiter("memcached"), + } + + // Memcached插件不提供利用功能 + exploiter.setupExploitMethods() + + return exploiter +} + +// setupExploitMethods 设置利用方法 +func (e *MemcachedExploiter) setupExploitMethods() { + // Memcached插件不提供利用功能,仅进行未授权访问检测 +} + +// Exploit 利用接口实现 - 空实现 +func (e *MemcachedExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + // Memcached插件不提供利用功能 + return nil, nil +} \ No newline at end of file diff --git a/Plugins/services/memcached/plugin.go b/Plugins/services/memcached/plugin.go new file mode 100644 index 0000000..8873655 --- /dev/null +++ b/Plugins/services/memcached/plugin.go @@ -0,0 +1,229 @@ +package memcached + +import ( + "context" + "fmt" + "net" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/plugins/base" +) + +// MemcachedPlugin Memcached插件实现 +type MemcachedPlugin struct { + *base.ServicePlugin + exploiter *MemcachedExploiter +} + +// NewMemcachedPlugin 创建Memcached插件 +func NewMemcachedPlugin() *MemcachedPlugin { + // 插件元数据 + metadata := &base.PluginMetadata{ + Name: "memcached", + Version: "2.0.0", + Author: "fscan-team", + Description: "Memcached分布式内存缓存系统扫描和利用插件", + Category: "service", + Ports: []int{11211}, // 默认Memcached端口 + Protocols: []string{"tcp"}, + Tags: []string{"memcached", "cache", "unauthorized"}, + } + + // 创建连接器和服务插件 + connector := NewMemcachedConnector() + servicePlugin := base.NewServicePlugin(metadata, connector) + + // 创建Memcached插件 + plugin := &MemcachedPlugin{ + ServicePlugin: servicePlugin, + exploiter: NewMemcachedExploiter(), + } + + // 设置能力 + plugin.SetCapabilities([]base.Capability{ + base.CapUnauthorized, + base.CapDataExtraction, + }) + + return plugin +} + +// Scan 重写扫描方法,检测未授权访问 +func (p *MemcachedPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + // 如果禁用了暴力破解,只进行服务识别 + if common.DisableBrute { + return p.performServiceIdentification(ctx, info) + } + + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // Memcached通常无认证,直接检查未授权访问 + unauthCred := &base.Credential{Username: "", Password: ""} + result, err := p.ScanCredential(ctx, info, unauthCred) + if err == nil && result.Success { + // 未授权访问成功 + common.LogSuccess(i18n.GetText("memcached_unauth_access", target)) + + return &base.ScanResult{ + Success: true, + Service: "Memcached", + Credentials: []*base.Credential{unauthCred}, + Extra: map[string]interface{}{ + "service": "Memcached", + "port": info.Ports, + "unauthorized": true, + "access_type": "no_authentication", + }, + }, nil + } + + // 如果未授权访问失败,返回失败结果 + return &base.ScanResult{ + Success: false, + Error: fmt.Errorf("Memcached服务不可访问或需要认证"), + }, nil +} + +// generateCredentials Memcached通常无需凭据,返回空凭据 +func (p *MemcachedPlugin) generateCredentials() []*base.Credential { + // Memcached通常无认证机制,只返回空凭据用于未授权访问检测 + return []*base.Credential{ + {Username: "", Password: ""}, + } +} + +// Exploit 使用exploiter执行利用 +func (p *MemcachedPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + return p.exploiter.Exploit(ctx, info, creds) +} + +// GetExploitMethods 获取利用方法 +func (p *MemcachedPlugin) GetExploitMethods() []base.ExploitMethod { + return p.exploiter.GetExploitMethods() +} + +// IsExploitSupported 检查利用支持 +func (p *MemcachedPlugin) IsExploitSupported(method base.ExploitType) bool { + return p.exploiter.IsExploitSupported(method) +} + +// performServiceIdentification 执行Memcached服务识别(-nobr模式) +func (p *MemcachedPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 尝试连接Memcached服务获取基本信息 + memcachedInfo, isMemcached := p.identifyMemcachedService(ctx, info) + if isMemcached { + // 记录服务识别成功 + common.LogSuccess(i18n.GetText("memcached_service_identified", target, memcachedInfo)) + + return &base.ScanResult{ + Success: true, + Service: "Memcached", + Banner: memcachedInfo, + Extra: map[string]interface{}{ + "service": "Memcached", + "port": info.Ports, + "info": memcachedInfo, + }, + }, nil + } + + // 如果无法识别为Memcached,返回失败 + return &base.ScanResult{ + Success: false, + Error: fmt.Errorf("无法识别为Memcached服务"), + }, nil +} + +// identifyMemcachedService 通过连接识别Memcached服务 +func (p *MemcachedPlugin) identifyMemcachedService(ctx context.Context, info *common.HostInfo) (string, bool) { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + timeout := time.Duration(common.Timeout) * time.Second + + // 尝试建立TCP连接 + conn, err := net.DialTimeout("tcp", target, timeout) + if err != nil { + return "", false + } + defer conn.Close() + + // 设置操作超时 + conn.SetDeadline(time.Now().Add(timeout)) + + // 发送version命令获取版本信息 + if _, err := conn.Write([]byte("version\n")); err != nil { + return "", false + } + + // 读取响应 + buffer := make([]byte, 512) + n, err := conn.Read(buffer) + if err != nil { + return "", false + } + + response := strings.TrimSpace(string(buffer[:n])) + + // 检查是否是Memcached版本响应 + if strings.HasPrefix(response, "VERSION") { + // 提取版本信息 + parts := strings.Fields(response) + if len(parts) >= 2 { + return fmt.Sprintf("Memcached %s", parts[1]), true + } + return "Memcached服务", true + } + + // 尝试stats命令进行二次确认 + if _, err := conn.Write([]byte("stats\n")); err != nil { + return "", false + } + + n, err = conn.Read(buffer) + if err != nil { + return "", false + } + + response = string(buffer[:n]) + + // 检查是否包含STAT关键字 + if strings.Contains(response, "STAT") { + return "Memcached服务", true + } + + return "", false +} + +// ============================================================================= +// 插件注册 +// ============================================================================= + +// RegisterMemcachedPlugin 注册Memcached插件 +func RegisterMemcachedPlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "memcached", + Version: "2.0.0", + Author: "fscan-team", + Description: "Memcached分布式内存缓存系统扫描和利用插件", + Category: "service", + Ports: []int{11211}, // 默认Memcached端口 + Protocols: []string{"tcp"}, + Tags: []string{"memcached", "cache", "unauthorized"}, + }, + func() base.Plugin { + return NewMemcachedPlugin() + }, + ) + + base.GlobalPluginRegistry.Register("memcached", factory) +} + +// 自动注册 +func init() { + RegisterMemcachedPlugin() +} \ No newline at end of file diff --git a/Plugins/services/modbus/connector.go b/Plugins/services/modbus/connector.go new file mode 100644 index 0000000..460e20f --- /dev/null +++ b/Plugins/services/modbus/connector.go @@ -0,0 +1,160 @@ +package modbus + +import ( + "context" + "encoding/binary" + "fmt" + "net" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" +) + +// ModbusConnection Modbus连接包装器 +type ModbusConnection struct { + client net.Conn + target string + deviceID uint8 +} + +// ModbusConnector Modbus连接器实现 +type ModbusConnector struct { + host string + port string +} + +// NewModbusConnector 创建Modbus连接器 +func NewModbusConnector() *ModbusConnector { + return &ModbusConnector{} +} + +// Connect 连接到Modbus服务 +func (c *ModbusConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) { + c.host = info.Host + c.port = info.Ports + + target := fmt.Sprintf("%s:%s", c.host, c.port) + timeout := time.Duration(common.Timeout) * time.Second + + // 建立TCP连接 + conn, err := net.DialTimeout("tcp", target, timeout) + if err != nil { + return nil, fmt.Errorf("Modbus连接失败: %v", err) + } + + return &ModbusConnection{ + client: conn, + target: target, + }, nil +} + +// Authenticate 认证 - Modbus通常无认证,检查协议响应 +func (c *ModbusConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error { + modbusConn, ok := conn.(*ModbusConnection) + if !ok { + return fmt.Errorf("无效的连接类型") + } + + // 在goroutine中进行操作,支持Context取消 + resultChan := make(chan error, 1) + + go func() { + // 设置操作超时 + timeout := time.Duration(common.Timeout) * time.Second + if err := modbusConn.client.SetDeadline(time.Now().Add(timeout)); err != nil { + resultChan <- fmt.Errorf("设置超时失败: %v", err) + return + } + + // 构建Modbus TCP请求包 - 读取设备ID + request := buildModbusReadCoilsRequest() + + // 发送请求 + if _, err := modbusConn.client.Write(request); err != nil { + resultChan <- fmt.Errorf("发送Modbus请求失败: %v", err) + return + } + + // 读取响应 + response := make([]byte, 256) + n, err := modbusConn.client.Read(response) + if err != nil { + resultChan <- fmt.Errorf("读取Modbus响应失败: %v", err) + return + } + + // 验证是否为有效Modbus响应 + if isValidModbusResponse(response[:n]) { + // 提取设备ID + if len(response) >= 7 { + modbusConn.deviceID = response[6] // Unit ID + } + resultChan <- nil + return + } + + resultChan <- fmt.Errorf("非Modbus服务或访问被拒绝") + }() + + // 等待操作结果或Context取消 + select { + case err := <-resultChan: + return err + case <-ctx.Done(): + return fmt.Errorf("Modbus操作超时: %v", ctx.Err()) + } +} + +// Close 关闭连接 +func (c *ModbusConnector) Close(conn interface{}) error { + if modbusConn, ok := conn.(*ModbusConnection); ok && modbusConn.client != nil { + return modbusConn.client.Close() + } + return nil +} + +// buildModbusReadCoilsRequest 构建Modbus TCP读取线圈请求包 +func buildModbusReadCoilsRequest() []byte { + request := make([]byte, 12) + + // Modbus TCP头部 + binary.BigEndian.PutUint16(request[0:], 0x0001) // 事务标识符 + binary.BigEndian.PutUint16(request[2:], 0x0000) // 协议标识符 + binary.BigEndian.PutUint16(request[4:], 0x0006) // 长度 + request[6] = 0x01 // 单元标识符 + + // Modbus 功能码和数据 + request[7] = 0x01 // 功能码: Read Coils + binary.BigEndian.PutUint16(request[8:], 0x0000) // 起始地址 + binary.BigEndian.PutUint16(request[10:], 0x0001) // 读取数量 + + return request +} + +// isValidModbusResponse 验证Modbus响应是否有效 +func isValidModbusResponse(response []byte) bool { + if len(response) < 8 { + return false + } + + // 检查协议标识符 (应为0) + protocolID := binary.BigEndian.Uint16(response[2:]) + if protocolID != 0 { + return false + } + + // 检查功能码是否为错误响应 + funcCode := response[7] + if funcCode >= 0x80 { // 错误响应的功能码都大于等于0x80 + return false + } + + // 基本长度检查 + length := binary.BigEndian.Uint16(response[4:]) + if int(length) != len(response)-6 { + return false + } + + return true +} \ No newline at end of file diff --git a/Plugins/services/modbus/exploiter.go b/Plugins/services/modbus/exploiter.go new file mode 100644 index 0000000..912837f --- /dev/null +++ b/Plugins/services/modbus/exploiter.go @@ -0,0 +1,36 @@ +package modbus + +import ( + "context" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" +) + +// ModbusExploiter Modbus利用器实现 - 最小化版本,不提供利用功能 +type ModbusExploiter struct { + *base.BaseExploiter +} + +// NewModbusExploiter 创建Modbus利用器 +func NewModbusExploiter() *ModbusExploiter { + exploiter := &ModbusExploiter{ + BaseExploiter: base.NewBaseExploiter("modbus"), + } + + // Modbus插件不提供利用功能 + exploiter.setupExploitMethods() + + return exploiter +} + +// setupExploitMethods 设置利用方法 +func (e *ModbusExploiter) setupExploitMethods() { + // Modbus插件不提供利用功能,仅进行协议识别和未授权访问检测 +} + +// Exploit 利用接口实现 - 空实现 +func (e *ModbusExploiter) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + // Modbus插件不提供利用功能 + return nil, nil +} \ No newline at end of file diff --git a/Plugins/services/modbus/plugin.go b/Plugins/services/modbus/plugin.go new file mode 100644 index 0000000..9f5c55c --- /dev/null +++ b/Plugins/services/modbus/plugin.go @@ -0,0 +1,297 @@ +package modbus + +import ( + "context" + "encoding/binary" + "fmt" + "net" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/common/i18n" + "github.com/shadow1ng/fscan/plugins/base" +) + +// ModbusPlugin Modbus插件实现 +type ModbusPlugin struct { + *base.ServicePlugin + exploiter *ModbusExploiter +} + +// NewModbusPlugin 创建Modbus插件 +func NewModbusPlugin() *ModbusPlugin { + // 插件元数据 + metadata := &base.PluginMetadata{ + Name: "modbus", + Version: "2.0.0", + Author: "fscan-team", + Description: "Modbus工业协议扫描和利用插件", + Category: "service", + Ports: []int{502, 5020}, // 502: 标准Modbus TCP端口, 5020: 测试端口 + Protocols: []string{"tcp"}, + Tags: []string{"modbus", "industrial", "scada", "unauthorized"}, + } + + // 创建连接器和服务插件 + connector := NewModbusConnector() + servicePlugin := base.NewServicePlugin(metadata, connector) + + // 创建Modbus插件 + plugin := &ModbusPlugin{ + ServicePlugin: servicePlugin, + exploiter: NewModbusExploiter(), + } + + // 设置能力 + plugin.SetCapabilities([]base.Capability{ + base.CapUnauthorized, + base.CapDataExtraction, + }) + + return plugin +} + +// Scan 重写扫描方法,检测Modbus协议和未授权访问 +func (p *ModbusPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + // 如果禁用了暴力破解,只进行服务识别 + if common.DisableBrute { + return p.performServiceIdentification(ctx, info) + } + + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // Modbus协议通常无认证,直接检查协议响应 + unauthCred := &base.Credential{Username: "", Password: ""} + result, err := p.ScanCredential(ctx, info, unauthCred) + if err == nil && result.Success { + // 获取设备信息 + deviceInfo := p.getDeviceInfo(ctx, info) + + // 未授权访问成功 + common.LogSuccess(i18n.GetText("modbus_unauth_access", target)) + if deviceInfo != "" { + common.LogSuccess(i18n.GetText("modbus_device_info", deviceInfo)) + } + + return &base.ScanResult{ + Success: true, + Service: "Modbus", + Credentials: []*base.Credential{unauthCred}, + Extra: map[string]interface{}{ + "service": "Modbus", + "port": info.Ports, + "unauthorized": true, + "access_type": "no_authentication", + "device_info": deviceInfo, + }, + }, nil + } + + // 如果Modbus协议检测失败,返回失败结果 + return &base.ScanResult{ + Success: false, + Error: fmt.Errorf("非Modbus服务或连接失败"), + }, nil +} + +// generateCredentials Modbus通常无需凭据,返回空凭据 +func (p *ModbusPlugin) generateCredentials() []*base.Credential { + // Modbus协议通常无认证机制,只返回空凭据用于协议检测 + return []*base.Credential{ + {Username: "", Password: ""}, + } +} + +// getDeviceInfo 获取Modbus设备信息 +func (p *ModbusPlugin) getDeviceInfo(ctx context.Context, info *common.HostInfo) string { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + timeout := time.Duration(common.Timeout) * time.Second + + // 尝试建立连接 + conn, err := net.DialTimeout("tcp", target, timeout) + if err != nil { + return "" + } + defer conn.Close() + + // 设置超时 + conn.SetDeadline(time.Now().Add(timeout)) + + // 发送读取设备标识请求 (功能码0x11) + request := buildModbusDeviceIdentifyRequest() + if _, err := conn.Write(request); err != nil { + return "" + } + + // 读取响应 + response := make([]byte, 256) + n, err := conn.Read(response) + if err != nil { + return "" + } + + return parseDeviceInfo(response[:n]) +} + +// buildModbusDeviceIdentifyRequest 构建Modbus设备标识请求 +func buildModbusDeviceIdentifyRequest() []byte { + request := make([]byte, 8) + + // Modbus TCP头部 + binary.BigEndian.PutUint16(request[0:], 0x0002) // 事务标识符 + binary.BigEndian.PutUint16(request[2:], 0x0000) // 协议标识符 + binary.BigEndian.PutUint16(request[4:], 0x0002) // 长度 + request[6] = 0x01 // 单元标识符 + request[7] = 0x11 // 功能码: Report Server ID + + return request +} + +// parseDeviceInfo 解析设备信息 +func parseDeviceInfo(response []byte) string { + if len(response) < 8 { + return "Unknown Device" + } + + // 检查是否为有效响应 + if !isValidModbusResponse(response) { + return "Unknown Device" + } + + unitID := response[6] + funcCode := response[7] + + info := fmt.Sprintf("Unit ID: %d, Function: 0x%02X", unitID, funcCode) + + // 如果是设备标识响应,尝试解析设备信息 + if funcCode == 0x11 && len(response) > 9 { + byteCount := response[8] + if byteCount > 0 && len(response) >= 9+int(byteCount) { + // 提取设备ID信息 + deviceData := response[9 : 9+int(byteCount)] + if len(deviceData) > 0 { + info += fmt.Sprintf(", Device Data: %x", deviceData) + } + } + } else if funcCode == 0x01 && len(response) >= 10 { + // 读取线圈响应,显示线圈状态 + byteCount := response[8] + if byteCount > 0 && len(response) >= 9+int(byteCount) { + coilValue := response[9] & 0x01 + info += fmt.Sprintf(", Coil Status: %d", coilValue) + } + } + + return info +} + +// Exploit 使用exploiter执行利用 +func (p *ModbusPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) { + return p.exploiter.Exploit(ctx, info, creds) +} + +// GetExploitMethods 获取利用方法 +func (p *ModbusPlugin) GetExploitMethods() []base.ExploitMethod { + return p.exploiter.GetExploitMethods() +} + +// IsExploitSupported 检查利用支持 +func (p *ModbusPlugin) IsExploitSupported(method base.ExploitType) bool { + return p.exploiter.IsExploitSupported(method) +} + +// performServiceIdentification 执行Modbus服务识别(-nobr模式) +func (p *ModbusPlugin) performServiceIdentification(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + + // 尝试识别Modbus服务 + modbusInfo, isModbus := p.identifyModbusService(ctx, info) + if isModbus { + // 记录服务识别成功 + common.LogSuccess(i18n.GetText("modbus_service_identified", target, modbusInfo)) + + return &base.ScanResult{ + Success: true, + Service: "Modbus", + Banner: modbusInfo, + Extra: map[string]interface{}{ + "service": "Modbus", + "port": info.Ports, + "info": modbusInfo, + }, + }, nil + } + + // 如果无法识别为Modbus,返回失败 + return &base.ScanResult{ + Success: false, + Error: fmt.Errorf("无法识别为Modbus服务"), + }, nil +} + +// identifyModbusService 通过协议识别Modbus服务 +func (p *ModbusPlugin) identifyModbusService(ctx context.Context, info *common.HostInfo) (string, bool) { + target := fmt.Sprintf("%s:%s", info.Host, info.Ports) + timeout := time.Duration(common.Timeout) * time.Second + + // 尝试建立TCP连接 + conn, err := net.DialTimeout("tcp", target, timeout) + if err != nil { + return "", false + } + defer conn.Close() + + // 设置操作超时 + conn.SetDeadline(time.Now().Add(timeout)) + + // 发送Modbus读取线圈请求 + request := buildModbusReadCoilsRequest() + if _, err := conn.Write(request); err != nil { + return "", false + } + + // 读取响应 + response := make([]byte, 256) + n, err := conn.Read(response) + if err != nil { + return "", false + } + + // 检查是否为有效Modbus响应 + if isValidModbusResponse(response[:n]) { + deviceInfo := parseDeviceInfo(response[:n]) + return fmt.Sprintf("Modbus TCP服务: %s", deviceInfo), true + } + + return "", false +} + +// ============================================================================= +// 插件注册 +// ============================================================================= + +// RegisterModbusPlugin 注册Modbus插件 +func RegisterModbusPlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "modbus", + Version: "2.0.0", + Author: "fscan-team", + Description: "Modbus工业协议扫描和利用插件", + Category: "service", + Ports: []int{502, 5020}, // 502: 标准Modbus TCP端口, 5020: 测试端口 + Protocols: []string{"tcp"}, + Tags: []string{"modbus", "industrial", "scada", "unauthorized"}, + }, + func() base.Plugin { + return NewModbusPlugin() + }, + ) + + base.GlobalPluginRegistry.Register("modbus", factory) +} + +// 自动注册 +func init() { + RegisterModbusPlugin() +} \ No newline at end of file