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:
parent
d3d2a3746a
commit
17a5257565
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue