mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00

- 创建通用SMB框架,包含抽象接口、凭据管理和并发扫描引擎 - SMB2插件代码量从492行减少到159行,减少68%代码量 - 统一错误分类和处理机制,提高代码可维护性 - 支持密码和哈希两种认证方式,保持向后兼容性 - 模块化设计便于单元测试和功能扩展
198 lines
4.4 KiB
Go
198 lines
4.4 KiB
Go
package common
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/shadow1ng/fscan/common"
|
|
)
|
|
|
|
// DefaultScanner 默认并发扫描器实现
|
|
type DefaultScanner struct{}
|
|
|
|
// NewScanner 创建新的扫描器
|
|
func NewScanner() *DefaultScanner {
|
|
return &DefaultScanner{}
|
|
}
|
|
|
|
// Scan 执行并发扫描
|
|
func (s *DefaultScanner) Scan(ctx context.Context, target *TargetInfo,
|
|
connector SmbConnector, credMgr CredentialManager, config *ScanConfig) (*ScanResult, error) {
|
|
|
|
credentials := credMgr.GenerateCredentials()
|
|
if len(credentials) == 0 {
|
|
return nil, fmt.Errorf("没有可用的认证凭据")
|
|
}
|
|
|
|
common.LogDebug(fmt.Sprintf("开始%s扫描 %s:%d (总用户数: %d, 总组合数: %d)",
|
|
connector.GetProtocolName(), target.Host, target.Port,
|
|
len(common.Userdict["smb"]), len(credentials)))
|
|
|
|
// 确定并发数
|
|
maxConcurrent := config.MaxConcurrent
|
|
if maxConcurrent <= 0 {
|
|
maxConcurrent = 10
|
|
}
|
|
if maxConcurrent > len(credentials) {
|
|
maxConcurrent = len(credentials)
|
|
}
|
|
|
|
// 创建工作池
|
|
var wg sync.WaitGroup
|
|
resultChan := make(chan *ScanResult, 1)
|
|
workChan := make(chan Credential, maxConcurrent)
|
|
scanCtx, scanCancel := context.WithCancel(ctx)
|
|
defer scanCancel()
|
|
|
|
// 启动工作协程
|
|
for i := 0; i < maxConcurrent; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for cred := range workChan {
|
|
select {
|
|
case <-scanCtx.Done():
|
|
return
|
|
default:
|
|
// 检查用户是否已锁定
|
|
if credMgr.IsUserLocked(cred.Username) {
|
|
common.LogDebug(fmt.Sprintf("跳过已锁定用户: %s", cred.Username))
|
|
continue
|
|
}
|
|
|
|
// 尝试连接
|
|
result := s.tryCredential(scanCtx, target, connector, cred, config.Timeout)
|
|
|
|
// 处理认证失败
|
|
if !result.Success && result.Error != nil {
|
|
credMgr.HandleAuthFailure(cred.Username, result.Error)
|
|
}
|
|
|
|
// 如果成功,发送结果并取消其他工作
|
|
if result.Success {
|
|
select {
|
|
case resultChan <- result:
|
|
scanCancel()
|
|
default:
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// 发送工作
|
|
go func() {
|
|
defer close(workChan)
|
|
for i, cred := range credentials {
|
|
select {
|
|
case <-scanCtx.Done():
|
|
return
|
|
default:
|
|
// 跳过已锁定用户
|
|
if credMgr.IsUserLocked(cred.Username) {
|
|
continue
|
|
}
|
|
|
|
// 记录尝试日志
|
|
s.logAttempt(connector.GetProtocolName(), i+1, len(credentials), cred)
|
|
|
|
workChan <- cred
|
|
}
|
|
}
|
|
}()
|
|
|
|
// 等待结果或完成
|
|
go func() {
|
|
wg.Wait()
|
|
close(resultChan)
|
|
}()
|
|
|
|
// 获取结果
|
|
select {
|
|
case result, ok := <-resultChan:
|
|
if ok && result != nil && result.Success {
|
|
return result, nil
|
|
}
|
|
return nil, nil
|
|
case <-ctx.Done():
|
|
common.LogDebug(fmt.Sprintf("%s扫描全局超时", connector.GetProtocolName()))
|
|
scanCancel()
|
|
return nil, fmt.Errorf("全局超时")
|
|
}
|
|
}
|
|
|
|
// tryCredential 尝试单个凭据
|
|
func (s *DefaultScanner) tryCredential(ctx context.Context, target *TargetInfo,
|
|
connector SmbConnector, cred Credential, timeout time.Duration) *ScanResult {
|
|
|
|
// 创建连接超时上下文
|
|
connCtx, cancel := context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
|
|
// 在协程中尝试连接
|
|
resultChan := make(chan *ConnectionResult, 1)
|
|
go func() {
|
|
result, err := connector.Connect(connCtx, target, &cred)
|
|
if err != nil {
|
|
result = &ConnectionResult{
|
|
Success: false,
|
|
Error: err,
|
|
}
|
|
}
|
|
|
|
select {
|
|
case <-connCtx.Done():
|
|
case resultChan <- result:
|
|
}
|
|
}()
|
|
|
|
// 等待结果或超时
|
|
select {
|
|
case result := <-resultChan:
|
|
if result.Success {
|
|
return &ScanResult{
|
|
Success: true,
|
|
Credential: cred,
|
|
Shares: result.Shares,
|
|
}
|
|
}
|
|
|
|
// 分类错误
|
|
classifiedError := ClassifySmbError(result.Error)
|
|
return &ScanResult{
|
|
Success: false,
|
|
Error: classifiedError,
|
|
Credential: cred,
|
|
Shares: result.Shares,
|
|
}
|
|
|
|
case <-connCtx.Done():
|
|
var err error
|
|
if ctx.Err() != nil {
|
|
err = ctx.Err() // 全局超时
|
|
} else {
|
|
err = ErrTimeout // 连接超时
|
|
}
|
|
|
|
return &ScanResult{
|
|
Success: false,
|
|
Error: err,
|
|
Credential: cred,
|
|
}
|
|
}
|
|
}
|
|
|
|
// logAttempt 记录尝试日志
|
|
func (s *DefaultScanner) logAttempt(protocol string, current, total int, cred Credential) {
|
|
if cred.IsHash {
|
|
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试%s: %s Hash:%s",
|
|
current, total, protocol, cred.Username, common.HashValue))
|
|
} else {
|
|
common.LogDebug(fmt.Sprintf("[%d/%d] 尝试%s: %s:%s",
|
|
current, total, protocol, cred.Username, cred.Password))
|
|
}
|
|
} |