Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 

2# 

3# This software is provided under under a slightly modified version 

4# of the Apache Software License. See the accompanying LICENSE file 

5# for more information. 

6# 

7# Author: Alberto Solino (@agsolino) 

8# 

9# Description: 

10# 

11# Wrapper class for SMB1/2/3 so it's transparent for the client. 

12# You can still play with the low level methods (version dependent) 

13# by calling getSMBServer() 

14# 

15import ntpath 

16import socket 

17 

18from impacket import smb, smb3, nmb, nt_errors, LOG 

19from impacket.ntlm import compute_lmhash, compute_nthash 

20from impacket.smb3structs import SMB2Packet, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30, GENERIC_ALL, FILE_SHARE_READ, \ 

21 FILE_SHARE_WRITE, FILE_SHARE_DELETE, FILE_NON_DIRECTORY_FILE, FILE_OVERWRITE_IF, FILE_ATTRIBUTE_NORMAL, \ 

22 SMB2_IL_IMPERSONATION, SMB2_OPLOCK_LEVEL_NONE, FILE_READ_DATA , FILE_WRITE_DATA, FILE_OPEN, GENERIC_READ, GENERIC_WRITE, \ 

23 FILE_OPEN_REPARSE_POINT, MOUNT_POINT_REPARSE_DATA_STRUCTURE, FSCTL_SET_REPARSE_POINT, SMB2_0_IOCTL_IS_FSCTL, \ 

24 MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE, FSCTL_DELETE_REPARSE_POINT, FSCTL_SRV_ENUMERATE_SNAPSHOTS, SRV_SNAPSHOT_ARRAY, \ 

25 FILE_SYNCHRONOUS_IO_NONALERT, FILE_READ_EA, FILE_READ_ATTRIBUTES, READ_CONTROL, SYNCHRONIZE, SMB2_DIALECT_311 

26 

27 

28# So the user doesn't need to import smb, the smb3 are already in here 

29SMB_DIALECT = smb.SMB_DIALECT 

30 

31class SMBConnection: 

32 """ 

33 SMBConnection class 

34 

35 :param string remoteName: name of the remote host, can be its NETBIOS name, IP or *\*SMBSERVER*. If the later, 

36 and port is 139, the library will try to get the target's server name. 

37 :param string remoteHost: target server's remote address (IPv4, IPv6) or FQDN 

38 :param string/optional myName: client's NETBIOS name 

39 :param integer/optional sess_port: target port to connect 

40 :param integer/optional timeout: timeout in seconds when receiving packets 

41 :param optional preferredDialect: the dialect desired to talk with the target server. If not specified the highest 

42 one available will be used 

43 :param optional boolean manualNegotiate: the user manually performs SMB_COM_NEGOTIATE 

44 

45 :return: a SMBConnection instance, if not raises a SessionError exception 

46 """ 

47 

48 def __init__(self, remoteName='', remoteHost='', myName=None, sess_port=nmb.SMB_SESSION_PORT, timeout=60, preferredDialect=None, 

49 existingConnection=None, manualNegotiate=False): 

50 

51 self._SMBConnection = 0 

52 self._dialect = '' 

53 self._nmbSession = 0 

54 self._sess_port = sess_port 

55 self._myName = myName 

56 self._remoteHost = remoteHost 

57 self._remoteName = remoteName 

58 self._timeout = timeout 

59 self._preferredDialect = preferredDialect 

60 self._existingConnection = existingConnection 

61 self._manualNegotiate = manualNegotiate 

62 self._doKerberos = False 

63 self._kdcHost = None 

64 self._useCache = True 

65 self._ntlmFallback = True 

66 

67 if existingConnection is not None: 67 ↛ 69line 67 didn't jump to line 69, because the condition on line 67 was never true

68 # Existing Connection must be a smb or smb3 instance 

69 assert ( isinstance(existingConnection,smb.SMB) or isinstance(existingConnection, smb3.SMB3)) 

70 self._SMBConnection = existingConnection 

71 self._preferredDialect = self._SMBConnection.getDialect() 

72 self._doKerberos = self._SMBConnection.getKerberos() 

73 return 

74 

75 ##preferredDialect = smb.SMB_DIALECT 

76 

77 if manualNegotiate is False: 

78 self.negotiateSession(preferredDialect) 

79 

80 def negotiateSession(self, preferredDialect=None, 

81 flags1=smb.SMB.FLAGS1_PATHCASELESS | smb.SMB.FLAGS1_CANONICALIZED_PATHS, 

82 flags2=smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES, 

83 negoData='\x02NT LM 0.12\x00\x02SMB 2.002\x00\x02SMB 2.???\x00'): 

84 """ 

85 Perform protocol negotiation 

86 

87 :param string preferredDialect: the dialect desired to talk with the target server. If None is specified the highest one available will be used 

88 :param string flags1: the SMB FLAGS capabilities 

89 :param string flags2: the SMB FLAGS2 capabilities 

90 :param string negoData: data to be sent as part of the nego handshake 

91 

92 :return: True, raises a Session Error if error. 

93 """ 

94 

95 # If port 445 and the name sent is *SMBSERVER we're setting the name to the IP. This is to help some old 

96 # applications still believing 

97 # *SMSBSERVER will work against modern OSes. If port is NETBIOS_SESSION_PORT the user better know about i 

98 # *SMBSERVER's limitations 

99 if self._sess_port == nmb.SMB_SESSION_PORT and self._remoteName == '*SMBSERVER': 

100 self._remoteName = self._remoteHost 

101 elif self._sess_port == nmb.NETBIOS_SESSION_PORT and self._remoteName == '*SMBSERVER': 

102 # If remote name is *SMBSERVER let's try to query its name.. if can't be guessed, continue and hope for the best 

103 nb = nmb.NetBIOS() 

104 try: 

105 res = nb.getnetbiosname(self._remoteHost) 

106 except: 

107 pass 

108 else: 

109 self._remoteName = res 

110 

111 if self._sess_port == nmb.NETBIOS_SESSION_PORT: 

112 negoData = '\x02NT LM 0.12\x00\x02SMB 2.002\x00' 

113 

114 hostType = nmb.TYPE_SERVER 

115 if preferredDialect is None: 

116 # If no preferredDialect sent, we try the highest available one. 

117 packet = self.negotiateSessionWildcard(self._myName, self._remoteName, self._remoteHost, self._sess_port, 

118 self._timeout, True, flags1=flags1, flags2=flags2, data=negoData) 

119 if packet[0:1] == b'\xfe': 119 ↛ 126line 119 didn't jump to line 126, because the condition on line 119 was never false

120 # Answer is SMB2 packet 

121 self._SMBConnection = smb3.SMB3(self._remoteName, self._remoteHost, self._myName, hostType, 

122 self._sess_port, self._timeout, session=self._nmbSession, 

123 negSessionResponse=SMB2Packet(packet)) 

124 else: 

125 # Answer is SMB packet, sticking to SMBv1 

126 self._SMBConnection = smb.SMB(self._remoteName, self._remoteHost, self._myName, hostType, 

127 self._sess_port, self._timeout, session=self._nmbSession, 

128 negPacket=packet) 

129 else: 

130 if preferredDialect == smb.SMB_DIALECT: 

131 self._SMBConnection = smb.SMB(self._remoteName, self._remoteHost, self._myName, hostType, 

132 self._sess_port, self._timeout) 

133 elif preferredDialect in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30, SMB2_DIALECT_311]: 133 ↛ 137line 133 didn't jump to line 137, because the condition on line 133 was never false

134 self._SMBConnection = smb3.SMB3(self._remoteName, self._remoteHost, self._myName, hostType, 

135 self._sess_port, self._timeout, preferredDialect=preferredDialect) 

136 else: 

137 raise Exception("Unknown dialect %s") 

138 

139 # propagate flags to the smb sub-object, except for Unicode (if server supports) 

140 # does not affect smb3 objects 

141 if isinstance(self._SMBConnection, smb.SMB): 

142 if self._SMBConnection.get_flags()[1] & smb.SMB.FLAGS2_UNICODE: 142 ↛ 143line 142 didn't jump to line 143, because the condition on line 142 was never true

143 flags2 |= smb.SMB.FLAGS2_UNICODE 

144 self._SMBConnection.set_flags(flags1=flags1, flags2=flags2) 

145 

146 return True 

147 

148 def negotiateSessionWildcard(self, myName, remoteName, remoteHost, sess_port, timeout, extended_security=True, flags1=0, 

149 flags2=0, data=None): 

150 # Here we follow [MS-SMB2] negotiation handshake trying to understand what dialects 

151 # (including SMB1) is supported on the other end. 

152 

153 if not myName: 153 ↛ 159line 153 didn't jump to line 159, because the condition on line 153 was never false

154 myName = socket.gethostname() 

155 i = myName.find('.') 

156 if i > -1: 156 ↛ 157line 156 didn't jump to line 157, because the condition on line 156 was never true

157 myName = myName[:i] 

158 

159 tries = 0 

160 smbp = smb.NewSMBPacket() 

161 smbp['Flags1'] = flags1 

162 # FLAGS2_UNICODE is required by some stacks to continue, regardless of subsequent support 

163 smbp['Flags2'] = flags2 | smb.SMB.FLAGS2_UNICODE 

164 resp = None 

165 while tries < 2: 165 ↛ 186line 165 didn't jump to line 186, because the condition on line 165 was never false

166 self._nmbSession = nmb.NetBIOSTCPSession(myName, remoteName, remoteHost, nmb.TYPE_SERVER, sess_port, 

167 timeout) 

168 

169 negSession = smb.SMBCommand(smb.SMB.SMB_COM_NEGOTIATE) 

170 if extended_security is True: 170 ↛ 172line 170 didn't jump to line 172, because the condition on line 170 was never false

171 smbp['Flags2'] |= smb.SMB.FLAGS2_EXTENDED_SECURITY 

172 negSession['Data'] = data 

173 smbp.addCommand(negSession) 

174 self._nmbSession.send_packet(smbp.getData()) 

175 

176 try: 

177 resp = self._nmbSession.recv_packet(timeout) 

178 break 

179 except nmb.NetBIOSError: 

180 # OSX Yosemite asks for more Flags. Let's give it a try and see what happens 

181 smbp['Flags2'] |= smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES | smb.SMB.FLAGS2_UNICODE 

182 smbp['Data'] = [] 

183 

184 tries += 1 

185 

186 if resp is None: 186 ↛ 188line 186 didn't jump to line 188, because the condition on line 186 was never true

187 # No luck, quitting 

188 raise Exception('No answer!') 

189 

190 return resp.get_trailer() 

191 

192 

193 def getNMBServer(self): 

194 return self._nmbSession 

195 

196 def getSMBServer(self): 

197 """ 

198 returns the SMB/SMB3 instance being used. Useful for calling low level methods 

199 """ 

200 return self._SMBConnection 

201 

202 def getDialect(self): 

203 return self._SMBConnection.getDialect() 

204 

205 def getServerName(self): 

206 return self._SMBConnection.get_server_name() 

207 

208 def getClientName(self): 

209 return self._SMBConnection.get_client_name() 

210 

211 def getRemoteHost(self): 

212 return self._SMBConnection.get_remote_host() 

213 

214 def getRemoteName(self): 

215 return self._SMBConnection.get_remote_name() 

216 

217 def setRemoteName(self, name): 

218 return self._SMBConnection.set_remote_name(name) 

219 

220 def getServerDomain(self): 

221 return self._SMBConnection.get_server_domain() 

222 

