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.
This commit is contained in:
Jeff Cody 2019-06-07 16:33:23 -04:00
parent f2b76412fb
commit 1232ca4e60
No known key found for this signature in database
GPG Key ID: BDBE7B27C0DE3057
3 changed files with 106 additions and 0 deletions

View File

@ -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:
}

View File

@ -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

View File

@ -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()