# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Author: Alberto Solino (@agsolino)
#
# Description:
# [MS-SMB2] Protocol Implementation (SMB2 and SMB3)
# As you might see in the code, it's implemented strictly following
# the structures defined in the protocol specification. This may
# not be the most efficient way (e.g. self._Connection is the
# same to self._Session in the context of this library ) but
# it certainly helps following the document way easier.
#
# ToDo:
# [X] Implement SMB2_CHANGE_NOTIFY
# [X] Implement SMB2_QUERY_INFO
# [X] Implement SMB2_SET_INFO
# [ ] Implement SMB2_OPLOCK_BREAK
# [X] Implement SMB3 signing
# [X] Implement SMB3 encryption
# [ ] Add more backward compatible commands from the smb.py code
# [ ] Fix up all the 'ToDo' comments inside the code
#
from __future__ import division
from __future__ import print_function
import socket
import ntpath
import random
import string
import struct
from six import indexbytes, b
from binascii import a2b_hex
from contextlib import contextmanager
from pyasn1.type.univ import noValue
from Cryptodome.Cipher import AES
from impacket import nmb, ntlm, uuid, crypto
from impacket.smb3structs import *
from impacket.nt_errors import STATUS_SUCCESS, STATUS_MORE_PROCESSING_REQUIRED, STATUS_INVALID_PARAMETER, \
STATUS_NO_MORE_FILES, STATUS_PENDING, STATUS_NOT_IMPLEMENTED, ERROR_MESSAGES
from impacket.spnego import SPNEGO_NegTokenInit, TypesMech, SPNEGO_NegTokenResp, ASN1_OID, asn1encode, ASN1_AID
from impacket.krb5.gssapi import KRB5_AP_REQ
# For signing
import hashlib, hmac, copy
# Our random number generator
try:
rand = random.SystemRandom()
except NotImplementedError:
rand = random
pass
# Structs to be used
TREE_CONNECT = {
'ShareName' : '',
'TreeConnectId' : 0,
'Session' : 0,
'IsDfsShare' : False,
# If the client implements the SMB 3.0 dialect,
# the client MUST also implement the following
'IsCAShare' : False,
'EncryptData' : False,
'IsScaleoutShare' : False,
# Outside the protocol
'NumberOfUses' : 0,
}
FILE = {
'OpenTable' : [],
'LeaseKey' : '',
'LeaseState' : 0,
'LeaseEpoch' : 0,
}
OPEN = {
'FileID' : '',
'TreeConnect' : 0,
'Connection' : 0, # Not Used
'Oplocklevel' : 0,
'Durable' : False,
'FileName' : '',
'ResilientHandle' : False,
'LastDisconnectTime' : 0,
'ResilientTimeout' : 0,
'OperationBuckets' : [],
# If the client implements the SMB 3.0 dialect,
# the client MUST implement the following
'CreateGuid' : '',
'IsPersistent' : False,
'DesiredAccess' : '',
'ShareMode' : 0,
'CreateOption' : '',
'FileAttributes' : '',
'CreateDisposition' : '',
}
REQUEST = {
'CancelID' : '',
'Message' : '',
'Timestamp' : 0,
}
CHANNEL = {
'SigningKey' : '',
'Connection' : 0,
}
class SessionError(Exception):
def __init__( self, error = 0, packet=0):
Exception.__init__(self)
self.error = error
self.packet = packet
def get_error_code( self ):
return self.error
def get_error_packet( self ):
return self.packet
def __str__( self ):
return 'SMB SessionError: %s(%s)' % (ERROR_MESSAGES[self.error])
class SMB3:
def __init__(self, remote_name, remote_host, my_name=None, host_type=nmb.TYPE_SERVER, sess_port=445, timeout=60,
UDP=0, preferredDialect=None, session=None, negSessionResponse=None):
# [MS-SMB2] Section 3
self.RequireMessageSigning = False #
self.ConnectionTable = {}
self.GlobalFileTable = {}
self.ClientGuid = ''.join([random.choice(string.ascii_letters) for i in range(16)])
# Only for SMB 3.0
self.EncryptionAlgorithmList = ['AES-CCM']
self.MaxDialect = []
self.RequireSecureNegotiate = False
# Per Transport Connection Data
self._Connection = {
# Indexed by SessionID
#'SessionTable' : {},
# Indexed by MessageID
'OutstandingRequests' : {},
'OutstandingResponses' : {}, #
'SequenceWindow' : 0, #
'GSSNegotiateToken' : '', #
'MaxTransactSize' : 0, #
'MaxReadSize' : 0, #
'MaxWriteSize' : 0, #
'ServerGuid' : '', #
'RequireSigning' : False, #
'ServerName' : '', #
# If the client implements the SMB 2.1 or SMB 3.0 dialects, it MUST
# also implement the following
'Dialect' : 0, #
'SupportsFileLeasing' : False, #
'SupportsMultiCredit' : False, #
# If the client implements the SMB 3.0 dialect,
# it MUST also implement the following
'SupportsDirectoryLeasing' : False, #
'SupportsMultiChannel' : False, #
'SupportsPersistentHandles': False, #
'SupportsEncryption' : False, #
'ClientCapabilities' : 0,
'ServerCapabilities' : 0, #
'ClientSecurityMode' : 0, #
'ServerSecurityMode' : 0, #
# Outside the protocol
'ServerIP' : '', #
'ClientName' : '', #
}
self._Session = {
'SessionID' : 0, #
'TreeConnectTable' : {}, #
'SessionKey' : b'', #
'SigningRequired' : False, #
'Connection' : 0, #
'UserCredentials' : '', #
'OpenTable' : {}, #
# If the client implements the SMB 3.0 dialect,
# it MUST also implement the following
'ChannelList' : [],
'ChannelSequence' : 0,
#'EncryptData' : False,
'EncryptData' : True,
'EncryptionKey' : '',
'DecryptionKey' : '',
'SigningKey' : '',
'ApplicationKey' : b'',
# Outside the protocol
'SessionFlags' : 0, #
'ServerName' : '', #
'ServerDomain' : '', #
'ServerDNSDomainName' : '', #
'ServerOS' : '', #
'SigningActivated' : False, #
}
self.SMB_PACKET = SMB2Packet
self._timeout = timeout
self._Connection['ServerIP'] = remote_host
self._NetBIOSSession = None
self._preferredDialect = preferredDialect
self._doKerberos = False
self.__userName = ''
self.__password = ''
self.__domain = ''
self.__lmhash = ''
self.__nthash = ''
self.__kdc = ''
self.__aesKey = ''
self.__TGT = None
self.__TGS = None
225 ↛ 226line 225 didn't jump to line 226, because the condition on line 225 was never true if sess_port == 445 and remote_name == '*SMBSERVER':
self._Connection['ServerName'] = remote_host
else:
self._Connection['ServerName'] = remote_name
# This is on purpose. I'm still not convinced to do a socket.gethostname() if not specified
231 ↛ 234line 231 didn't jump to line 234, because the condition on line 231 was never false if my_name is None:
self._Connection['ClientName'] = ''
else:
self._Connection['ClientName'] = my_name
if session is None:
237 ↛ 244line 237 didn't jump to line 244, because the condition on line 237 was never false if not my_name:
# If destination port is 139 yes, there's some client disclosure
my_name = socket.gethostname()
i = my_name.find('.')
241 ↛ 244line 241 didn't jump to line 244, because the condition on line 241 was never false if i > -1:
my_name = my_name[:i]
244 ↛ 245line 244 didn't jump to line 245, because the condition on line 244 was never true if UDP:
self._NetBIOSSession = nmb.NetBIOSUDPSession(my_name, self._Connection['ServerName'], remote_host, host_type, sess_port, self._timeout)
else:
self._NetBIOSSession = nmb.NetBIOSTCPSession(my_name, self._Connection['ServerName'], remote_host, host_type, sess_port, self._timeout)
self.negotiateSession(preferredDialect)
else:
self._NetBIOSSession = session
# We should increase the SequenceWindow since a packet was already received.
self._Connection['SequenceWindow'] += 1
# Let's negotiate again if needed (or parse the existing response) using the same connection
self.negotiateSession(preferredDialect, negSessionResponse)
def printStatus(self):
print("CONNECTION")
for i in list(self._Connection.items()):
print("%-40s : %s" % i)
print()
print("SESSION")
for i in list(self._Session.items()):
print("%-40s : %s" % i)
def getKerberos(self):
return self._doKerberos
def getServerName(self):
return self._Session['ServerName']
def getClientName(self):
return self._Session['ClientName']
def getRemoteName(self):
276 ↛ 277line 276 didn't jump to line 277, because the condition on line 276 was never true if self._Session['ServerName'] == '':
return self._Connection['ServerName']
return self._Session['ServerName']
def setRemoteName(self, name):
self._Session['ServerName'] = name
return True
def getServerIP(self):
return self._Connection['ServerIP']
def getServerDomain(self):
return self._Session['ServerDomain']
def getServerDNSDomainName(self):
return self._Session['ServerDNSDomainName']
def getServerOS(self):
return self._Session['ServerOS']
def getServerOSMajor(self):
return self._Session['ServerOSMajor']
def getServerOSMinor(self):
return self._Session['ServerOSMinor']
def getServerOSBuild(self):
return self._Session['ServerOSBuild']
def isGuestSession(self):
return self._Session['SessionFlags'] & SMB2_SESSION_FLAG_IS_GUEST
def setTimeout(self, timeout):
self._timeout = timeout
@contextmanager
def useTimeout(self, timeout):
prev_timeout = self.getTimeout(timeout)
try:
yield
finally:
self.setTimeout(prev_timeout)
def getDialect(self):
return self._Connection['Dialect']
def signSMB(self, packet):
packet['Signature'] = '\x00'*16
if self._Connection['Dialect'] == SMB2_DIALECT_21 or self._Connection['Dialect'] == SMB2_DIALECT_002:
325 ↛ exitline 325 didn't return from function 'signSMB', because the condition on line 325 was never false if len(self._Session['SessionKey']) > 0:
signature = hmac.new(self._Session['SessionKey'], packet.getData(), hashlib.sha256).digest()
packet['Signature'] = signature[:16]
else:
329 ↛ exitline 329 didn't return from function 'signSMB', because the condition on line 329 was never false if len(self._Session['SessionKey']) > 0:
p = packet.getData()
signature = crypto.AES_CMAC(self._Session['SigningKey'], p, len(p))
packet['Signature'] = signature
def sendSMB(self, packet):
# The idea here is to receive multiple/single commands and create a compound request, and send it
# Should return the MessageID for later retrieval. Implement compounded related requests.
# If Connection.Dialect is equal to "3.000" and if Connection.SupportsMultiChannel or
# Connection.SupportsPersistentHandles is TRUE, the client MUST set ChannelSequence in the
# SMB2 header to Session.ChannelSequence
# Check this is not a CANCEL request. If so, don't consume sequence numbers
343 ↛ 346line 343 didn't jump to line 346, because the condition on line 343 was never false if packet['Command'] is not SMB2_CANCEL:
packet['MessageID'] = self._Connection['SequenceWindow']
self._Connection['SequenceWindow'] += 1
packet['SessionID'] = self._Session['SessionID']
# Default the credit charge to 1 unless set by the caller
if ('CreditCharge' in packet.fields) is False:
packet['CreditCharge'] = 1
# Standard credit request after negotiating protocol
if self._Connection['SequenceWindow'] > 3:
packet['CreditRequestResponse'] = 127
messageId = packet['MessageID']
if self._Session['SigningActivated'] is True and self._Connection['SequenceWindow'] > 2:
if packet['TreeID'] > 0 and (packet['TreeID'] in self._Session['TreeConnectTable']) is True:
360 ↛ 367line 360 didn't jump to line 367, because the condition on line 360 was never false if self._Session['TreeConnectTable'][packet['TreeID']]['EncryptData'] is False:
packet['Flags'] = SMB2_FLAGS_SIGNED
self.signSMB(packet)
363 ↛ 367line 363 didn't jump to line 367, because the condition on line 363 was never false elif packet['TreeID'] == 0:
packet['Flags'] = SMB2_FLAGS_SIGNED
self.signSMB(packet)
if (self._Session['SessionFlags'] & SMB2_SESSION_FLAG_ENCRYPT_DATA) or ( packet['TreeID'] != 0 and self._Session['TreeConnectTable'][packet['TreeID']]['EncryptData'] is True):
plainText = packet.getData()
transformHeader = SMB2_TRANSFORM_HEADER()
transformHeader['Nonce'] = ''.join([rand.choice(string.ascii_letters) for _ in range(11)])
transformHeader['OriginalMessageSize'] = len(plainText)
transformHeader['EncryptionAlgorithm'] = SMB2_ENCRYPTION_AES128_CCM
transformHeader['SessionID'] = self._Session['SessionID']
cipher = AES.new(self._Session['EncryptionKey'], AES.MODE_CCM, b(transformHeader['Nonce']))
cipher.update(transformHeader.getData()[20:])
cipherText = cipher.encrypt(plainText)
transformHeader['Signature'] = cipher.digest()
packet = transformHeader.getData() + cipherText
self._NetBIOSSession.send_packet(packet)
else:
self._NetBIOSSession.send_packet(packet.getData())
return messageId
def recvSMB(self, packetID = None):
# First, verify we don't have the packet already
388 ↛ 389line 388 didn't jump to line 389, because the condition on line 388 was never true if packetID in self._Connection['OutstandingResponses']:
return self._Connection['OutstandingResponses'].pop(packetID)
data = self._NetBIOSSession.recv_packet(self._timeout)
if data.get_trailer().startswith(b'\xfdSMB'):
# Packet is encrypted
transformHeader = SMB2_TRANSFORM_HEADER(data.get_trailer())
cipher = AES.new(self._Session['DecryptionKey'], AES.MODE_CCM, transformHeader['Nonce'][:11])
cipher.update(transformHeader.getData()[20:])
plainText = cipher.decrypt(data.get_trailer()[len(SMB2_TRANSFORM_HEADER()):])
#cipher.verify(transformHeader['Signature'])
packet = SMB2Packet(plainText)
else:
# In all SMB dialects for a response this field is interpreted as the Status field.
# This field can be set to any value. For a list of valid status codes,
# see [MS-ERREF] section 2.3.
packet = SMB2Packet(data.get_trailer())
# Loop while we receive pending requests
if packet['Status'] == STATUS_PENDING:
status = STATUS_PENDING
while status == STATUS_PENDING:
data = self._NetBIOSSession.recv_packet(self._timeout)
412 ↛ 413line 412 didn't jump to line 413, because the condition on line 412 was never true if data.get_trailer().startswith(b'\xfeSMB'):
packet = SMB2Packet(data.get_trailer())
else:
# Packet is encrypted
transformHeader = SMB2_TRANSFORM_HEADER(data.get_trailer())
cipher = AES.new(self._Session['DecryptionKey'], AES.MODE_CCM, transformHeader['Nonce'][:11])
cipher.update(transformHeader.getData()[20:])
plainText = cipher.decrypt(data.get_trailer()[len(SMB2_TRANSFORM_HEADER()):])
#cipher.verify(transformHeader['Signature'])
packet = SMB2Packet(plainText)
status = packet['Status']
424 ↛ 433line 424 didn't jump to line 433, because the condition on line 424 was never false if packet['MessageID'] == packetID or packetID is None:
# Let's update the sequenceWindow based on the CreditsCharged
# In the SMB 2.0.2 dialect, this field MUST NOT be used and MUST be reserved.
# The sender MUST set this to 0, and the receiver MUST ignore it.
# In all other dialects, this field indicates the number of credits that this request consumes.
if self._Connection['Dialect'] > SMB2_DIALECT_002:
self._Connection['SequenceWindow'] += (packet['CreditCharge'] - 1)
return packet
else:
self._Connection['OutstandingResponses'][packet['MessageID']] = packet
return self.recvSMB(packetID)
def negotiateSession(self, preferredDialect = None, negSessionResponse = None):
# Let's store some data for later use
self._Connection['ClientSecurityMode'] = SMB2_NEGOTIATE_SIGNING_ENABLED
439 ↛ 440line 439 didn't jump to line 440, because the condition on line 439 was never true if self.RequireMessageSigning is True:
self._Connection['ClientSecurityMode'] |= SMB2_NEGOTIATE_SIGNING_REQUIRED
self._Connection['Capabilities'] = SMB2_GLOBAL_CAP_ENCRYPTION
currentDialect = SMB2_DIALECT_WILDCARD
# Do we have a negSessionPacket already?
if negSessionResponse is not None:
# Yes, let's store the dialect answered back
negResp = SMB2Negotiate_Response(negSessionResponse['Data'])
currentDialect = negResp['DialectRevision']
450 ↛ 472line 450 didn't jump to line 472, because the condition on line 450 was never false if currentDialect == SMB2_DIALECT_WILDCARD:
# Still don't know the chosen dialect, let's send our options
packet = self.SMB_PACKET()
packet['Command'] = SMB2_NEGOTIATE
negSession = SMB2Negotiate()
negSession['SecurityMode'] = self._Connection['ClientSecurityMode']
negSession['Capabilities'] = self._Connection['Capabilities']
negSession['ClientGuid'] = self.ClientGuid
if preferredDialect is not None:
negSession['Dialects'] = [preferredDialect]
else:
negSession['Dialects'] = [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]
negSession['DialectCount'] = len(negSession['Dialects'])
packet['Data'] = negSession
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
469 ↛ 472line 469 didn't jump to line 472, because the condition on line 469 was never false if ans.isValidAnswer(STATUS_SUCCESS):
negResp = SMB2Negotiate_Response(ans['Data'])
self._Connection['MaxTransactSize'] = min(0x100000,negResp['MaxTransactSize'])
self._Connection['MaxReadSize'] = min(0x100000,negResp['MaxReadSize'])
self._Connection['MaxWriteSize'] = min(0x100000,negResp['MaxWriteSize'])
self._Connection['ServerGuid'] = negResp['ServerGuid']
self._Connection['GSSNegotiateToken'] = negResp['Buffer']
self._Connection['Dialect'] = negResp['DialectRevision']
478 ↛ 480line 478 didn't jump to line 480, because the condition on line 478 was never false if (negResp['SecurityMode'] & SMB2_NEGOTIATE_SIGNING_REQUIRED) == SMB2_NEGOTIATE_SIGNING_REQUIRED:
self._Connection['RequireSigning'] = True
if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LEASING) == SMB2_GLOBAL_CAP_LEASING:
self._Connection['SupportsFileLeasing'] = True
if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LARGE_MTU) == SMB2_GLOBAL_CAP_LARGE_MTU:
self._Connection['SupportsMultiCredit'] = True
if self._Connection['Dialect'] == SMB2_DIALECT_30:
# Switching to the right packet format
self.SMB_PACKET = SMB3Packet
488 ↛ 489line 488 didn't jump to line 489, because the condition on line 488 was never true if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_DIRECTORY_LEASING) == SMB2_GLOBAL_CAP_DIRECTORY_LEASING:
self._Connection['SupportsDirectoryLeasing'] = True
490 ↛ 491line 490 didn't jump to line 491, because the condition on line 490 was never true if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_MULTI_CHANNEL) == SMB2_GLOBAL_CAP_MULTI_CHANNEL:
self._Connection['SupportsMultiChannel'] = True
492 ↛ 493line 492 didn't jump to line 493, because the condition on line 492 was never true if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_PERSISTENT_HANDLES) == SMB2_GLOBAL_CAP_PERSISTENT_HANDLES:
self._Connection['SupportsPersistentHandles'] = True
494 ↛ 497line 494 didn't jump to line 497, because the condition on line 494 was never false if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_ENCRYPTION) == SMB2_GLOBAL_CAP_ENCRYPTION:
self._Connection['SupportsEncryption'] = True
self._Connection['ServerCapabilities'] = negResp['Capabilities']
self._Connection['ServerSecurityMode'] = negResp['SecurityMode']
def getCredentials(self):
return (
self.__userName,
self.__password,
self.__domain,
self.__lmhash,
self.__nthash,
self.__aesKey,
self.__TGT,
self.__TGS)
def kerberosLogin(self, user, password, domain = '', lmhash = '', nthash = '', aesKey='', kdcHost = '', TGT=None, TGS=None):
# If TGT or TGS are specified, they are in the form of:
# TGS['KDC_REP'] = the response from the server
# TGS['cipher'] = the cipher used
# TGS['sessionKey'] = the sessionKey
# If we have hashes, normalize them
if lmhash != '' or nthash != '':
if len(lmhash) % 2: lmhash = '0%s' % lmhash
if len(nthash) % 2: nthash = '0%s' % nthash
try: # just in case they were converted already
lmhash = a2b_hex(lmhash)
nthash = a2b_hex(nthash)
except:
pass
self.__userName = user
self.__password = password
self.__domain = domain
self.__lmhash = lmhash
self.__nthash = nthash
self.__kdc = kdcHost
self.__aesKey = aesKey
self.__TGT = TGT
self.__TGS = TGS
self._doKerberos= True
sessionSetup = SMB2SessionSetup()
538 ↛ 539line 538 didn't jump to line 539, because the condition on line 538 was never true if self.RequireMessageSigning is True:
sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_REQUIRED
else:
sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_ENABLED
sessionSetup['Flags'] = 0
#sessionSetup['Capabilities'] = SMB2_GLOBAL_CAP_LARGE_MTU | SMB2_GLOBAL_CAP_LEASING | SMB2_GLOBAL_CAP_DFS
# Importing down here so pyasn1 is not required if kerberos is not used.
from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set
from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
from impacket.krb5 import constants
from impacket.krb5.types import Principal, KerberosTime, Ticket
from pyasn1.codec.der import decoder, encoder
import datetime
# First of all, we need to get a TGT for the user
userName = Principal(user, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
556 ↛ 560line 556 didn't jump to line 560, because the condition on line 556 was never false if TGT is None:
557 ↛ 580line 557 didn't jump to line 580, because the condition on line 557 was never false if TGS is None:
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost)
else:
tgt = TGT['KDC_REP']
cipher = TGT['cipher']
sessionKey = TGT['sessionKey']
# Save the ticket
# If you want, for debugging purposes
# from impacket.krb5.ccache import CCache
# ccache = CCache()
# try:
# if TGS is None:
# ccache.fromTGT(tgt, oldSessionKey, sessionKey)
# else:
# ccache.fromTGS(TGS['KDC_REP'], TGS['oldSessionKey'], TGS['sessionKey'] )
# ccache.saveFile('/tmp/ticket.bin')
# except Exception, e:
# print e
# pass
# Now that we have the TGT, we should ask for a TGS for cifs
580 ↛ 584line 580 didn't jump to line 584, because the condition on line 580 was never false if TGS is None:
serverName = Principal('cifs/%s' % (self._Connection['ServerName']), type=constants.PrincipalNameType.NT_SRV_INST.value)
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey)
else:
tgs = TGS['KDC_REP']
cipher = TGS['cipher']
sessionKey = TGS['sessionKey']
# Let's build a NegTokenInit with a Kerberos REQ_AP
blob = SPNEGO_NegTokenInit()
# Kerberos
blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']]
# Let's extract the ticket from the TGS
tgs = decoder.decode(tgs, asn1Spec = TGS_REP())[0]
ticket = Ticket()
ticket.from_asn1(tgs['ticket'])
# Now let's build the AP_REQ
apReq = AP_REQ()
apReq['pvno'] = 5
apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)
opts = list()
apReq['ap-options'] = constants.encodeFlags(opts)
seq_set(apReq,'ticket', ticket.to_asn1)
authenticator = Authenticator()
authenticator['authenticator-vno'] = 5
authenticator['crealm'] = domain
seq_set(authenticator, 'cname', userName.components_to_asn1)
now = datetime.datetime.utcnow()
authenticator['cusec'] = now.microsecond
authenticator['ctime'] = KerberosTime.to_asn1(now)
encodedAuthenticator = encoder.encode(authenticator)
# Key Usage 11
# AP-REQ Authenticator (includes application authenticator
# subkey), encrypted with the application session key
# (Section 5.5.1)
encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None)
apReq['authenticator'] = noValue
apReq['authenticator']['etype'] = cipher.enctype
apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator
blob['MechToken'] = struct.pack('B', ASN1_AID) + asn1encode( struct.pack('B', ASN1_OID) + asn1encode(
TypesMech['KRB5 - Kerberos 5'] ) + KRB5_AP_REQ + encoder.encode(apReq))
sessionSetup['SecurityBufferLength'] = len(blob)
sessionSetup['Buffer'] = blob.getData()
packet = self.SMB_PACKET()
packet['Command'] = SMB2_SESSION_SETUP
packet['Data'] = sessionSetup
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
642 ↛ 670line 642 didn't jump to line 670, because the condition on line 642 was never false if ans.isValidAnswer(STATUS_SUCCESS):
self._Session['SessionID'] = ans['SessionID']
self._Session['SigningRequired'] = self._Connection['RequireSigning']
self._Session['UserCredentials'] = (user, password, domain, lmhash, nthash)
self._Session['Connection'] = self._NetBIOSSession.get_socket()
self._Session['SessionKey'] = sessionKey.contents[:16]
if self._Session['SigningRequired'] is True and self._Connection['Dialect'] == SMB2_DIALECT_30:
self._Session['SigningKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], b"SMB2AESCMAC\x00", b"SmbSign\x00", 128)
# Do not encrypt anonymous connections
653 ↛ 654line 653 didn't jump to line 654, because the condition on line 653 was never true if user == '' or self.isGuestSession():
self._Connection['SupportsEncryption'] = False
# Calculate the key derivations for dialect 3.0
657 ↛ 659line 657 didn't jump to line 659, because the condition on line 657 was never false if self._Session['SigningRequired'] is True:
self._Session['SigningActivated'] = True
if self._Connection['Dialect'] == SMB2_DIALECT_30 and self._Connection['SupportsEncryption'] is True:
# SMB 3.0. Encryption available. Let's enforce it if we have AES CCM available
self._Session['SessionFlags'] |= SMB2_SESSION_FLAG_ENCRYPT_DATA
self._Session['ApplicationKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], b"SMB2APP\x00", b"SmbRpc\x00", 128)
self._Session['EncryptionKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], b"SMB2AESCCM\x00", b"ServerIn \x00", 128)
self._Session['DecryptionKey'] = crypto.KDF_CounterMode(self._Session['SessionKey'], b"SMB2AESCCM\x00", b"ServerOut\x00", 128)
return True
else:
# We clean the stuff we used in case we want to authenticate again
# within the same connection
self._Session['UserCredentials'] = ''
self._Session['Connection'] = 0
self._Session['SessionID'] = 0
self._Session['SigningRequired'] = False
self._Session['SigningKey'] = ''
self._Session['SessionKey'] = ''
self._Session['SigningActivated'] = False
raise Exception('Unsuccessful Login')
def login(self, user, password, domain = '', lmhash = '', nthash = ''):
# If we have hashes, normalize them
if lmhash != '' or nthash != '':
if len(lmhash) % 2: lmhash = '0%s' % lmhash
if len(nthash) % 2: nthash = '0%s' % nthash
try: # just in case they were converted already
lmhash = a2b_hex(lmhash)
nthash = a2b_hex(nthash)
except:
pass
self.__userName = user
self.__password = password
self.__domain = domain
self.__lmhash = lmhash
self.__nthash = nthash
self.__aesKey = ''
self.__TGT = None
self.__TGS = None
sessionSetup = SMB2SessionSetup()
701 ↛ 702line 701 didn't jump to line 702, because the condition on line 701 was never true if self.RequireMessageSigning is True:
sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_REQUIRED
else:
sessionSetup['SecurityMode'] = SMB2_NEGOTIATE_SIGNING_ENABLED
sessionSetup['Flags'] = 0
#sessionSetup['Capabilities'] = SMB2_GLOBAL_CAP_LARGE_MTU | SMB2_GLOBAL_CAP_LEASING | SMB2_GLOBAL_CAP_DFS
# Let's build a NegTokenInit with the NTLMSSP
# TODO: In the future we should be able to choose different providers
blob = SPNEGO_NegTokenInit()
# NTLMSSP
blob['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']]
auth = ntlm.getNTLMSSPType1(self._Connection['ClientName'],domain, self._Connection['RequireSigning'])
blob['MechToken'] = auth.getData()
sessionSetup['SecurityBufferLength'] = len(blob)
sessionSetup['Buffer'] = blob.getData()
# ToDo:
# If this authentication is for establishing an alternative channel for an existing Session, as specified
# in section 3.2.4.1.7, the client MUST also set the following values:
# The SessionId field in the SMB2 header MUST be set to the Session.SessionId for the new
# channel being established.
# The SMB2_SESSION_FLAG_BINDING bit MUST be set in the Flags field.
# The PreviousSessionId field MUST be set to zero.
packet = self.SMB_PACKET()
packet['Command'] = SMB2_SESSION_SETUP
packet['Data'] = sessionSetup
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
736 ↛ exitline 736 didn't return from function 'login', because the condition on line 736 was never false if ans.isValidAnswer(STATUS_MORE_PROCESSING_REQUIRED):
self._Session['SessionID'] = ans['SessionID']
self._Session['SigningRequired'] = self._Connection['RequireSigning']
self._Session['UserCredentials'] = (user, password, domain, lmhash, nthash)
self._Session['Connection'] = self._NetBIOSSession.get_socket()
sessionSetupResponse = SMB2SessionSetup_Response(ans['Data'])
respToken = SPNEGO_NegTokenResp(sessionSetupResponse['Buffer'])
# Let's parse some data and keep it to ourselves in case it is asked
ntlmChallenge = ntlm.NTLMAuthChallenge(respToken['ResponseToken'])
746 ↛ 778line 746 didn't jump to line 778, because the condition on line 746 was never false if ntlmChallenge['TargetInfoFields_len'] > 0:
av_pairs = ntlm.AV_PAIRS(ntlmChallenge['TargetInfoFields'][:ntlmChallenge['TargetInfoFields_len']])
748 ↛ 754line 748 didn't jump to line 754, because the condition on line 748 was never false if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] is not None:
try:
self._Session['ServerName'] = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode('utf-16le')
except:
# For some reason, we couldn't decode Unicode here.. silently discard the operation
pass
754 ↛ 761line 754 didn't jump to line 761, because the condition on line 754 was never false if av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME] is not None:
try:
756 ↛ 761line 756 didn't jump to line 761, because the condition on line 756 was never false if self._Session['ServerName'] != av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME][1].decode('utf-16le'):
self._Session['ServerDomain'] = av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME][1].decode('utf-16le')
except:
# For some reason, we couldn't decode Unicode here.. silently discard the operation
pass
761 ↛ 769line 761 didn't jump to line 769, because the condition on line 761 was never false if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME] is not None:
try:
self._Session['ServerDNSDomainName'] = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode('utf-16le')
except:
# For some reason, we couldn't decode Unicode here.. silently discard the operation
pass
# Parse Version to know the target Operating system name. Not provided elsewhere anymore
769 ↛ 778line 769 didn't jump to line 778, because the condition on line 769 was never false if 'Version' in ntlmChallenge.fields:
version = ntlmChallenge['Version']
772 ↛ 778line 772 didn't jump to line 778, because the condition on line 772 was never false if len(version) >= 4:
self._Session['ServerOS'] = "Windows %d.%d Build %d" % (indexbytes(version,0), indexbytes(version,1), struct.unpack('<H',version[2:4])[0])
self._Session["ServerOSMajor"] = indexbytes(version,0)
self._Session["ServerOSMinor"] = indexbytes(version,1)
self._Session["ServerOSBuild"] = struct.unpack('<H',version[2:4])[0]
type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, respToken['ResponseToken'], user, password, domain, lmhash, nthash)
780 ↛ 785line 780 didn't jump to line 785, because the condition on line 780 was never false if exportedSessionKey is not None:
self._Session['SessionKey'] = exportedSessionKey
if self._Session['SigningRequired'] is True and self._Connection['Dialect'] == SMB2_DIALECT_30:
self._Session['SigningKey'] = crypto.KDF_CounterMode(exportedSessionKey, b"SMB2AESCMAC\x00", b"SmbSign\x00", 128)
respToken2 = SPNEGO_NegTokenResp()
respToken2['ResponseToken'] = type3.getData()
# Reusing the previous structure
sessionSetup['SecurityBufferLength'] = len(respToken2)
sessionSetup['Buffer'] = respToken2.getData()
packetID = self.sendSMB(packet)
packet = self.recvSMB(packetID)
try:
795 ↛ exitline 795 didn't return from function 'login', because the condition on line 795 was never false if packet.isValidAnswer(STATUS_SUCCESS):
sessionSetupResponse = SMB2SessionSetup_Response(packet['Data'])
self._Session['SessionFlags'] = sessionSetupResponse['SessionFlags']
self._Session['SessionID'] = packet['SessionID']
# Do not encrypt anonymous connections
if user == '' or self.isGuestSession():
self._Connection['SupportsEncryption'] = False
# Calculate the key derivations for dialect 3.0
805 ↛ 807line 805 didn't jump to line 807, because the condition on line 805 was never false if self._Session['SigningRequired'] is True:
self._Session['SigningActivated'] = True
if self._Connection['Dialect'] == SMB2_DIALECT_30 and self._Connection['SupportsEncryption'] is True:
# SMB 3.0. Encryption available. Let's enforce it if we have AES CCM available
self._Session['SessionFlags'] |= SMB2_SESSION_FLAG_ENCRYPT_DATA
self._Session['ApplicationKey'] = crypto.KDF_CounterMode(exportedSessionKey, b"SMB2APP\x00", b"SmbRpc\x00", 128)
self._Session['EncryptionKey'] = crypto.KDF_CounterMode(exportedSessionKey, b"SMB2AESCCM\x00",b"ServerIn \x00", 128)
self._Session['DecryptionKey'] = crypto.KDF_CounterMode(exportedSessionKey, b"SMB2AESCCM\x00",b"ServerOut\x00", 128)
return True
except:
# We clean the stuff we used in case we want to authenticate again
# within the same connection
self._Session['UserCredentials'] = ''
self._Session['Connection'] = 0
self._Session['SessionID'] = 0
self._Session['SigningRequired'] = False
self._Session['SigningKey'] = ''
self._Session['SessionKey'] = ''
self._Session['SigningActivated'] = False
raise
def connectTree(self, share):
# Just in case this came with the full path (maybe an SMB1 client), let's just leave
# the sharename, we'll take care of the rest
#print self._Session['TreeConnectTable']
share = share.split('\\')[-1]
if share in self._Session['TreeConnectTable']:
# Already connected, no need to reconnect
treeEntry = self._Session['TreeConnectTable'][share]
treeEntry['NumberOfUses'] += 1
self._Session['TreeConnectTable'][treeEntry['TreeConnectId']]['NumberOfUses'] += 1
return treeEntry['TreeConnectId']
#path = share
try:
_, _, _, _, sockaddr = socket.getaddrinfo(self._Connection['ServerIP'], 80, 0, 0, socket.IPPROTO_TCP)[0]
remoteHost = sockaddr[0]
except:
remoteHost = self._Connection['ServerIP']
path = '\\\\' + remoteHost + '\\' +share
treeConnect = SMB2TreeConnect()
treeConnect['Buffer'] = path.encode('utf-16le')
treeConnect['PathLength'] = len(path)*2
packet = self.SMB_PACKET()
packet['Command'] = SMB2_TREE_CONNECT
packet['Data'] = treeConnect
packetID = self.sendSMB(packet)
packet = self.recvSMB(packetID)
858 ↛ exitline 858 didn't return from function 'connectTree', because the condition on line 858 was never false if packet.isValidAnswer(STATUS_SUCCESS):
treeConnectResponse = SMB2TreeConnect_Response(packet['Data'])
treeEntry = copy.deepcopy(TREE_CONNECT)
treeEntry['ShareName'] = share
treeEntry['TreeConnectId'] = packet['TreeID']
treeEntry['Session'] = packet['SessionID']
treeEntry['NumberOfUses'] += 1
865 ↛ 866line 865 didn't jump to line 866, because the condition on line 865 was never true if (treeConnectResponse['Capabilities'] & SMB2_SHARE_CAP_DFS) == SMB2_SHARE_CAP_DFS:
treeEntry['IsDfsShare'] = True
867 ↛ 868line 867 didn't jump to line 868, because the condition on line 867 was never true if (treeConnectResponse['Capabilities'] & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY) == SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY:
treeEntry['IsCAShare'] = True
if self._Connection['Dialect'] == SMB2_DIALECT_30:
871 ↛ 872line 871 didn't jump to line 872, because the condition on line 871 was never true if (self._Connection['SupportsEncryption'] is True) and ((treeConnectResponse['ShareFlags'] & SMB2_SHAREFLAG_ENCRYPT_DATA) == SMB2_SHAREFLAG_ENCRYPT_DATA):
treeEntry['EncryptData'] = True
# ToDo: This and what follows
# If Session.EncryptData is FALSE, the client MUST then generate an encryption key, a
# decryption key as specified in section 3.1.4.2, by providing the following inputs and store
# them in Session.EncryptionKey and Session.DecryptionKey:
877 ↛ 878line 877 didn't jump to line 878, because the condition on line 877 was never true if (treeConnectResponse['Capabilities'] & SMB2_SHARE_CAP_SCALEOUT) == SMB2_SHARE_CAP_SCALEOUT:
treeEntry['IsScaleoutShare'] = True
self._Session['TreeConnectTable'][packet['TreeID']] = treeEntry
self._Session['TreeConnectTable'][share] = treeEntry
return packet['TreeID']
def disconnectTree(self, treeId):
886 ↛ 887line 886 didn't jump to line 887, because the condition on line 886 was never true if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
889 ↛ 897line 889 didn't jump to line 897, because the condition on line 889 was never false if treeId in self._Session['TreeConnectTable']:
# More than 1 use? descrease it and return, if not, send the packet
if self._Session['TreeConnectTable'][treeId]['NumberOfUses'] > 1:
treeEntry = self._Session['TreeConnectTable'][treeId]
treeEntry['NumberOfUses'] -= 1
self._Session['TreeConnectTable'][treeEntry['ShareName']]['NumberOfUses'] -= 1
return True
packet = self.SMB_PACKET()
packet['Command'] = SMB2_TREE_DISCONNECT
packet['TreeID'] = treeId
treeDisconnect = SMB2TreeDisconnect()
packet['Data'] = treeDisconnect
packetID = self.sendSMB(packet)
packet = self.recvSMB(packetID)
904 ↛ exitline 904 didn't return from function 'disconnectTree', because the condition on line 904 was never false if packet.isValidAnswer(STATUS_SUCCESS):
shareName = self._Session['TreeConnectTable'][treeId]['ShareName']
del(self._Session['TreeConnectTable'][shareName])
del(self._Session['TreeConnectTable'][treeId])
filesIDToBeRemoved = []
for fileID in list(self._Session['OpenTable'].keys()):
if self._Session['OpenTable'][fileID]['TreeConnect'] == treeId:
filesIDToBeRemoved.append(fileID)
for fileIDToBeRemoved in filesIDToBeRemoved:
del(self._Session['OpenTable'][fileIDToBeRemoved])
return True
def create(self, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition, fileAttributes, impersonationLevel = SMB2_IL_IMPERSONATION, securityFlags = 0, oplockLevel = SMB2_OPLOCK_LEVEL_NONE, createContexts = None):
917 ↛ 918line 917 didn't jump to line 918, because the condition on line 917 was never true if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
fileName = fileName.replace('/', '\\')
if len(fileName) > 0:
fileName = ntpath.normpath(fileName)
if fileName[0] == '\\':
fileName = fileName[1:]
926 ↛ 927line 926 didn't jump to line 927, because the condition on line 926 was never true if self._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True:
pathName = fileName
else:
pathName = '\\\\' + self._Connection['ServerName'] + '\\' + fileName
fileEntry = copy.deepcopy(FILE)
fileEntry['LeaseKey'] = uuid.generate()
fileEntry['LeaseState'] = SMB2_LEASE_NONE
self.GlobalFileTable[pathName] = fileEntry
936 ↛ 938line 936 didn't jump to line 938, because the condition on line 936 was never true if self._Connection['Dialect'] == SMB2_DIALECT_30 and self._Connection['SupportsDirectoryLeasing'] is True:
# Is this file NOT on the root directory?
if len(fileName.split('\\')) > 2:
parentDir = ntpath.dirname(pathName)
if parentDir in self.GlobalFileTable:
raise Exception("Don't know what to do now! :-o")
else:
parentEntry = copy.deepcopy(FILE)
parentEntry['LeaseKey'] = uuid.generate()
parentEntry['LeaseState'] = SMB2_LEASE_NONE
self.GlobalFileTable[parentDir] = parentEntry
packet = self.SMB_PACKET()
packet['Command'] = SMB2_CREATE
packet['TreeID'] = treeId
951 ↛ 952line 951 didn't jump to line 952, because the condition on line 951 was never true if self._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True:
packet['Flags'] = SMB2_FLAGS_DFS_OPERATIONS
smb2Create = SMB2Create()
smb2Create['SecurityFlags'] = 0
smb2Create['RequestedOplockLevel'] = oplockLevel
smb2Create['ImpersonationLevel'] = impersonationLevel
smb2Create['DesiredAccess'] = desiredAccess
smb2Create['FileAttributes'] = fileAttributes
smb2Create['ShareAccess'] = shareMode
smb2Create['CreateDisposition'] = creationDisposition
smb2Create['CreateOptions'] = creationOptions
smb2Create['NameLength'] = len(fileName)*2
if fileName != '':
smb2Create['Buffer'] = fileName.encode('utf-16le')
else:
smb2Create['Buffer'] = '\x00'
970 ↛ 971line 970 didn't jump to line 971, because the condition on line 970 was never true if createContexts is not None:
smb2Create['Buffer'] += createContexts
smb2Create['CreateContextsOffset'] = len(SMB2Packet()) + SMB2Create.SIZE + smb2Create['NameLength']
smb2Create['CreateContextsLength'] = len(createContexts)
else:
smb2Create['CreateContextsOffset'] = 0
smb2Create['CreateContextsLength'] = 0
packet['Data'] = smb2Create
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
createResponse = SMB2Create_Response(ans['Data'])
openFile = copy.deepcopy(OPEN)
openFile['FileID'] = createResponse['FileID']
openFile['TreeConnect'] = treeId
openFile['Oplocklevel'] = oplockLevel
openFile['Durable'] = False
openFile['ResilientHandle'] = False
openFile['LastDisconnectTime'] = 0
openFile['FileName'] = pathName
# ToDo: Complete the OperationBuckets
if self._Connection['Dialect'] == SMB2_DIALECT_30:
openFile['DesiredAccess'] = oplockLevel
openFile['ShareMode'] = oplockLevel
openFile['CreateOptions'] = oplockLevel
openFile['FileAttributes'] = oplockLevel
openFile['CreateDisposition'] = oplockLevel
# ToDo: Process the contexts
self._Session['OpenTable'][createResponse['FileID'].getData()] = openFile
# The client MUST generate a handle for the Open, and it MUST
# return success and the generated handle to the calling application.
# In our case, str(FileID)
return createResponse['FileID'].getData()
def close(self, treeId, fileId):
1011 ↛ 1012line 1011 didn't jump to line 1012, because the condition on line 1011 was never true if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
1013 ↛ 1014line 1013 didn't jump to line 1014, because the condition on line 1013 was never true if (fileId in self._Session['OpenTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
packet = self.SMB_PACKET()
packet['Command'] = SMB2_CLOSE
packet['TreeID'] = treeId
smbClose = SMB2Close()
smbClose['Flags'] = 0
smbClose['FileID'] = fileId
packet['Data'] = smbClose
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
1029 ↛ exitline 1029 didn't return from function 'close', because the condition on line 1029 was never false if ans.isValidAnswer(STATUS_SUCCESS):
del(self.GlobalFileTable[self._Session['OpenTable'][fileId]['FileName']])
del(self._Session['OpenTable'][fileId])
# ToDo Remove stuff from GlobalFileTable
return True
def read(self, treeId, fileId, offset = 0, bytesToRead = 0, waitAnswer = True):
# IMPORTANT NOTE: As you can see, this was coded as a recursive function
# Hence, you can exhaust the memory pretty easy ( large bytesToRead )
# This function should NOT be used for reading files directly, but another higher
# level function should be used that will break the read into smaller pieces
1042 ↛ 1043line 1042 didn't jump to line 1043, because the condition on line 1042 was never true if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
1044 ↛ 1045line 1044 didn't jump to line 1045, because the condition on line 1044 was never true if (fileId in self._Session['OpenTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
packet = self.SMB_PACKET()
packet['Command'] = SMB2_READ
packet['TreeID'] = treeId
1051 ↛ 1052line 1051 didn't jump to line 1052, because the condition on line 1051 was never true if self._Connection['MaxReadSize'] < bytesToRead:
maxBytesToRead = self._Connection['MaxReadSize']
else:
maxBytesToRead = bytesToRead
if self._Connection['Dialect'] != SMB2_DIALECT_002 and self._Connection['SupportsMultiCredit'] is True:
packet['CreditCharge'] = ( 1 + (maxBytesToRead - 1) // 65536)
else:
maxBytesToRead = min(65536,bytesToRead)
smbRead = SMB2Read()
smbRead['Padding'] = 0x50
smbRead['FileID'] = fileId
smbRead['Length'] = maxBytesToRead
smbRead['Offset'] = offset
packet['Data'] = smbRead
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
readResponse = SMB2Read_Response(ans['Data'])
retData = readResponse['Buffer']
1074 ↛ 1075line 1074 didn't jump to line 1075, because the condition on line 1074 was never true if readResponse['DataRemaining'] > 0:
retData += self.read(treeId, fileId, offset+len(retData), readResponse['DataRemaining'], waitAnswer)
return retData
def write(self, treeId, fileId, data, offset = 0, bytesToWrite = 0, waitAnswer = True):
# IMPORTANT NOTE: As you can see, this was coded as a recursive function
# Hence, you can exhaust the memory pretty easy ( large bytesToWrite )
# This function should NOT be used for writing directly to files, but another higher
# level function should be used that will break the writes into smaller pieces
1084 ↛ 1085line 1084 didn't jump to line 1085, because the condition on line 1084 was never true if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
1086 ↛ 1087line 1086 didn't jump to line 1087, because the condition on line 1086 was never true if (fileId in self._Session['OpenTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
packet = self.SMB_PACKET()
packet['Command'] = SMB2_WRITE
packet['TreeID'] = treeId
1093 ↛ 1094line 1093 didn't jump to line 1094, because the condition on line 1093 was never true if self._Connection['MaxWriteSize'] < bytesToWrite:
maxBytesToWrite = self._Connection['MaxWriteSize']
else:
maxBytesToWrite = bytesToWrite
if self._Connection['Dialect'] != SMB2_DIALECT_002 and self._Connection['SupportsMultiCredit'] is True:
packet['CreditCharge'] = ( 1 + (maxBytesToWrite - 1) // 65536)
else:
maxBytesToWrite = min(65536,bytesToWrite)
smbWrite = SMB2Write()
smbWrite['FileID'] = fileId
smbWrite['Length'] = maxBytesToWrite
smbWrite['Offset'] = offset
smbWrite['WriteChannelInfoOffset'] = 0
smbWrite['Buffer'] = data[:maxBytesToWrite]
packet['Data'] = smbWrite
packetID = self.sendSMB(packet)
1112 ↛ 1115line 1112 didn't jump to line 1115, because the condition on line 1112 was never false if waitAnswer is True:
ans = self.recvSMB(packetID)
else:
return maxBytesToWrite
if ans.isValidAnswer(STATUS_SUCCESS):
writeResponse = SMB2Write_Response(ans['Data'])
bytesWritten = writeResponse['Count']
1120 ↛ 1121line 1120 didn't jump to line 1121, because the condition on line 1120 was never true if bytesWritten < bytesToWrite:
bytesWritten += self.write(treeId, fileId, data[bytesWritten:], offset+bytesWritten, bytesToWrite-bytesWritten, waitAnswer)
return bytesWritten
def queryDirectory(self, treeId, fileId, searchString = '*', resumeIndex = 0, informationClass = FILENAMES_INFORMATION, maxBufferSize = None, enumRestart = False, singleEntry = False):
1125 ↛ 1126line 1125 didn't jump to line 1126, because the condition on line 1125 was never true if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
1127 ↛ 1128line 1127 didn't jump to line 1128, because the condition on line 1127 was never true if (fileId in self._Session['OpenTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
packet = self.SMB_PACKET()
packet['Command'] = SMB2_QUERY_DIRECTORY
packet['TreeID'] = treeId
queryDirectory = SMB2QueryDirectory()
queryDirectory['FileInformationClass'] = informationClass
1136 ↛ 1137line 1136 didn't jump to line 1137, because the condition on line 1136 was never true if resumeIndex != 0 :
queryDirectory['Flags'] = SMB2_INDEX_SPECIFIED
queryDirectory['FileIndex'] = resumeIndex
queryDirectory['FileID'] = fileId
1140 ↛ 1141line 1140 didn't jump to line 1141, because the condition on line 1140 was never true if maxBufferSize is None:
maxBufferSize = self._Connection['MaxReadSize']
queryDirectory['OutputBufferLength'] = maxBufferSize
queryDirectory['FileNameLength'] = len(searchString)*2
queryDirectory['Buffer'] = searchString.encode('utf-16le')
packet['Data'] = queryDirectory
if self._Connection['Dialect'] != SMB2_DIALECT_002 and self._Connection['SupportsMultiCredit'] is True:
packet['CreditCharge'] = ( 1 + (maxBufferSize - 1) // 65536)
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
queryDirectoryResponse = SMB2QueryDirectory_Response(ans['Data'])
return queryDirectoryResponse['Buffer']
def echo(self):
packet = self.SMB_PACKET()
packet['Command'] = SMB2_ECHO
smbEcho = SMB2Echo()
packet['Data'] = smbEcho
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
return True
def cancel(self, packetID):
packet = self.SMB_PACKET()
packet['Command'] = SMB2_CANCEL
packet['MessageID'] = packetID
smbCancel = SMB2Cancel()
packet['Data'] = smbCancel
self.sendSMB(packet)
def ioctl(self, treeId, fileId = None, ctlCode = -1, flags = 0, inputBlob = '', maxInputResponse = None, maxOutputResponse = None, waitAnswer = 1):
if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
if fileId is None:
fileId = '\xff'*16
else:
if (fileId in self._Session['OpenTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
packet = self.SMB_PACKET()
packet['Command'] = SMB2_IOCTL
packet['TreeID'] = treeId
smbIoctl = SMB2Ioctl()
smbIoctl['FileID'] = fileId
smbIoctl['CtlCode'] = ctlCode
smbIoctl['MaxInputResponse'] = maxInputResponse
smbIoctl['MaxOutputResponse'] = maxOutputResponse
smbIoctl['InputCount'] = len(inputBlob)
if len(inputBlob) == 0:
smbIoctl['InputOffset'] = 0
smbIoctl['Buffer'] = '\x00'
else:
smbIoctl['Buffer'] = inputBlob
smbIoctl['OutputOffset'] = 0
smbIoctl['MaxOutputResponse'] = maxOutputResponse
smbIoctl['Flags'] = flags
packet['Data'] = smbIoctl
packetID = self.sendSMB(packet)
if waitAnswer == 0:
return True
ans = self.recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
smbIoctlResponse = SMB2Ioctl_Response(ans['Data'])
return smbIoctlResponse['Buffer']
def flush(self,treeId, fileId):
if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
if (fileId in self._Session['OpenTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
packet = self.SMB_PACKET()
packet['Command'] = SMB2_FLUSH
packet['TreeID'] = treeId
smbFlush = SMB2Flush()
smbFlush['FileID'] = fileId
packet['Data'] = smbFlush
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
return True
def lock(self, treeId, fileId, locks, lockSequence = 0):
if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
if (fileId in self._Session['OpenTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
packet = self.SMB_PACKET()
packet['Command'] = SMB2_LOCK
packet['TreeID'] = treeId
smbLock = SMB2Lock()
smbLock['FileID'] = fileId
smbLock['LockCount'] = len(locks)
smbLock['LockSequence'] = lockSequence
smbLock['Locks'] = ''.join(str(x) for x in locks)
packet['Data'] = smbLock
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
smbFlushResponse = SMB2Lock_Response(ans['Data'])
return True
# ToDo:
# If Open.ResilientHandle is TRUE or Connection.SupportsMultiChannel is TRUE, the client MUST
# do the following:
# The client MUST scan through Open.OperationBuckets and find an element with its Free field
# set to TRUE. If no such element could be found, an implementation-specific error MUST be
# returned to the application.
# Let the zero-based array index of the element chosen above be referred to as BucketIndex, and
# let BucketNumber = BucketIndex +1.
# Set Open.OperationBuckets[BucketIndex].Free = FALSE
# Let the SequenceNumber of the element chosen above be referred to as BucketSequence.
# The LockSequence field of the SMB2 lock request MUST be set to (BucketNumber<< 4) +
# BucketSequence.
# Increment the SequenceNumber of the element chosen above using MOD 16 arithmetic.
def logoff(self):
packet = self.SMB_PACKET()
packet['Command'] = SMB2_LOGOFF
smbLogoff = SMB2Logoff()
packet['Data'] = smbLogoff
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
1289 ↛ exitline 1289 didn't return from function 'logoff', because the condition on line 1289 was never false if ans.isValidAnswer(STATUS_SUCCESS):
# We clean the stuff we used in case we want to authenticate again
# within the same connection
self._Session['UserCredentials'] = ''
self._Session['Connection'] = 0
self._Session['SessionID'] = 0
self._Session['SigningRequired'] = False
self._Session['SigningKey'] = ''
self._Session['SessionKey'] = ''
self._Session['SigningActivated'] = False
return True
def queryInfo(self, treeId, fileId, inputBlob = '', infoType = SMB2_0_INFO_FILE, fileInfoClass = SMB2_FILE_STANDARD_INFO, additionalInformation = 0, flags = 0 ):
1302 ↛ 1303line 1302 didn't jump to line 1303, because the condition on line 1302 was never true if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
1304 ↛ 1305line 1304 didn't jump to line 1305, because the condition on line 1304 was never true if (fileId in self._Session['OpenTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
packet = self.SMB_PACKET()
packet['Command'] = SMB2_QUERY_INFO
packet['TreeID'] = treeId
queryInfo = SMB2QueryInfo()
queryInfo['FileID'] = fileId
queryInfo['InfoType'] = infoType
queryInfo['FileInfoClass'] = fileInfoClass
queryInfo['OutputBufferLength'] = 65535
queryInfo['AdditionalInformation'] = additionalInformation
1317 ↛ 1321line 1317 didn't jump to line 1321, because the condition on line 1317 was never false if len(inputBlob) == 0:
queryInfo['InputBufferOffset'] = 0
queryInfo['Buffer'] = '\x00'
else:
queryInfo['InputBufferLength'] = len(inputBlob)
queryInfo['Buffer'] = inputBlob
queryInfo['Flags'] = flags
packet['Data'] = queryInfo
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
1329 ↛ exitline 1329 didn't return from function 'queryInfo', because the condition on line 1329 was never false if ans.isValidAnswer(STATUS_SUCCESS):
queryResponse = SMB2QueryInfo_Response(ans['Data'])
return queryResponse['Buffer']
def setInfo(self, treeId, fileId, inputBlob = '', infoType = SMB2_0_INFO_FILE, fileInfoClass = SMB2_FILE_STANDARD_INFO, additionalInformation = 0 ):
1334 ↛ 1335line 1334 didn't jump to line 1335, because the condition on line 1334 was never true if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
1336 ↛ 1337line 1336 didn't jump to line 1337, because the condition on line 1336 was never true if (fileId in self._Session['OpenTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
packet = self.SMB_PACKET()
packet['Command'] = SMB2_SET_INFO
packet['TreeID'] = treeId
setInfo = SMB2SetInfo()
setInfo['InfoType'] = infoType
setInfo['FileInfoClass'] = fileInfoClass
setInfo['BufferLength'] = len(inputBlob)
setInfo['AdditionalInformation'] = additionalInformation
setInfo['FileID'] = fileId
setInfo['Buffer'] = inputBlob
packet['Data'] = setInfo
packetID = self.sendSMB(packet)
ans = self.recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
return True
def getSessionKey(self):
if self.getDialect() == SMB2_DIALECT_30:
return self._Session['ApplicationKey']
else:
return self._Session['SessionKey']
def setSessionKey(self, key):
if self.getDialect() == SMB2_DIALECT_30:
self._Session['ApplicationKey'] = key
else:
self._Session['SessionKey'] = key
######################################################################
# Higher level functions
def rename(self, shareName, oldPath, newPath):
oldPath = oldPath.replace('/', '\\')
oldPath = ntpath.normpath(oldPath)
1376 ↛ 1379line 1376 didn't jump to line 1379, because the condition on line 1376 was never false if len(oldPath) > 0 and oldPath[0] == '\\':
oldPath = oldPath[1:]
newPath = newPath.replace('/', '\\')
newPath = ntpath.normpath(newPath)
1381 ↛ 1384line 1381 didn't jump to line 1384, because the condition on line 1381 was never false if len(newPath) > 0 and newPath[0] == '\\':
newPath = newPath[1:]
treeId = self.connectTree(shareName)
fileId = None
try:
fileId = self.create(treeId, oldPath, MAXIMUM_ALLOWED ,FILE_SHARE_READ | FILE_SHARE_WRITE |FILE_SHARE_DELETE, 0x200020, FILE_OPEN, 0)
renameReq = FILE_RENAME_INFORMATION_TYPE_2()
renameReq['ReplaceIfExists'] = 1
renameReq['RootDirectory'] = '\x00'*8
renameReq['FileNameLength'] = len(newPath)*2
renameReq['FileName'] = newPath.encode('utf-16le')
self.setInfo(treeId, fileId, renameReq, infoType = SMB2_0_INFO_FILE, fileInfoClass = SMB2_FILE_RENAME_INFO)
finally:
1395 ↛ 1397line 1395 didn't jump to line 1397, because the condition on line 1395 was never false if fileId is not None:
self.close(treeId, fileId)
self.disconnectTree(treeId)
return True
def writeFile(self, treeId, fileId, data, offset = 0):
finished = False
writeOffset = offset
1404 ↛ 1411line 1404 didn't jump to line 1411, because the condition on line 1404 was never false while not finished:
if len(data) == 0:
break
writeData = data[:self._Connection['MaxWriteSize']]
data = data[self._Connection['MaxWriteSize']:]
written = self.write(treeId, fileId, writeData, writeOffset, len(writeData))
writeOffset += written
return writeOffset - offset
def listPath(self, shareName, path, password = None):
# ToDo: Handle situations where share is password protected
path = path.replace('/', '\\')
path = ntpath.normpath(path)
1417 ↛ 1418line 1417 didn't jump to line 1418, because the condition on line 1417 was never true if len(path) > 0 and path[0] == '\\':
path = path[1:]
treeId = self.connectTree(shareName)
fileId = None
try:
# ToDo, we're assuming it's a directory, we should check what the file type is
fileId = self.create(treeId, ntpath.dirname(path), FILE_READ_ATTRIBUTES | FILE_READ_DATA, FILE_SHARE_READ |
FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN, 0)
res = ''
files = []
from impacket import smb
while True:
try:
res = self.queryDirectory(treeId, fileId, ntpath.basename(path), maxBufferSize=65535,
informationClass=FILE_FULL_DIRECTORY_INFORMATION)
nextOffset = 1
while nextOffset != 0:
fileInfo = smb.SMBFindFileFullDirectoryInfo(smb.SMB.FLAGS2_UNICODE)
fileInfo.fromString(res)
files.append(smb.SharedFile(fileInfo['CreationTime'], fileInfo['LastAccessTime'],
fileInfo['LastChangeTime'], fileInfo['EndOfFile'],
fileInfo['AllocationSize'], fileInfo['ExtFileAttributes'],
fileInfo['FileName'].decode('utf-16le'),
fileInfo['FileName'].decode('utf-16le')))
nextOffset = fileInfo['NextEntryOffset']
res = res[nextOffset:]
1446 ↛ 1450line 1446 didn't jump to line 1450 except SessionError as e:
1447 ↛ 1448line 1447 didn't jump to line 1448, because the condition on line 1447 was never true if (e.get_error_code()) != STATUS_NO_MORE_FILES:
raise
break
except Exception as e:
print(str(e))
raise
finally:
1454 ↛ 1456line 1454 didn't jump to line 1456, because the condition on line 1454 was never false if fileId is not None:
self.close(treeId, fileId)
1456 ↛ exitline 1456 didn't except from function 'listPath', because the raise on line 1448 wasn't executed or the raise on line 1452 wasn't executed self.disconnectTree(treeId)
return files
def mkdir(self, shareName, pathName, password = None):
# ToDo: Handle situations where share is password protected
pathName = pathName.replace('/', '\\')
pathName = ntpath.normpath(pathName)
1464 ↛ 1467line 1464 didn't jump to line 1467, because the condition on line 1464 was never false if len(pathName) > 0 and pathName[0] == '\\':
pathName = pathName[1:]
treeId = self.connectTree(shareName)
fileId = None
try:
fileId = self.create(treeId, pathName, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATE, 0)
finally:
1474 ↛ 1476line 1474 didn't jump to line 1476, because the condition on line 1474 was never false if fileId is not None:
self.close(treeId, fileId)
self.disconnectTree(treeId)
return True
def rmdir(self, shareName, pathName, password = None):
# ToDo: Handle situations where share is password protected
pathName = pathName.replace('/', '\\')
pathName = ntpath.normpath(pathName)
1484 ↛ 1487line 1484 didn't jump to line 1487, because the condition on line 1484 was never false if len(pathName) > 0 and pathName[0] == '\\':
pathName = pathName[1:]
treeId = self.connectTree(shareName)
fileId = None
try:
fileId = self.create(treeId, pathName, desiredAccess=DELETE | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
shareMode=FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
creationOptions=FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT,
creationDisposition=FILE_OPEN, fileAttributes=0)
from impacket import smb
delete_req = smb.SMBSetFileDispositionInfo()
delete_req['DeletePending'] = True
self.setInfo(treeId, fileId, inputBlob=delete_req, fileInfoClass=SMB2_FILE_DISPOSITION_INFO)
finally:
1500 ↛ 1502line 1500 didn't jump to line 1502, because the condition on line 1500 was never false if fileId is not None:
self.close(treeId, fileId)
self.disconnectTree(treeId)
return True
def remove(self, shareName, pathName, password = None):
# ToDo: Handle situations where share is password protected
pathName = pathName.replace('/', '\\')
pathName = ntpath.normpath(pathName)
if len(pathName) > 0 and pathName[0] == '\\':
pathName = pathName[1:]
treeId = self.connectTree(shareName)
fileId = None
try:
fileId = self.create(treeId, pathName,DELETE | FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_NON_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE, FILE_OPEN, 0)
finally:
1519 ↛ 1521line 1519 didn't jump to line 1521, because the condition on line 1519 was never false if fileId is not None:
self.close(treeId, fileId)
self.disconnectTree(treeId)
return True
def retrieveFile(self, shareName, path, callback, mode = FILE_OPEN, offset = 0, password = None, shareAccessMode = FILE_SHARE_READ):
# ToDo: Handle situations where share is password protected
path = path.replace('/', '\\')
path = ntpath.normpath(path)
if len(path) > 0 and path[0] == '\\':
path = path[1:]
treeId = self.connectTree(shareName)
fileId = None
from impacket import smb
try:
fileId = self.create(treeId, path, FILE_READ_DATA, shareAccessMode, FILE_NON_DIRECTORY_FILE, mode, 0)
res = self.queryInfo(treeId, fileId)
fileInfo = smb.SMBQueryFileStandardInfo(res)
fileSize = fileInfo['EndOfFile']
if (fileSize-offset) < self._Connection['MaxReadSize']:
# Skip reading 0 bytes files.
1542 ↛ 1554line 1542 didn't jump to line 1554, because the condition on line 1542 was never false if (fileSize-offset) > 0:
data = self.read(treeId, fileId, offset, fileSize-offset)
callback(data)
else:
written = 0
toBeRead = fileSize-offset
while written < toBeRead:
data = self.read(treeId, fileId, offset, self._Connection['MaxReadSize'])
written += len(data)
offset += len(data)
callback(data)
finally:
1554 ↛ 1556line 1554 didn't jump to line 1556, because the condition on line 1554 was never false if fileId is not None:
self.close(treeId, fileId)
self.disconnectTree(treeId)
def storeFile(self, shareName, path, callback, mode = FILE_OVERWRITE_IF, offset = 0, password = None, shareAccessMode = FILE_SHARE_WRITE):
# ToDo: Handle situations where share is password protected
path = path.replace('/', '\\')
path = ntpath.normpath(path)
1562 ↛ 1565line 1562 didn't jump to line 1565, because the condition on line 1562 was never false if len(path) > 0 and path[0] == '\\':
path = path[1:]
treeId = self.connectTree(shareName)
fileId = None
try:
fileId = self.create(treeId, path, FILE_WRITE_DATA, shareAccessMode, FILE_NON_DIRECTORY_FILE, mode, 0)
finished = False
writeOffset = offset
1571 ↛ 1578line 1571 didn't jump to line 1578, because the condition on line 1571 was never false while not finished:
data = callback(self._Connection['MaxWriteSize'])
if len(data) == 0:
break
written = self.write(treeId, fileId, data, writeOffset, len(data))
writeOffset += written
finally:
1578 ↛ 1580line 1578 didn't jump to line 1580, because the condition on line 1578 was never false if fileId is not None:
self.close(treeId, fileId)
self.disconnectTree(treeId)
def waitNamedPipe(self, treeId, pipename, timeout = 5):
pipename = ntpath.basename(pipename)
if (treeId in self._Session['TreeConnectTable']) is False:
raise SessionError(STATUS_INVALID_PARAMETER)
if len(pipename) > 0xffff:
raise SessionError(STATUS_INVALID_PARAMETER)
pipeWait = FSCTL_PIPE_WAIT_STRUCTURE()
pipeWait['Timeout'] = timeout*100000
pipeWait['NameLength'] = len(pipename)*2
pipeWait['TimeoutSpecified'] = 1
pipeWait['Name'] = pipename.encode('utf-16le')
return self.ioctl(treeId, None, FSCTL_PIPE_WAIT,flags=SMB2_0_IOCTL_IS_FSCTL, inputBlob=pipeWait, maxInputResponse = 0, maxOutputResponse=0)
def getIOCapabilities(self):
res = dict()
res['MaxReadSize'] = self._Connection['MaxReadSize']
res['MaxWriteSize'] = self._Connection['MaxWriteSize']
return res
######################################################################
# Backward compatibility functions and alias for SMB1 and DCE Transports
# NOTE: It is strongly recommended not to use these commands
# when implementing new client calls.
get_server_name = getServerName
get_client_name = getClientName
get_server_domain = getServerDomain
get_server_dns_domain_name = getServerDNSDomainName
get_remote_name = getRemoteName
set_remote_name = setRemoteName
get_remote_host = getServerIP
get_server_os = getServerOS
get_server_os_major = getServerOSMajor
get_server_os_minor = getServerOSMinor
get_server_os_build = getServerOSBuild
tree_connect_andx = connectTree
tree_connect = connectTree
connect_tree = connectTree
disconnect_tree = disconnectTree
set_timeout = setTimeout
use_timeout = useTimeout
stor_file = storeFile
retr_file = retrieveFile
list_path = listPath
def close_session(self):
1631 ↛ exitline 1631 didn't return from function 'close_session', because the condition on line 1631 was never false if self._NetBIOSSession:
self._NetBIOSSession.close()
self._NetBIOSSession = None
def doesSupportNTLMv2(self):
# Always true :P
return True
def is_login_required(self):
# Always true :P
return True
def is_signing_required(self):
return self._Connection['RequireSigning']
def nt_create_andx(self, treeId, fileName, smb_packet=None, cmd = None):
if len(fileName) > 0 and fileName[0] == '\\':
fileName = fileName[1:]
if cmd is not None:
from impacket import smb
ntCreate = smb.SMBCommand(data = cmd.getData())
params = smb.SMBNtCreateAndX_Parameters(ntCreate['Parameters'])
return self.create(treeId, fileName, params['AccessMask'], params['ShareAccess'],
params['CreateOptions'], params['Disposition'], params['FileAttributes'],
params['Impersonation'], params['SecurityFlags'])
else:
return self.create(treeId, fileName,
FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_READ_EA |
FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | READ_CONTROL,
FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_NON_DIRECTORY_FILE, FILE_OPEN, 0 )
def get_socket(self):
return self._NetBIOSSession.get_socket()
def write_andx(self,tid,fid,data, offset = 0, wait_answer=1, write_pipe_mode = False, smb_packet=None):
# ToDo: Handle the custom smb_packet situation
return self.write(tid, fid, data, offset, len(data))
def TransactNamedPipe(self, tid, fid, data, noAnswer = 0, waitAnswer = 1, offset = 0):
return self.ioctl(tid, fid, FSCTL_PIPE_TRANSCEIVE, SMB2_0_IOCTL_IS_FSCTL, data, maxOutputResponse = 65535, waitAnswer = noAnswer | waitAnswer)
def TransactNamedPipeRecv(self):
ans = self.recvSMB()
if ans.isValidAnswer(STATUS_SUCCESS):
smbIoctlResponse = SMB2Ioctl_Response(ans['Data'])
return smbIoctlResponse['Buffer']
def read_andx(self, tid, fid, offset=0, max_size = None, wait_answer=1, smb_packet=None):
# ToDo: Handle the custom smb_packet situation
1685 ↛ 1686line 1685 didn't jump to line 1686, because the condition on line 1685 was never true if max_size is None:
max_size = self._Connection['MaxReadSize']
return self.read(tid, fid, offset, max_size, wait_answer)
def list_shared(self):
# In the context of SMB2/3, forget about the old LANMAN, throw NOT IMPLEMENTED
raise SessionError(STATUS_NOT_IMPLEMENTED)
def open_andx(self, tid, fileName, open_mode, desired_access):
# ToDo Return all the attributes of the file
if len(fileName) > 0 and fileName[0] == '\\':
fileName = fileName[1:]
fileId = self.create(tid,fileName,desired_access, open_mode, FILE_NON_DIRECTORY_FILE, open_mode, 0)
return fileId, 0, 0, 0, 0, 0, 0, 0, 0
def set_session_key(self, signingKey):
self._Session['SessionKey'] = signingKey
self._Session['SigningActivated'] = True
self._Session['SigningRequired'] = True
|