223 def getServerDNSDomainName(self): 

224 return self._SMBConnection.get_server_dns_domain_name() 

225 

226 def getServerDNSHostName(self): 

227 return self._SMBConnection.get_server_dns_host_name() 

228 

229 def getServerOS(self): 

230 return self._SMBConnection.get_server_os() 

231 

232 def getServerOSMajor(self): 

233 return self._SMBConnection.get_server_os_major() 

234 

235 def getServerOSMinor(self): 

236 return self._SMBConnection.get_server_os_minor() 

237 

238 def getServerOSBuild(self): 

239 return self._SMBConnection.get_server_os_build() 

240 

241 def doesSupportNTLMv2(self): 

242 return self._SMBConnection.doesSupportNTLMv2() 

243 

244 def isLoginRequired(self): 

245 return self._SMBConnection.is_login_required() 

246 

247 def isSigningRequired(self): 

248 return self._SMBConnection.is_signing_required() 

249 

250 def getCredentials(self): 

251 return self._SMBConnection.getCredentials() 

252 

253 def getIOCapabilities(self): 

254 return self._SMBConnection.getIOCapabilities() 

255 

256 def login(self, user, password, domain = '', lmhash = '', nthash = '', ntlmFallback = True): 

257 """ 

258 logins into the target system 

259 

260 :param string user: username 

261 :param string password: password for the user 

262 :param string domain: domain where the account is valid for 

263 :param string lmhash: LMHASH used to authenticate using hashes (password is not used) 

264 :param string nthash: NTHASH used to authenticate using hashes (password is not used) 

265 :param bool ntlmFallback: If True it will try NTLMv1 authentication if NTLMv2 fails. Only available for SMBv1 

266 

267 :return: None, raises a Session Error if error. 

268 """ 

269 self._ntlmFallback = ntlmFallback 

270 try: 

271 if self.getDialect() == smb.SMB_DIALECT: 

272 return self._SMBConnection.login(user, password, domain, lmhash, nthash, ntlmFallback) 

273 else: 

274 return self._SMBConnection.login(user, password, domain, lmhash, nthash) 

275 except (smb.SessionError, smb3.SessionError) as e: 

276 raise SessionError(e.get_error_code(), e.get_error_packet()) 

277 

278 def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, 

279 TGS=None, useCache=True): 

280 """ 

281 logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. 

282 

283 :param string user: username 

284 :param string password: password for the user 

285 :param string domain: domain where the account is valid for (required) 

286 :param string lmhash: LMHASH used to authenticate using hashes (password is not used) 

287 :param string nthash: NTHASH used to authenticate using hashes (password is not used) 

288 :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication 

289 :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) 

290 :param struct TGT: If there's a TGT available, send the structure here and it will be used 

291 :param struct TGS: same for TGS. See smb3.py for the format 

292 :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False 

293 

294 :return: None, raises a Session Error if error. 

295 """ 

296 import os 

297 from impacket.krb5.ccache import CCache 

298 from impacket.krb5.kerberosv5 import KerberosError 

299 from impacket.krb5 import constants 

300 

301 self._kdcHost = kdcHost 

302 self._useCache = useCache 

303 

304 if TGT is not None or TGS is not None: 304 ↛ 305line 304 didn't jump to line 305, because the condition on line 304 was never true

305 useCache = False 

306 

307 if useCache is True: 307 ↛ 344line 307 didn't jump to line 344, because the condition on line 307 was never false

308 try: 

309 ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) 

310 except: 

311 # No cache present 

312 pass 

313 else: 

314 LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) 

315 # retrieve domain information from CCache file if needed 

316 if domain == '': 

317 domain = ccache.principal.realm['data'].decode('utf-8') 

318 LOG.debug('Domain retrieved from CCache: %s' % domain) 

319 

320 principal = 'cifs/%s@%s' % (self.getRemoteName().upper(), domain.upper()) 

321 creds = ccache.getCredential(principal) 

322 if creds is None: 

323 # Let's try for the TGT and go from there 

324 principal = 'krbtgt/%s@%s' % (domain.upper(),domain.upper()) 

325 creds = ccache.getCredential(principal) 

326 if creds is not None: 

327 TGT = creds.toTGT() 

328 LOG.debug('Using TGT from cache') 

329 else: 

330 LOG.debug("No valid credentials found in cache. ") 

331 else: 

332 TGS = creds.toTGS(principal) 

333 LOG.debug('Using TGS from cache') 

334 

335 # retrieve user information from CCache file if needed 

336 if user == '' and creds is not None: 

337 user = creds['client'].prettyPrint().split(b'@')[0].decode('utf-8') 

338 LOG.debug('Username retrieved from CCache: %s' % user) 

339 elif user == '' and len(ccache.principal.components) > 0: 

340 user = ccache.principal.components[0]['data'].decode('utf-8') 

341 LOG.debug('Username retrieved from CCache: %s' % user) 

342 

343 while True: 

344 try: 

345 if self.getDialect() == smb.SMB_DIALECT: 

346 return self._SMBConnection.kerberos_login(user, password, domain, lmhash, nthash, aesKey, kdcHost, 

347 TGT, TGS) 

348 return self._SMBConnection.kerberosLogin(user, password, domain, lmhash, nthash, aesKey, kdcHost, TGT, 

349 TGS) 

350 except (smb.SessionError, smb3.SessionError) as e: 

351 raise SessionError(e.get_error_code(), e.get_error_packet()) 

352 except KerberosError as e: 

353 if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: 

354 # We might face this if the target does not support AES 

355 # So, if that's the case we'll force using RC4 by converting 

356 # the password to lm/nt hashes and hope for the best. If that's already 

357 # done, byebye. 

358 if lmhash == '' and nthash == '' and (aesKey == '' or aesKey is None) and TGT is None and TGS is None: 

359 lmhash = compute_lmhash(password) 

