From 1232ca4e60ed053acbb53395ccf9831363284170 Mon Sep 17 00:00:00 2001 From: Jeff Cody Date: Fri, 7 Jun 2019 16:33:23 -0400 Subject: [PATCH] SMB: Add Negotiation Req & Response for v1 Send SMB1 header, and Negotiation Request message for SMB1. This brings the zgrab2 smb1 scanner to parity with the zgrab smb1 scanner, with presence detection via smbv1_support. We check the ProtocolID in the raw data response, for two reasons: 1. Even if the full unmarshal fails for the message, we will log that it is an smbv1 server 2. We need to add more response types structs, because the format is different for various SMB1 dialects. The negotiation response v1 structure is for the SMB1 "NT LM 0.12" dialect, and is essentially placeholder for now for future parsing. TODO: Unmarshal into the appropriate message struct based on SMB1 dialect, and parse dialect and capabilities, and return those results. --- lib/smb/smb/session.go | 1 + lib/smb/smb/smb.go | 59 ++++++++++++++++++++++++++++++++++++++++++ lib/smb/smb/zgrab.go | 46 ++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) diff --git a/lib/smb/smb/session.go b/lib/smb/smb/session.go index d0fe1fe..fb4f3dd 100644 --- a/lib/smb/smb/session.go +++ b/lib/smb/smb/session.go @@ -381,6 +381,7 @@ func (s *Session) send(req interface{}) (res []byte, err error) { switch string(protID) { default: return nil, errors.New("Protocol Not Implemented") + case ProtocolSmb: case ProtocolSmb2: } diff --git a/lib/smb/smb/smb.go b/lib/smb/smb/smb.go index ba924e6..7100a2b 100644 --- a/lib/smb/smb/smb.go +++ b/lib/smb/smb/smb.go @@ -33,6 +33,8 @@ const DialectSmb_3_0_2 = 0x0302 const DialectSmb_3_1_1 = 0x0311 const DialectSmb2_ALL = 0x02FF +const DialectSmb_1_0 = "\x02NT LM 0.12\x00" + const ( CommandNegotiate uint16 = iota CommandSessionSetup @@ -93,6 +95,21 @@ const ( ShareCapAsymmetric uint32 = 0x00000080 ) +type HeaderV1 struct { + ProtocolID []byte `smb:"fixed:4"` + Command uint8 + Status uint32 + Flags uint8 + Flags2 uint16 + PIDHigh uint16 + SecurityFeatures []byte `smb:"fixed:8"` + Reserved uint16 + TID uint16 + PIDLow uint16 + UID uint16 + MID uint16 +} + type Header struct { ProtocolID []byte `smb:"fixed:4"` StructureSize uint16 @@ -109,6 +126,31 @@ type Header struct { Signature []byte `smb:"fixed:16"` } +type NegotiateReqV1 struct { + HeaderV1 + WordCount uint8 + ByteCount uint16 // hardcoded to 14 + Dialects []uint8 `smb:"fixed:12"` +} + +type NegotiateResV1 struct { + HeaderV1 + WordCount uint8 + DialectIndex uint16 + SecurityMode uint8 + MaxMpxCount uint16 + MaxNumberVcs uint16 + MaxBufferSize uint32 + MaxRawSize uint32 + SessionKey uint32 + Capabilities uint32 + SystemTime uint64 + ServerTimezon uint16 + ChallengeLength uint8 + ByteCount uint16 + // variable data afterwords that we don't care about +} + type NegotiateReq struct { Header StructureSize uint16 @@ -215,6 +257,12 @@ type TreeDisconnectRes struct { Reserved uint16 } +func newHeaderV1() HeaderV1 { + return HeaderV1{ + ProtocolID: []byte(ProtocolSmb), + } +} + func newHeader() Header { return Header{ ProtocolID: []byte(ProtocolSmb2), @@ -233,6 +281,17 @@ func newHeader() Header { } } +func (s *Session) NewNegotiateReqV1() NegotiateReqV1 { + header := newHeaderV1() + header.Command = 0x72 // SMB1 Negotiate + return NegotiateReqV1{ + HeaderV1: header, + WordCount: 0, + ByteCount: 14, + Dialects: []uint8(DialectSmb_1_0), + } +} + func (s *Session) NewNegotiateReq() NegotiateReq { header := newHeader() header.Command = CommandNegotiate diff --git a/lib/smb/smb/zgrab.go b/lib/smb/smb/zgrab.go index baa1eed..a9fce8e 100644 --- a/lib/smb/smb/zgrab.go +++ b/lib/smb/smb/zgrab.go @@ -221,11 +221,57 @@ func wstring(input []byte) string { return string(utf16.Decode(u16)) } +// Temporary placeholder to detect SMB v1 by sending a simple v1 +// header with an invalid command; the response with be an error +// code, but with a v1 ProtocolID +// TODO: Parse the unmarshaled results. +func (ls *LoggedSession) LoggedNegotiateProtocolv1(setup bool) error { + s := &ls.Session + + negReq := s.NewNegotiateReqV1() + s.Debug("Sending LoggedNegotiateProtocolV1 request", nil) + buf, err := s.send(negReq) + if err != nil { + s.Debug("", err) + return err + } + + logStruct := new(SMBLog) + ls.Log = logStruct + + // s.send() will return error if buf size is < 4. + // Check the for the protocol identifier here, so that we at least + // log that this is an SMB1 server even if the full unmarshal fails. + if string(buf[0:4]) == ProtocolSmb { + ls.Log.SupportV1 = true + ls.Log.Version = &SMBVersions{Major: 1, + Minor: 0, + Revision: 0, + VerString: "SMB 1.0"} + } else { + return fmt.Errorf("Invalid v1 Protocol ID\n") + } + + negRes := NegotiateResV1{} + // TODO: Unmarshal struct depends on the CIF dialect response field. + if err := encoder.Unmarshal(buf, &negRes); err != nil { + s.Debug("Raw:\n"+hex.Dump(buf), err) + // Not returning error here, because the NegotiationResV1 is + // only valid for the extended NT LM 0.12 dialect of SMB1. + } + + // TODO: Parse capabilities and return those results + + return nil +} + // LoggedNegotiateProtocol performs the same operations as // Session.NegotiateProtocol() up to the point where user credentials would be // required, and logs the server's responses. // If setup is false, stop after reading the response to Negotiate. // If setup is true, send a SessionSetup1 request. +// +// Note: This supports SMB2 only. func (ls *LoggedSession) LoggedNegotiateProtocol(setup bool) error { s := &ls.Session negReq := s.NewNegotiateReq()