nbns
import "github.com/TheManticoreProject/Manticore/network/netbios/nbns"
Index
- Constants
- func DecodeSessionServiceName(b []byte) (name string, suffix byte, n int, err error)
- func DefaultInterfaceIPv4() (net.IP, error)
- func EncodeSessionServiceName(name string, suffix byte) ([]byte, error)
- func ParseIPFromRData(rdata []byte) (net.IP, error)
- func SuffixLabel(suffix byte, group bool) string
- type ADDR_ENTRY
- type Client
- func NewClient() *Client
- func NewClientWithServer(server string) *Client
- func (c *Client) BuildNameQueryRequest(name string, suffix byte, scope string) (*NBNSPacket, error)
- func (c *Client) NodeStatus(target string) (*NodeStatusResult, error)
- func (c *Client) Resolve(name string, suffix byte) ([]net.IP, error)
- func (c *Client) ResolveWithScope(name string, suffix byte, scope string) ([]net.IP, error)
- type MatchMode
- type NBNSHeader
- type NBNSPacket
- type NBNSQuestion
- type NBNSResourceRecord
- type NameChallenger
- type NameRecord
- type NameStatus
- type NameType
- type NetBIOSName
- type NetBIOSNameServer
- func NewNetBIOSNameServer(secured bool) *NetBIOSNameServer
- func (n *NetBIOSNameServer) CleanExpiredNames()
- func (n *NetBIOSNameServer) MarkNameConflict(name string, scopeID string) error
- func (n *NetBIOSNameServer) NameTable() []NodeName
- func (n *NetBIOSNameServer) QueryName(name string, scopeID string) ([]net.IP, NameType, time.Duration, error)
- func (n *NetBIOSNameServer) RefreshName(name string, scopeID string, owner net.IP) error
- func (n *NetBIOSNameServer) RegisterName(name string, scopeID string, nameType NameType, owner net.IP, ttl time.Duration) error
- func (n *NetBIOSNameServer) ReleaseName(name string, scopeID string, owner net.IP) error
- func (n *NetBIOSNameServer) SetCleanupInterval(interval time.Duration)
- func (n *NetBIOSNameServer) SetConflictDemandSender(send func(packet *NBNSPacket, owner net.IP) error)
- func (n *NetBIOSNameServer) StartCleanup()
- func (n *NetBIOSNameServer) StopCleanup()
- type NodeName
- type NodeStatusResult
- type PacketHandler
- type RedirectInfo
- type RedirectManager
- func NewRedirectManager() *RedirectManager
- func (r *RedirectManager) AddRedirect(scope string, serverIP net.IP, port uint16)
- func (r *RedirectManager) GetRedirect(scope string) (RedirectInfo, bool)
- func (r *RedirectManager) HandleRedirect(request *NBNSPacket, response *NBNSPacket) bool
- func (r *RedirectManager) RemoveRedirect(scope string)
- type SpoofConfig
- type SpoofHandler
- type TCPServer
- type UDPServer
- func NewUDPServer(addr string, nbns *NetBIOSNameServer) (*UDPServer, error)
- func (s *UDPServer) EnableNameDefense()
- func (s *UDPServer) EnableNodeStatus(mac net.HardwareAddr)
- func (s *UDPServer) SetRedirectManager(r *RedirectManager)
- func (s *UDPServer) SetSpoofHandler(h *SpoofHandler)
- func (s *UDPServer) Start() error
- func (s *UDPServer) Stop()
Constants
const (
// Challenge timeouts and retries
ChallengeTimeout = 2 * time.Second
ChallengeRetries = 3
)
NetBIOS name-resolution client defaults and well-known transport parameters (RFC 1002 4.2.12 / RFC 1001 15.1.1).
const (
// LimitedBroadcastAddr is the IPv4 limited-broadcast address a B-node NAME
// QUERY REQUEST is sent to when no NBNS/WINS server is configured. Combined
// with DefaultNBNSUDPPort it forms the default destination 255.255.255.255:137.
LimitedBroadcastAddr = "255.255.255.255"
// DefaultClientTimeout bounds how long the client waits for a response to a
// single transmission before retransmitting (RFC 1001 15.1.1 models this as
// BCAST_REQ_RETRY_TIMEOUT / UCAST_REQ_RETRY_TIMEOUT). One second is a
// forgiving value that works for both broadcast and unicast/WINS queries.
DefaultClientTimeout = 1 * time.Second
// DefaultRetransmitCount is how many times an unanswered query is
// retransmitted after the first transmission, mirroring the RFC 1001 15.1.1
// retry counters (BCAST_REQ_RETRY_COUNT / UCAST_REQ_RETRY_COUNT).
DefaultRetransmitCount = 2
)
Well-known NetBIOS name suffixes (the 16th byte of a NetBIOS name). The suffix selects the service registered under a name; the same base name is registered several times with different suffixes. These cover the suffixes an outbound resolver most commonly asks for.
const (
SuffixWorkstation byte = 0x00 // Workstation Service (the computer name)
SuffixMessenger byte = 0x03 // Messenger Service
SuffixServer byte = 0x20 // Server Service (file/print sharing)
SuffixDomainMasterBrows byte = 0x1B // Domain Master Browser
SuffixDomainControllers byte = 0x1C // Domain Controllers (group)
SuffixMasterBrowser byte = 0x1D // Master Browser
SuffixBrowserElections byte = 0x1E // Browser Service Elections (group)
)
Constants for name encoding
const (
NetBIOSNameLength = 16 // NetBIOS names are exactly 16 bytes
EncodedNameLength = 32 // Each half-byte becomes a byte in encoded form
ASCII_A = 0x41
)
NAME_FLAGS bits carried in each NODE_NAME entry of a NODE STATUS RESPONSE (RFC 1002 4.2.18). The field is a 16-bit big-endian word laid out, from the most-significant bit, as: G | ONT | DRG | CNF | ACT | PRM | RESERVED. The Group (G) bit is the most significant bit, matching the NB_FLAGS layout of an NB resource record.
const (
NameFlagGroup uint16 = 0x8000 // G: set for a group name, clear for a unique name
NameFlagONTMask uint16 = 0x6000 // ONT: owner node type (see OwnerNodeType)
NameFlagDeregister uint16 = 0x1000 // DRG: the name is in the process of being deregistered
NameFlagConflict uint16 = 0x0800 // CNF: the name is in conflict
NameFlagActive uint16 = 0x0400 // ACT: the name is active
NameFlagPermanent uint16 = 0x0200 // PRM: the name is a permanent node name
)
Constants for packet types and flags
const (
// Operation codes
OpNameQuery uint16 = 0x0000
OpRegistration uint16 = 0x2800
OpRelease uint16 = 0x3000
OpWACK uint16 = 0x3800
OpRefresh uint16 = 0x4000
OpRedirect uint16 = 0x4800
OpConflict uint16 = 0x5000
OpNodeStatus uint16 = 0x2100
// Response codes
RcodeSuccess uint16 = 0x0000
RcodeFormatError uint16 = 0x0001
RcodeServerError uint16 = 0x0002
RcodeNameError uint16 = 0x0003
RcodeNotImpl uint16 = 0x0004
RcodeRefused uint16 = 0x0005
RcodeActive uint16 = 0x0006
RcodeConflict uint16 = 0x0007
// Header NM_FLAGS (RFC 1002 4.2.1.1: R | OPCODE | AA TC RD RA 0 0 B | RCODE)
FlagResponse uint16 = 0x8000 // R: response
FlagAuthoritative uint16 = 0x0400 // AA: authoritative answer
FlagTruncated uint16 = 0x0200 // TC: truncated
FlagRecursion uint16 = 0x0100 // RD: recursion desired
FlagRecursionAvailable uint16 = 0x0080 // RA: recursion available
FlagBroadcast uint16 = 0x0010 // B: broadcast/multicast
// RcodeMask isolates the RCODE field (low nibble) of the header flags.
RcodeMask uint16 = 0x000F
// OpcodeMask isolates the 4-bit OPCODE field of the header flags. In the
// RFC 1002 4.2.1.1 layout the flags word is R | OPCODE | NM_FLAGS | RCODE,
// so the OPCODE occupies bits 14..11 (0x7800), with the R (response) bit at
// 0x8000 sitting just above it. Masking with 0x7800 therefore yields the
// opcode of both a request (R=0) and a response (R=1); the Op* constants are
// already positioned within this field.
OpcodeMask uint16 = 0x7800
// Question Type
QuestionTypeNB uint16 = 0x0020
QuestionTypeNBSTAT uint16 = 0x0021
// Question Class
QuestionClassIn uint16 = 0x0001 // Internet class
)
const (
// Default TCP port for NetBIOS name service
DefaultNBNSTCPPort = 137
// TCP timeouts
TCPReadTimeout = 30 * time.Second
TCPWriteTimeout = 30 * time.Second
// Maximum message size
MaxTCPMessageSize = 65535
)
const (
// Default UDP port for NetBIOS name service
DefaultNBNSUDPPort = 137
// UDP timeouts and buffer sizes
UDPReadTimeout = 5 * time.Second
UDPWriteTimeout = 5 * time.Second
MaxUDPSize = 576 // Minimum reassembly buffer size per RFC 1001
)
const (
// DefaultCleanupInterval is how often expired names are cleaned up
DefaultCleanupInterval = 5 * time.Minute
)
DefaultSpoofTTL is the TTL (in seconds) placed in spoofed answer records when no explicit TTL is configured. RFC 1002 does not mandate a value; a short TTL keeps a stale poisoned mapping from being cached for long, and 165 seconds is a value commonly emitted by NBT-NS responders.
const DefaultSpoofTTL uint32 = 165
const (
// DefaultWACKWait is the time-to-wait (in seconds) advertised in a WAIT FOR
// ACKNOWLEDGEMENT (WACK) RESPONSE while the server runs a name challenge
// before answering a registration (RFC 1002 4.2.16). It comfortably covers
// the challenger's retry budget (ChallengeRetries * ChallengeTimeout).
DefaultWACKWait = 8 * time.Second
)
NB_FLAGS bits carried in the ADDR_ENTRY.Flags field of an NB resource record (RFC 1002 4.2.1.3: G | ONT | RESERVED). The Group (G) bit is the most significant bit of the 16-bit NB_FLAGS field, distinct from the header flags.
const (
NBFlagGroup uint16 = 0x8000 // G: set for a group name, clear for a unique name
)
func DecodeSessionServiceName
func DecodeSessionServiceName(b []byte) (name string, suffix byte, n int, err error)
DecodeSessionServiceName decodes one second-level-encoded NetBIOS name as it appears in a SESSION REQUEST (RFC 1002 4.3.2), the inverse of EncodeSessionServiceName: a 0x20 length byte, EncodedNameLength encoded bytes, then the label sequence terminating the name — a bare 0x00 for the default scope, or one or more length-prefixed scope labels followed by 0x00.
It returns the name with its trailing space padding removed, the one-byte service suffix that occupied the 16th position, and the total number of bytes consumed, so a caller can decode the CALLED name and then the CALLING name that follows it.
Parameters:
- b: the buffer positioned at the start of the encoded name
Returns:
- name: the decoded name, trailing padding removed
- suffix: the one-byte service suffix
- n: the number of bytes consumed from b
- err: non-nil if the encoding is malformed
func DefaultInterfaceIPv4
func DefaultInterfaceIPv4() (net.IP, error)
DefaultInterfaceIPv4 returns the IPv4 address of the first non-loopback, up interface, a convenience for pointing the poisoner at the operator’s own address so victims connect back. It is used when SpoofConfig.SpoofIP is not set explicitly by the caller.
func EncodeSessionServiceName
func EncodeSessionServiceName(name string, suffix byte) ([]byte, error)
EncodeSessionServiceName encodes name (with the given one-byte service suffix) into the 34-byte “second-level” form the NetBIOS session service carries in a SESSION REQUEST (RFC 1002 4.3.2 / RFC 1001 14.1): a 0x20 length byte, the 32-byte first-level encoding of the 16-byte name (up to 15 characters padded with spaces plus the suffix byte in the final position), and a single 0x00 label terminator (no scope for the default). Unlike FirstLevelEncode it permits a leading ‘*’ wildcard (e.g. the “*SMBSERVER” convention), which the session service requires and which Validate would otherwise reject.
func ParseIPFromRData
func ParseIPFromRData(rdata []byte) (net.IP, error)
ParseIPFromRData extracts an IP address from NB resource record RData. RData is expected to be in ADDR_ENTRY format (2 bytes flags + 4 bytes address).
func SuffixLabel
func SuffixLabel(suffix byte, group bool) string
SuffixLabel maps a NetBIOS name suffix (the 16th name byte) to a human-readable service description. group selects between the unique and group interpretations of the suffixes whose meaning depends on the G bit (notably 0x1C, a domain group, versus the unique services). Unknown suffixes are rendered as their hex value.
type ADDR\_ENTRY
POSITIVE NAME QUERY RESPONSE
type ADDR_ENTRY struct {
Flags uint16
Address uint32
}
func (*ADDR_ENTRY) IP
func (n *ADDR_ENTRY) IP() net.IP
IP returns the address as a net.IP
func (*ADDR_ENTRY) Length
func (n *ADDR_ENTRY) Length() uint16
returns static 6 bytes number
func (*ADDR_ENTRY) Marshal
func (n *ADDR_ENTRY) Marshal() []byte
Marshals ADDR_ENTRY to buffer
func (*ADDR_ENTRY) Unmarshal
func (n *ADDR_ENTRY) Unmarshal(data []byte) error
Unmarshal decodes an ADDR_ENTRY from a byte slice
type Client
Client resolves NetBIOS names to their owner addresses by emitting NAME QUERY REQUEST packets (RFC 1002 4.2.12) and decoding the positive/negative NAME QUERY RESPONSE (RFC 1002 4.2.13). It is the outbound counterpart to the server-side name table in this package and mirrors the resolver shape of the sibling network/llmnr client (a name in, a slice of net.IP out).
A Client is a lightweight, stateless value: each Resolve call opens its own short-lived UDP socket, so a single Client is safe to reuse across sequential resolutions.
type Client struct {
// Server is the NBNS/WINS server the query is sent to (P-node/H-node style
// unicast). It may be a bare host ("10.0.0.1") or host:port; a missing port
// defaults to DefaultNBNSUDPPort (137). When Server is empty the client falls
// back to a B-node broadcast to LimitedBroadcastAddr:137.
Server string
// Timeout bounds the wait for a response to each individual transmission.
// A zero value is treated as DefaultClientTimeout.
Timeout time.Duration
// Retransmit is the number of times an unanswered query is retransmitted
// after the first send. A negative value is treated as zero (a single
// transmission).
Retransmit int
// RecursionDesired requests, for a unicast query, that the destination
// NBNS/WINS server perform recursive resolution (the RD bit, RFC 1002
// 4.2.12). It is meaningful only when querying an actual name server: a
// plain end node is not a name server and will not answer a unicast query
// with RD set, so this defaults to false and a broadcast query always sets
// RD regardless (the RFC B-node NAME QUERY REQUEST sets RD+B).
RecursionDesired bool
}
func NewClient
func NewClient() *Client
NewClient returns a Client that resolves names by B-node broadcast to 255.255.255.255:137, using the default timeout and retransmit count. This is the analogue of llmnr.NewClient for NetBIOS name resolution.
func NewClientWithServer
func NewClientWithServer(server string) *Client
NewClientWithServer returns a Client that unicasts its queries to the given NBNS/WINS server (P-node style) instead of broadcasting. server may be a bare host or host:port; a missing port defaults to 137.
func (*Client) BuildNameQueryRequest
func (c *Client) BuildNameQueryRequest(name string, suffix byte, scope string) (*NBNSPacket, error)
BuildNameQueryRequest assembles a NAME QUERY REQUEST (RFC 1002 4.2.12) for the given base name, 16th-byte suffix, and optional scope. The packet carries a freshly generated transaction ID, a single NB (0x0020) question in class IN, and the header flags appropriate to the client’s transport: OPCODE query with the RD (recursion desired) bit set, plus the B (broadcast) bit when the client is broadcasting rather than unicasting to a name server.
func (*Client) NodeStatus
func (c *Client) NodeStatus(target string) (*NodeStatusResult, error)
NodeStatus performs a NetBIOS node-status query (the equivalent of “nbtstat -A”) against target: it unicasts a NODE STATUS REQUEST to target:137/udp and parses the NODE STATUS RESPONSE into the remote node’s NetBIOS name table and adapter MAC address. target may be a bare host (“10.0.0.1”) or host:port; a missing port defaults to DefaultNBNSUDPPort (137).
The request is transmitted, then retransmitted up to the client’s Retransmit count if no matching response arrives within the per-transmission timeout (RFC 1001 15.1.1 retry model). Only a response echoing the request’s transaction ID is accepted, so unrelated datagrams are ignored.
func (*Client) Resolve
func (c *Client) Resolve(name string, suffix byte) ([]net.IP, error)
Resolve resolves a NetBIOS name+suffix to its owner IPv4 address(es) using an empty scope. It is the high-level entry point mirroring llmnr.Client.Resolve: a name in, a slice of net.IP out.
A NAME_ERROR (RCODE 0x03) negative response means the name is not registered with the queried responder; this is reported as a not-found result (an empty, non-nil slice and a nil error) rather than an error, matching the LLMNR resolver’s no-answer semantics. A genuine failure to obtain any response (every transmission timing out) is returned as an error.
func (*Client) ResolveWithScope
func (c *Client) ResolveWithScope(name string, suffix byte, scope string) ([]net.IP, error)
ResolveWithScope is the scope-aware form of Resolve: it resolves name+suffix within the given NetBIOS scope ID (pass "" for the default empty scope). See Resolve for the return and not-found semantics.
type MatchMode
MatchMode selects how a SpoofHandler decides whether a queried name should be answered with a spoofed address.
type MatchMode int
const (
// MatchAll answers every queried name (subject to the deny-list). This is the
// aggressive default an operator uses to poison a whole segment.
MatchAll MatchMode = iota
// MatchList answers only names present in the case-insensitive allowlist
// (SpoofConfig.Names). Any other name is ignored so it can still be resolved
// by a legitimate responder on the link.
MatchList
// MatchRegex answers only names matching the configured regular expression
// (SpoofConfig.Regex), e.g. `^(WPAD|PROXY)$`.
MatchRegex
)
type NBNSHeader
NBNSHeader represents the header of a NetBIOS name service packet
type NBNSHeader struct {
TransactionID uint16
Flags uint16
Questions uint16
Answers uint16
Authority uint16
Additional uint16
}
type NBNSPacket
NBNSPacket represents a complete NetBIOS name service packet
type NBNSPacket struct {
Header NBNSHeader
Questions []NBNSQuestion
Answers []NBNSResourceRecord
Authority []NBNSResourceRecord
Additional []NBNSResourceRecord
}
func (*NBNSPacket) Marshal
func (p *NBNSPacket) Marshal() ([]byte, error)
Marshal encodes an NBNSPacket into a byte slice
func (*NBNSPacket) Unmarshal
func (p *NBNSPacket) Unmarshal(data []byte) (int, error)
Unmarshal decodes a byte slice into an NBNSPacket
type NBNSQuestion
NBNSQuestion represents a question section in a NetBIOS name service packet
type NBNSQuestion struct {
Name *NetBIOSName
Type uint16
Class uint16
}
type NBNSResourceRecord
NBNSResourceRecord represents a resource record in a NetBIOS name service packet
type NBNSResourceRecord struct {
Name *NetBIOSName
Type uint16
Class uint16
TTL uint32
RDLength uint16
RData []byte
}
type NameChallenger
NameChallenger handles name conflict detection and resolution
type NameChallenger struct {
// contains filtered or unexported fields
}
func NewNameChallenger
func NewNameChallenger(nbns *NetBIOSNameServer, handlers *PacketHandler) *NameChallenger
NewNameChallenger creates a new name challenger instance
func (*NameChallenger) ChallengeOwnership
func (c *NameChallenger) ChallengeOwnership(name string, owner net.IP) (bool, error)
ChallengeOwnership verifies if a node still owns a name
func (*NameChallenger) DefendName
func (c *NameChallenger) DefendName(packet *NBNSPacket, response *NBNSPacket)
DefendName actively defends a name against challenges
type NameRecord
NameRecord represents a registered NetBIOS name and its attributes
type NameRecord struct {
Name string
Type NameType
Status NameStatus
Owners []net.IP // IP addresses of nodes that own this name
TTL time.Time // Time-to-live for name registration
RefreshInterval time.Duration
ScopeID string // NetBIOS scope identifier
}
type NameStatus
NameStatus represents the current state of a name
type NameStatus uint8
const (
Active NameStatus = iota
Conflict
Releasing
)
type NameType
NameType indicates whether a name is unique or group
type NameType uint8
const (
Unique NameType = iota // Only one owner allowed
Group // Multiple owners allowed
)
type NetBIOSName
NetBIOSName represents a NetBIOS name with its scope
type NetBIOSName struct {
Name string
ScopeID string
}
func FirstLevelDecode
func FirstLevelDecode(encoded string) (*NetBIOSName, error)
FirstLevelDecode decodes a first level encoded NetBIOS name
func (*NetBIOSName) FirstLevelEncode
func (n *NetBIOSName) FirstLevelEncode() (string, error)
FirstLevelEncode performs the first level encoding of a NetBIOS name as specified in RFC 1001 section 14.1
func (*NetBIOSName) Validate
func (n *NetBIOSName) Validate() error
Validate checks if a NetBIOS name is valid
type NetBIOSNameServer
NetBIOSNameServer represents a NetBIOS Name Server
type NetBIOSNameServer struct {
// contains filtered or unexported fields
}
func NewNetBIOSNameServer
func NewNetBIOSNameServer(secured bool) *NetBIOSNameServer
NewNetBIOSNameServer creates a new NetBIOS Name Server instance. Call StartCleanup to begin background TTL expiration, and StopCleanup to stop it.
func (*NetBIOSNameServer) CleanExpiredNames
func (n *NetBIOSNameServer) CleanExpiredNames()
CleanExpiredNames removes names that have exceeded their TTL
func (*NetBIOSNameServer) MarkNameConflict
func (n *NetBIOSNameServer) MarkNameConflict(name string, scopeID string) error
MarkNameConflict marks a name as being in conflict and, when a conflict-demand sender has been installed, emits a NAME CONFLICT DEMAND (RFC 1002 4.2.15) to every current owner of the name so the offending node learns its registration is disputed. The demand is built once per owner from the record’s scope-aware name and the owner’s address; a send failure is not fatal to marking the conflict, matching the best-effort nature of a broadcast/unicast demand.
func (*NetBIOSNameServer) NameTable
func (n *NetBIOSNameServer) NameTable() []NodeName
NameTable returns a snapshot of the currently registered names as NODE_NAME entries for a NODE STATUS RESPONSE (RFC 1002 4.2.18). Each entry carries the record’s base name and NAME_FLAGS derived from the record: the G bit reflects a group name, ACT reflects an active registration and CNF a name in conflict. The owner-node-type (ONT) bits are left at 0 (B node). The name table does not track a separate service suffix, so the 16th name byte is emitted as a space (0x20) unless a caller registered a full 16-byte name whose final byte is the suffix; the marshaller (NodeName.marshal) supplies that padding.
func (*NetBIOSNameServer) QueryName
func (n *NetBIOSNameServer) QueryName(name string, scopeID string) ([]net.IP, NameType, time.Duration, error)
QueryName looks up a name and returns its owners, type, and remaining TTL
func (*NetBIOSNameServer) RefreshName
func (n *NetBIOSNameServer) RefreshName(name string, scopeID string, owner net.IP) error
RefreshName updates the TTL for a name registration
func (*NetBIOSNameServer) RegisterName
func (n *NetBIOSNameServer) RegisterName(name string, scopeID string, nameType NameType, owner net.IP, ttl time.Duration) error
RegisterName attempts to register a name with the name server
func (*NetBIOSNameServer) ReleaseName
func (n *NetBIOSNameServer) ReleaseName(name string, scopeID string, owner net.IP) error
ReleaseName removes a name registration for a specific owner
func (*NetBIOSNameServer) SetCleanupInterval
func (n *NetBIOSNameServer) SetCleanupInterval(interval time.Duration)
SetCleanupInterval configures the interval between cleanup cycles. Must be called before StartCleanup.
func (*NetBIOSNameServer) SetConflictDemandSender
func (n *NetBIOSNameServer) SetConflictDemandSender(send func(packet *NBNSPacket, owner net.IP) error)
SetConflictDemandSender installs the transport callback MarkNameConflict uses to emit a NAME CONFLICT DEMAND to a conflicting name’s owner. Passing nil (the default) restores the local-only behaviour in which MarkNameConflict merely flips the record’s status.
func (*NetBIOSNameServer) StartCleanup
func (n *NetBIOSNameServer) StartCleanup()
StartCleanup begins a background goroutine that periodically removes expired name registrations.
func (*NetBIOSNameServer) StopCleanup
func (n *NetBIOSNameServer) StopCleanup()
StopCleanup stops the background cleanup goroutine and waits for it to finish.
type NodeName
NodeName is one entry of a remote node’s NetBIOS name table as returned in a NODE STATUS RESPONSE: the (space-trimmed) 15-character base name, its 16th service suffix byte, and the raw NAME_FLAGS word. The helper predicates below decode the individual NAME_FLAGS bits.
type NodeName struct {
Name string // base name with trailing padding spaces trimmed
Suffix byte // the 16th name byte selecting the registered service
Flags uint16 // raw NAME_FLAGS (see the NameFlag* constants)
}
func (NodeName) IsActive
func (n NodeName) IsActive() bool
IsActive reports whether the entry’s ACT (active) bit is set.
func (NodeName) IsConflict
func (n NodeName) IsConflict() bool
IsConflict reports whether the entry’s CNF (conflict) bit is set.
func (NodeName) IsDeregistering
func (n NodeName) IsDeregistering() bool
IsDeregistering reports whether the entry’s DRG (deregister-in-progress) bit is set.
func (NodeName) IsGroup
func (n NodeName) IsGroup() bool
IsGroup reports whether the entry is a group name (the G bit is set).
func (NodeName) IsPermanent
func (n NodeName) IsPermanent() bool
IsPermanent reports whether the entry’s PRM (permanent) bit is set.
func (NodeName) OwnerNodeType
func (n NodeName) OwnerNodeType() string
OwnerNodeType decodes the two ONT bits into their RFC 1002 node-type label: “B”, “P”, “M”, or “reserved”.
func (NodeName) String
func (n NodeName) String() string
String renders the entry the way an interactive node-status listing would: name, suffix (hex + label), and the unique/group flavour.
func (NodeName) SuffixLabel
func (n NodeName) SuffixLabel() string
SuffixLabel returns a human-readable description of the entry’s service suffix, distinguishing the group and unique forms of the ambiguous suffixes.
type NodeStatusResult
NodeStatusResult is the parsed outcome of a NODE STATUS RESPONSE: the remote node’s full NetBIOS name table and the adapter MAC address taken from the STATISTICS block’s UNIT_ID field.
type NodeStatusResult struct {
Names []NodeName // the remote node's registered NetBIOS names
MAC net.HardwareAddr // adapter unit id (MAC) from the STATISTICS block, nil if absent
}
type PacketHandler
PacketHandler provides common packet handling methods for both TCP and UDP servers
type PacketHandler struct {
// contains filtered or unexported fields
}
func NewPacketHandler
func NewPacketHandler(nbns *NetBIOSNameServer) *PacketHandler
NewPacketHandler creates a new packet handler instance
func (*PacketHandler) EnableNodeStatus
func (h *PacketHandler) EnableNodeStatus(mac net.HardwareAddr)
EnableNodeStatus turns on the NODE STATUS responder and sets the adapter MAC reported as the STATISTICS UNIT_ID. A nil or non-6-byte mac reports a zeroed UNIT_ID.
func (*PacketHandler) SetChallenger
func (h *PacketHandler) SetChallenger(c *NameChallenger)
SetChallenger installs (or, with nil, removes) the name challenger used to defend owned unique names on a conflicting registration.
func (*PacketHandler) SetRedirectManager
func (h *PacketHandler) SetRedirectManager(r *RedirectManager)
SetRedirectManager installs (or, with nil, removes) the redirect manager consulted on the name-query path.
type RedirectInfo
RedirectInfo contains information about where to redirect a client
type RedirectInfo struct {
ServerIP net.IP
ServerPort uint16
}
type RedirectManager
RedirectManager handles NBNS redirection
type RedirectManager struct {
// contains filtered or unexported fields
}
func NewRedirectManager
func NewRedirectManager() *RedirectManager
NewRedirectManager creates a new redirect manager
func (*RedirectManager) AddRedirect
func (r *RedirectManager) AddRedirect(scope string, serverIP net.IP, port uint16)
AddRedirect adds or updates a redirect mapping
func (*RedirectManager) GetRedirect
func (r *RedirectManager) GetRedirect(scope string) (RedirectInfo, bool)
GetRedirect returns redirect information for a scope
func (*RedirectManager) HandleRedirect
func (r *RedirectManager) HandleRedirect(request *NBNSPacket, response *NBNSPacket) bool
HandleRedirect modifies a response packet for redirection if needed
func (*RedirectManager) RemoveRedirect
func (r *RedirectManager) RemoveRedirect(scope string)
RemoveRedirect removes a redirect mapping
type SpoofConfig
SpoofConfig configures a SpoofHandler.
A SpoofHandler answers NBNS NAME QUERY REQUESTs for names the host does not own with an attacker-chosen address, the classic NBT-NS name-resolution poisoning primitive used to coerce a victim into connecting to the operator’s host. Which names are answered is controlled by Mode (answer-all, an exact allowlist, or a regex), and the address returned is SpoofIP (typically a local interface address so the victim connects back to the operator). It is the NetBIOS analogue of the LLMNR SpoofHandler.
type SpoofConfig struct {
// Mode selects the name-matching strategy (MatchAll, MatchList or MatchRegex).
Mode MatchMode
// Names is the case-insensitive allowlist consulted when Mode is MatchList.
// Entries are compared against the queried name's base label with the
// service suffix and padding stripped.
Names []string
// Regex is the regular expression consulted when Mode is MatchRegex. It is
// tested against the upper-cased base label of the queried name.
Regex *regexp.Regexp
// SpoofIP is the IPv4 address returned in the spoofed NB answer. It is
// required: the NBNS answer record (ADDR_ENTRY) carries a 4-byte IPv4 address
// only, so there is no IPv6 counterpart.
SpoofIP net.IP
// TTL is the TTL (seconds) placed in the answer record. Zero selects
// DefaultSpoofTTL.
TTL uint32
// Deny is a case-insensitive deny-list of base names that are never answered,
// even when Mode would otherwise match them. It is the place to suppress the
// host's own name and benign lookups (e.g. add "WPAD" to opt out of proxy
// auto-discovery poisoning) so the poisoner can coexist on a segment.
Deny []string
// Suffixes, when non-empty, restricts answering to queries whose NetBIOS
// service suffix (the 16th name byte, RFC 1001 15) is in the set, e.g.
// {0x00} to poison only workstation lookups or {0x20} for the file-server
// service. Suffix recovery from a decoded name is best-effort (see
// spoofNameSuffix); leave empty to answer any suffix.
Suffixes []byte
// Verbose logs every query that is answered.
Verbose bool
}
type SpoofHandler
SpoofHandler answers matched NBNS NAME QUERY REQUESTs with a spoofed address. It is wired into a UDPServer with UDPServer.SetSpoofHandler, where it replaces the authoritative name-table lookup for the NAME QUERY opcode; because a poisoner answers for names it does not own, no name-table registration is required.
type SpoofHandler struct {
// contains filtered or unexported fields
}
func NewSpoofHandler
func NewSpoofHandler(cfg SpoofConfig) (*SpoofHandler, error)
NewSpoofHandler builds a SpoofHandler from cfg, validating that the configuration can actually answer something.
It returns an error when no usable IPv4 SpoofIP is configured, when MatchList is selected with an empty allowlist, or when MatchRegex is selected without a compiled regex, so a misconfigured poisoner fails fast instead of silently answering nothing.
func (*SpoofHandler) BuildResponse
func (h *SpoofHandler) BuildResponse(request *NBNSPacket) (*NBNSPacket, bool)
BuildResponse assembles the spoofed positive NAME QUERY RESPONSE for request without transmitting it, or returns (nil, false) when the request is not a NAME QUERY or no question matched so the caller can stay silent and let legitimate resolution proceed.
The response is built per RFC 1002 4.2.13: it echoes the request’s transaction ID, sets the R (response) and AA (authoritative answer) header flags with B clear, copies the RD bit from the request (a B-node echoes the requester’s recursion-desired bit), and appends a single NB answer resource record whose ADDR_ENTRY carries the configured spoof address with the Group bit clear (a poisoned name is claimed as a unique owner, RFC 1002 4.2.1.3) and the configured TTL. The queried question is echoed into the response; RFC 1002 4.2.13 shows QDCOUNT=0, but echoing the question is widely accepted (including by this package’s own resolver) and keeps the exchange easy to correlate.
func (*SpoofHandler) HandleNameQuery
func (h *SpoofHandler) HandleNameQuery(remoteAddr net.Addr, request *NBNSPacket) (*NBNSPacket, bool)
HandleNameQuery builds the spoofed response for a NAME QUERY REQUEST and, when verbose, logs the poisoned query. It mirrors the shape of the authoritative PacketHandler.handleNameQuery so the UDPServer can invoke it in the same place in the dispatch. It returns (response, true) when a name matched and (nil, false) when nothing matched, in which case the server stays silent.
func (*SpoofHandler) Matches
func (h *SpoofHandler) Matches(name string) bool
Matches reports whether name should be answered under the handler’s match mode, deny-list and suffix filter. It is exported to make the matching logic directly testable and reusable.
type TCPServer
TCPServer represents a NetBIOS Name Server TCP component
type TCPServer struct {
// contains filtered or unexported fields
}
func NewTCPServer
func NewTCPServer(addr string, nbns *NetBIOSNameServer) (*TCPServer, error)
NewTCPServer creates a new NBNS TCP server instance
func (*TCPServer) EnableNameDefense
func (s *TCPServer) EnableNameDefense()
EnableNameDefense installs a NameChallenger so a conflicting registration of an owned name triggers an END-NODE CHALLENGE before the registration is refused or accepted (RFC 1002 4.2.10), and a conflict-demand sender so MarkNameConflict emits a NAME CONFLICT DEMAND to the offending owner. The TCP message path returns a single framed response, so no intermediate WACK is emitted here; the challenge still runs. Off by default.
func (*TCPServer) EnableNodeStatus
func (s *TCPServer) EnableNodeStatus(mac net.HardwareAddr)
EnableNodeStatus turns on the NODE STATUS responder so an NBSTAT (0x0021) query is answered from the local name table (RFC 1002 4.2.18). mac is reported as the STATISTICS UNIT_ID; a nil or non-6-byte mac reports a zeroed UNIT_ID. Node status is off by default.
func (*TCPServer) SetRedirectManager
func (s *TCPServer) SetRedirectManager(r *RedirectManager)
SetRedirectManager installs a redirect manager so a NAME QUERY for a configured scope is answered with a REDIRECT NAME QUERY RESPONSE (RFC 1002 4.2.14). Passing nil disables redirection. Off by default.
func (*TCPServer) Start
func (s *TCPServer) Start() error
Start begins listening for TCP connections
func (*TCPServer) Stop
func (s *TCPServer) Stop()
Stop gracefully shuts down the server
type UDPServer
UDPServer represents a NetBIOS Name Server UDP component
type UDPServer struct {
// contains filtered or unexported fields
}
func NewUDPServer
func NewUDPServer(addr string, nbns *NetBIOSNameServer) (*UDPServer, error)
NewUDPServer creates a new NBNS UDP server instance
func (*UDPServer) EnableNameDefense
func (s *UDPServer) EnableNameDefense()
EnableNameDefense wires the name-defence path into the UDP server: a NameChallenger so a conflicting registration of an owned name triggers an END-NODE CHALLENGE (preceded by a WACK telling the requestor to wait), and a conflict-demand sender so MarkNameConflict emits a NAME CONFLICT DEMAND to the offending owner. Both are off until this is called, leaving default behaviour unchanged.
func (*UDPServer) EnableNodeStatus
func (s *UDPServer) EnableNodeStatus(mac net.HardwareAddr)
EnableNodeStatus turns on the NODE STATUS responder so an NBSTAT (0x0021) query is answered from the local name table (RFC 1002 4.2.18). mac is reported as the STATISTICS UNIT_ID; a nil or non-6-byte mac reports a zeroed UNIT_ID. Node status is off by default.
func (*UDPServer) SetRedirectManager
func (s *UDPServer) SetRedirectManager(r *RedirectManager)
SetRedirectManager installs a redirect manager so a NAME QUERY for a configured scope is answered with a REDIRECT NAME QUERY RESPONSE (RFC 1002 4.2.14). Passing nil disables redirection. Off by default.
func (*UDPServer) SetSpoofHandler
func (s *UDPServer) SetSpoofHandler(h *SpoofHandler)
SetSpoofHandler installs an NBNS poisoning handler on the UDP server. Once set, NAME QUERY REQUESTs are answered by the SpoofHandler (which claims names the host does not own with an attacker-chosen address) rather than by the authoritative name table; every other opcode is handled as before. Passing nil restores the authoritative behaviour.
func (*UDPServer) Start
func (s *UDPServer) Start() error
Start begins listening for UDP packets
func (*UDPServer) Stop
func (s *UDPServer) Stop()
Stop gracefully shuts down the server