360 nthash = compute_nthash(password) 

361 else: 

362 raise e 

363 else: 

364 raise e 

365 

366 def isGuestSession(self): 

367 try: 

368 return self._SMBConnection.isGuestSession() 

369 except (smb.SessionError, smb3.SessionError) as e: 

370 raise SessionError(e.get_error_code(), e.get_error_packet()) 

371 

372 def logoff(self): 

373 try: 

374 return self._SMBConnection.logoff() 

375 except (smb.SessionError, smb3.SessionError) as e: 

376 raise SessionError(e.get_error_code(), e.get_error_packet()) 

377 

378 

379 def connectTree(self,share): 

380 if self.getDialect() == smb.SMB_DIALECT: 

381 # If we already have a UNC we do nothing. 

382 if ntpath.ismount(share) is False: 

383 # Else we build it 

384 share = ntpath.basename(share) 

385 share = '\\\\' + self.getRemoteHost() + '\\' + share 

386 try: 

387 return self._SMBConnection.connect_tree(share) 

388 except (smb.SessionError, smb3.SessionError) as e: 

389 raise SessionError(e.get_error_code(), e.get_error_packet()) 

390 

391 

392 def disconnectTree(self, treeId): 

393 try: 

394 return self._SMBConnection.disconnect_tree(treeId) 

395 except (smb.SessionError, smb3.SessionError) as e: 

396 raise SessionError(e.get_error_code(), e.get_error_packet()) 

397 

398 

399 def listShares(self): 

400 """ 

401 get a list of available shares at the connected target 

402 

403 :return: a list containing dict entries for each share, raises exception if error 

404 """ 

405 # Get the shares through RPC 

406 from impacket.dcerpc.v5 import transport, srvs 

407 rpctransport = transport.SMBTransport(self.getRemoteName(), self.getRemoteHost(), filename=r'\srvsvc', 

408 smb_connection=self) 

409 dce = rpctransport.get_dce_rpc() 

410 dce.connect() 

411 dce.bind(srvs.MSRPC_UUID_SRVS) 

412 resp = srvs.hNetrShareEnum(dce, 1) 

413 return resp['InfoStruct']['ShareInfo']['Level1']['Buffer'] 

414 

415 def listPath(self, shareName, path, password = None): 

416 """ 

417 list the files/directories under shareName/path 

418 

419 :param string shareName: a valid name for the share where the files/directories are going to be searched 

420 :param string path: a base path relative to shareName 

421 :param string password: the password for the share 

422 

423 :return: a list containing smb.SharedFile items, raises a SessionError exception if error. 

424 """ 

425 

426 try: 

427 return self._SMBConnection.list_path(shareName, path, password) 

428 except (smb.SessionError, smb3.SessionError) as e: 

429 raise SessionError(e.get_error_code(), e.get_error_packet()) 

430 

431 def createFile(self, treeId, pathName, desiredAccess=GENERIC_ALL, 

432 shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 

433 creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OVERWRITE_IF, 

434 fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0, 

435 oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None): 

436 """ 

437 creates a remote file 

438 

439 

440 :param HANDLE treeId: a valid handle for the share where the file is to be created 

441 :param string pathName: the path name of the file to create 

442 :param int desiredAccess: The level of access that is required, as specified in https://msdn.microsoft.com/en-us/library/cc246503.aspx 

443 :param int shareMode: Specifies the sharing mode for the open. 

444 :param int creationOption: Specifies the options to be applied when creating or opening the file. 

445 :param int creationDisposition: Defines the action the server MUST take if the file that is specified in the name 

446 field already exists. 

447 :param int fileAttributes: This field MUST be a combination of the values specified in [MS-FSCC] section 2.6, and MUST NOT include any values other than those specified in that section. 

448 :param int impersonationLevel: This field specifies the impersonation level requested by the application that is issuing the create request. 

449 :param int securityFlags: This field MUST NOT be used and MUST be reserved. The client MUST set this to 0, and the server MUST ignore it. 

450 :param int oplockLevel: The requested oplock level 

451 :param createContexts: A variable-length attribute that is sent with an SMB2 CREATE Request or SMB2 CREATE Response that either gives extra information about how the create will be processed, or returns extra information about how the create was processed. 

452 

453 :return: a valid file descriptor, if not raises a SessionError exception. 

454 """ 

455 

456 if self.getDialect() == smb.SMB_DIALECT: 

457 _, flags2 = self._SMBConnection.get_flags() 

458 

459 pathName = pathName.replace('/', '\\') 

460 packetPathName = pathName.encode('utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName 

461 

462 ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX) 

463 ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters() 

464 ntCreate['Data'] = smb.SMBNtCreateAndX_Data(flags=flags2) 

465 ntCreate['Parameters']['FileNameLength']= len(packetPathName) 

466 ntCreate['Parameters']['AccessMask'] = desiredAccess 

467 ntCreate['Parameters']['FileAttributes']= fileAttributes 

468 ntCreate['Parameters']['ShareAccess'] = shareMode 

469 ntCreate['Parameters']['Disposition'] = creationDisposition 

470 ntCreate['Parameters']['CreateOptions'] = creationOption 

471 ntCreate['Parameters']['Impersonation'] = impersonationLevel 

472 ntCreate['Parameters']['SecurityFlags'] = securityFlags 

473 ntCreate['Parameters']['CreateFlags'] = 0x16 

474 ntCreate['Data']['FileName'] = packetPathName 

475 

476 if flags2 & smb.SMB.FLAGS2_UNICODE: 

477 ntCreate['Data']['Pad'] = 0x0 

478 

479 if createContexts is not None: 479 ↛ 480line 479 didn't jump to line 480, because the condition on line 479 was never true

480 LOG.error("CreateContexts not supported in SMB1") 

481 

482 try: 

483 return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate) 

484 except (smb.SessionError, smb3.SessionError) as e: 

