Protocol Helpers

May 8, 2026 · View on GitHub

The protocol package and its sub-packages provide helpers for crafting and sending protocol-specific packets. The intent is not to provide full protocol stacks but rather the building blocks that are commonly needed during exploit development — enough to trigger a vulnerability without reimplementing an entire client library.

For general exploit structure, see Getting Started. For fingerprinting and version detection patterns, see Version Checking.

SMB / NetBIOS (protocol/smb)

The smb package covers three layers that come up repeatedly in SMB-related exploit work:

  • NetBIOS Name Service (UDP 137) — packet construction and delivery for WINS name operations
  • NetBIOS Session Service (TCP 139) — session establishment before SMB traffic
  • SMB1 — dialect negotiation for fingerprinting and version detection

The package is protocol-focused rather than OS-focused, so it is equally applicable to Linux Samba targets, NAS appliances, and Windows hosts.

NetBIOS Name Service

The Name Service runs over UDP 137 and handles name registration, queries, and releases against a WINS server (or via broadcast). All packet builders return a []byte that is passed directly to Send or SendAndRecv. Send is a fire-and-forget UDP send suited for registrations and releases that don't expect a reply; SendAndRecv sends a packet and waits up to 5 seconds for a response, which is needed for name queries.

Name registration comes in two forms. Standard registration (opcode 0x5) is a normal unicast WINS registration:

packet := smb.BuildNameRegistration("MYHOST", net.ParseIP("10.0.0.50"), 0x00)
smb.Send(conf.Rhost, 137, packet)

MULTI-HOME registration (opcode 0xF) is a separate code path in WINS, used by clients with multiple network interfaces:

packet := smb.BuildMultiHomeRegistration("MYHOST", net.ParseIP("10.0.0.1"), 0x00)
smb.Send(conf.Rhost, 137, packet)

To query a name from a WINS server and read the reply:

packet := smb.BuildNameQuery("TARGETDC", 0x1C) // 0x1C = domain controller group name
resp, ok := smb.SendAndRecv(conf.Rhost, 137, packet, 512)
if !ok {
    return false
}

To release a previously registered name:

packet := smb.BuildNameRelease("MYHOST", net.ParseIP("10.0.0.50"), 0x00)
smb.Send(conf.Rhost, 137, packet)

EncodeName is also exported for cases where the raw RFC 1001/1002 encoded form is needed directly, though the packet builders above call it internally:

encoded := smb.EncodeName("FILESERVER", 0x20) // 0x20 = file service name type

NetBIOS Session Service

The Session Service runs over TCP 139 and must be completed before any SMB traffic can flow on that port. Direct-hosted SMB on port 445 still uses the 4-byte NBT length-framed header (type 0x00 + length); only the NetBIOS SESSION REQUEST handshake is skipped. The Negotiate helper applies framing automatically for both ports.

DialSession handles the full TCP connect and SESSION REQUEST/POSITIVE RESPONSE handshake, returning an open connection ready for SMB traffic:

conn, ok := smb.DialSession("192.168.1.10", "FILESERVER", "ATTACKER")
if !ok {
    return false
}
defer conn.Close()

The calledName is the server's NetBIOS name; callingName is the name the client presents. Both are encoded automatically. For cases where finer control is needed — for example, when fuzzing the session handshake itself — the individual builders and response constants are also exported:

// Build a SESSION REQUEST without dialing
req := smb.BuildSessionRequest("FILESERVER", "ATTACKER")

// Wrap an SMB payload in a SESSION MESSAGE header (used for both port 139
// and port 445 — only the SESSION REQUEST handshake is 139-specific).
// Returns (nil, false) if the payload exceeds the 16-bit NBT length field.
msg, ok := smb.BuildSessionMessage(smbPayload)
if !ok {
    return false
}

// Parse a raw response byte
switch resp[0] {
case smb.PositiveSessionRsp: // 0x82
    // proceed
case smb.NegativeSessionRsp: // 0x83
    // rejected
}

SMB1 Negotiate

Negotiate fingerprints a target by sending an SMB1 NEGOTIATE PROTOCOL request and parsing the server's dialect selection. It handles both port 445 and port 139 automatically — on port 139, the NetBIOS session layer is negotiated internally before the SMB request is sent:

resp, ok := smb.Negotiate(conf.Rhost, conf.Rport, nil) // nil uses DefaultDialects
if !ok {
    return false
}

output.PrintfStatus("Server selected dialect: %s", resp.SelectedDialect)
output.PrintfStatus("OS version string: %s", resp.OsVersion)

DefaultDialects covers the full range from PC NETWORK PROGRAM 1.0 through NT LM 0.12. Pass a custom list to restrict the negotiation or probe for a specific dialect:

resp, ok := smb.Negotiate(conf.Rhost, conf.Rport, []string{"NT LM 0.12"})

A DialectIndex of 0xFFFF in the response means the server rejected all offered dialects.