Files
honeydany/app/services/smb.go
T

123 lines
4.6 KiB
Go

package services
import (
"bytes"
"encoding/binary"
"net"
"strconv"
"strings"
"time"
)
// NewSMBHandler logs incoming SMB negotiate/session setup attempts and returns a minimal response.
// It is NOT a full SMB implementation; it's enough to keep scanners interacting and capture tokens.
func NewSMBHandler(log LoggerFunc) Handler {
return func(conn net.Conn) {
defer conn.Close()
remote := conn.RemoteAddr().String()
conn.SetDeadline(time.Now().Add(15 * time.Second))
buf := make([]byte, 2048)
n, _ := conn.Read(buf)
if n <= 0 {
return
}
first := buf[:n]
det := map[string]string{"event":"smb_probe","bytes_received":strconv.Itoa(n)}
// rough fingerprint if NTLMSSP is present
if bytes.Contains(first, []byte("NTLMSSP")) {
det["ntlmssp"] = "present"
if u, d, w, lmLen, ntLen := parseNTLMSSP(first); u != "" || d != "" || w != "" {
if u != "" { det["user"] = u }
if d != "" { det["domain"] = d }
if w != "" { det["workstation"] = w }
if lmLen > 0 { det["lm_resp_len"] = strconv.Itoa(lmLen) }
if ntLen > 0 { det["nt_resp_len"] = strconv.Itoa(ntLen) }
}
}
// Try to guess dialect by sniffing strings
fstr := strings.ToUpper(string(first))
if strings.Contains(fstr, "SMB 2.1") || strings.Contains(fstr, "SMB2") {
det["dialect"] = "smb2"
} else if strings.Contains(fstr, "SMB 3") {
det["dialect"] = "smb3"
} else {
det["dialect"] = "unknown"
}
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "smb", Details: det})
// Send a minimal SMB2 NEGOTIATE failure-like response (not a valid implementation, just a banner)
resp := []byte{
0x00, 0x00, 0x00, 0x3F, // NetBIOS length header (len following)
0xFE, 0x53, 0x4D, 0x42, // SMB2 magic
0x40, 0x00, 0x00, 0x00, // header fields
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x24, 0x00, // StructureSize
0x00, 0x00, // SecurityMode
0x01, 0x00, // DialectRevision (SMB 2.1)
0x00, 0x00, // NegotiateContextCount
0x00, 0x00, 0x00, 0x00, // ServerGuid part (fake)
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // Capabilities & MaxTrans/Read/WriteSize (fake zeros)
}
_, _ = conn.Write(resp)
}
}
// parseNTLMSSP tries to extract fields from an NTLMSSP message embedded in SMB payloads.
// It supports Type 1, 2, and 3 and focuses on Type 3 credential info.
func parseNTLMSSP(p []byte) (username, domain, workstation string, lmLen, ntLen int) {
sig := []byte("NTLMSSP\x00")
i := bytes.Index(p, sig)
if i < 0 || i+12 >= len(p) { return "","","",0,0 }
// Message type at offset i+8 (LE uint32)
if i+12 > len(p) { return }
mtype := binary.LittleEndian.Uint32(p[i+8 : i+12])
switch mtype {
case 1: // Negotiate - no creds
return "","","",0,0
case 2: // Challenge - no creds to extract
return "","","",0,0
case 3: // Authenticate - contains domain, user, workstation
// Offsets are relative to start of NTLMSSP message
base := i
// Helper to read fields: len(2), maxLen(2), offset(4)
readField := func(off int) (l, o int) {
if base+off+8 > len(p) { return 0, 0 }
l = int(binary.LittleEndian.Uint16(p[base+off : base+off+2]))
o = int(binary.LittleEndian.Uint32(p[base+off+4 : base+off+8]))
return
}
// Per spec Type 3 layout:
// 0x14 LMResp, 0x1C NTResp, 0x24 DomainName, 0x2C UserName, 0x34 Workstation, 0x3C EncryptedRandomSessionKey
lmLen, _ = readField(0x14)
ntLen, _ = readField(0x1C)
domLen, domOff := readField(0x24)
usrLen, usrOff := readField(0x2C)
wsLen, wsOff := readField(0x34)
// Extract UTF-16LE strings safely
username = readUTF16LE(p, usrOff, usrLen)
domain = readUTF16LE(p, domOff, domLen)
workstation = readUTF16LE(p, wsOff, wsLen)
return
default:
return "","","",0,0
}
}
func readUTF16LE(p []byte, off, length int) string {
if off <= 0 || length <= 1 || off+length > len(p) { return "" }
// best-effort: convert UTF-16LE to UTF-8 by dropping high bytes if ASCII
// for simplicity; full decoding not necessary for logging
b := make([]byte, 0, length/2)
for j := 0; j+1 < length && off+j+1 < len(p); j += 2 {
b = append(b, p[off+j])
}
s := strings.TrimSpace(string(b))
return s
}