485 raise SessionError(e.get_error_code(), e.get_error_packet()) 

486 else: 

487 try: 

488 return self._SMBConnection.create(treeId, pathName, desiredAccess, shareMode, creationOption, 

489 creationDisposition, fileAttributes, impersonationLevel, 

490 securityFlags, oplockLevel, createContexts) 

491 except (smb.SessionError, smb3.SessionError) as e: 

492 raise SessionError(e.get_error_code(), e.get_error_packet()) 

493 

494 def openFile(self, treeId, pathName, desiredAccess=FILE_READ_DATA | FILE_WRITE_DATA, shareMode=FILE_SHARE_READ, 

495 creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OPEN, 

496 fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0, 

497 oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None): 

498 """ 

499 opens a remote file 

500 

501 :param HANDLE treeId: a valid handle for the share where the file is to be opened 

502 :param string pathName: the path name to open 

503 :param int desiredAccess: The level of access that is required, as specified in https://msdn.microsoft.com/en-us/library/cc246503.aspx 

504 :param int shareMode: Specifies the sharing mode for the open. 

505 :param int creationOption: Specifies the options to be applied when creating or opening the file. 

506 :param int creationDisposition: Defines the action the server MUST take if the file that is specified in the name 

507 field already exists. 

508 :param int fileAttributes: This field MUST be a combination of the values specified in [MS-FSCC] section 2.6, and MUST NOT include any values other than those specified in that section. 

509 :param int impersonationLevel: This field specifies the impersonation level requested by the application that is issuing the create request. 

510 :param int securityFlags: This field MUST NOT be used and MUST be reserved. The client MUST set this to 0, and the server MUST ignore it. 

511 :param int oplockLevel: The requested oplock level 

512 :param createContexts: A variable-length attribute that is sent with an SMB2 CREATE Request or SMB2 CREATE Response that either gives extra information about how the create will be processed, or returns extra information about how the create was processed. 

513 

514 

515 :return: a valid file descriptor, if not raises a SessionError exception. 

516 """ 

517 

518 if self.getDialect() == smb.SMB_DIALECT: 

519 _, flags2 = self._SMBConnection.get_flags() 

520 

521 pathName = pathName.replace('/', '\\') 

522 packetPathName = pathName.encode('utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName 

523 

524 ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX) 

525 ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters() 

526 ntCreate['Data'] = smb.SMBNtCreateAndX_Data(flags=flags2) 

527 ntCreate['Parameters']['FileNameLength']= len(packetPathName) 

528 ntCreate['Parameters']['AccessMask'] = desiredAccess 

529 ntCreate['Parameters']['FileAttributes']= fileAttributes 

530 ntCreate['Parameters']['ShareAccess'] = shareMode 

531 ntCreate['Parameters']['Disposition'] = creationDisposition 

532 ntCreate['Parameters']['CreateOptions'] = creationOption 

533 ntCreate['Parameters']['Impersonation'] = impersonationLevel 

534 ntCreate['Parameters']['SecurityFlags'] = securityFlags 

535 ntCreate['Parameters']['CreateFlags'] = 0x16 

536 ntCreate['Data']['FileName'] = packetPathName 

537 

538 if flags2 & smb.SMB.FLAGS2_UNICODE: 

539 ntCreate['Data']['Pad'] = 0x0 

540 

541 if createContexts is not None: 541 ↛ 542line 541 didn't jump to line 542, because the condition on line 541 was never true

542 LOG.error("CreateContexts not supported in SMB1") 

543 

544 try: 

545 return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate) 

546 except (smb.SessionError, smb3.SessionError) as e: 

547 raise SessionError(e.get_error_code(), e.get_error_packet()) 

548 else: 

549 try: 

550 return self._SMBConnection.create(treeId, pathName, desiredAccess, shareMode, creationOption, 

551 creationDisposition, fileAttributes, impersonationLevel, 

552 securityFlags, oplockLevel, createContexts) 

553 except (smb.SessionError, smb3.SessionError) as e: 

554 raise SessionError(e.get_error_code(), e.get_error_packet()) 

555 

556 def writeFile(self, treeId, fileId, data, offset=0): 

557 """ 

558 writes data to a file 

559 

560 :param HANDLE treeId: a valid handle for the share where the file is to be written 

561 :param HANDLE fileId: a valid handle for the file 

562 :param string data: buffer with the data to write 

563 :param integer offset: offset where to start writing the data 

564 

565 :return: amount of bytes written, if not raises a SessionError exception. 

566 """ 

567 try: 

568 return self._SMBConnection.writeFile(treeId, fileId, data, offset) 

569 except (smb.SessionError, smb3.SessionError) as e: 

570 raise SessionError(e.get_error_code(), e.get_error_packet()) 

571 

572 

573 def readFile(self, treeId, fileId, offset = 0, bytesToRead = None, singleCall = True): 

574 """ 

575 reads data from a file 

576 

577 :param HANDLE treeId: a valid handle for the share where the file is to be read 

578 :param HANDLE fileId: a valid handle for the file to be read 

579 :param integer offset: offset where to start reading the data 

580 :param integer bytesToRead: amount of bytes to attempt reading. If None, it will attempt to read Dialect['MaxBufferSize'] bytes. 

581 :param boolean singleCall: If True it won't attempt to read all bytesToRead. It will only make a single read call 

582 

583 :return: the data read, if not raises a SessionError exception. Length of data read is not always bytesToRead 

584 """ 

585 finished = False 

586 data = b'' 

587 maxReadSize = self._SMBConnection.getIOCapabilities()['MaxReadSize'] 

588 if bytesToRead is None: 

589 bytesToRead = maxReadSize 

590 remainingBytesToRead = bytesToRead 

591 while not finished: 

592 if remainingBytesToRead > maxReadSize: 

593 toRead = maxReadSize 

594 else: 

595 toRead = remainingBytesToRead 

596 try: 

597 bytesRead = self._SMBConnection.read_andx(treeId, fileId, offset, toRead) 

598 except (smb.SessionError, smb3.SessionError) as e: 

599 if e.get_error_code() == nt_errors.STATUS_END_OF_FILE: 

600 toRead = b'' 

601 break 

602 else: 

603 raise SessionError(e.get_error_code(), e.get_error_packet()) 

604 

605 data += bytesRead 

606 if len(data) >= bytesToRead: 

607 finished = True 

608 elif len(bytesRead) == 0: 608 ↛ 610line 608 didn't jump to line 610, because the condition on line 608 was never true

609 # End of the file achieved. 

610 finished = True 

611 elif singleCall is True: 611 ↛ 614line 611 didn't jump to line 614, because the condition on line 611 was never false

612 finished = True 

613 else: 

614 offset += len(bytesRead) 

615 remainingBytesToRead -= len(bytesRead) 

616 

617 return data 

618 

619 def closeFile(self, treeId, fileId): 

620 """ 

621 closes a file handle 

622 

623 :param HANDLE treeId: a valid handle for the share where the file is to be opened 

624 :param HANDLE fileId: a valid handle for the file/directory to be closed 

625 

626 :return: None, raises a SessionError exception if error. 

627 

628 """ 

629 try: 

630 return self._SMBConnection.close(treeId, fileId) 

631 except (smb.SessionError, smb3.SessionError) as e: 

632 raise SessionError(e.get_error_code(), e.get_error_packet()) 

633 

634 def deleteFile(self, shareName, pathName): 

635 """ 

636 removes a file 

637 

638 :param string shareName: a valid name for the share where the file is to be deleted 

639 :param string pathName: the path name to remove 

640 

641 :return: None, raises a SessionError exception if error. 

642 

643 """ 

644 try: 

645 return self._SMBConnection.remove(shareName, pathName) 

646 except (smb.SessionError, smb3.SessionError) as e: 

647 raise SessionError(e.get_error_code(), e.get_error_packet()) 

648 

649 def queryInfo(self, treeId, fileId): 

650 """ 

651 queries basic information about an opened file/directory 

652 

653 :param HANDLE treeId: a valid handle for the share where the file is to be opened 

654 :param HANDLE fileId: a valid handle for the file/directory to be closed 

655 

656 :return: a smb.SMBQueryFileBasicInfo structure. raises a SessionError exception if error. 

657 

658 """ 

659 try: 

660 if self.getDialect() == smb.SMB_DIALECT: 

661 res = self._SMBConnection.query_file_info(treeId, fileId) 

662 else: 

663 res = self._SMBConnection.queryInfo(treeId, fileId) 

664 return smb.SMBQueryFileStandardInfo(res) 

665 except (smb.SessionError, smb3.SessionError) as e: 

666 raise SessionError(e.get_error_code(), e.get_error_packet()) 

667 

668 def createDirectory(self, shareName, pathName ): 

669 """ 

670 creates a directory 

671 

672 :param string shareName: a valid name for the share where the directory is to be created 

673 :param string pathName: the path name or the directory to create 

674 

675 :return: None, raises a SessionError exception if error. 

676 

677 """ 

678 try: 

679 return self._SMBConnection.mkdir(shareName, pathName) 

680 except (smb.SessionError, smb3.SessionError) as e: 

681 raise SessionError(e.get_error_code(), e.get_error_packet()) 

682 

683 def deleteDirectory(self, shareName, pathName): 

684 """ 

685 deletes a directory 

686 

687 :param string shareName: a valid name for the share where directory is to be deleted 

688 :param string pathName: the path name or the directory to delete 

689 

690 :return: None, raises a SessionError exception if error. 

691 

692 """ 

693 try: 

694 return self._SMBConnection.rmdir(shareName, pathName) 

695 except (smb.SessionError, smb3.SessionError) as e: 

696 raise SessionError(e.get_error_code(), e.get_error_packet()) 

697 

698 def waitNamedPipe(self, treeId, pipeName, timeout = 5): 

699 """ 

700 waits for a named pipe 

701 

702 :param HANDLE treeId: a valid handle for the share where the pipe is 

703 :param string pipeName: the pipe name to check 

704 :param integer timeout: time to wait for an answer 

705 

706 :return: None, raises a SessionError exception if error. 

707 

708 """ 

709 try: 

710 return self._SMBConnection.waitNamedPipe(treeId, pipeName, timeout = timeout) 

711 except (smb.SessionError, smb3.SessionError) as e: 

712 raise SessionError(e.get_error_code(), e.get_error_packet()) 

713 

714 def transactNamedPipe(self, treeId, fileId, data, waitAnswer = True): 

715 """ 

716 writes to a named pipe using a transaction command 

717 

718 :param HANDLE treeId: a valid handle for the share where the pipe is 

719 :param HANDLE fileId: a valid handle for the pipe 

720 :param string data: buffer with the data to write 

721 :param boolean waitAnswer: whether or not to wait for an answer 

722 

723 :return: None, raises a SessionError exception if error. 

724 

725 """ 

726 try: 

727 return self._SMBConnection.TransactNamedPipe(treeId, fileId, data, waitAnswer = waitAnswer) 

728 except (smb.SessionError, smb3.SessionError) as e: 

729 raise SessionError(e.get_error_code(), e.get_error_packet()) 

730 

731 

732 def transactNamedPipeRecv(self): 

733 """ 

734 reads from a named pipe using a transaction command 

735 

736 :return: data read, raises a SessionError exception if error. 

737 

738 """ 

739 try: 

740 return self._SMBConnection.TransactNamedPipeRecv() 

741 except (smb.SessionError, smb3.SessionError) as e: 

742 raise SessionError(e.get_error_code(), e.get_error_packet()) 

743 

744 def writeNamedPipe(self, treeId, fileId, data, waitAnswer = True): 

745 """ 

746 writes to a named pipe 

747 

748 :param HANDLE treeId: a valid handle for the share where the pipe is 

749 :param HANDLE fileId: a valid handle for the pipe 

750 :param string data: buffer with the data to write 

751 :param boolean waitAnswer: whether or not to wait for an answer 

752 

753 :return: None, raises a SessionError exception if error. 

754 

755 """ 

756 try: 

757 if self.getDialect() == smb.SMB_DIALECT: 

758 return self._SMBConnection.write_andx(treeId, fileId, data, wait_answer = waitAnswer, write_pipe_mode = True) 

759 else: 

760 return self.writeFile(treeId, fileId, data, 0) 

761 except (smb.SessionError, smb3.SessionError) as e: 

762 raise SessionError(e.get_error_code(), e.get_error_packet()) 

763 

764 

765 def readNamedPipe(self,treeId, fileId, bytesToRead = None ): 

766 """ 

767 read from a named pipe 

768 

769 :param HANDLE treeId: a valid handle for the share where the pipe resides 

770 :param HANDLE fileId: a valid handle for the pipe 

771 :param integer bytesToRead: amount of data to read 

772 

773 :return: None, raises a SessionError exception if error. 

774 

775 """ 

776 

777 try: 

778 return self.readFile(treeId, fileId, bytesToRead = bytesToRead, singleCall = True) 

779 except (smb.SessionError, smb3.SessionError) as e: 

780 raise SessionError(e.get_error_code(), e.get_error_packet()) 

781 

782 

783 def getFile(self, shareName, pathName, callback, shareAccessMode = None): 

784 """ 

785 downloads a file 

786 

787 :param string shareName: name for the share where the file is to be retrieved 

788 :param string pathName: the path name to retrieve 

789 :param callback callback: function called to write the contents read. 

790 :param int shareAccessMode: 

791 

792 :return: None, raises a SessionError exception if error. 

793 

794 """ 

795 try: 

796 if shareAccessMode is None: 796 ↛ 800line 796 didn't jump to line 800, because the condition on line 796 was never false

797 # if share access mode is none, let's the underlying API deals with it 

798 return self._SMBConnection.retr_file(shareName, pathName, callback) 

799 else: 

800 return self._SMBConnection.retr_file(shareName, pathName, callback, shareAccessMode=shareAccessMode) 

801 except (smb.SessionError, smb3.SessionError) as e: 

802 raise SessionError(e.get_error_code(), e.get_error_packet()) 

803 

804 def putFile(self, shareName, pathName, callback, shareAccessMode = None): 

805 """ 

806 uploads a file 

807 

808 :param string shareName: name for the share where the file is to be uploaded 

809 :param string pathName: the path name to upload 

810 :param callback callback: function called to read the contents to be written. 

811 :param int shareAccessMode: 

812 

813 :return: None, raises a SessionError exception if error. 

814 

815 """ 

816 try: 

817 if shareAccessMode is None: 817 ↛ 821line 817 didn't jump to line 821, because the condition on line 817 was never false

818 # if share access mode is none, let's the underlying API deals with it 

819 return self._SMBConnection.stor_file(shareName, pathName, callback) 

820 else: 

821 return self._SMBConnection.stor_file(shareName, pathName, callback, shareAccessMode) 

822 except (smb.SessionError, smb3.SessionError) as e: 

823 raise SessionError(e.get_error_code(), e.get_error_packet()) 

824 

825 def listSnapshots(self, tid, path): 

826 """ 

827 lists the snapshots for the given directory 

828 

829 :param int tid: tree id of current connection 

830 :param string path: directory to list the snapshots of 

831 """ 

832 

833 # Verify we're under SMB2+ session 

834 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]: 

835 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED) 

836 

837 fid = self.openFile(tid, path, FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | READ_CONTROL | SYNCHRONIZE, 

838 fileAttributes=None, creationOption=FILE_SYNCHRONOUS_IO_NONALERT, 

839 shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE) 

840 

841 # first send with maxOutputResponse=16 to get the required size 

842 try: 

843 snapshotData = SRV_SNAPSHOT_ARRAY(self._SMBConnection.ioctl(tid, fid, FSCTL_SRV_ENUMERATE_SNAPSHOTS, 

844 flags=SMB2_0_IOCTL_IS_FSCTL, maxOutputResponse=16)) 

845 except (smb.SessionError, smb3.SessionError) as e: 

846 self.closeFile(tid, fid) 

847 raise SessionError(e.get_error_code(), e.get_error_packet()) 

848 

849 if snapshotData['SnapShotArraySize'] >= 52: 

850 # now send an appropriate sized buffer 

851 try: 

852 snapshotData = SRV_SNAPSHOT_ARRAY(self._SMBConnection.ioctl(tid, fid, FSCTL_SRV_ENUMERATE_SNAPSHOTS, 

853 flags=SMB2_0_IOCTL_IS_FSCTL, maxOutputResponse=snapshotData['SnapShotArraySize']+12)) 

854 except (smb.SessionError, smb3.SessionError) as e: 

855 self.closeFile(tid, fid) 

856 raise SessionError(e.get_error_code(), e.get_error_packet()) 

857 

858 self.closeFile(tid, fid) 

859 return list(filter(None, snapshotData['SnapShots'].decode('utf16').split('\x00'))) 

860 

861 def createMountPoint(self, tid, path, target): 

862 """ 

863 creates a mount point at an existing directory 

864 

865 :param int tid: tree id of current connection 

866 :param string path: directory at which to create mount point (must already exist) 

867 :param string target: target address of mount point 

868 """ 

869 

870 # Verify we're under SMB2+ session 

871 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]: 

872 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED) 

873 

874 fid = self.openFile(tid, path, GENERIC_READ | GENERIC_WRITE, 

875 creationOption=FILE_OPEN_REPARSE_POINT) 

876 

877 if target.startswith("\\"): 

878 fixed_name = target.encode('utf-16le') 

879 else: 

880 fixed_name = ("\\??\\" + target).encode('utf-16le') 

881 

882 name = target.encode('utf-16le') 

883 

884 reparseData = MOUNT_POINT_REPARSE_DATA_STRUCTURE() 

885 

886 reparseData['PathBuffer'] = fixed_name + b"\x00\x00" + name + b"\x00\x00" 

887 reparseData['SubstituteNameLength'] = len(fixed_name) 

888 reparseData['PrintNameOffset'] = len(fixed_name) + 2 

889 reparseData['PrintNameLength'] = len(name) 

890 

891 self._SMBConnection.ioctl(tid, fid, FSCTL_SET_REPARSE_POINT, flags=SMB2_0_IOCTL_IS_FSCTL, 

892 inputBlob=reparseData) 

893 

894 self.closeFile(tid, fid) 

895 

896 def removeMountPoint(self, tid, path): 

897 """ 

898 removes a mount point without deleting the underlying directory 

899 

900 :param int tid: tree id of current connection 

901 :param string path: path to mount point to remove 

902 """ 

903 

904 # Verify we're under SMB2+ session 

905 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]: 

906 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED) 

907 

908 fid = self.openFile(tid, path, GENERIC_READ | GENERIC_WRITE, 

909 creationOption=FILE_OPEN_REPARSE_POINT) 

910 

911 reparseData = MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE() 

912 

913 reparseData['DataBuffer'] = b"" 

914 

915 try: 

916 self._SMBConnection.ioctl(tid, fid, FSCTL_DELETE_REPARSE_POINT, flags=SMB2_0_IOCTL_IS_FSCTL, 

917 inputBlob=reparseData) 

918 except (smb.SessionError, smb3.SessionError) as e: 

919 self.closeFile(tid, fid) 

920 raise SessionError(e.get_error_code(), e.get_error_packet()) 

921 

922 self.closeFile(tid, fid) 

923 

924 def rename(self, shareName, oldPath, newPath): 

925 """ 

926 renames a file/directory 

927 

928 :param string shareName: name for the share where the files/directories are 

929 :param string oldPath: the old path name or the directory/file to rename 

930 :param string newPath: the new path name or the directory/file to rename 

931 

932 :return: True, raises a SessionError exception if error. 

933 

934 """ 

935 

936 try: 

937 return self._SMBConnection.rename(shareName, oldPath, newPath) 

938 except (smb.SessionError, smb3.SessionError) as e: 

939 raise SessionError(e.get_error_code(), e.get_error_packet()) 

940 

941 def reconnect(self): 

942 """ 

943 reconnects the SMB object based on the original options and credentials used. Only exception is that 

944 manualNegotiate will not be honored. 

945 Not only the connection will be created but also a login attempt using the original credentials and 

946 method (Kerberos, PtH, etc) 

947 

948 :return: True, raises a SessionError exception if error 

949 """ 

950 userName, password, domain, lmhash, nthash, aesKey, TGT, TGS = self.getCredentials() 

951 self.negotiateSession(self._preferredDialect) 

952 if self._doKerberos is True: 952 ↛ 953line 952 didn't jump to line 953, because the condition on line 952 was never true

953 self.kerberosLogin(userName, password, domain, lmhash, nthash, aesKey, self._kdcHost, TGT, TGS, self._useCache) 

954 else: 

955 self.login(userName, password, domain, lmhash, nthash, self._ntlmFallback) 

956 

957 return True 

958 

959 def setTimeout(self, timeout): 

960 try: 

961 return self._SMBConnection.set_timeout(timeout) 

962 except (smb.SessionError, smb3.SessionError) as e: 

963 raise SessionError(e.get_error_code(), e.get_error_packet()) 

964 

965 def getSessionKey(self): 

966 if self.getDialect() == smb.SMB_DIALECT: 

967 return self._SMBConnection.get_session_key() 

968 else: 

969 return self._SMBConnection.getSessionKey() 

970 

971 def setSessionKey(self, key): 

972 if self.getDialect() == smb.SMB_DIALECT: 

973 return self._SMBConnection.set_session_key(key) 

974 else: 

975 return self._SMBConnection.setSessionKey(key) 

976 

977 def setHostnameValidation(self, validate, accept_empty, hostname): 

978 return self._SMBConnection.set_hostname_validation(validate, accept_empty, hostname) 

979 

980 def close(self): 

981 """ 

982 logs off and closes the underlying _NetBIOSSession() 

983 

984 :return: None 

985 """ 

986 try: 

987 self.logoff() 

988 except: 

989 pass 

990 self._SMBConnection.close_session() 

991 

992class SessionError(Exception): 

993 """ 

994 This is the exception every client should catch regardless of the underlying 

995 SMB version used. We'll take care of that. NETBIOS exceptions are NOT included, 

996 since all SMB versions share the same NETBIOS instances. 

997 """ 

998 def __init__( self, error = 0, packet=0): 

999 Exception.__init__(self) 

1000 self.error = error 

1001 self.packet = packet 

1002 

1003 def getErrorCode( self ): 

1004 return self.error 

1005 

1006 def getErrorPacket( self ): 

1007 return self.packet 

1008 

1009 def getErrorString( self ): 

1010 return nt_errors.ERROR_MESSAGES[self.error] 

1011 

1012 def __str__( self ): 

1013 if self.error in nt_errors.ERROR_MESSAGES: 1013 ↛ 1016line 1013 didn't jump to line 1016, because the condition on line 1013 was never false

1014 return 'SMB SessionError: %s(%s)' % (nt_errors.ERROR_MESSAGES[self.error]) 

1015 else: 

1016 return 'SMB SessionError: 0x%x' % self.error