├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── gss ├── gss.go └── oid.go ├── ntlmssp ├── crypto.go ├── crypto_test.go ├── ntlmssp.go └── ntlmssp_test.go └── smb ├── encoder ├── encoder.go ├── encoder_test.go └── unicode.go ├── session.go └── smb.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | .idea 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 stacktitan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SMB 2 | A Go package for communicating over SMB. Currently only minimal funcationality exists for client-side functions. 3 | 4 | Here is a sample client that establishes a session with a server: 5 | 6 | ```go 7 | package main 8 | 9 | import ( 10 | "log" 11 | 12 | "github.com/projectdiscovery/smb/smb" 13 | ) 14 | 15 | func main() { 16 | 17 | host := "172.16.248.192" 18 | options := smb.Options{ 19 | Host: host, 20 | Port: 445, 21 | User: "alice", 22 | Domain: "corp", 23 | Workstation: "", 24 | Password: "Password123!", 25 | } 26 | debug := false 27 | session, err := smb.NewSession(options, debug) 28 | if err != nil { 29 | log.Fatalln("[!]", err) 30 | } 31 | defer session.Close() 32 | 33 | if session.IsSigningRequired { 34 | log.Println("[-] Signing is required") 35 | } else { 36 | log.Println("[+] Signing is NOT required") 37 | } 38 | 39 | if session.IsAuthenticated { 40 | log.Println("[+] Login successful") 41 | } else { 42 | log.Println("[-] Login failed") 43 | } 44 | 45 | if err != nil { 46 | log.Fatalln("[!]", err) 47 | } 48 | } 49 | 50 | ``` 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/projectdiscovery/smb 2 | 3 | go 1.19 4 | 5 | require golang.org/x/crypto v0.1.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= 2 | golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 3 | -------------------------------------------------------------------------------- /gss/gss.go: -------------------------------------------------------------------------------- 1 | package gss 2 | 3 | import ( 4 | "encoding/asn1" 5 | "log" 6 | 7 | "github.com/projectdiscovery/smb/smb/encoder" 8 | ) 9 | 10 | const SpnegoOid = "1.3.6.1.5.5.2" 11 | const NtLmSSPMechTypeOid = "1.3.6.1.4.1.311.2.2.10" 12 | 13 | const GssStateAcceptCompleted = 0 14 | const GssStateAcceptIncomplete = 1 15 | const GssStateReject = 2 16 | const GssStateRequestMic = 3 17 | 18 | type NegTokenInitData struct { 19 | MechTypes []asn1.ObjectIdentifier `asn1:"explicit,tag:0"` 20 | ReqFlags asn1.BitString `asn1:"explicit,optional,omitempty,tag:1"` 21 | MechToken []byte `asn1:"explicit,optional,omitempty,tag:2"` 22 | MechTokenMIC []byte `asn1:"explicit,optional,omitempty,tag:3"` 23 | } 24 | 25 | type NegTokenInit struct { 26 | OID asn1.ObjectIdentifier 27 | Data NegTokenInitData `asn1:"explicit"` 28 | } 29 | 30 | type NegTokenResp struct { 31 | State asn1.Enumerated `asn1:"explicit,optional,omitempty,tag:0"` 32 | SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,omitempty,tag:1"` 33 | ResponseToken []byte `asn1:"explicit,optional,omitempty,tag:2"` 34 | MechListMIC []byte `asn1:"explicit,optional,omitempty,tag:3"` 35 | } 36 | 37 | // gsswrapped used to force ASN1 encoding to include explicit sequence tags 38 | // Type does not fulfill the BinaryMarshallable interfce and is used only as a 39 | // helper to marshal a NegTokenResp 40 | type gsswrapped struct{ G interface{} } 41 | 42 | func NewNegTokenInit() (NegTokenInit, error) { 43 | oid, err := ObjectIDStrToInt(SpnegoOid) 44 | if err != nil { 45 | return NegTokenInit{}, err 46 | } 47 | ntlmoid, err := ObjectIDStrToInt(NtLmSSPMechTypeOid) 48 | if err != nil { 49 | return NegTokenInit{}, err 50 | } 51 | return NegTokenInit{ 52 | OID: oid, 53 | Data: NegTokenInitData{ 54 | MechTypes: []asn1.ObjectIdentifier{ntlmoid}, 55 | ReqFlags: asn1.BitString{}, 56 | MechToken: []byte{}, 57 | MechTokenMIC: []byte{}, 58 | }, 59 | }, nil 60 | } 61 | 62 | func NewNegTokenResp() (NegTokenResp, error) { 63 | return NegTokenResp{}, nil 64 | } 65 | 66 | func (n *NegTokenInit) MarshalBinary(meta *encoder.Metadata) ([]byte, error) { 67 | buf, err := asn1.Marshal(*n) 68 | if err != nil { 69 | log.Panicln(err) 70 | return nil, err 71 | } 72 | 73 | // When marshalling struct, asn1 uses 30 (sequence) tag by default. 74 | // Override to set 60 (application) to remain consistent with GSS/SMB 75 | buf[0] = 0x60 76 | return buf, nil 77 | } 78 | 79 | func (n *NegTokenInit) UnmarshalBinary(buf []byte, meta *encoder.Metadata) error { 80 | data := NegTokenInit{} 81 | if _, err := asn1.UnmarshalWithParams(buf, &data, "application"); err != nil { 82 | return err 83 | } 84 | *n = data 85 | return nil 86 | } 87 | 88 | func (r *NegTokenResp) MarshalBinary(meta *encoder.Metadata) ([]byte, error) { 89 | // Oddities in Go's ASN1 package vs SMB encoding mean we have to wrap our 90 | // struct in another struct to ensure proper tags and lengths are added 91 | // to encoded data 92 | wrapped := &gsswrapped{*r} 93 | return wrapped.MarshalBinary(meta) 94 | } 95 | 96 | func (r *NegTokenResp) UnmarshalBinary(buf []byte, meta *encoder.Metadata) error { 97 | data := NegTokenResp{} 98 | if _, err := asn1.UnmarshalWithParams(buf, &data, "explicit,tag:1"); err != nil { 99 | return err 100 | } 101 | *r = data 102 | return nil 103 | } 104 | 105 | func (g *gsswrapped) MarshalBinary(meta *encoder.Metadata) ([]byte, error) { 106 | buf, err := asn1.Marshal(*g) 107 | if err != nil { 108 | return nil, err 109 | } 110 | buf[0] = 0xa1 111 | return buf, nil 112 | } 113 | -------------------------------------------------------------------------------- /gss/oid.go: -------------------------------------------------------------------------------- 1 | package gss 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | func ObjectIDStrToInt(oid string) ([]int, error) { 9 | ret := []int{} 10 | tokens := strings.Split(oid, ".") 11 | for _, token := range tokens { 12 | i, err := strconv.Atoi(token) 13 | if err != nil { 14 | return nil, err 15 | } 16 | ret = append(ret, i) 17 | } 18 | return ret, nil 19 | } 20 | -------------------------------------------------------------------------------- /ntlmssp/crypto.go: -------------------------------------------------------------------------------- 1 | package ntlmssp 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "strings" 7 | 8 | "github.com/projectdiscovery/smb/smb/encoder" 9 | "golang.org/x/crypto/md4" 10 | ) 11 | 12 | func Ntowfv1(pass string) []byte { 13 | hash := md4.New() 14 | hash.Write(encoder.ToUnicode(pass)) 15 | return hash.Sum(nil) 16 | } 17 | 18 | func Ntowfv2(pass, user, domain string) []byte { 19 | h := hmac.New(md5.New, Ntowfv1(pass)) 20 | h.Write(encoder.ToUnicode(strings.ToUpper(user) + domain)) 21 | return h.Sum(nil) 22 | } 23 | 24 | func Lmowfv2(pass, user, domain string) []byte { 25 | return Ntowfv2(pass, user, domain) 26 | } 27 | 28 | func ComputeResponseNTLMv2(nthash, lmhash, clientChallenge, serverChallenge, timestamp, serverName []byte) []byte { 29 | 30 | temp := []byte{1, 1} 31 | temp = append(temp, 0, 0, 0, 0, 0, 0) 32 | temp = append(temp, timestamp...) 33 | temp = append(temp, clientChallenge...) 34 | temp = append(temp, 0, 0, 0, 0) 35 | temp = append(temp, serverName...) 36 | temp = append(temp, 0, 0, 0, 0) 37 | 38 | h := hmac.New(md5.New, nthash) 39 | h.Write(append(serverChallenge, temp...)) 40 | ntproof := h.Sum(nil) 41 | return append(ntproof, temp...) 42 | } 43 | -------------------------------------------------------------------------------- /ntlmssp/crypto_test.go: -------------------------------------------------------------------------------- 1 | package ntlmssp 2 | 3 | -------------------------------------------------------------------------------- /ntlmssp/ntlmssp.go: -------------------------------------------------------------------------------- 1 | package ntlmssp 2 | 3 | import ( 4 | "bytes" 5 | "crypto/hmac" 6 | "crypto/md5" 7 | "crypto/rand" 8 | "encoding/binary" 9 | "errors" 10 | "fmt" 11 | "time" 12 | 13 | "encoding/hex" 14 | 15 | "github.com/projectdiscovery/smb/smb/encoder" 16 | ) 17 | 18 | const Signature = "NTLMSSP\x00" 19 | 20 | const ( 21 | _ uint32 = iota 22 | TypeNtLmNegotiate 23 | TypeNtLmChallenge 24 | TypeNtLmAuthenticate 25 | ) 26 | 27 | const ( 28 | FlgNegUnicode uint32 = 1 << iota 29 | FlgNegOEM 30 | FlgNegRequestTarget 31 | FlgNegReserved10 32 | FlgNegSign 33 | FlgNegSeal 34 | FlgNegDatagram 35 | FlgNegLmKey 36 | FlgNegReserved9 37 | FlgNegNtLm 38 | FlgNegReserved8 39 | FlgNegAnonymous 40 | FlgNegOEMDomainSupplied 41 | FlgNegOEMWorkstationSupplied 42 | FlgNegReserved7 43 | FlgNegAlwaysSign 44 | FlgNegTargetTypeDomain 45 | FlgNegTargetTypeServer 46 | FlgNegReserved6 47 | FlgNegExtendedSessionSecurity 48 | FlgNegIdentify 49 | FlgNegReserved5 50 | FlgNegRequestNonNtSessionKey 51 | FlgNegTargetInfo 52 | FlgNegReserved4 53 | FlgNegVersion 54 | FlgNegReserved3 55 | FlgNegReserved2 56 | FlgNegReserved1 57 | FlgNeg128 58 | FlgNegKeyExch 59 | FlgNeg56 60 | ) 61 | 62 | const ( 63 | MsvAvEOL uint16 = iota 64 | MsvAvNbComputerName 65 | MsvAvNbDomainName 66 | MsvAvDnsComputerName 67 | MsvAvDnsDomainName 68 | MsvAvDnsTreeName 69 | MsvAvFlags 70 | MsvAvTimestamp 71 | MsvAvSingleHost 72 | MsvAvTargetName 73 | MsvChannelBindings 74 | ) 75 | 76 | type Header struct { 77 | Signature []byte `smb:"fixed:8"` 78 | MessageType uint32 79 | } 80 | 81 | type Negotiate struct { 82 | Header 83 | NegotiateFlags uint32 84 | DomainNameLen uint16 `smb:"len:DomainName"` 85 | DomainNameMaxLen uint16 `smb:"len:DomainName"` 86 | DomainNameBufferOffset uint32 `smb:"offset:DomainName"` 87 | WorkstationLen uint16 `smb:"len:Workstation"` 88 | WorkstationMaxLen uint16 `smb:"len:Workstation"` 89 | WorkstationBufferOffset uint32 `smb:"offset:Workstation"` 90 | DomainName []byte 91 | Workstation []byte 92 | } 93 | 94 | type Challenge struct { 95 | Header 96 | TargetNameLen uint16 `smb:"len:TargetName"` 97 | TargetNameMaxLen uint16 `smb:"len:TargetName"` 98 | TargetNameBufferOffset uint32 `smb:"offset:TargetName"` 99 | NegotiateFlags uint32 100 | ServerChallenge uint64 101 | Reserved uint64 102 | TargetInfoLen uint16 `smb:"len:TargetInfo"` 103 | TargetInfoMaxLen uint16 `smb:"len:TargetInfo"` 104 | TargetInfoBufferOffset uint32 `smb:"offset:TargetInfo"` 105 | Version uint64 106 | TargetName []byte 107 | TargetInfo *AvPairSlice 108 | } 109 | 110 | type Authenticate struct { 111 | Header 112 | LmChallengeResponseLen uint16 `smb:"len:LmChallengeResponse"` 113 | LmChallengeResponseMaxLen uint16 `smb:"len:LmChallengeResponse"` 114 | LmChallengeResponseBufferOffset uint32 `smb:"offset:LmChallengeResponse"` 115 | NtChallengeResponseLen uint16 `smb:"len:NtChallengeResponse"` 116 | NtChallengeResponseMaxLen uint16 `smb:"len:NtChallengeResponse"` 117 | NtChallengResponseBufferOffset uint32 `smb:"offset:NtChallengeResponse"` 118 | DomainNameLen uint16 `smb:"len:DomainName"` 119 | DomainNameMaxLen uint16 `smb:"len:DomainName"` 120 | DomainNameBufferOffset uint32 `smb:"offset:DomainName"` 121 | UserNameLen uint16 `smb:"len:UserName"` 122 | UserNameMaxLen uint16 `smb:"len:UserName"` 123 | UserNameBufferOffset uint32 `smb:"offset:UserName"` 124 | WorkstationLen uint16 `smb:"len:Workstation"` 125 | WorkstationMaxLen uint16 `smb:"len:Workstation"` 126 | WorkstationBufferOffset uint32 `smb:"offset:Workstation"` 127 | EncryptedRandomSessionKeyLen uint16 `smb:"len:EncryptedRandomSessionKey"` 128 | EncryptedRandomSessionKeyMaxLen uint16 `smb:"len:EncryptedRandomSessionKey"` 129 | EncryptedRandomSessionKeyBufferOffset uint32 `smb:"offset:EncryptedRandomSessionKey"` 130 | NegotiateFlags uint32 131 | DomainName []byte `smb:"unicode"` 132 | UserName []byte `smb:"unicode"` 133 | Workstation []byte `smb:"unicode"` 134 | EncryptedRandomSessionKey []byte 135 | LmChallengeResponse []byte 136 | NtChallengeResponse []byte 137 | } 138 | 139 | type AvPair struct { 140 | AvID uint16 141 | AvLen uint16 `smb:"len:Value"` 142 | Value []byte 143 | } 144 | type AvPairSlice []AvPair 145 | 146 | func (p AvPair) Size() uint64 { 147 | return uint64(binary.Size(p.AvID) + binary.Size(p.AvLen) + int(p.AvLen)) 148 | } 149 | 150 | func (s *AvPairSlice) MarshalBinary(meta *encoder.Metadata) ([]byte, error) { 151 | var ret []byte 152 | w := bytes.NewBuffer(ret) 153 | for _, pair := range *s { 154 | buf, err := encoder.Marshal(pair) 155 | if err != nil { 156 | return nil, err 157 | } 158 | if err := binary.Write(w, binary.LittleEndian, buf); err != nil { 159 | return nil, err 160 | } 161 | } 162 | return w.Bytes(), nil 163 | } 164 | 165 | func (s *AvPairSlice) UnmarshalBinary(buf []byte, meta *encoder.Metadata) error { 166 | slice := []AvPair{} 167 | l, ok := meta.Lens[meta.CurrField] 168 | if !ok { 169 | return errors.New(fmt.Sprintf("Cannot unmarshal field '%s'. Missing length\n", meta.CurrField)) 170 | } 171 | o, ok := meta.Offsets[meta.CurrField] 172 | if !ok { 173 | return errors.New(fmt.Sprintf("Cannot unmarshal field '%s'. Missing offset\n", meta.CurrField)) 174 | } 175 | for i := l; i > 0; { 176 | var avPair AvPair 177 | err := encoder.Unmarshal(meta.ParentBuf[o:o+i], &avPair) 178 | if err != nil { 179 | return err 180 | } 181 | slice = append(slice, avPair) 182 | size := avPair.Size() 183 | o += size 184 | i -= size 185 | } 186 | *s = slice 187 | return nil 188 | } 189 | 190 | func NewNegotiate(domainName, workstation string) Negotiate { 191 | return Negotiate{ 192 | Header: Header{ 193 | Signature: []byte(Signature), 194 | MessageType: TypeNtLmNegotiate, 195 | }, 196 | NegotiateFlags: FlgNeg56 | 197 | FlgNeg128 | 198 | FlgNegTargetInfo | 199 | FlgNegExtendedSessionSecurity | 200 | FlgNegOEMDomainSupplied | 201 | FlgNegNtLm | 202 | FlgNegRequestTarget | 203 | FlgNegUnicode, 204 | DomainNameLen: 0, 205 | DomainNameMaxLen: 0, 206 | DomainNameBufferOffset: 0, 207 | WorkstationLen: 0, 208 | WorkstationMaxLen: 0, 209 | WorkstationBufferOffset: 0, 210 | DomainName: []byte(domainName), 211 | Workstation: []byte(workstation), 212 | } 213 | } 214 | 215 | func NewChallenge() Challenge { 216 | return Challenge{ 217 | Header: Header{ 218 | Signature: []byte(Signature), 219 | MessageType: TypeNtLmChallenge, 220 | }, 221 | TargetNameLen: 0, 222 | TargetNameMaxLen: 0, 223 | TargetNameBufferOffset: 0, 224 | NegotiateFlags: FlgNeg56 | 225 | FlgNeg128 | 226 | FlgNegVersion | 227 | FlgNegTargetInfo | 228 | FlgNegExtendedSessionSecurity | 229 | FlgNegTargetTypeServer | 230 | FlgNegNtLm | 231 | FlgNegRequestTarget | 232 | FlgNegUnicode, 233 | ServerChallenge: 0, 234 | Reserved: 0, 235 | TargetInfoLen: 0, 236 | TargetInfoMaxLen: 0, 237 | TargetInfoBufferOffset: 0, 238 | Version: 0, 239 | TargetName: []byte{}, 240 | TargetInfo: new(AvPairSlice), 241 | } 242 | } 243 | 244 | func NewAuthenticatePass(domain, user, workstation, password string, c Challenge) Authenticate { 245 | // Assumes domain, user, and workstation are not unicode 246 | nthash := Ntowfv2(password, user, domain) 247 | lmhash := Lmowfv2(password, user, domain) 248 | return newAuthenticate(domain, user, workstation, nthash, lmhash, c) 249 | } 250 | 251 | func NewAuthenticateHash(domain, user, workstation, hash string, c Challenge) Authenticate { 252 | // Assumes domain, user, and workstation are not unicode 253 | buf := make([]byte, len(hash)/2) 254 | hex.Decode(buf, []byte(hash)) 255 | return newAuthenticate(domain, user, workstation, buf, buf, c) 256 | } 257 | 258 | func newAuthenticate(domain, user, workstation string, nthash, lmhash []byte, c Challenge) Authenticate { 259 | // Assumes domain, user, and workstation are not unicode 260 | var timestamp []byte 261 | for k, av := range *c.TargetInfo { 262 | if av.AvID == MsvAvTimestamp { 263 | timestamp = (*c.TargetInfo)[k].Value 264 | } 265 | } 266 | if timestamp == nil { 267 | // Credit to https://github.com/Azure/go-ntlmssp/blob/master/unicode.go for logic 268 | ft := uint64(time.Now().UnixNano()) / 100 269 | ft += 116444736000000000 // add time between unix & windows offset 270 | timestamp = make([]byte, 8) 271 | binary.LittleEndian.PutUint64(timestamp, ft) 272 | } 273 | 274 | clientChallenge := make([]byte, 8) 275 | rand.Reader.Read(clientChallenge) 276 | serverChallenge := make([]byte, 8) 277 | w := bytes.NewBuffer(make([]byte, 0)) 278 | binary.Write(w, binary.LittleEndian, c.ServerChallenge) 279 | serverChallenge = w.Bytes() 280 | w = bytes.NewBuffer(make([]byte, 0)) 281 | for _, av := range *c.TargetInfo { 282 | binary.Write(w, binary.LittleEndian, av.AvID) 283 | binary.Write(w, binary.LittleEndian, av.AvLen) 284 | binary.Write(w, binary.LittleEndian, av.Value) 285 | } 286 | response := ComputeResponseNTLMv2(nthash, lmhash, clientChallenge, serverChallenge, timestamp, w.Bytes()) 287 | 288 | h := hmac.New(md5.New, lmhash) 289 | h.Write(append(serverChallenge, clientChallenge...)) 290 | lmChallengeResponse := h.Sum(nil) 291 | lmChallengeResponse = append(lmChallengeResponse, clientChallenge...) 292 | 293 | return Authenticate{ 294 | Header: Header{ 295 | Signature: []byte(Signature), 296 | MessageType: TypeNtLmAuthenticate, 297 | }, 298 | DomainName: encoder.ToUnicode(domain), 299 | UserName: encoder.ToUnicode(user), 300 | Workstation: encoder.ToUnicode(workstation), 301 | NegotiateFlags: FlgNeg56 | 302 | FlgNeg128 | 303 | FlgNegTargetInfo | 304 | FlgNegExtendedSessionSecurity | 305 | FlgNegOEMDomainSupplied | 306 | FlgNegNtLm | 307 | FlgNegRequestTarget | 308 | FlgNegUnicode, 309 | NtChallengeResponse: response, 310 | LmChallengeResponse: lmChallengeResponse, 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /ntlmssp/ntlmssp_test.go: -------------------------------------------------------------------------------- 1 | package ntlmssp 2 | -------------------------------------------------------------------------------- /smb/encoder/encoder.go: -------------------------------------------------------------------------------- 1 | package encoder 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "reflect" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type BinaryMarshallable interface { 14 | MarshalBinary(*Metadata) ([]byte, error) 15 | UnmarshalBinary([]byte, *Metadata) error 16 | } 17 | 18 | type Metadata struct { 19 | Tags *TagMap 20 | Lens map[string]uint64 21 | Offsets map[string]uint64 22 | Parent interface{} 23 | ParentBuf []byte 24 | CurrOffset uint64 25 | CurrField string 26 | } 27 | 28 | type TagMap struct { 29 | m map[string]interface{} 30 | has map[string]bool 31 | } 32 | 33 | func (t TagMap) Has(key string) bool { 34 | return t.has[key] 35 | } 36 | 37 | func (t TagMap) Set(key string, val interface{}) { 38 | t.m[key] = val 39 | t.has[key] = true 40 | } 41 | 42 | func (t TagMap) Get(key string) interface{} { 43 | return t.m[key] 44 | } 45 | 46 | func (t TagMap) GetInt(key string) (int, error) { 47 | if !t.Has(key) { 48 | return 0, errors.New("Key does not exist in tag") 49 | } 50 | return t.Get(key).(int), nil 51 | } 52 | 53 | func (t TagMap) GetString(key string) (string, error) { 54 | if !t.Has(key) { 55 | return "", errors.New("Key does not exist in tag") 56 | } 57 | return t.Get(key).(string), nil 58 | } 59 | 60 | func parseTags(sf reflect.StructField) (*TagMap, error) { 61 | ret := &TagMap{ 62 | m: make(map[string]interface{}), 63 | has: make(map[string]bool), 64 | } 65 | tag := sf.Tag.Get("smb") 66 | smbTags := strings.Split(tag, ",") 67 | for _, smbTag := range smbTags { 68 | tokens := strings.Split(smbTag, ":") 69 | switch tokens[0] { 70 | case "len", "offset", "count": 71 | if len(tokens) != 2 { 72 | return nil, errors.New("Missing required tag data. Expecting key:val") 73 | } 74 | ret.Set(tokens[0], tokens[1]) 75 | case "fixed": 76 | if len(tokens) != 2 { 77 | return nil, errors.New("Missing required tag data. Expecting key:val") 78 | } 79 | i, err := strconv.Atoi(tokens[1]) 80 | if err != nil { 81 | return nil, err 82 | } 83 | ret.Set(tokens[0], i) 84 | case "asn1": 85 | ret.Set(tokens[0], true) 86 | } 87 | } 88 | 89 | return ret, nil 90 | } 91 | 92 | func getOffsetByFieldName(fieldName string, meta *Metadata) (uint64, error) { 93 | if meta == nil || meta.Tags == nil || meta.Parent == nil || meta.Lens == nil { 94 | return 0, errors.New("Cannot determine field offset. Missing required metadata") 95 | } 96 | var ret uint64 97 | var found bool 98 | parentvf := reflect.Indirect(reflect.ValueOf(meta.Parent)) 99 | // To determine offset, we loop through all fields of the struct, summing lengths of previous elements 100 | // until we reach our field 101 | for i := 0; i < parentvf.NumField(); i++ { 102 | tf := parentvf.Type().Field(i) 103 | if tf.Name == fieldName { 104 | found = true 105 | break 106 | } 107 | if l, ok := meta.Lens[tf.Name]; ok { 108 | // Length of field is in cache 109 | ret += l 110 | } else { 111 | // Not in cache. Must marshal field to determine length. Add to cache after 112 | buf, err := Marshal(parentvf.Field(i).Interface()) 113 | if err != nil { 114 | return 0, err 115 | } 116 | l := uint64(len(buf)) 117 | meta.Lens[tf.Name] = l 118 | ret += l 119 | } 120 | } 121 | if !found { 122 | return 0, errors.New("Cannot find field name within struct: " + fieldName) 123 | } 124 | return ret, nil 125 | } 126 | 127 | func getFieldLengthByName(fieldName string, meta *Metadata) (uint64, error) { 128 | var ret uint64 129 | if meta == nil || meta.Tags == nil || meta.Parent == nil || meta.Lens == nil { 130 | return 0, errors.New("Cannot determine field length. Missing required metadata") 131 | } 132 | 133 | // Check if length is stored in field length cache 134 | if val, ok := meta.Lens[fieldName]; ok { 135 | return uint64(val), nil 136 | } 137 | 138 | parentvf := reflect.Indirect(reflect.ValueOf(meta.Parent)) 139 | 140 | field := parentvf.FieldByName(fieldName) 141 | if !field.IsValid() { 142 | return 0, errors.New("Invalid field. Cannot determine length.") 143 | } 144 | 145 | bm, ok := field.Interface().(BinaryMarshallable) 146 | if ok { 147 | // Custom marshallable interface found. 148 | buf, err := bm.(BinaryMarshallable).MarshalBinary(meta) 149 | if err != nil { 150 | return 0, err 151 | } 152 | return uint64(len(buf)), nil 153 | } 154 | 155 | if field.Kind() == reflect.Ptr { 156 | field = field.Elem() 157 | } 158 | 159 | switch field.Kind() { 160 | case reflect.Struct: 161 | buf, err := Marshal(field.Interface()) 162 | if err != nil { 163 | return 0, err 164 | } 165 | ret = uint64(len(buf)) 166 | case reflect.Interface: 167 | return 0, errors.New("Interface length calculation not implemented") 168 | case reflect.Slice, reflect.Array: 169 | switch field.Type().Elem().Kind() { 170 | case reflect.Uint8: 171 | ret = uint64(len(field.Interface().([]byte))) 172 | default: 173 | return 0, errors.New("Cannot calculate the length of unknown slice type for " + fieldName) 174 | } 175 | case reflect.Uint8: 176 | ret = uint64(binary.Size(field.Interface().(uint8))) 177 | case reflect.Uint16: 178 | ret = uint64(binary.Size(field.Interface().(uint16))) 179 | case reflect.Uint32: 180 | ret = uint64(binary.Size(field.Interface().(uint32))) 181 | case reflect.Uint64: 182 | ret = uint64(binary.Size(field.Interface().(uint64))) 183 | default: 184 | return 0, errors.New("Cannot calculate the length of unknown kind for field " + fieldName) 185 | } 186 | meta.Lens[fieldName] = ret 187 | return ret, nil 188 | } 189 | 190 | func Marshal(v interface{}) ([]byte, error) { 191 | return marshal(v, nil) 192 | } 193 | 194 | func marshal(v interface{}, meta *Metadata) ([]byte, error) { 195 | var ret []byte 196 | typev := reflect.TypeOf(v) 197 | valuev := reflect.ValueOf(v) 198 | 199 | bm, ok := v.(BinaryMarshallable) 200 | if ok { 201 | // Custom marshallable interface found. 202 | buf, err := bm.MarshalBinary(meta) 203 | if err != nil { 204 | return nil, err 205 | } 206 | return buf, nil 207 | } 208 | 209 | if typev.Kind() == reflect.Ptr { 210 | valuev = reflect.Indirect(reflect.ValueOf(v)) 211 | typev = valuev.Type() 212 | } 213 | 214 | w := bytes.NewBuffer(ret) 215 | switch typev.Kind() { 216 | case reflect.Struct: 217 | m := &Metadata{ 218 | Tags: &TagMap{}, 219 | Lens: make(map[string]uint64), 220 | Parent: v, 221 | } 222 | for j := 0; j < valuev.NumField(); j++ { 223 | tags, err := parseTags(typev.Field(j)) 224 | if err != nil { 225 | return nil, err 226 | } 227 | m.Tags = tags 228 | buf, err := marshal(valuev.Field(j).Interface(), m) 229 | if err != nil { 230 | return nil, err 231 | } 232 | m.Lens[typev.Field(j).Name] = uint64(len(buf)) 233 | if err := binary.Write(w, binary.LittleEndian, buf); err != nil { 234 | return nil, err 235 | } 236 | } 237 | case reflect.Slice, reflect.Array: 238 | switch typev.Elem().Kind() { 239 | case reflect.Uint8: 240 | if err := binary.Write(w, binary.LittleEndian, v.([]uint8)); err != nil { 241 | return nil, err 242 | } 243 | case reflect.Uint16: 244 | if err := binary.Write(w, binary.LittleEndian, v.([]uint16)); err != nil { 245 | return nil, err 246 | } 247 | } 248 | case reflect.Uint8: 249 | if err := binary.Write(w, binary.LittleEndian, valuev.Interface().(uint8)); err != nil { 250 | return nil, err 251 | } 252 | case reflect.Uint16: 253 | data := valuev.Interface().(uint16) 254 | if meta != nil && meta.Tags.Has("len") { 255 | fieldName, err := meta.Tags.GetString("len") 256 | if err != nil { 257 | return nil, err 258 | } 259 | l, err := getFieldLengthByName(fieldName, meta) 260 | if err != nil { 261 | return nil, err 262 | } 263 | data = uint16(l) 264 | } 265 | if meta != nil && meta.Tags.Has("offset") { 266 | fieldName, err := meta.Tags.GetString("offset") 267 | if err != nil { 268 | return nil, err 269 | } 270 | l, err := getOffsetByFieldName(fieldName, meta) 271 | if err != nil { 272 | return nil, err 273 | } 274 | data = uint16(l) 275 | } 276 | if err := binary.Write(w, binary.LittleEndian, data); err != nil { 277 | return nil, err 278 | } 279 | case reflect.Uint32: 280 | data := valuev.Interface().(uint32) 281 | if meta != nil && meta.Tags.Has("len") { 282 | fieldName, err := meta.Tags.GetString("len") 283 | if err != nil { 284 | return nil, err 285 | } 286 | l, err := getFieldLengthByName(fieldName, meta) 287 | if err != nil { 288 | return nil, err 289 | } 290 | data = uint32(l) 291 | } 292 | if meta != nil && meta.Tags.Has("offset") { 293 | fieldName, err := meta.Tags.GetString("offset") 294 | if err != nil { 295 | return nil, err 296 | } 297 | l, err := getOffsetByFieldName(fieldName, meta) 298 | if err != nil { 299 | return nil, err 300 | } 301 | data = uint32(l) 302 | } 303 | if err := binary.Write(w, binary.LittleEndian, data); err != nil { 304 | return nil, err 305 | } 306 | case reflect.Uint64: 307 | if err := binary.Write(w, binary.LittleEndian, valuev.Interface().(uint64)); err != nil { 308 | return nil, err 309 | } 310 | default: 311 | return nil, errors.New(fmt.Sprintf("Marshal not implemented for kind: %s", typev.Kind())) 312 | } 313 | return w.Bytes(), nil 314 | } 315 | 316 | func unmarshal(buf []byte, v interface{}, meta *Metadata) (interface{}, error) { 317 | typev := reflect.TypeOf(v) 318 | valuev := reflect.ValueOf(v) 319 | 320 | bm, ok := v.(BinaryMarshallable) 321 | if ok { 322 | // Custom marshallable interface found. 323 | if err := bm.UnmarshalBinary(buf, meta); err != nil { 324 | return nil, err 325 | } 326 | return bm, nil 327 | } 328 | 329 | if typev.Kind() == reflect.Ptr { 330 | valuev = reflect.ValueOf(v).Elem() 331 | typev = valuev.Type() 332 | } 333 | 334 | if meta == nil { 335 | meta = &Metadata{ 336 | Tags: &TagMap{}, 337 | Lens: make(map[string]uint64), 338 | Parent: v, 339 | ParentBuf: buf, 340 | Offsets: make(map[string]uint64), 341 | CurrOffset: 0, 342 | } 343 | } 344 | 345 | r := bytes.NewBuffer(buf) 346 | switch typev.Kind() { 347 | case reflect.Struct: 348 | m := &Metadata{ 349 | Tags: &TagMap{}, 350 | Lens: make(map[string]uint64), 351 | Parent: v, 352 | ParentBuf: buf, 353 | Offsets: make(map[string]uint64), 354 | CurrOffset: 0, 355 | } 356 | for i := 0; i < typev.NumField(); i++ { 357 | m.CurrField = typev.Field(i).Name 358 | tags, err := parseTags(typev.Field(i)) 359 | if err != nil { 360 | return nil, err 361 | } 362 | m.Tags = tags 363 | var data interface{} 364 | switch typev.Field(i).Type.Kind() { 365 | case reflect.Struct: 366 | data, err = unmarshal(buf[m.CurrOffset:], valuev.Field(i).Addr().Interface(), m) 367 | default: 368 | data, err = unmarshal(buf[m.CurrOffset:], valuev.Field(i).Interface(), m) 369 | } 370 | if err != nil { 371 | return nil, err 372 | } 373 | valuev.Field(i).Set(reflect.ValueOf(data)) 374 | } 375 | v = reflect.Indirect(reflect.ValueOf(v)).Interface() 376 | meta.CurrOffset += m.CurrOffset 377 | return v, nil 378 | case reflect.Uint8: 379 | var ret uint8 380 | if err := binary.Read(r, binary.LittleEndian, &ret); err != nil { 381 | return nil, err 382 | } 383 | meta.CurrOffset += uint64(binary.Size(ret)) 384 | return ret, nil 385 | case reflect.Uint16: 386 | var ret uint16 387 | if err := binary.Read(r, binary.LittleEndian, &ret); err != nil { 388 | return nil, err 389 | } 390 | if meta.Tags.Has("len") { 391 | ref, err := meta.Tags.GetString("len") 392 | if err != nil { 393 | return nil, err 394 | } 395 | meta.Lens[ref] = uint64(ret) 396 | } 397 | meta.CurrOffset += uint64(binary.Size(ret)) 398 | return ret, nil 399 | case reflect.Uint32: 400 | var ret uint32 401 | if err := binary.Read(r, binary.LittleEndian, &ret); err != nil { 402 | return nil, err 403 | } 404 | if meta.Tags.Has("offset") { 405 | ref, err := meta.Tags.GetString("offset") 406 | if err != nil { 407 | return nil, err 408 | } 409 | meta.Offsets[ref] = uint64(ret) 410 | } 411 | meta.CurrOffset += uint64(binary.Size(ret)) 412 | return ret, nil 413 | case reflect.Uint64: 414 | var ret uint64 415 | if err := binary.Read(r, binary.LittleEndian, &ret); err != nil { 416 | return nil, err 417 | } 418 | meta.CurrOffset += uint64(binary.Size(ret)) 419 | return ret, nil 420 | case reflect.Slice, reflect.Array: 421 | switch typev.Elem().Kind() { 422 | case reflect.Uint8: 423 | var length, offset int 424 | var err error 425 | if meta.Tags.Has("fixed") { 426 | if length, err = meta.Tags.GetInt("fixed"); err != nil { 427 | return nil, err 428 | } 429 | // Fixed length fields advance current offset 430 | meta.CurrOffset += uint64(length) 431 | } else { 432 | if val, ok := meta.Lens[meta.CurrField]; ok { 433 | length = int(val) 434 | } else { 435 | return nil, errors.New("Variable length field missing length reference in struct: " + meta.CurrField) 436 | } 437 | if val, ok := meta.Offsets[meta.CurrField]; ok { 438 | offset = int(val) 439 | } else { 440 | // No offset found in map. Use current offset 441 | offset = int(meta.CurrOffset) 442 | } 443 | // Variable length data is relative to parent/outer struct. Reset reader to point to beginning of data 444 | r = bytes.NewBuffer(meta.ParentBuf[offset : offset+length]) 445 | // Variable length data fields do NOT advance current offset. 446 | } 447 | data := make([]byte, length) 448 | if err := binary.Read(r, binary.LittleEndian, &data); err != nil { 449 | return nil, err 450 | } 451 | return data, nil 452 | case reflect.Uint16: 453 | return errors.New("Unmarshal not implemented for slice kind:" + typev.Kind().String()), nil 454 | } 455 | default: 456 | return errors.New("Unmarshal not implemented for kind:" + typev.Kind().String()), nil 457 | } 458 | 459 | return nil, nil 460 | 461 | } 462 | 463 | func Unmarshal(buf []byte, v interface{}) error { 464 | _, err := unmarshal(buf, v, nil) 465 | return err 466 | } 467 | -------------------------------------------------------------------------------- /smb/encoder/encoder_test.go: -------------------------------------------------------------------------------- 1 | package encoder 2 | -------------------------------------------------------------------------------- /smb/encoder/unicode.go: -------------------------------------------------------------------------------- 1 | package encoder 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "unicode/utf16" 8 | ) 9 | 10 | func FromUnicode(d []byte) (string, error) { 11 | // Credit to https://github.com/Azure/go-ntlmssp/blob/master/unicode.go for logic 12 | if len(d)%2 > 0 { 13 | return "", errors.New("Unicode (UTF 16 LE) specified, but uneven data length") 14 | } 15 | s := make([]uint16, len(d)/2) 16 | err := binary.Read(bytes.NewReader(d), binary.LittleEndian, &s) 17 | if err != nil { 18 | return "", err 19 | } 20 | return string(utf16.Decode(s)), nil 21 | } 22 | 23 | func ToUnicode(s string) []byte { 24 | // Credit to https://github.com/Azure/go-ntlmssp/blob/master/unicode.go for logic 25 | uints := utf16.Encode([]rune(s)) 26 | b := bytes.Buffer{} 27 | binary.Write(&b, binary.LittleEndian, &uints) 28 | return b.Bytes() 29 | } 30 | -------------------------------------------------------------------------------- /smb/session.go: -------------------------------------------------------------------------------- 1 | package smb 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/asn1" 7 | "encoding/binary" 8 | "encoding/hex" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "log" 13 | "net" 14 | "runtime/debug" 15 | "time" 16 | 17 | "github.com/projectdiscovery/smb/gss" 18 | "github.com/projectdiscovery/smb/ntlmssp" 19 | "github.com/projectdiscovery/smb/smb/encoder" 20 | ) 21 | 22 | type Session struct { 23 | IsSigningRequired bool 24 | IsAuthenticated bool 25 | debug bool 26 | securityMode uint16 27 | messageID uint64 28 | sessionID uint64 29 | conn net.Conn 30 | dialect uint16 31 | options Options 32 | trees map[string]uint32 33 | } 34 | 35 | type Options struct { 36 | Host string 37 | Port int 38 | Workstation string 39 | Domain string 40 | User string 41 | Password string 42 | Hash string 43 | Timeout time.Duration 44 | } 45 | 46 | func validateOptions(opt Options) error { 47 | if opt.Host == "" { 48 | return errors.New("Missing required option: Host") 49 | } 50 | if opt.Port < 1 || opt.Port > 65535 { 51 | return errors.New("Invalid or missing value: Port") 52 | } 53 | return nil 54 | } 55 | 56 | func NewSession(opt Options, debug bool) (s *Session, err error) { 57 | 58 | if err := validateOptions(opt); err != nil { 59 | return nil, err 60 | } 61 | 62 | conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", opt.Host, opt.Port), opt.Timeout) 63 | if err != nil { 64 | return 65 | } 66 | 67 | s = &Session{ 68 | IsSigningRequired: false, 69 | IsAuthenticated: false, 70 | debug: debug, 71 | securityMode: 0, 72 | messageID: 0, 73 | sessionID: 0, 74 | dialect: 0, 75 | conn: conn, 76 | options: opt, 77 | trees: make(map[string]uint32), 78 | } 79 | 80 | s.Debug("Negotiating protocol", nil) 81 | err = s.NegotiateProtocol() 82 | if err != nil { 83 | return 84 | } 85 | 86 | return s, nil 87 | } 88 | 89 | func (s *Session) Debug(msg string, err error) { 90 | if s.debug { 91 | log.Println("[ DEBUG ] ", msg) 92 | if err != nil { 93 | debug.PrintStack() 94 | } 95 | } 96 | } 97 | 98 | func (s *Session) NegotiateProtocol() error { 99 | negReq := s.NewNegotiateReq() 100 | s.Debug("Sending NegotiateProtocol request", nil) 101 | buf, err := s.send(negReq) 102 | if err != nil { 103 | s.Debug("", err) 104 | return err 105 | } 106 | 107 | negRes := NewNegotiateRes() 108 | s.Debug("Unmarshalling NegotiateProtocol response", nil) 109 | if err := encoder.Unmarshal(buf, &negRes); err != nil { 110 | s.Debug("Raw:\n"+hex.Dump(buf), err) 111 | return err 112 | } 113 | 114 | if negRes.Header.Status != StatusOk { 115 | return errors.New(fmt.Sprintf("NT Status Error: %d\n", negRes.Header.Status)) 116 | } 117 | 118 | // Check SPNEGO security blob 119 | spnegoOID, err := gss.ObjectIDStrToInt(gss.SpnegoOid) 120 | if err != nil { 121 | return err 122 | } 123 | oid := negRes.SecurityBlob.OID 124 | if !oid.Equal(asn1.ObjectIdentifier(spnegoOID)) { 125 | return errors.New(fmt.Sprintf( 126 | "Unknown security type OID [expecting %s]: %s\n", 127 | gss.SpnegoOid, 128 | negRes.SecurityBlob.OID)) 129 | } 130 | 131 | // Check for NTLMSSP support 132 | ntlmsspOID, err := gss.ObjectIDStrToInt(gss.NtLmSSPMechTypeOid) 133 | if err != nil { 134 | s.Debug("", err) 135 | return err 136 | } 137 | 138 | hasNTLMSSP := false 139 | for _, mechType := range negRes.SecurityBlob.Data.MechTypes { 140 | if mechType.Equal(asn1.ObjectIdentifier(ntlmsspOID)) { 141 | hasNTLMSSP = true 142 | break 143 | } 144 | } 145 | if !hasNTLMSSP { 146 | return errors.New("Server does not support NTLMSSP") 147 | } 148 | 149 | s.securityMode = negRes.SecurityMode 150 | s.dialect = negRes.DialectRevision 151 | 152 | // Determine whether signing is required 153 | mode := uint16(s.securityMode) 154 | if mode&SecurityModeSigningEnabled > 0 { 155 | if mode&SecurityModeSigningRequired > 0 { 156 | s.IsSigningRequired = true 157 | } else { 158 | s.IsSigningRequired = false 159 | } 160 | } else { 161 | s.IsSigningRequired = false 162 | } 163 | 164 | s.Debug("Sending SessionSetup1 request", nil) 165 | ssreq, err := s.NewSessionSetup1Req() 166 | if err != nil { 167 | s.Debug("", err) 168 | return err 169 | } 170 | ssres, err := NewSessionSetup1Res() 171 | if err != nil { 172 | s.Debug("", err) 173 | return err 174 | } 175 | buf, err = encoder.Marshal(ssreq) 176 | if err != nil { 177 | s.Debug("", err) 178 | return err 179 | } 180 | 181 | buf, err = s.send(ssreq) 182 | if err != nil { 183 | s.Debug("Raw:\n"+hex.Dump(buf), err) 184 | return err 185 | } 186 | 187 | s.Debug("Unmarshalling SessionSetup1 response", nil) 188 | if err := encoder.Unmarshal(buf, &ssres); err != nil { 189 | s.Debug("", err) 190 | return err 191 | } 192 | 193 | challenge := ntlmssp.NewChallenge() 194 | resp := ssres.SecurityBlob 195 | if err := encoder.Unmarshal(resp.ResponseToken, &challenge); err != nil { 196 | s.Debug("", err) 197 | return err 198 | } 199 | 200 | if ssres.Header.Status != StatusMoreProcessingRequired { 201 | status, _ := StatusMap[negRes.Header.Status] 202 | return errors.New(fmt.Sprintf("NT Status Error: %s\n", status)) 203 | } 204 | s.sessionID = ssres.Header.SessionID 205 | 206 | s.Debug("Sending SessionSetup2 request", nil) 207 | ss2req, err := s.NewSessionSetup2Req() 208 | if err != nil { 209 | s.Debug("", err) 210 | return err 211 | } 212 | 213 | var auth ntlmssp.Authenticate 214 | if s.options.Hash != "" { 215 | // Hash present, use it for auth 216 | s.Debug("Performing hash-based authentication", nil) 217 | auth = ntlmssp.NewAuthenticateHash(s.options.Domain, s.options.User, s.options.Workstation, s.options.Hash, challenge) 218 | } else { 219 | // No hash, use password 220 | s.Debug("Performing password-based authentication", nil) 221 | auth = ntlmssp.NewAuthenticatePass(s.options.Domain, s.options.User, s.options.Workstation, s.options.Password, challenge) 222 | } 223 | 224 | responseToken, err := encoder.Marshal(auth) 225 | if err != nil { 226 | s.Debug("", err) 227 | return err 228 | } 229 | resp2 := ss2req.SecurityBlob 230 | resp2.ResponseToken = responseToken 231 | ss2req.SecurityBlob = resp2 232 | ss2req.Header.Credits = 127 233 | buf, err = encoder.Marshal(ss2req) 234 | if err != nil { 235 | s.Debug("", err) 236 | return err 237 | } 238 | 239 | buf, err = s.send(ss2req) 240 | if err != nil { 241 | s.Debug("", err) 242 | return err 243 | } 244 | s.Debug("Unmarshalling SessionSetup2 response", nil) 245 | var authResp Header 246 | if err := encoder.Unmarshal(buf, &authResp); err != nil { 247 | s.Debug("Raw:\n"+hex.Dump(buf), err) 248 | return err 249 | } 250 | if authResp.Status != StatusOk { 251 | status, _ := StatusMap[authResp.Status] 252 | return errors.New(fmt.Sprintf("NT Status Error: %s\n", status)) 253 | } 254 | s.IsAuthenticated = true 255 | 256 | s.Debug("Completed NegotiateProtocol and SessionSetup", nil) 257 | return nil 258 | } 259 | 260 | func (s *Session) TreeConnect(name string) error { 261 | s.Debug("Sending TreeConnect request ["+name+"]", nil) 262 | req, err := s.NewTreeConnectReq(name) 263 | if err != nil { 264 | s.Debug("", err) 265 | return err 266 | } 267 | buf, err := s.send(req) 268 | if err != nil { 269 | s.Debug("", err) 270 | return err 271 | } 272 | var res TreeConnectRes 273 | s.Debug("Unmarshalling TreeConnect response ["+name+"]", nil) 274 | if err := encoder.Unmarshal(buf, &res); err != nil { 275 | s.Debug("Raw:\n"+hex.Dump(buf), err) 276 | return err 277 | } 278 | 279 | if res.Header.Status != StatusOk { 280 | return errors.New("Failed to connect to tree: " + StatusMap[res.Header.Status]) 281 | } 282 | s.trees[name] = res.Header.TreeID 283 | 284 | s.Debug("Completed TreeConnect ["+name+"]", nil) 285 | return nil 286 | } 287 | 288 | func (s *Session) TreeDisconnect(name string) error { 289 | 290 | var ( 291 | treeid uint32 292 | pathFound bool 293 | ) 294 | for k, v := range s.trees { 295 | if k == name { 296 | treeid = v 297 | pathFound = true 298 | break 299 | } 300 | } 301 | 302 | if !pathFound { 303 | err := errors.New("Unable to find tree path for disconnect") 304 | s.Debug("", err) 305 | return err 306 | } 307 | 308 | s.Debug("Sending TreeDisconnect request ["+name+"]", nil) 309 | req, err := s.NewTreeDisconnectReq(treeid) 310 | if err != nil { 311 | s.Debug("", err) 312 | return err 313 | } 314 | buf, err := s.send(req) 315 | if err != nil { 316 | s.Debug("", err) 317 | return err 318 | } 319 | s.Debug("Unmarshalling TreeDisconnect response for ["+name+"]", nil) 320 | var res TreeDisconnectRes 321 | if err := encoder.Unmarshal(buf, &res); err != nil { 322 | s.Debug("Raw:\n"+hex.Dump(buf), err) 323 | return err 324 | } 325 | if res.Header.Status != StatusOk { 326 | return errors.New("Failed to disconnect from tree: " + StatusMap[res.Header.Status]) 327 | } 328 | delete(s.trees, name) 329 | 330 | s.Debug("TreeDisconnect completed ["+name+"]", nil) 331 | return nil 332 | } 333 | 334 | func (s *Session) Close() { 335 | s.Debug("Closing session", nil) 336 | for k, _ := range s.trees { 337 | s.TreeDisconnect(k) 338 | } 339 | s.Debug("Closing TCP connection", nil) 340 | s.conn.Close() 341 | s.Debug("Session close completed", nil) 342 | } 343 | 344 | func (s *Session) send(req interface{}) (res []byte, err error) { 345 | buf, err := encoder.Marshal(req) 346 | if err != nil { 347 | s.Debug("", err) 348 | return nil, err 349 | } 350 | 351 | b := new(bytes.Buffer) 352 | if err = binary.Write(b, binary.BigEndian, uint32(len(buf))); err != nil { 353 | s.Debug("", err) 354 | return 355 | } 356 | 357 | rw := bufio.NewReadWriter(bufio.NewReader(s.conn), bufio.NewWriter(s.conn)) 358 | if _, err = rw.Write(append(b.Bytes(), buf...)); err != nil { 359 | s.Debug("", err) 360 | return 361 | } 362 | rw.Flush() 363 | 364 | var size uint32 365 | if err = binary.Read(rw, binary.BigEndian, &size); err != nil { 366 | s.Debug("", err) 367 | return 368 | } 369 | if size > 0x00FFFFFF { 370 | return nil, errors.New("Invalid NetBIOS Session message") 371 | } 372 | 373 | data := make([]byte, size) 374 | l, err := io.ReadFull(rw, data) 375 | if err != nil { 376 | s.Debug("", err) 377 | return nil, err 378 | } 379 | if uint32(l) != size { 380 | return nil, errors.New("Message size invalid") 381 | } 382 | 383 | protID := data[0:4] 384 | switch string(protID) { 385 | default: 386 | return nil, errors.New("Protocol Not Implemented") 387 | case ProtocolSmb2: 388 | } 389 | 390 | s.messageID++ 391 | return data, nil 392 | } 393 | -------------------------------------------------------------------------------- /smb/smb.go: -------------------------------------------------------------------------------- 1 | package smb 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/projectdiscovery/smb/gss" 8 | "github.com/projectdiscovery/smb/ntlmssp" 9 | "github.com/projectdiscovery/smb/smb/encoder" 10 | ) 11 | 12 | const ProtocolSmb = "\xFFSMB" 13 | const ProtocolSmb2 = "\xFESMB" 14 | 15 | const StatusOk = 0x00000000 16 | const StatusMoreProcessingRequired = 0xc0000016 17 | const StatusInvalidParameter = 0xc000000d 18 | const StatusLogonFailure = 0xc000006d 19 | const StatusUserSessionDeleted = 0xc0000203 20 | 21 | var StatusMap = map[uint32]string{ 22 | StatusOk: "OK", 23 | StatusMoreProcessingRequired: "More Processing Required", 24 | StatusInvalidParameter: "Invalid Parameter", 25 | StatusLogonFailure: "Logon failed", 26 | StatusUserSessionDeleted: "User session deleted", 27 | } 28 | 29 | const DialectSmb_2_0_2 = 0x0202 30 | const DialectSmb_2_1 = 0x0210 31 | const DialectSmb_3_0 = 0x0300 32 | const DialectSmb_3_0_2 = 0x0302 33 | const DialectSmb_3_1_1 = 0x0311 34 | const DialectSmb2_ALL = 0x02FF 35 | 36 | const ( 37 | CommandNegotiate uint16 = iota 38 | CommandSessionSetup 39 | CommandLogoff 40 | CommandTreeConnect 41 | CommandTreeDisconnect 42 | CommandCreate 43 | CommandClose 44 | CommandFlush 45 | CommandRead 46 | CommandWrite 47 | CommandLock 48 | CommandIOCtl 49 | CommandCancel 50 | CommandEcho 51 | CommandQueryDirectory 52 | CommandChangeNotify 53 | CommandQueryInfo 54 | CommandSetInfo 55 | CommandOplockBreak 56 | ) 57 | 58 | const ( 59 | _ uint16 = iota 60 | SecurityModeSigningEnabled 61 | SecurityModeSigningRequired 62 | ) 63 | 64 | const ( 65 | _ byte = iota 66 | ShareTypeDisk 67 | ShareTypePipe 68 | ShareTypePrint 69 | ) 70 | 71 | const ( 72 | ShareFlagManualCaching uint32 = 0x00000000 73 | ShareFlagAutoCaching uint32 = 0x00000010 74 | ShareFlagVDOCaching uint32 = 0x00000020 75 | ShareFlagNoCaching uint32 = 0x00000030 76 | ShareFlagDFS uint32 = 0x00000001 77 | ShareFlagDFSRoot uint32 = 0x00000002 78 | ShareFlagRestriceExclusiveOpens uint32 = 0x00000100 79 | ShareFlagForceSharedDelete uint32 = 0x00000200 80 | ShareFlagAllowNamespaceCaching uint32 = 0x00000400 81 | ShareFlagAccessBasedDirectoryEnum uint32 = 0x00000800 82 | ShareFlagForceLevelIIOplock uint32 = 0x00001000 83 | ShareFlagEnableHashV1 uint32 = 0x00002000 84 | ShareFlagEnableHashV2 uint32 = 0x00004000 85 | ShareFlagEncryptData uint32 = 0x00008000 86 | ) 87 | 88 | const ( 89 | ShareCapDFS uint32 = 0x00000008 90 | ShareCapContinuousAvailability uint32 = 0x00000010 91 | ShareCapScaleout uint32 = 0x00000020 92 | ShareCapCluster uint32 = 0x00000040 93 | ShareCapAsymmetric uint32 = 0x00000080 94 | ) 95 | 96 | type Header struct { 97 | ProtocolID []byte `smb:"fixed:4"` 98 | StructureSize uint16 99 | CreditCharge uint16 100 | Status uint32 101 | Command uint16 102 | Credits uint16 103 | Flags uint32 104 | NextCommand uint32 105 | MessageID uint64 106 | Reserved uint32 107 | TreeID uint32 108 | SessionID uint64 109 | Signature []byte `smb:"fixed:16"` 110 | } 111 | 112 | type NegotiateReq struct { 113 | Header 114 | StructureSize uint16 115 | DialectCount uint16 `smb:"count:Dialects"` 116 | SecurityMode uint16 117 | Reserved uint16 118 | Capabilities uint32 119 | ClientGuid []byte `smb:"fixed:16"` 120 | ClientStartTime uint64 121 | Dialects []uint16 122 | } 123 | 124 | type NegotiateRes struct { 125 | Header 126 | StructureSize uint16 127 | SecurityMode uint16 128 | DialectRevision uint16 129 | Reserved uint16 130 | ServerGuid []byte `smb:"fixed:16"` 131 | Capabilities uint32 132 | MaxTransactSize uint32 133 | MaxReadSize uint32 134 | MaxWriteSize uint32 135 | SystemTime uint64 136 | ServerStartTime uint64 137 | SecurityBufferOffset uint16 `smb:"offset:SecurityBlob"` 138 | SecurityBufferLength uint16 `smb:"len:SecurityBlob"` 139 | Reserved2 uint32 140 | SecurityBlob *gss.NegTokenInit 141 | } 142 | 143 | type SessionSetup1Req struct { 144 | Header 145 | StructureSize uint16 146 | Flags byte 147 | SecurityMode byte 148 | Capabilities uint32 149 | Channel uint32 150 | SecurityBufferOffset uint16 `smb:"offset:SecurityBlob"` 151 | SecurityBufferLength uint16 `smb:"len:SecurityBlob"` 152 | PreviousSessionID uint64 153 | SecurityBlob *gss.NegTokenInit 154 | } 155 | 156 | type SessionSetup1Res struct { 157 | Header 158 | StructureSize uint16 159 | Flags uint16 160 | SecurityBufferOffset uint16 `smb:"offset:SecurityBlob"` 161 | SecurityBufferLength uint16 `smb:"len:SecurityBlob"` 162 | SecurityBlob *gss.NegTokenResp 163 | } 164 | 165 | type SessionSetup2Req struct { 166 | Header 167 | StructureSize uint16 168 | Flags byte 169 | SecurityMode byte 170 | Capabilities uint32 171 | Channel uint32 172 | SecurityBufferOffset uint16 `smb:"offset:SecurityBlob"` 173 | SecurityBufferLength uint16 `smb:"len:SecurityBlob"` 174 | PreviousSessionID uint64 175 | SecurityBlob *gss.NegTokenResp 176 | } 177 | 178 | type SessionSetup2Res struct { 179 | Header 180 | StructureSize uint16 181 | Flags uint16 182 | SecurityBufferOffset uint16 `smb:"offset:SecurityBlob"` 183 | SecurityBufferLength uint16 `smb:"len:SecurityBlob"` 184 | SecurityBlob *gss.NegTokenResp 185 | } 186 | 187 | type TreeConnectReq struct { 188 | Header 189 | StructureSize uint16 190 | Reserved uint16 191 | PathOffset uint16 `smb:"offset:Path"` 192 | PathLength uint16 `smb:"len:Path"` 193 | Path []byte 194 | } 195 | 196 | type TreeConnectRes struct { 197 | Header 198 | StructureSize uint16 199 | ShareType byte 200 | Reserved byte 201 | ShareFlags uint32 202 | Capabilities uint32 203 | MaximalAccess uint32 204 | } 205 | 206 | type TreeDisconnectReq struct { 207 | Header 208 | StructureSize uint16 209 | Reserved uint16 210 | } 211 | 212 | type TreeDisconnectRes struct { 213 | Header 214 | StructureSize uint16 215 | Reserved uint16 216 | } 217 | 218 | func newHeader() Header { 219 | return Header{ 220 | ProtocolID: []byte(ProtocolSmb2), 221 | StructureSize: 64, 222 | CreditCharge: 0, 223 | Status: 0, 224 | Command: 0, 225 | Credits: 0, 226 | Flags: 0, 227 | NextCommand: 0, 228 | MessageID: 0, 229 | Reserved: 0, 230 | TreeID: 0, 231 | SessionID: 0, 232 | Signature: make([]byte, 16), 233 | } 234 | } 235 | 236 | func (s *Session) NewNegotiateReq() NegotiateReq { 237 | header := newHeader() 238 | header.Command = CommandNegotiate 239 | header.CreditCharge = 1 240 | header.MessageID = s.messageID 241 | 242 | dialects := []uint16{ 243 | uint16(DialectSmb_2_1), 244 | } 245 | return NegotiateReq{ 246 | Header: header, 247 | StructureSize: 36, 248 | DialectCount: uint16(len(dialects)), 249 | SecurityMode: SecurityModeSigningEnabled, 250 | Reserved: 0, 251 | Capabilities: 0, 252 | ClientGuid: make([]byte, 16), 253 | ClientStartTime: 0, 254 | Dialects: dialects, 255 | } 256 | } 257 | 258 | func NewNegotiateRes() NegotiateRes { 259 | return NegotiateRes{ 260 | Header: newHeader(), 261 | StructureSize: 0, 262 | SecurityMode: 0, 263 | DialectRevision: 0, 264 | Reserved: 0, 265 | ServerGuid: make([]byte, 16), 266 | Capabilities: 0, 267 | MaxTransactSize: 0, 268 | MaxReadSize: 0, 269 | MaxWriteSize: 0, 270 | SystemTime: 0, 271 | ServerStartTime: 0, 272 | SecurityBufferOffset: 0, 273 | SecurityBufferLength: 0, 274 | Reserved2: 0, 275 | SecurityBlob: &gss.NegTokenInit{}, 276 | } 277 | } 278 | 279 | func (s *Session) NewSessionSetup1Req() (SessionSetup1Req, error) { 280 | header := newHeader() 281 | header.Command = CommandSessionSetup 282 | header.CreditCharge = 1 283 | header.MessageID = s.messageID 284 | header.SessionID = s.sessionID 285 | 286 | ntlmsspneg := ntlmssp.NewNegotiate(s.options.Domain, s.options.Workstation) 287 | data, err := encoder.Marshal(ntlmsspneg) 288 | if err != nil { 289 | return SessionSetup1Req{}, err 290 | } 291 | 292 | if s.sessionID != 0 { 293 | return SessionSetup1Req{}, errors.New("Bad session ID for session setup 1 message") 294 | } 295 | 296 | // Initial session setup request 297 | init, err := gss.NewNegTokenInit() 298 | if err != nil { 299 | return SessionSetup1Req{}, err 300 | } 301 | init.Data.MechToken = data 302 | 303 | return SessionSetup1Req{ 304 | Header: header, 305 | StructureSize: 25, 306 | Flags: 0x00, 307 | SecurityMode: byte(SecurityModeSigningEnabled), 308 | Capabilities: 0, 309 | Channel: 0, 310 | SecurityBufferOffset: 88, 311 | SecurityBufferLength: 0, 312 | PreviousSessionID: 0, 313 | SecurityBlob: &init, 314 | }, nil 315 | } 316 | 317 | func NewSessionSetup1Res() (SessionSetup1Res, error) { 318 | resp, err := gss.NewNegTokenResp() 319 | if err != nil { 320 | return SessionSetup1Res{}, err 321 | } 322 | ret := SessionSetup1Res{ 323 | Header: newHeader(), 324 | SecurityBlob: &resp, 325 | } 326 | return ret, nil 327 | } 328 | 329 | func (s *Session) NewSessionSetup2Req() (SessionSetup2Req, error) { 330 | header := newHeader() 331 | header.Command = CommandSessionSetup 332 | header.CreditCharge = 1 333 | header.MessageID = s.messageID 334 | header.SessionID = s.sessionID 335 | 336 | ntlmsspneg := ntlmssp.NewNegotiate(s.options.Domain, s.options.Workstation) 337 | data, err := encoder.Marshal(ntlmsspneg) 338 | if err != nil { 339 | return SessionSetup2Req{}, err 340 | } 341 | 342 | if s.sessionID == 0 { 343 | return SessionSetup2Req{}, errors.New("Bad session ID for session setup 2 message") 344 | } 345 | 346 | // Session setup request #2 347 | resp, err := gss.NewNegTokenResp() 348 | if err != nil { 349 | return SessionSetup2Req{}, err 350 | } 351 | resp.ResponseToken = data 352 | 353 | return SessionSetup2Req{ 354 | Header: header, 355 | StructureSize: 25, 356 | Flags: 0x00, 357 | SecurityMode: byte(SecurityModeSigningEnabled), 358 | Capabilities: 0, 359 | Channel: 0, 360 | SecurityBufferOffset: 88, 361 | SecurityBufferLength: 0, 362 | PreviousSessionID: 0, 363 | SecurityBlob: &resp, 364 | }, nil 365 | } 366 | 367 | func NewSessionSetup2Res() (SessionSetup2Res, error) { 368 | resp, err := gss.NewNegTokenResp() 369 | if err != nil { 370 | return SessionSetup2Res{}, err 371 | } 372 | ret := SessionSetup2Res{ 373 | Header: newHeader(), 374 | SecurityBlob: &resp, 375 | } 376 | return ret, nil 377 | } 378 | 379 | // NewTreeConnectReq creates a new TreeConnect message and accepts the share name 380 | // as input. 381 | func (s *Session) NewTreeConnectReq(name string) (TreeConnectReq, error) { 382 | header := newHeader() 383 | header.Command = CommandTreeConnect 384 | header.CreditCharge = 1 385 | header.MessageID = s.messageID 386 | header.SessionID = s.sessionID 387 | 388 | path := fmt.Sprintf("\\\\%s\\%s", s.options.Host, name) 389 | return TreeConnectReq{ 390 | Header: header, 391 | StructureSize: 9, 392 | Reserved: 0, 393 | PathOffset: 0, 394 | PathLength: 0, 395 | Path: encoder.ToUnicode(path), 396 | }, nil 397 | } 398 | 399 | func NewTreeConnectRes() (TreeConnectRes, error) { 400 | return TreeConnectRes{}, nil 401 | } 402 | 403 | func (s *Session) NewTreeDisconnectReq(treeId uint32) (TreeDisconnectReq, error) { 404 | header := newHeader() 405 | header.Command = CommandTreeDisconnect 406 | header.CreditCharge = 1 407 | header.MessageID = s.messageID 408 | header.SessionID = s.sessionID 409 | header.TreeID = treeId 410 | 411 | return TreeDisconnectReq{ 412 | Header: header, 413 | StructureSize: 4, 414 | Reserved: 0, 415 | }, nil 416 | } 417 | 418 | func NewTreeDisconnectRes() (TreeDisconnectRes, error) { 419 | return TreeDisconnectRes{}, nil 420 | } 421 | --------------------------------------------------------------------------------