Port SMB (???) scanner from ZGrab

This commit is contained in:
Justin Bastress 2018-03-15 16:58:57 -04:00
parent 85f4b8f06a
commit 70314ce92b
11 changed files with 1240 additions and 0 deletions

490
lib/smb/encoder/encoder.go Normal file
View File

@ -0,0 +1,490 @@
/*
* MIT License
*
* Copyright (c) 2017 stacktitan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package encoder
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
)
type BinaryMarshallable interface {
MarshalBinary(*Metadata) ([]byte, error)
UnmarshalBinary([]byte, *Metadata) error
}
type Metadata struct {
Tags *TagMap
Lens map[string]uint64
Offsets map[string]uint64
Parent interface{}
ParentBuf []byte
CurrOffset uint64
CurrField string
}
type TagMap struct {
m map[string]interface{}
has map[string]bool
}
func (t TagMap) Has(key string) bool {
return t.has[key]
}
func (t TagMap) Set(key string, val interface{}) {
t.m[key] = val
t.has[key] = true
}
func (t TagMap) Get(key string) interface{} {
return t.m[key]
}
func (t TagMap) GetInt(key string) (int, error) {
if !t.Has(key) {
return 0, errors.New("Key does not exist in tag")
}
return t.Get(key).(int), nil
}
func (t TagMap) GetString(key string) (string, error) {
if !t.Has(key) {
return "", errors.New("Key does not exist in tag")
}
return t.Get(key).(string), nil
}
func parseTags(sf reflect.StructField) (*TagMap, error) {
ret := &TagMap{
m: make(map[string]interface{}),
has: make(map[string]bool),
}
tag := sf.Tag.Get("smb")
smbTags := strings.Split(tag, ",")
for _, smbTag := range smbTags {
tokens := strings.Split(smbTag, ":")
switch tokens[0] {
case "len", "offset", "count":
if len(tokens) != 2 {
return nil, errors.New("Missing required tag data. Expecting key:val")
}
ret.Set(tokens[0], tokens[1])
case "fixed":
if len(tokens) != 2 {
return nil, errors.New("Missing required tag data. Expecting key:val")
}
i, err := strconv.Atoi(tokens[1])
if err != nil {
return nil, err
}
ret.Set(tokens[0], i)
case "asn1":
ret.Set(tokens[0], true)
}
}
return ret, nil
}
func getOffsetByFieldName(fieldName string, meta *Metadata) (uint64, error) {
if meta == nil || meta.Tags == nil || meta.Parent == nil || meta.Lens == nil {
return 0, errors.New("Cannot determine field offset. Missing required metadata")
}
var ret uint64
var found bool
parentvf := reflect.Indirect(reflect.ValueOf(meta.Parent))
// To determine offset, we loop through all fields of the struct, summing lengths of previous elements
// until we reach our field
for i := 0; i < parentvf.NumField(); i++ {
tf := parentvf.Type().Field(i)
if tf.Name == fieldName {
found = true
break
}
if l, ok := meta.Lens[tf.Name]; ok {
// Length of field is in cache
ret += l
} else {
// Not in cache. Must marshal field to determine length. Add to cache after
buf, err := Marshal(parentvf.Field(i).Interface())
if err != nil {
return 0, err
}
l := uint64(len(buf))
meta.Lens[tf.Name] = l
ret += l
}
}
if !found {
return 0, errors.New("Cannot find field name within struct: " + fieldName)
}
return ret, nil
}
func getFieldLengthByName(fieldName string, meta *Metadata) (uint64, error) {
var ret uint64
if meta == nil || meta.Tags == nil || meta.Parent == nil || meta.Lens == nil {
return 0, errors.New("Cannot determine field length. Missing required metadata")
}
// Check if length is stored in field length cache
if val, ok := meta.Lens[fieldName]; ok {
return uint64(val), nil
}
parentvf := reflect.Indirect(reflect.ValueOf(meta.Parent))
field := parentvf.FieldByName(fieldName)
if !field.IsValid() {
return 0, errors.New("Invalid field. Cannot determine length.")
}
bm, ok := field.Interface().(BinaryMarshallable)
if ok {
// Custom marshallable interface found.
buf, err := bm.(BinaryMarshallable).MarshalBinary(meta)
if err != nil {
return 0, err
}
return uint64(len(buf)), nil
}
if field.Kind() == reflect.Ptr {
field = field.Elem()
}
switch field.Kind() {
case reflect.Struct:
buf, err := Marshal(field.Interface())
if err != nil {
return 0, err
}
ret = uint64(len(buf))
case reflect.Interface:
return 0, errors.New("Interface length calculation not implemented")
case reflect.Slice, reflect.Array:
switch field.Type().Elem().Kind() {
case reflect.Uint8:
ret = uint64(len(field.Interface().([]byte)))
default:
return 0, errors.New("Cannot calculate the length of unknown slice type for " + fieldName)
}
case reflect.Uint8:
ret = uint64(binary.Size(field.Interface().(uint8)))
case reflect.Uint16:
ret = uint64(binary.Size(field.Interface().(uint16)))
case reflect.Uint32:
ret = uint64(binary.Size(field.Interface().(uint32)))
case reflect.Uint64:
ret = uint64(binary.Size(field.Interface().(uint64)))
default:
return 0, errors.New("Cannot calculate the length of unknown kind for field " + fieldName)
}
meta.Lens[fieldName] = ret
return ret, nil
}
func Marshal(v interface{}) ([]byte, error) {
return marshal(v, nil)
}
func marshal(v interface{}, meta *Metadata) ([]byte, error) {
var ret []byte
tf := reflect.TypeOf(v)
vf := reflect.ValueOf(v)
bm, ok := v.(BinaryMarshallable)
if ok {
// Custom marshallable interface found.
buf, err := bm.MarshalBinary(meta)
if err != nil {
return nil, err
}
return buf, nil
}
if tf.Kind() == reflect.Ptr {
vf = reflect.Indirect(reflect.ValueOf(v))
tf = vf.Type()
}
w := bytes.NewBuffer(ret)
switch tf.Kind() {
case reflect.Struct:
m := &Metadata{
Tags: &TagMap{},
Lens: make(map[string]uint64),
Parent: v,
}
for j := 0; j < vf.NumField(); j++ {
tags, err := parseTags(tf.Field(j))
if err != nil {
return nil, err
}
m.Tags = tags
buf, err := marshal(vf.Field(j).Interface(), m)
if err != nil {
return nil, err
}
m.Lens[tf.Field(j).Name] = uint64(len(buf))
if err := binary.Write(w, binary.LittleEndian, buf); err != nil {
return nil, err
}
}
case reflect.Slice, reflect.Array:
switch tf.Elem().Kind() {
case reflect.Uint8:
if err := binary.Write(w, binary.LittleEndian, v.([]uint8)); err != nil {
return nil, err
}
case reflect.Uint16:
if err := binary.Write(w, binary.LittleEndian, v.([]uint16)); err != nil {
return nil, err
}
}
case reflect.Uint8:
if err := binary.Write(w, binary.LittleEndian, vf.Interface().(uint8)); err != nil {
return nil, err
}
case reflect.Uint16:
data := vf.Interface().(uint16)
if meta != nil && meta.Tags.Has("len") {
fieldName, err := meta.Tags.GetString("len")
if err != nil {
return nil, err
}
l, err := getFieldLengthByName(fieldName, meta)
if err != nil {
return nil, err
}
data = uint16(l)
}
if meta != nil && meta.Tags.Has("offset") {
fieldName, err := meta.Tags.GetString("offset")
if err != nil {
return nil, err
}
l, err := getOffsetByFieldName(fieldName, meta)
if err != nil {
return nil, err
}
data = uint16(l)
}
if err := binary.Write(w, binary.LittleEndian, data); err != nil {
return nil, err
}
case reflect.Uint32:
data := vf.Interface().(uint32)
if meta != nil && meta.Tags.Has("len") {
fieldName, err := meta.Tags.GetString("len")
if err != nil {
return nil, err
}
l, err := getFieldLengthByName(fieldName, meta)
if err != nil {
return nil, err
}
data = uint32(l)
}
if meta != nil && meta.Tags.Has("offset") {
fieldName, err := meta.Tags.GetString("offset")
if err != nil {
return nil, err
}
l, err := getOffsetByFieldName(fieldName, meta)
if err != nil {
return nil, err
}
data = uint32(l)
}
if err := binary.Write(w, binary.LittleEndian, data); err != nil {
return nil, err
}
case reflect.Uint64:
if err := binary.Write(w, binary.LittleEndian, vf.Interface().(uint64)); err != nil {
return nil, err
}
default:
return nil, errors.New(fmt.Sprintf("Marshal not implemented for kind: %s", tf.Kind()))
}
return w.Bytes(), nil
}
func unmarshal(buf []byte, v interface{}, meta *Metadata) (interface{}, error) {
tf := reflect.TypeOf(v)
vf := reflect.ValueOf(v)
bm, ok := v.(BinaryMarshallable)
if ok {
// Custom marshallable interface found.
if err := bm.UnmarshalBinary(buf, meta); err != nil {
return nil, err
}
return bm, nil
}
if tf.Kind() == reflect.Ptr {
vf = reflect.ValueOf(v).Elem()
tf = vf.Type()
}
if meta == nil {
meta = &Metadata{
Tags: &TagMap{},
Lens: make(map[string]uint64),
Parent: v,
ParentBuf: buf,
Offsets: make(map[string]uint64),
CurrOffset: 0,
}
}
r := bytes.NewBuffer(buf)
switch tf.Kind() {
case reflect.Struct:
m := &Metadata{
Tags: &TagMap{},
Lens: make(map[string]uint64),
Parent: v,
ParentBuf: buf,
Offsets: make(map[string]uint64),
CurrOffset: 0,
}
for i := 0; i < tf.NumField(); i++ {
m.CurrField = tf.Field(i).Name
tags, err := parseTags(tf.Field(i))
if err != nil {
return nil, err
}
m.Tags = tags
var data interface{}
switch tf.Field(i).Type.Kind() {
case reflect.Struct:
data, err = unmarshal(buf[m.CurrOffset:], vf.Field(i).Addr().Interface(), m)
default:
data, err = unmarshal(buf[m.CurrOffset:], vf.Field(i).Interface(), m)
}
if err != nil {
return nil, err
}
vf.Field(i).Set(reflect.ValueOf(data))
}
v = reflect.Indirect(reflect.ValueOf(v)).Interface()
meta.CurrOffset += m.CurrOffset
return v, nil
case reflect.Uint8:
var ret uint8
if err := binary.Read(r, binary.LittleEndian, &ret); err != nil {
return nil, err
}
meta.CurrOffset += uint64(binary.Size(ret))
return ret, nil
case reflect.Uint16:
var ret uint16
if err := binary.Read(r, binary.LittleEndian, &ret); err != nil {
return nil, err
}
if meta.Tags.Has("len") {
ref, err := meta.Tags.GetString("len")
if err != nil {
return nil, err
}
meta.Lens[ref] = uint64(ret)
}
meta.CurrOffset += uint64(binary.Size(ret))
return ret, nil
case reflect.Uint32:
var ret uint32
if err := binary.Read(r, binary.LittleEndian, &ret); err != nil {
return nil, err
}
if meta.Tags.Has("offset") {
ref, err := meta.Tags.GetString("offset")
if err != nil {
return nil, err
}
meta.Offsets[ref] = uint64(ret)
}
meta.CurrOffset += uint64(binary.Size(ret))
return ret, nil
case reflect.Uint64:
var ret uint64
if err := binary.Read(r, binary.LittleEndian, &ret); err != nil {
return nil, err
}
meta.CurrOffset += uint64(binary.Size(ret))
return ret, nil
case reflect.Slice, reflect.Array:
switch tf.Elem().Kind() {
case reflect.Uint8:
var l, o int
var err error
if meta.Tags.Has("fixed") {
if l, err = meta.Tags.GetInt("fixed"); err != nil {
return nil, err
}
// Fixed length fields advance current offset
meta.CurrOffset += uint64(l)
} else {
if val, ok := meta.Lens[meta.CurrField]; ok {
l = int(val)
} else {
return nil, errors.New("Variable length field missing length reference in struct: " + meta.CurrField)
}
if val, ok := meta.Offsets[meta.CurrField]; ok {
o = int(val)
} else {
// No offset found in map. Use current offset
o = int(meta.CurrOffset)
}
// Variable length data is relative to parent/outer struct. Reset reader to point to beginning of data
r = bytes.NewBuffer(meta.ParentBuf[o : o+l])
// Variable length data fields do NOT advance current offset.
}
data := make([]byte, l)
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
return nil, err
}
return data, nil
case reflect.Uint16:
return errors.New("Unmarshal not implemented for slice kind:" + tf.Kind().String()), nil
}
default:
return errors.New("Unmarshal not implemented for kind:" + tf.Kind().String()), nil
}
return nil, nil
}
func Unmarshal(buf []byte, v interface{}) error {
_, err := unmarshal(buf, v, nil)
return err
}

View File

@ -0,0 +1,53 @@
/*
* MIT License
*
* Copyright (c) 2017 stacktitan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package encoder
import (
"bytes"
"encoding/binary"
"errors"
"unicode/utf16"
)
func FromUnicode(d []byte) (string, error) {
// Credit to https://github.com/Azure/go-ntlmssp/blob/master/unicode.go for logic
if len(d)%2 > 0 {
return "", errors.New("Unicode (UTF 16 LE) specified, but uneven data length")
}
s := make([]uint16, len(d)/2)
err := binary.Read(bytes.NewReader(d), binary.LittleEndian, &s)
if err != nil {
return "", err
}
return string(utf16.Decode(s)), nil
}
func ToUnicode(s string) []byte {
// Credit to https://github.com/Azure/go-ntlmssp/blob/master/unicode.go for logic
uints := utf16.Encode([]rune(s))
b := bytes.Buffer{}
binary.Write(&b, binary.LittleEndian, &uints)
return b.Bytes()
}

136
lib/smb/gss/gss.go Normal file
View File

@ -0,0 +1,136 @@
/*
* MIT License
*
* Copyright (c) 2017 stacktitan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package gss
import (
"encoding/asn1"
"log"
"github.com/zmap/zgrab2/lib/smb/encoder"
)
const SpnegoOid = "1.3.6.1.5.5.2"
const NtLmSSPMechTypeOid = "1.3.6.1.4.1.311.2.2.10"
const GssStateAcceptCompleted = 0
const GssStateAcceptIncomplete = 1
const GssStateReject = 2
const GssStateRequestMic = 3
type NegTokenInitData struct {
MechTypes []asn1.ObjectIdentifier `asn1:"explicit,tag:0"`
ReqFlags asn1.BitString `asn1:"explicit,optional,omitempty,tag:1"`
MechToken []byte `asn1:"explicit,optional,omitempty,tag:2"`
MechTokenMIC []byte `asn1:"explicit,optional,omitempty,tag:3"`
}
type NegTokenInit struct {
OID asn1.ObjectIdentifier
Data NegTokenInitData `asn1:"explicit"`
}
type NegTokenResp struct {
State asn1.Enumerated `asn1:"explicit,optional,omitempty,tag:0"`
SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,omitempty,tag:1"`
ResponseToken []byte `asn1:"explicit,optional,omitempty,tag:2"`
MechListMIC []byte `asn1:"explicit,optional,omitempty,tag:3"`
}
// gsswrapped used to force ASN1 encoding to include explicit sequence tags
// Type does not fulfill the BinaryMarshallable interface and is used only as a
// helper to marshal a NegTokenResp
type gsswrapped struct{ G interface{} }
func NewNegTokenInit() (NegTokenInit, error) {
oid, err := ObjectIDStrToInt(SpnegoOid)
if err != nil {
return NegTokenInit{}, err
}
ntlmoid, err := ObjectIDStrToInt(NtLmSSPMechTypeOid)
if err != nil {
return NegTokenInit{}, err
}
return NegTokenInit{
OID: oid,
Data: NegTokenInitData{
MechTypes: []asn1.ObjectIdentifier{ntlmoid},
ReqFlags: asn1.BitString{},
MechToken: []byte{},
MechTokenMIC: []byte{},
},
}, nil
}
func NewNegTokenResp() (NegTokenResp, error) {
return NegTokenResp{}, nil
}
func (n *NegTokenInit) MarshalBinary(meta *encoder.Metadata) ([]byte, error) {
buf, err := asn1.Marshal(*n)
if err != nil {
log.Panicln(err)
return nil, err
}
// When marshalling struct, asn1 uses 30 (sequence) tag by default.
// Override to set 60 (application) to remain consistent with GSS/SMB
buf[0] = 0x60
return buf, nil
}
func (n *NegTokenInit) UnmarshalBinary(buf []byte, meta *encoder.Metadata) error {
data := NegTokenInit{}
if _, err := asn1.UnmarshalWithParams(buf, &data, "application"); err != nil {
return err
}
*n = data
return nil
}
func (r *NegTokenResp) MarshalBinary(meta *encoder.Metadata) ([]byte, error) {
// Oddities in Go's ASN1 package vs SMB encoding mean we have to wrap our
// struct in another struct to ensure proper tags and lengths are added
// to encoded data
wrapped := &gsswrapped{*r}
return wrapped.MarshalBinary(meta)
}
func (r *NegTokenResp) UnmarshalBinary(buf []byte, meta *encoder.Metadata) error {
data := NegTokenResp{}
if _, err := asn1.UnmarshalWithParams(buf, &data, "explicit,tag:1"); err != nil {
return err
}
*r = data
return nil
}
func (g *gsswrapped) MarshalBinary(meta *encoder.Metadata) ([]byte, error) {
buf, err := asn1.Marshal(*g)
if err != nil {
return nil, err
}
buf[0] = 0xa1
return buf, nil
}

42
lib/smb/gss/oid.go Normal file
View File

@ -0,0 +1,42 @@
/*
* MIT License
*
* Copyright (c) 2017 stacktitan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package gss
import (
"strconv"
"strings"
)
func ObjectIDStrToInt(oid string) ([]int, error) {
ret := []int{}
tokens := strings.Split(oid, ".")
for _, token := range tokens {
i, err := strconv.Atoi(token)
if err != nil {
return nil, err
}
ret = append(ret, i)
}
return ret, nil
}

5
lib/smb/log.go Normal file
View File

@ -0,0 +1,5 @@
package smb
type SMBLog struct {
SupportV1 bool `json:"smbv1_support"`
}

161
lib/smb/session.go Normal file
View File

@ -0,0 +1,161 @@
/*
* MIT License
*
* Copyright (c) 2017 stacktitan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package smb
import (
"bufio"
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"io"
"log"
"net"
"github.com/zmap/zgrab2/lib/smb/encoder"
)
type Session struct {
IsSigningRequired bool
IsAuthenticated bool
debug bool
securityMode uint16
messageID uint64
sessionID uint64
conn net.Conn
dialect uint16
options Options
trees map[string]uint32
}
type Options struct {
Host string
Port int
Workstation string
Domain string
User string
Password string
Hash string
}
func GetSMBBanner(logStruct *SMBLog, conn net.Conn) (err error) {
opt := Options{}
s := &Session{
IsSigningRequired: false,
IsAuthenticated: false,
debug: false,
securityMode: 0,
messageID: 0,
sessionID: 0,
dialect: 0,
conn: conn,
options: opt,
trees: make(map[string]uint32),
}
err = s.NegotiateProtocol(logStruct)
return err
}
func (s *Session) NegotiateProtocol(logStruct *SMBLog) error {
negReq := s.NewNegotiateReq()
buf, err := s.send(negReq, logStruct)
if err != nil {
return err
}
negRes := NewNegotiateRes()
if err := encoder.Unmarshal(buf, &negRes); err != nil {
s.Debug("Raw:\n"+hex.Dump(buf), err)
}
if negRes.Header.Status != StatusOk {
return nil
}
s.conn.Close()
return nil
}
func (s *Session) send(req interface{}, logStruct *SMBLog) (res []byte, err error) {
buf, err := encoder.Marshal(req)
if err != nil {
s.Debug("", err)
return nil, err
}
b := new(bytes.Buffer)
if err = binary.Write(b, binary.BigEndian, uint32(len(buf))); err != nil {
s.Debug("", err)
return
}
rw := bufio.NewReadWriter(bufio.NewReader(s.conn), bufio.NewWriter(s.conn))
if _, err = rw.Write(append(b.Bytes(), buf...)); err != nil {
s.Debug("", err)
return
}
rw.Flush()
var size uint32
if err = binary.Read(rw, binary.BigEndian, &size); err != nil {
s.Debug("", err)
return
}
if size > 0x00FFFFFF {
return nil, errors.New("invalid NetBIOS Session message")
}
data := make([]byte, size)
l, err := io.ReadFull(rw, data)
if err != nil {
s.Debug("", err)
return nil, err
}
if uint32(l) != size || len(data) < 4 {
return nil, errors.New("message size invalid")
}
//First four bytes contain protocol ID
protID := data[0:4]
switch string(protID) {
default:
return nil, errors.New("protocol not implemented")
case ProtocolSmb:
logStruct.SupportV1 = true
}
s.messageID++
return data, nil
}
func (s *Session) Debug(msg string, err error) {
if s.debug {
log.Println("[ DEBUG ] ", msg)
}
}

221
lib/smb/smb.go Normal file
View File

@ -0,0 +1,221 @@
/*
* MIT License
*
* Copyright (c) 2017 stacktitan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package smb
import (
"github.com/zmap/zgrab2/lib/smb/gss"
)
const ProtocolSmb = "\xFFSMB"
const ProtocolSmb2 = "\xFESMB"
const StatusOk = 0x00000000
const StatusMoreProcessingRequired = 0xc0000016
const StatusInvalidParameter = 0xc000000d
const StatusLogonFailure = 0xc000006d
const StatusUserSessionDeleted = 0xc0000203
var StatusMap = map[uint32]string{
StatusOk: "OK",
StatusMoreProcessingRequired: "More Processing Required",
StatusInvalidParameter: "Invalid Parameter",
StatusLogonFailure: "Logon failed",
StatusUserSessionDeleted: "User session deleted",
}
const DialectSmb_2_0_2 = 0x0202
const DialectSmb_2_1 = 0x0210
const DialectSmb_3_0 = 0x0300
const DialectSmb_3_0_2 = 0x0302
const DialectSmb_3_1_1 = 0x0311
const DialectSmb2_ALL = 0x02FF
const (
CommandNegotiate uint16 = iota
CommandSessionSetup
CommandLogoff
CommandTreeConnect
CommandTreeDisconnect
CommandCreate
CommandClose
CommandFlush
CommandRead
CommandWrite
CommandLock
CommandIOCtl
CommandCancel
CommandEcho
CommandQueryDirectory
CommandChangeNotify
CommandQueryInfo
CommandSetInfo
CommandOplockBreak
)
const (
_ uint16 = iota
SecurityModeSigningEnabled
SecurityModeSigningRequired
)
const (
_ byte = iota
ShareTypeDisk
ShareTypePipe
ShareTypePrint
)
const (
ShareFlagManualCaching uint32 = 0x00000000
ShareFlagAutoCaching uint32 = 0x00000010
ShareFlagVDOCaching uint32 = 0x00000020
ShareFlagNoCaching uint32 = 0x00000030
ShareFlagDFS uint32 = 0x00000001
ShareFlagDFSRoot uint32 = 0x00000002
ShareFlagRestriceExclusiveOpens uint32 = 0x00000100
ShareFlagForceSharedDelete uint32 = 0x00000200
ShareFlagAllowNamespaceCaching uint32 = 0x00000400
ShareFlagAccessBasedDirectoryEnum uint32 = 0x00000800
ShareFlagForceLevelIIOplock uint32 = 0x00001000
ShareFlagEnableHashV1 uint32 = 0x00002000
ShareFlagEnableHashV2 uint32 = 0x00004000
ShareFlagEncryptData uint32 = 0x00008000
)
const (
ShareCapDFS uint32 = 0x00000008
ShareCapContinuousAvailability uint32 = 0x00000010
ShareCapScaleout uint32 = 0x00000020
ShareCapCluster uint32 = 0x00000040
ShareCapAsymmetric uint32 = 0x00000080
)
type Header struct {
ProtocolID []byte `smb:"fixed:4"`
StructureSize uint16
CreditCharge uint16
Status uint32
Command uint16
Credits uint16
Flags uint32
NextCommand uint32
MessageID uint64
Reserved uint32
TreeID uint32
SessionID uint64
Signature []byte `smb:"fixed:16"`
}
type NegotiateReq struct {
Header
StructureSize uint16
DialectCount uint16 `smb:"count:Dialects"`
SecurityMode uint16
Reserved uint16
Capabilities uint32
ClientGuid []byte `smb:"fixed:16"`
ClientStartTime uint64
Dialects []uint16
}
type NegotiateRes struct {
Header
StructureSize uint16
SecurityMode uint16
DialectRevision uint16
Reserved uint16
ServerGuid []byte `smb:"fixed:16"`
Capabilities uint32
MaxTransactSize uint32
MaxReadSize uint32
MaxWriteSize uint32
SystemTime uint64
ServerStartTime uint64
SecurityBufferOffset uint16 `smb:"offset:SecurityBlob"`
SecurityBufferLength uint16 `smb:"len:SecurityBlob"`
Reserved2 uint32
SecurityBlob *gss.NegTokenInit
}
func newHeader() Header {
return Header{
ProtocolID: []byte(ProtocolSmb),
StructureSize: 64,
CreditCharge: 0,
Status: 0,
Command: 0,
Credits: 0,
Flags: 0,
NextCommand: 0,
MessageID: 0,
Reserved: 0,
TreeID: 0,
SessionID: 0,
Signature: make([]byte, 16),
}
}
func (s *Session) NewNegotiateReq() NegotiateReq {
header := newHeader()
header.Command = CommandNegotiate
header.CreditCharge = 1
header.MessageID = s.messageID
dialects := []uint16{
uint16(DialectSmb_2_1),
}
return NegotiateReq{
Header: header,
StructureSize: 36,
DialectCount: uint16(len(dialects)),
SecurityMode: SecurityModeSigningEnabled,
Reserved: 0,
Capabilities: 0,
ClientGuid: make([]byte, 16),
ClientStartTime: 0,
Dialects: dialects,
}
}
func NewNegotiateRes() NegotiateRes {
return NegotiateRes{
Header: newHeader(),
StructureSize: 0,
SecurityMode: 0,
DialectRevision: 0,
Reserved: 0,
ServerGuid: make([]byte, 16),
Capabilities: 0,
MaxTransactSize: 0,
MaxReadSize: 0,
MaxWriteSize: 0,
SystemTime: 0,
ServerStartTime: 0,
SecurityBufferOffset: 0,
SecurityBufferLength: 0,
Reserved2: 0,
SecurityBlob: &gss.NegTokenInit{},
}
}

7
modules/smb.go Normal file
View File

@ -0,0 +1,7 @@
package modules
import "github.com/zmap/zgrab2/modules/smb"
func init() {
smb.RegisterModule()
}

106
modules/smb/scanner.go Normal file
View File

@ -0,0 +1,106 @@
// Package smb provides a zgrab2 module that scans for smb.
// This was ported directly from zgrab.
package smb
import (
log "github.com/sirupsen/logrus"
"github.com/zmap/zgrab2"
"github.com/zmap/zgrab2/lib/smb"
)
// ScanResults instances are returned by the module's Scan function.
type ScanResults struct {
smb.SMBLog
}
// Flags holds the command-line configuration for the smb scan module.
// Populated by the framework.
type Flags struct {
zgrab2.BaseFlags
Verbose bool `long:"verbose" description:"More verbose logging, include debug fields in the scan results"`
}
// Module implements the zgrab2.Module interface.
type Module struct {
}
// Scanner implements the zgrab2.Scanner interface.
type Scanner struct {
config *Flags
}
// RegisterModule registers the zgrab2 module.
func RegisterModule() {
var module Module
_, err := zgrab2.AddCommand("smb", "smb", "Probe for smb", 445, &module)
if err != nil {
log.Fatal(err)
}
}
// NewFlags returns a default Flags object.
func (module *Module) NewFlags() interface{} {
return new(Flags)
}
// NewScanner returns a new Scanner instance.
func (module *Module) NewScanner() zgrab2.Scanner {
return new(Scanner)
}
// Validate checks that the flags are valid.
// On success, returns nil.
// On failure, returns an error instance describing the error.
func (flags *Flags) Validate(args []string) error {
return nil
}
// Help returns the module's help string.
func (flags *Flags) Help() string {
return ""
}
// Init initializes the Scanner.
func (scanner *Scanner) Init(flags zgrab2.ScanFlags) error {
f, _ := flags.(*Flags)
scanner.config = f
return nil
}
// InitPerSender initializes the scanner for a given sender.
func (scanner *Scanner) InitPerSender(senderID int) error {
return nil
}
// GetName returns the Scanner name defined in the Flags.
func (scanner *Scanner) GetName() string {
return scanner.config.Name
}
// Protocol returns the protocol identifier of the scan.
func (scanner *Scanner) Protocol() string {
return "smb"
}
// GetPort returns the port being scanned.
func (scanner *Scanner) GetPort() uint {
return scanner.config.Port
}
// Scan performs the following:
// 1. Connect to the TCP port (default 445).
// 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)
if err != nil {
return zgrab2.TryGetScanStatus(err), nil, err
}
smbLog := smb.SMBLog{}
if err := smb.GetSMBBanner(&smbLog, conn); err != nil {
return zgrab2.TryGetScanStatus(err), nil, err
}
ret := ScanResults{SMBLog: smbLog}
return zgrab2.SCAN_SUCCESS, ret, nil
}

View File

@ -10,3 +10,4 @@ import schemas.redis
import schemas.smtp
import schemas.telnet
import schemas.pop3
import schemas.smb

18
schemas/smb.py Normal file
View File

@ -0,0 +1,18 @@
# zschema sub-schema for zgrab2's smb module
# Registers zgrab2-smb globally, and smb with the main zgrab2 schema.
from zschema.leaves import *
from zschema.compounds import *
import zschema.registry
import schemas.zcrypto as zcrypto
import schemas.zgrab2 as zgrab2
smb_scan_response = SubRecord({
"result": SubRecord({
"smbv1_support": Boolean(),
})
}, extends=zgrab2.base_scan_response)
zschema.registry.register_schema("zgrab2-smb", smb_scan_response)
zgrab2.register_scan_response_type("smb", smb_scan_response)