Add NativeOS, NTLM, and GroupName to SMBv1 results (#286)

* add smbv1 session setup scan

* remove unused values

* rename os_name to native_os to match smb documentation

* remove superfluous comment

* update zschema to include new SMB fields

* improve clarity on bounds checking for SMBv1 requests
This commit is contained in:
Elliot Cubit 2021-03-15 09:23:55 -04:00 committed by GitHub
parent d3d2a3746a
commit 17a5257565
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 5 deletions

View File

@ -27,3 +27,21 @@ func ToUnicode(s string) []byte {
binary.Write(&b, binary.LittleEndian, &uints)
return b.Bytes()
}
func ToSmbString(s string) []byte {
res := ToUnicode(s)
res = append(res, 0x0, 0x0)
return res
}
func FromSmbString(d []byte) (string, error) {
res, err := FromUnicode(d)
if err != nil {
return "", err
}
if len(res) == 0 {
return "", nil
}
// Trim null terminator
return res[:len(res)-1], nil
}

View File

@ -95,6 +95,10 @@ const (
ShareCapAsymmetric uint32 = 0x00000080
)
const (
SmbHeaderV1Length = 32
)
type HeaderV1 struct {
ProtocolID []byte `smb:"fixed:4"`
Command uint8
@ -133,6 +137,24 @@ type NegotiateReqV1 struct {
Dialects []uint8 `smb:"fixed:12"`
}
type SessionSetupV1Req struct {
HeaderV1
WordCount uint8
AndCommand uint8
Reserved1 uint8
AndOffset uint16
MaxBuffer uint16
MaxMPXCount uint16
VCNumber uint16
SessionKey uint32
OEMPasswordLength uint16
UnicodePasswordLength uint16
Reserved2 uint32
Capabilities uint32
ByteCount uint16
VarData []byte
}
type NegotiateResV1 struct {
HeaderV1
WordCount uint8
@ -147,8 +169,8 @@ type NegotiateResV1 struct {
SystemTime uint64
ServerTimezon uint16
ChallengeLength uint8
ByteCount uint16
// variable data afterwords that we don't care about
ByteCount uint16 `smb:"len:VarData"`
VarData []byte
}
type NegotiateReq struct {
@ -260,6 +282,17 @@ type TreeDisconnectRes struct {
func newHeaderV1() HeaderV1 {
return HeaderV1{
ProtocolID: []byte(ProtocolSmb),
Status: 0,
Flags: 0x18,
Flags2: 0xc843,
PIDHigh: 0,
// These bytes must be explicit here
SecurityFeatures: []byte{0, 0, 0, 0, 0, 0, 0, 0},
Reserved: 0,
TID: 0xffff,
PIDLow: 0xfeff,
UID: 0,
MID: 0,
}
}
@ -287,11 +320,24 @@ func (s *Session) NewNegotiateReqV1() NegotiateReqV1 {
return NegotiateReqV1{
HeaderV1: header,
WordCount: 0,
ByteCount: 14,
ByteCount: 12,
Dialects: []uint8(DialectSmb_1_0),
}
}
func (s *Session) NewSessionSetupV1Req() SessionSetupV1Req {
header := newHeaderV1()
header.Command = 0x73 // SMB1 Session Setup
return SessionSetupV1Req{
HeaderV1: header,
WordCount: 0xd,
AndCommand: 0xff,
MaxBuffer: 0x1111,
MaxMPXCount: 0xa,
VarData: []byte{},
}
}
func (s *Session) NewNegotiateReq() NegotiateReq {
header := newHeader()
header.Command = CommandNegotiate

View File

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net"
"strings"
"unicode/utf16"
@ -130,6 +131,12 @@ type SMBLog struct {
Version *SMBVersions `json:"smb_version,omitempty"`
// If present, represent the NativeOS, NTLM, and GroupName fields of SMBv1 Session Setup Negotiation
// An empty string for these values indicate the data was not available
NativeOs string `json:"native_os"`
NTLM string `json:"ntlm"`
GroupName string `json:"group_name"`
// While the NegotiationLogs and SessionSetupLog each have their own
// Capabilties field, we are ignoring the SessionsSetupLog capability
// when decoding, and only representing the server capabilties based
@ -208,7 +215,10 @@ func GetSMBLog(conn net.Conn, session bool, v1 bool, debug bool) (smbLog *SMBLog
}
if v1 {
err = s.LoggedNegotiateProtocolv1(session)
err := s.LoggedNegotiateProtocolv1(session)
if err == nil && session {
s.LoggedSessionSetupV1()
}
} else {
err = s.LoggedNegotiateProtocol(session)
}
@ -269,6 +279,63 @@ func (ls *LoggedSession) LoggedNegotiateProtocolv1(setup bool) error {
return nil
}
func (ls *LoggedSession) LoggedSessionSetupV1() (err error) {
s := &ls.Session
var buf []byte
req := s.NewSessionSetupV1Req()
s.Debug("Sending LoggedSessionSetupV1 Request", nil)
buf, err = s.send(req)
if err != nil {
s.Debug("No response to SMBv1 cleartext SessionSetup", nil)
return nil
}
// Safely trim down everything except the payload
if len(buf) < SmbHeaderV1Length {
return nil
}
// When using unicode, a padding byte will exist after the header
paddingLength := int((buf[11] >> 7) & 1)
// Skip header
buf = buf[SmbHeaderV1Length:]
// The byte after the header holds the number of words remaining in uint16s
// words + 3 bytes for wordlength & bytecount + potential unicode padding
claimedRemainingSize := int(buf[0])*2 + 3 + paddingLength
if len(buf) < claimedRemainingSize {
return nil
}
buf = buf[claimedRemainingSize:]
var decoded string
if paddingLength == 1 {
// Unicode string
decoded, err = encoder.FromSmbString(buf)
if err != nil {
s.Debug("Error encountered while decoding SMB string", err)
return nil
}
} else {
// ASCII string
decoded = string(buf)
}
// We expect 3 null-terminated strings in this order;
// These fields are technically all optional, but guaranteed to be in this order
fields := strings.Split(decoded, "\000")
if len(fields) > 0 {
ls.Log.NativeOs = fields[0]
}
if len(fields) > 1 {
ls.Log.NTLM = fields[1]
}
if len(fields) > 2 {
ls.Log.GroupName = fields[2]
}
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.

View File

@ -52,6 +52,9 @@ smb_scan_response = SubRecord({
"revision": Unsigned8BitInteger(doc="Protocol Revision"),
"version_string": String(doc="Full SMB Version String"),
}),
"native_os": String(doc="Operating system claimed by server"),
"ntlm": String(doc="Native LAN Manager"),
"group_name": String(doc="Group name"),
"smb_capabilities": SubRecord({
"smb_dfs_support": Boolean(doc="Server supports Distributed File System"),
"smb_leasing_support": Boolean(doc="Server supports Leasing"),
@ -62,7 +65,7 @@ smb_scan_response = SubRecord({
"smb_encryption_support": Boolean(doc="Server supports encryption"),
}, doc="Capabilities flags for the connection. See [MS-SMB2] Sect. 2.2.4."),
'negotiation_log': negotiate_log,
'has_ntlm': Boolean(),
'has_ntlm': Boolean(doc="Server supports the NTLM authentication method"),
'session_setup_log': session_setup_log,
})
}, extends=zgrab2.base_scan_response)