Files
fscan/plugins/services/smb_protocol.go
T
2026-05-23 15:18:40 +08:00

958 lines
25 KiB
Go

//go:build plugin_smb || !plugin_selective
package services
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"io"
iofs "io/fs"
"net"
"strconv"
"strings"
"time"
"github.com/hirochachacha/go-smb2"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/stacktitan/smb/smb"
)
// SMBProtocol SMB协议版本
type SMBProtocol int
const (
SMBProtocolUnknown SMBProtocol = iota
SMBProtocol1
SMBProtocol2
)
func (p SMBProtocol) String() string {
switch p {
case SMBProtocol1:
return "SMBv1"
case SMBProtocol2:
return "SMBv2"
default:
return "Unknown"
}
}
// SMBTarget 目标信息(一次探测,到处使用)
type SMBTarget struct {
Protocol SMBProtocol
ComputerName string
DomainName string
OSVersion string
NativeOS string
NativeLM string
NTLMFlags []string
Vulnerable *SMBVuln
}
// SMBVuln 漏洞信息
type SMBVuln struct {
CVE20200796 bool // SMB Ghost
}
// Summary 返回SMB信息摘要
func (t *SMBTarget) Summary() string {
var parts []string
parts = append(parts, t.Protocol.String())
if t.OSVersion != "" {
parts = append(parts, t.OSVersion)
}
if t.ComputerName != "" {
parts = append(parts, t.ComputerName)
}
return strings.Join(parts, " ")
}
// SMB协议数据包定义
var (
smbv1NegotiatePacket = []byte{
0x00, 0x00, 0x00, 0x85, 0xFF, 0x53, 0x4D, 0x42, 0x72, 0x00, 0x00, 0x00, 0x00, 0x18, 0x53, 0xC8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x02, 0x50, 0x43, 0x20, 0x4E, 0x45, 0x54, 0x57, 0x4F,
0x52, 0x4B, 0x20, 0x50, 0x52, 0x4F, 0x47, 0x52, 0x41, 0x4D, 0x20, 0x31, 0x2E, 0x30, 0x00, 0x02,
0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x31, 0x2E, 0x30, 0x00, 0x02, 0x57, 0x69, 0x6E, 0x64, 0x6F,
0x77, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x57, 0x6F, 0x72, 0x6B, 0x67, 0x72, 0x6F, 0x75, 0x70,
0x73, 0x20, 0x33, 0x2E, 0x31, 0x61, 0x00, 0x02, 0x4C, 0x4D, 0x31, 0x2E, 0x32, 0x58, 0x30, 0x30,
0x32, 0x00, 0x02, 0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x32, 0x2E, 0x31, 0x00, 0x02, 0x4E, 0x54,
0x20, 0x4C, 0x4D, 0x20, 0x30, 0x2E, 0x31, 0x32, 0x00,
}
smbv1SessionSetupPacket = []byte{
0x00, 0x00, 0x01, 0x0A, 0xFF, 0x53, 0x4D, 0x42, 0x73, 0x00, 0x00, 0x00, 0x00, 0x18, 0x07, 0xC8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE,
0x00, 0x00, 0x40, 0x00, 0x0C, 0xFF, 0x00, 0x0A, 0x01, 0x04, 0x41, 0x32, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD4, 0x00, 0x00, 0xA0, 0xCF, 0x00, 0x60,
0x48, 0x06, 0x06, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x02, 0xA0, 0x3E, 0x30, 0x3C, 0xA0, 0x0E, 0x30,
0x0C, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0A, 0xA2, 0x2A, 0x04,
0x28, 0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x82, 0x08,
0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x05, 0x02, 0xCE, 0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6E, 0x00,
0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00,
0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x33, 0x00,
0x20, 0x00, 0x33, 0x00, 0x37, 0x00, 0x39, 0x00, 0x30, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00,
0x72, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x61, 0x00,
0x63, 0x00, 0x6B, 0x00, 0x20, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x69, 0x00,
0x6E, 0x00, 0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00,
0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00,
0x33, 0x00, 0x20, 0x00, 0x35, 0x00, 0x2E, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
}
smbv2NegotiatePacket = []byte{
0x00, 0x00, 0x00, 0x45, 0xFF, 0x53, 0x4D, 0x42, 0x72, 0x00,
0x00, 0x00, 0x00, 0x18, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xAC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x02,
0x4E, 0x54, 0x20, 0x4C, 0x4D, 0x20, 0x30, 0x2E, 0x31, 0x32,
0x00, 0x02, 0x53, 0x4D, 0x42, 0x20, 0x32, 0x2E, 0x30, 0x30,
0x32, 0x00, 0x02, 0x53, 0x4D, 0x42, 0x20, 0x32, 0x2E, 0x3F,
0x3F, 0x3F, 0x00,
}
smbv2SessionSetupPacket = []byte{
0x00, 0x00, 0x00, 0x68, 0xFE, 0x53, 0x4D, 0x42, 0x40, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00,
0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x02,
}
// SMB Ghost (CVE-2020-0796) 检测数据包
smbGhostPacket = "\x00" +
"\x00\x00\xc0" +
"\xfeSMB@\x00" +
"\x00\x00" +
"\x00\x00" +
"\x00\x00" +
"\x00\x00" +
"\x1f\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"$\x00" +
"\x08\x00" +
"\x01\x00" +
"\x00\x00" +
"\x7f\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"x\x00" +
"\x00\x00" +
"\x02\x00" +
"\x00\x00" +
"\x02\x02" +
"\x10\x02" +
"\x22\x02" +
"$\x02" +
"\x00\x03" +
"\x02\x03" +
"\x10\x03" +
"\x11\x03" +
"\x00\x00\x00\x00" +
"\x01\x00" +
"&\x00" +
"\x00\x00\x00\x00" +
"\x01\x00" +
"\x20\x00" +
"\x01\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00\x00\x00" +
"\x00\x00" +
"\x03\x00" +
"\x0e\x00" +
"\x00\x00\x00\x00" +
"\x01\x00" +
"\x00\x00" +
"\x01\x00\x00\x00" +
"\x01\x00" +
"\x00\x00" +
"\x00\x00\x00\x00"
)
// probeTarget 探测目标SMB信息(协议版本、系统信息)
func probeTarget(ctx context.Context, host string, port int, timeout time.Duration, session *common.ScanSession) (*SMBTarget, error) {
target := fmt.Sprintf("%s:%d", host, port)
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
if err != nil {
return nil, fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err)
}
defer func() { _ = conn.Close() }()
_ = conn.SetDeadline(time.Now().Add(timeout))
// 首先尝试SMBv1协商
_, err = conn.Write(smbv1NegotiatePacket)
if err != nil {
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv1_negotiate_send_failed"), err)
}
// 读取SMBv1协商响应
r1, err := readSMBMessage(conn)
if err != nil {
common.LogDebug(i18n.Tr("smbv1_negotiate_read_failed", err))
}
// 检查是否支持SMBv1
if len(r1) > 0 {
return probeSMBv1(conn, target, timeout)
}
// SMBv2路径
return probeSMBv2(ctx, target, timeout, session)
}
// probeSMBv1 处理SMBv1协议信息收集
func probeSMBv1(conn net.Conn, target string, timeout time.Duration) (*SMBTarget, error) {
// 发送Session Setup请求
_, err := conn.Write(smbv1SessionSetupPacket)
if err != nil {
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("%s: %w", i18n.GetText("smbv1_session_read_failed"), err)
}
info := &SMBTarget{
Protocol: SMBProtocol1,
}
// 解析blob信息
blobLength := int(bytesToUint16(ret[43:45]))
blobCount := int(bytesToUint16(ret[45:47]))
gssNative := ret[47:]
gssLen := len(gssNative)
// 校验远端返回的偏移量
if blobLength > gssLen || blobCount > gssLen || blobLength > blobCount {
return info, nil
}
offNTLM := bytes.Index(gssNative, []byte("NTLMSSP"))
if offNTLM == -1 {
return info, nil
}
// 提取native OS和LM信息
native := gssNative[blobLength:blobCount]
ss := strings.Split(string(native), "\x00\x00")
if len(ss) > 0 {
info.NativeOS = trimSMBString(ss[0])
}
if len(ss) > 1 {
info.NativeLM = trimSMBString(ss[1])
}
// 解析NTLM信息
if offNTLM <= blobLength {
bs := gssNative[offNTLM:blobLength]
parseNTLMChallenge(bs, info)
}
return info, nil
}
// probeSMBv2 处理SMBv2协议信息收集
func probeSMBv2(ctx context.Context, target string, timeout time.Duration, session *common.ScanSession) (*SMBTarget, error) {
conn2, err := session.DialTCP(ctx, "tcp", target, timeout)
if err != nil {
return nil, fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err)
}
defer func() { _ = conn2.Close() }()
_ = conn2.SetDeadline(time.Now().Add(timeout))
// 发送SMBv2协商包
_, err = conn2.Write(smbv2NegotiatePacket)
if err != nil {
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv2_negotiate_send_failed"), err)
}
r2, err := readSMBMessage(conn2)
if err != nil {
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv2_negotiate_read_failed"), err)
}
// 构建NTLM数据包
var ntlmData []byte
if len(r2) > 70 && hex.EncodeToString(r2[70:71]) == "03" {
flags := []byte{0x15, 0x82, 0x08, 0xa0}
ntlmData = buildNTLMSSPData(flags)
} else {
flags := []byte{0x05, 0x80, 0x08, 0xa0}
ntlmData = buildNTLMSSPData(flags)
}
// 发送Session Setup
_, err = conn2.Write(smbv2SessionSetupPacket)
if err != nil {
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv2_session_send_failed"), err)
}
_, err = readSMBMessage(conn2)
if err != nil {
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("%s: %w", i18n.GetText("smbv2_ntlm_send_failed"), err)
}
ret, err := readSMBMessage(conn2)
if err != nil {
return nil, fmt.Errorf("%s: %w", i18n.GetText("smbv2_ntlm_read_failed"), err)
}
ntlmOff := bytes.Index(ret, []byte("NTLMSSP"))
if ntlmOff == -1 {
return &SMBTarget{Protocol: SMBProtocol2}, nil
}
info := &SMBTarget{
Protocol: SMBProtocol2,
}
parseNTLMChallenge(ret[ntlmOff:], info)
return info, nil
}
// checkSMBGhost 检测CVE-2020-0796漏洞
func checkSMBGhost(ctx context.Context, host string, timeout time.Duration, session *common.ScanSession) bool {
addr := fmt.Sprintf("%s:445", host)
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
if err != nil {
return false
}
defer func() { _ = conn.Close() }()
if err = conn.SetDeadline(time.Now().Add(timeout)); err != nil {
return false
}
if _, err = conn.Write([]byte(smbGhostPacket)); err != nil {
return false
}
buff := make([]byte, 1024)
n, err := conn.Read(buff)
if err != nil || n == 0 {
return false
}
// 检测CVE-2020-0796特征
if bytes.Contains(buff[:n], []byte("Public")) &&
len(buff[:n]) >= 76 &&
bytes.Equal(buff[72:74], []byte{0x11, 0x03}) &&
bytes.Equal(buff[74:76], []byte{0x02, 0x00}) {
return true
}
return false
}
// SMBAuthenticator 统一认证接口
type SMBAuthenticator interface {
Authenticate(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) (*AuthResult, error)
ListShares(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) ([]string, error)
}
// SMB1Authenticator SMB1认证器
type SMB1Authenticator struct{}
// Authenticate 执行SMB1认证
func (a *SMB1Authenticator) Authenticate(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) (*AuthResult, error) {
options := smb.Options{
Host: host,
Port: port,
User: cred.Username,
Password: cred.Password,
Domain: domain,
Workstation: "",
}
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
resultChan := make(chan *AuthResult, 1)
go func() {
session, err := smb.NewSession(options, false)
if err != nil {
resultChan <- &AuthResult{
Success: false,
ErrorType: classifySMBError(err),
Error: err,
}
return
}
if session.IsAuthenticated {
resultChan <- &AuthResult{
Success: true,
Conn: &smb1SessionWrapper{session},
ErrorType: ErrorTypeUnknown,
Error: nil,
}
} else {
session.Close()
resultChan <- &AuthResult{
Success: false,
ErrorType: ErrorTypeAuth,
Error: fmt.Errorf("%s", i18n.GetText("service_auth_failed")),
}
}
}()
select {
case result := <-resultChan:
return result, nil
case <-timeoutCtx.Done():
go func() {
result := <-resultChan
if result != nil && result.Conn != nil {
_ = result.Conn.Close()
}
}()
return &AuthResult{
Success: false,
ErrorType: ErrorTypeNetwork,
Error: fmt.Errorf("%s", i18n.GetText("connection_timeout")),
}, nil
case <-ctx.Done():
go func() {
result := <-resultChan
if result != nil && result.Conn != nil {
_ = result.Conn.Close()
}
}()
return &AuthResult{
Success: false,
ErrorType: ErrorTypeNetwork,
Error: ctx.Err(),
}, nil
}
}
// ListShares 列举SMB共享(SMB1使用SMB2库列举)
func (a *SMB1Authenticator) ListShares(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) ([]string, error) {
return listSMBSharesInternal(ctx, host, port, cred, domain, timeout, session)
}
// SMB2Authenticator SMB2认证器
type SMB2Authenticator struct{}
// Authenticate 执行SMB2认证
func (a *SMB2Authenticator) Authenticate(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) (*AuthResult, error) {
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
conn, err := session.DialTCP(ctx, "tcp", fmt.Sprintf("%s:%d", host, port), timeout)
if err != nil {
return &AuthResult{
Success: false,
ErrorType: classifySMBError(err),
Error: err,
}, nil
}
d := &smb2.Dialer{
Initiator: &smb2.NTLMInitiator{
User: cred.Username,
Password: cred.Password,
Domain: domain,
},
}
s, err := d.DialContext(timeoutCtx, conn)
if err != nil {
_ = conn.Close()
return &AuthResult{
Success: false,
ErrorType: classifySMBError(err),
Error: fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err),
}, nil
}
// 尝试列举共享来验证认证成功
_, _ = s.ListSharenames()
return &AuthResult{
Success: true,
Conn: &smb2SessionWrapper{s, conn},
ErrorType: ErrorTypeUnknown,
Error: nil,
}, nil
}
// ListShares 列举SMB2共享
func (a *SMB2Authenticator) ListShares(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) ([]string, error) {
return listSMBSharesInternal(ctx, host, port, cred, domain, timeout, session)
}
// listSMBSharesInternal 内部共享列举实现
func listSMBSharesInternal(ctx context.Context, host string, port int, cred Credential, domain string, timeout time.Duration, session *common.ScanSession) ([]string, error) {
target := net.JoinHostPort(host, strconv.Itoa(port))
conn, err := session.DialTCP(ctx, "tcp", target, timeout*2)
if err != nil {
return nil, err
}
defer func() { _ = conn.Close() }()
d := &smb2.Dialer{
Initiator: &smb2.NTLMInitiator{
User: cred.Username,
Password: cred.Password,
Domain: domain,
},
}
s, err := d.Dial(conn)
if err != nil {
return nil, err
}
defer func() { _ = s.Logoff() }()
shares, err := s.ListSharenames()
if err != nil {
return nil, err
}
var shareInfo []string
systemShares := map[string]bool{
"ADMIN$": true,
"C$": true,
"IPC$": true,
}
for _, shareName := range shares {
if systemShares[shareName] {
continue
}
fs, err := s.Mount(shareName)
if err != nil {
continue
}
fileCount := 0
maxFiles := 10
_ = iofs.WalkDir(fs.DirFS("."), ".", func(path string, d iofs.DirEntry, err error) error {
if err != nil {
return nil
}
if path != "." && fileCount < maxFiles {
shareInfo = append(shareInfo, fmt.Sprintf(" [->] [%s] %s", shareName, path))
fileCount++
}
if fileCount >= maxFiles {
return iofs.SkipDir
}
return nil
})
_ = fs.Umount()
}
return shareInfo, nil
}
// smb1SessionWrapper 包装SMB1会话以实现io.Closer
type smb1SessionWrapper struct {
session *smb.Session
}
func (w *smb1SessionWrapper) Close() error {
w.session.Close()
return nil
}
// smb2SessionWrapper 包装SMB2会话以实现io.Closer
type smb2SessionWrapper struct {
session *smb2.Session
conn io.Closer
}
func (w *smb2SessionWrapper) Close() error {
_ = w.session.Logoff()
return w.conn.Close()
}
// classifySMBError 统一SMB错误分类
func classifySMBError(err error) ErrorType {
if err == nil {
return ErrorTypeUnknown
}
smbAuthErrors := []string{
// 通用认证错误
"invalid username",
"invalid password",
"authentication failed",
"logon failed",
"logon failure",
"access denied",
"permission denied",
"unauthorized",
"login failed",
"bad username",
"bad password",
"wrong password",
"incorrect password",
"invalid credentials",
"bad credentials",
"authentication error",
"auth failed",
"login denied",
"credential",
"user not found",
"invalid account",
"account locked",
"account disabled",
"password expired",
// SMB特定错误
"smb: authentication failed",
"smb: invalid user",
"smb: invalid password",
"smb: access denied",
"smb: logon failure",
"smb: bad password",
"smb: user unknown",
"smb: wrong password",
"smb: login failed",
"smb: unauthorized",
i18n.GetText("service_auth_failed"),
"ntlm authentication failed",
"ntlm auth failed",
// NT Status codes
"nt_status_logon_failure",
"nt_status_wrong_password",
"nt_status_no_such_user",
"nt_status_access_denied",
"nt_status_account_disabled",
"nt_status_account_locked_out",
"nt_status_password_expired",
"status_logon_failure",
"status_wrong_password",
"status_access_denied",
"status_invalid_parameter",
"status_no_such_user",
"status_account_locked_out",
"status_password_expired",
"status_account_disabled",
// 十六进制状态码
"0xc000006d",
"0xc0000022",
"0xc000006a",
"0xc0000064",
"0xc0000234",
}
return ClassifyError(err, smbAuthErrors, CommonNetworkErrors)
}
// readSMBMessage 从连接读取NetBIOS消息
func readSMBMessage(conn net.Conn) ([]byte, error) {
headerBuf := make([]byte, 4)
n, err := conn.Read(headerBuf)
if err != nil {
return nil, err
}
if n != 4 {
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(i18n.GetText("message_length_too_large")+": %d", messageLength)
}
if messageLength == 0 {
return headerBuf, nil
}
messageBuf := make([]byte, messageLength)
totalRead := 0
for totalRead < messageLength {
n, err := conn.Read(messageBuf[totalRead:])
if err != nil {
return nil, err
}
totalRead += n
}
result := make([]byte, 0, 4+messageLength)
result = append(result, headerBuf...)
result = append(result, messageBuf...)
return result, nil
}
// parseNTLMChallenge 解析NTLM Challenge消息
func parseNTLMChallenge(data []byte, info *SMBTarget) {
if len(data) < 32 {
return
}
if !bytes.Equal(data[0:8], []byte("NTLMSSP\x00")) {
return
}
if len(data) < 12 {
return
}
messageType := bytesToUint32(data[8:12])
if messageType != 2 {
return
}
// 解析Target Name
if len(data) >= 20 {
targetLength := bytesToUint16(data[12:14])
targetOffset := bytesToUint32(data[16:20])
if targetLength > 0 && int(targetOffset) < len(data) && int(targetOffset+uint32(targetLength)) <= len(data) {
targetName := parseUnicodeString(data[targetOffset : targetOffset+uint32(targetLength)])
if targetName != "" {
info.DomainName = targetName
}
}
}
// 解析Flags
if len(data) >= 24 {
flags := bytesToUint32(data[20:24])
info.NTLMFlags = parseNTLMFlags(flags)
}
// 解析Target Info (AV_PAIR结构)
if len(data) >= 52 {
targetInfoLength := bytesToUint16(data[40:42])
targetInfoOffset := bytesToUint32(data[44:48])
if targetInfoLength > 0 && int(targetInfoOffset) < len(data) &&
int(targetInfoOffset+uint32(targetInfoLength)) <= len(data) {
targetInfoData := data[targetInfoOffset : targetInfoOffset+uint32(targetInfoLength)]
parseTargetInfo(targetInfoData, info)
}
}
// 解析OS版本信息
if len(data) >= 56 {
flags := bytesToUint32(data[20:24])
if flags&0x02000000 != 0 && len(data) >= 56 {
parseOSVersion(data[48:56], info)
}
}
}
// parseTargetInfo 解析Target Information
func parseTargetInfo(data []byte, info *SMBTarget) {
offset := 0
for offset+4 <= len(data) {
avId := bytesToUint16(data[offset : offset+2])
avLen := bytesToUint16(data[offset+2 : offset+4])
if avId == 0x0000 {
break
}
if offset+4+int(avLen) > len(data) {
break
}
value := data[offset+4 : offset+4+int(avLen)]
switch avId {
case 0x0001: // MsvAvNbComputerName
computerName := parseUnicodeString(value)
if computerName != "" {
info.ComputerName = computerName
}
case 0x0002: // MsvAvNbDomainName
if info.DomainName == "" {
domainName := parseUnicodeString(value)
if domainName != "" {
info.DomainName = domainName
}
}
case 0x0003: // MsvAvDnsComputerName
if info.ComputerName == "" {
dnsComputerName := parseUnicodeString(value)
if dnsComputerName != "" {
info.ComputerName = dnsComputerName
}
}
}
offset += 4 + int(avLen)
}
}
// parseOSVersion 解析操作系统版本
func parseOSVersion(data []byte, info *SMBTarget) {
if len(data) < 8 {
return
}
majorVersion := data[0]
minorVersion := data[1]
buildNumber := bytesToUint16(data[2:4])
var osName string
switch {
case majorVersion == 10 && minorVersion == 0:
if buildNumber >= 22000 {
osName = "Windows 11"
} else {
osName = "Windows 10"
}
case majorVersion == 6 && minorVersion == 3:
osName = "Windows 8.1/Server 2012 R2"
case majorVersion == 6 && minorVersion == 2:
osName = "Windows 8/Server 2012"
case majorVersion == 6 && minorVersion == 1:
osName = "Windows 7/Server 2008 R2"
case majorVersion == 6 && minorVersion == 0:
osName = "Windows Vista/Server 2008"
case majorVersion == 5 && minorVersion == 2:
osName = "Windows XP x64/Server 2003"
case majorVersion == 5 && minorVersion == 1:
osName = "Windows XP"
case majorVersion == 5 && minorVersion == 0:
osName = "Windows 2000"
default:
osName = fmt.Sprintf("Windows %d.%d", majorVersion, minorVersion)
}
info.OSVersion = fmt.Sprintf("%s (Build %d)", osName, buildNumber)
}
// 辅助函数
func bytesToUint16(b []byte) uint16 {
if len(b) < 2 {
return 0
}
return uint16(b[0]) | uint16(b[1])<<8
}
func bytesToUint32(b []byte) uint32 {
if len(b) < 4 {
return 0
}
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
func trimSMBString(s string) string {
return strings.Trim(strings.TrimSpace(s), "\x00")
}
func parseUnicodeString(data []byte) string {
if len(data)%2 != 0 {
return ""
}
var runes []rune
for i := 0; i < len(data); i += 2 {
if i+1 >= len(data) {
break
}
r := uint16(data[i]) | uint16(data[i+1])<<8
if r == 0 {
break
}
runes = append(runes, rune(r))
}
return string(runes)
}
func parseNTLMFlags(flags uint32) []string {
flagNames := map[uint32]string{
0x00000001: "NEGOTIATE_UNICODE",
0x00000002: "NEGOTIATE_OEM",
0x00000004: "REQUEST_TARGET",
0x00000010: "NEGOTIATE_SIGN",
0x00000020: "NEGOTIATE_SEAL",
0x00000200: "NEGOTIATE_NTLM",
0x00080000: "NEGOTIATE_EXTENDED_SESSIONSECURITY",
0x02000000: "NEGOTIATE_VERSION",
0x20000000: "NEGOTIATE_128",
0x80000000: "NEGOTIATE_56",
}
var activeFlags []string
for flag, name := range flagNames {
if flags&flag != 0 {
activeFlags = append(activeFlags, name)
}
}
return activeFlags
}
func buildNTLMSSPData(flags []byte) []byte {
return []byte{
0x00, 0x00, 0x00, 0x9A, 0xFE, 0x53, 0x4D, 0x42, 0x40, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x58, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x60, 0x40, 0x06, 0x06, 0x2B, 0x06, 0x01, 0x05,
0x05, 0x02, 0xA0, 0x36, 0x30, 0x34, 0xA0, 0x0E, 0x30, 0x0C,
0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02,
0x02, 0x0A, 0xA2, 0x22, 0x04, 0x20, 0x4E, 0x54, 0x4C, 0x4D,
0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00,
flags[0], flags[1], flags[2], flags[3],
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
}