diff --git a/lib/smb/smb/zgrab.go b/lib/smb/smb/zgrab.go index fa0415b..ab3e477 100644 --- a/lib/smb/smb/zgrab.go +++ b/lib/smb/smb/zgrab.go @@ -7,52 +7,100 @@ import ( "fmt" "net" + "unicode/utf16" + "github.com/zmap/zgrab2/lib/smb/gss" "github.com/zmap/zgrab2/lib/smb/ntlmssp" "github.com/zmap/zgrab2/lib/smb/smb/encoder" - "unicode/utf16" ) +// HeaderLog contains the relevant parts of the header that is included with each packet. type HeaderLog struct { + // ProtocolID identifies the SMB protocol version (e.g. ProtocolSmb == "\xFFSMB") ProtocolID []byte `json:"protocol_id"` + + // Status is the server's status; e.g. NTSTATUS (https://msdn.microsoft.com/en-us/library/cc704588.aspx). Status uint32 `json:"status"` + + // Command is the command identifier. Command uint16 `json:"command"` + + // Credits is the number of credits granted to the client. Credits uint16 `json:"credits"` + + // Flags is the flags for the request (see https://msdn.microsoft.com/en-us/library/cc246529.aspx) Flags uint32 `json:"flags"` - NextCommand uint32 `json:"next_command"` - TreeID uint32 `json:"tree_id"` } +// NegotiationLog contains the relevant parts of the negotiation response packet. +// See https://msdn.microsoft.com/en-us/library/cc246561.aspx. type NegotiationLog struct { HeaderLog + // SecurityMode is the server's security mode (e.g. signing enabled/required). SecurityMode uint16 `json:"security_mode"` + + // DialectRevision is the SMB2 dialect number; 0x2FF is the wildcard. DialectRevision uint16 `json:"dialect_revision"` + + // ServerGuid is the server's globally unique identifier. ServerGuid []byte `json:"server_guid"` + + // Capabilities specifies protocol capabilities for the server. Capabilities uint32 `json:"capabilities"` - SystemTime uint64 `json:"system_time"` - ServerStartTime uint64 `json:"server_start_time"` + + // SystemTime is the time (in seconds since Unix epoch) the server received the negotiation request. + SystemTime uint32 `json:"system_time"` + + // ServerStartTime is the time (in seconds since the Unix epoch) the server started. + ServerStartTime uint32 `json:"server_start_time"` + + // AuthenticationTypes is a list of OBJECT IDENTIFIERs (in dotted-decimal format) identifying authentication modes + // // that the server supports. AuthenticationTypes []string `json:"authentication_types,omitempty"` } +// SessionSetupLog contains the relevant parts of the first session setup response packet. +// See https://msdn.microsoft.com/en-us/library/cc246564.aspx type SessionSetupLog struct { HeaderLog + + // SetupFlags is the gives additional information on the session. SetupFlags uint16 `json:"setup_flags"` + + // TargetName is the target name from the challenge packet TargetName string `json:"target_name"` + + // NegotiateFlags are the flags from the challenge packet NegotiateFlags uint32 `json:"negotiate_flags"` } +// SMBLog logs the relevant information about the session. type SMBLog struct { + // SupportV1 is true if the server's protocol ID indicates support for version 1. SupportV1 bool `json:"smbv1_support"` + + // NegotiationLog, if present, contains the server's response to the negotiation request. NegotiationLog *NegotiationLog `json:"negotiation_log"` + + // SessionSetupLog, if present, contains the server's response to the session setup request. SessionSetupLog *SessionSetupLog `json:"session_setup_log"` } +// LoggedSession wraps the Session struct, and holds a Log struct alongside it to track its progress. type LoggedSession struct { Session Log *SMBLog } +// zschema doesn't support uint64, so convert this into a standard 32-bit timestamp +func getTime(time uint64) uint32 { + // SMB timestamps are tenths of a millisecond since 1/1/1601. + // Between Jan 1, 1601 and Jan 1, 1970, you have 369 complete years, of which 89 are leap years (1700, 1800, and 1900 were not leap years). That gives you a total of 134774 days or 11644473600 seconds + const offset uint64 = 11644473600 + return uint32(time/1e7 - offset) +} + func getHeaderLog(src *Header) HeaderLog { return *fillHeaderLog(src, nil) } @@ -62,15 +110,14 @@ func fillHeaderLog(src *Header, dest *HeaderLog) *HeaderLog { dest = new(HeaderLog) } dest.ProtocolID = append(make([]byte, len(src.ProtocolID)), src.ProtocolID...) - dest.Status = src.Status - dest.Command = src.Command - dest.Credits = src.Credits + dest.Status = src.Status + dest.Command = src.Command + dest.Credits = src.Credits dest.Flags = src.Flags - dest.NextCommand = src.NextCommand - dest.TreeID = src.TreeID return dest } +// GetSMBLog attempts to negotiate a SMB session on the given connection. func GetSMBLog(conn net.Conn) (*SMBLog, error) { opt := Options{} @@ -94,15 +141,17 @@ func GetSMBLog(conn net.Conn) (*SMBLog, error) { } func wstring(input []byte) string { - u16 := make([]uint16, len(input) / 2) + u16 := make([]uint16, len(input)/2) for i := 0; i < len(u16); i++ { - u16[i] = uint16(input[i * 2]) | (uint16(input[i * 2 + 1]) << 8) + u16[i] = uint16(input[i*2]) | (uint16(input[i*2+1]) << 8) } return string(utf16.Decode(u16)) } +// LoggedNegotiateProtocol performs the same operations as Session.NegotiateProtocol() up to the point where user +// credentials would be required, and logs the server's responses. func (ls *LoggedSession) LoggedNegotiateProtocol() error { s := &ls.Session negReq := s.NewNegotiateReq() @@ -124,13 +173,13 @@ func (ls *LoggedSession) LoggedNegotiateProtocol() error { ls.Log = logStruct ls.Log.SupportV1 = string(negRes.Header.ProtocolID) == ProtocolSmb logStruct.NegotiationLog = &NegotiationLog{ - HeaderLog: getHeaderLog(&negRes.Header), - SecurityMode: negRes.SecurityMode, + HeaderLog: getHeaderLog(&negRes.Header), + SecurityMode: negRes.SecurityMode, DialectRevision: negRes.DialectRevision, - ServerGuid: append(make([]byte, len(negRes.ServerGuid)), negRes.ServerGuid...), - Capabilities: negRes.Capabilities, - SystemTime: negRes.SystemTime, - ServerStartTime: negRes.ServerStartTime, + ServerGuid: append(make([]byte, len(negRes.ServerGuid)), negRes.ServerGuid...), + Capabilities: negRes.Capabilities, + SystemTime: getTime(negRes.SystemTime), + ServerStartTime: getTime(negRes.ServerStartTime), } if negRes.Header.Status != StatusOk { return errors.New(fmt.Sprintf("NT Status Error: %d\n", negRes.Header.Status)) @@ -211,7 +260,7 @@ func (ls *LoggedSession) LoggedNegotiateProtocol() error { return err } logStruct.SessionSetupLog = &SessionSetupLog{ - HeaderLog: getHeaderLog(&ssres.Header), + HeaderLog: getHeaderLog(&ssres.Header), SetupFlags: ssres.Flags, } challenge := ntlmssp.NewChallenge() diff --git a/modules/smb/scanner.go b/modules/smb/scanner.go index b794123..cd6b830 100644 --- a/modules/smb/scanner.go +++ b/modules/smb/scanner.go @@ -3,10 +3,10 @@ package smb import ( + "github.com/jb/tcpwrap" log "github.com/sirupsen/logrus" "github.com/zmap/zgrab2" "github.com/zmap/zgrab2/lib/smb/smb" - "github.com/jb/tcpwrap" ) // ScanResults instances are returned by the module's Scan function. @@ -93,7 +93,7 @@ func (scanner *Scanner) GetPort() uint { // 2. Call smb.GetSMBBanner() on the connection // 3. Return the result. func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, interface{}, error) { - conn, err:= target.Open(&scanner.config.BaseFlags) + conn, err := target.Open(&scanner.config.BaseFlags) if err != nil { return zgrab2.TryGetScanStatus(err), nil, err } diff --git a/schemas/smb.py b/schemas/smb.py index 5ff7732..2620e0e 100644 --- a/schemas/smb.py +++ b/schemas/smb.py @@ -7,9 +7,44 @@ import zschema.registry import schemas.zcrypto as zcrypto import schemas.zgrab2 as zgrab2 +header_log = { + 'protocol_id': Binary(), + 'status': Unsigned32BitInteger(), + 'command': Unsigned16BitInteger(), + 'credits': Unsigned16BitInteger(), + 'flags': Unsigned32BitInteger(), +} + +# Return a (shallow) copy of base, with the fields of new merged atop it +def extended(base, new): + copy = { + k: v for k, v in base.items() + } + for k, v in new.items(): + copy[k] = v + return copy + +negotiate_log = SubRecord(extended(header_log, { + 'security_mode': Unsigned16BitInteger(), + 'dialect_revision': Unsigned16BitInteger(), + 'server_guid': Binary(), + 'capabilities': Unsigned32BitInteger(), + 'system_time': Unsigned32BitInteger(), + 'server_start_time': Unsigned32BitInteger(), + 'authentication_types': ListOf(String()), +})) + +session_setup_log = SubRecord(extended(header_log, { + 'setup_flags': Unsigned16BitInteger(), + 'target_name': String(), + 'negotiate_flags': Unsigned32BitInteger(), +})) + smb_scan_response = SubRecord({ "result": SubRecord({ "smbv1_support": Boolean(), + "negotiation_log": negotiate_log, + "session_setup_log": session_setup_log, }) }, extends=zgrab2.base_scan_response)