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# Impacket - Collection of Python classes for working with network protocols. 

2# 

3# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved. 

4# 

5# This software is provided under a slightly modified version 

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

7# for more information. 

8# 

9# Description: 

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

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

12# by calling getSMBServer() 

13# 

14# Author: Alberto Solino (@agsolino) 

15# 

16 

17import ntpath 

18import socket 

19 

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

21from impacket.ntlm import compute_lmhash, compute_nthash 

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

23 FILE_SHARE_WRITE, FILE_SHARE_DELETE, FILE_NON_DIRECTORY_FILE, FILE_OVERWRITE_IF, FILE_ATTRIBUTE_NORMAL, \ 

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

25 FILE_OPEN_REPARSE_POINT, MOUNT_POINT_REPARSE_DATA_STRUCTURE, FSCTL_SET_REPARSE_POINT, SMB2_0_IOCTL_IS_FSCTL, \ 

26 MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE, FSCTL_DELETE_REPARSE_POINT, FSCTL_SRV_ENUMERATE_SNAPSHOTS, SRV_SNAPSHOT_ARRAY, \ 

27 FILE_SYNCHRONOUS_IO_NONALERT, FILE_READ_EA, FILE_READ_ATTRIBUTES, READ_CONTROL, SYNCHRONIZE, SMB2_DIALECT_311 

28 

29 

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

31SMB_DIALECT = smb.SMB_DIALECT 

32 

33class SMBConnection: 

34 """ 

35 SMBConnection class 

36 

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

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

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

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

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

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

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

44 one available will be used 

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

46 

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

48 """ 

49 

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

51 existingConnection=None, manualNegotiate=False): 

52 

53 self._SMBConnection = 0 

54 self._dialect = '' 

55 self._nmbSession = 0 

56 self._sess_port = sess_port 

57 self._myName = myName 

58 self._remoteHost = remoteHost 

59 self._remoteName = remoteName 

60 self._timeout = timeout 

61 self._preferredDialect = preferredDialect 

62 self._existingConnection = existingConnection 

63 self._manualNegotiate = manualNegotiate 

64 self._doKerberos = False 

65 self._kdcHost = None 

66 self._useCache = True 

67 self._ntlmFallback = True 

68 

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

70 # Existing Connection must be a smb or smb3 instance 

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

72 self._SMBConnection = existingConnection 

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

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

75 return 

76 

77 ##preferredDialect = smb.SMB_DIALECT 

78 

79 if manualNegotiate is False: 

80 self.negotiateSession(preferredDialect) 

81 

82 def negotiateSession(self, preferredDialect=None, 

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

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

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

86 """ 

87 Perform protocol negotiation 

88 

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

90 :param string flags1: the SMB FLAGS capabilities 

91 :param string flags2: the SMB FLAGS2 capabilities 

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

93 

94 :return: True 

95 :raise SessionError: if error 

96 """ 

97 

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

99 # applications still believing 

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

101 # *SMBSERVER's limitations 

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

103 self._remoteName = self._remoteHost 

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

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

106 nb = nmb.NetBIOS() 

107 try: 

108 res = nb.getnetbiosname(self._remoteHost) 

109 except: 

110 pass 

111 else: 

112 self._remoteName = res 

113 

114 if self._sess_port == nmb.NETBIOS_SESSION_PORT: 

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

116 

117 hostType = nmb.TYPE_SERVER 

118 if preferredDialect is None: 

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

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

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

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

123 # Answer is SMB2 packet 

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

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

126 negSessionResponse=SMB2Packet(packet)) 

127 else: 

128 # Answer is SMB packet, sticking to SMBv1 

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

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

131 negPacket=packet) 

132 else: 

133 if preferredDialect == smb.SMB_DIALECT: 

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

135 self._sess_port, self._timeout) 

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

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

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

139 else: 

140 raise Exception("Unknown dialect %s") 

141 

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

143 # does not affect smb3 objects 

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

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

146 flags2 |= smb.SMB.FLAGS2_UNICODE 

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

148 

149 return True 

150 

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

152 flags2=0, data=None): 

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

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

155 

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

157 myName = socket.gethostname() 

158 i = myName.find('.') 

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

160 myName = myName[:i] 

161 

162 tries = 0 

163 smbp = smb.NewSMBPacket() 

164 smbp['Flags1'] = flags1 

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

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

167 resp = None 

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

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

170 timeout) 

171 

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

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

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

175 negSession['Data'] = data 

176 smbp.addCommand(negSession) 

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

178 

179 try: 

180 resp = self._nmbSession.recv_packet(timeout) 

181 break 

182 except nmb.NetBIOSError: 

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

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

185 smbp['Data'] = [] 

186 

187 tries += 1 

188 

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

190 # No luck, quitting 

191 raise Exception('No answer!') 

192 

193 return resp.get_trailer() 

194 

195 

196 def getNMBServer(self): 

197 return self._nmbSession 

198 

199 def getSMBServer(self): 

200 """ 

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

202 """ 

203 return self._SMBConnection 

204 

205 def getDialect(self): 

206 return self._SMBConnection.getDialect() 

207 

208 def getServerName(self): 

209 return self._SMBConnection.get_server_name() 

210 

211 def getClientName(self): 

212 return self._SMBConnection.get_client_name() 

213 

214 def getRemoteHost(self): 

215 return self._SMBConnection.get_remote_host() 

216 

217 def getRemoteName(self): 

218 return self._SMBConnection.get_remote_name() 

219 

220 def setRemoteName(self, name): 

221 return self._SMBConnection.set_remote_name(name) 

222 

223 def getServerDomain(self): 

224 return self._SMBConnection.get_server_domain() 

225 

226 def getServerDNSDomainName(self): 

227 return self._SMBConnection.get_server_dns_domain_name() 

228 

229 def getServerDNSHostName(self): 

230 return self._SMBConnection.get_server_dns_host_name() 

231 

232 def getServerOS(self): 

233 return self._SMBConnection.get_server_os() 

234 

235 def getServerOSMajor(self): 

236 return self._SMBConnection.get_server_os_major() 

237 

238 def getServerOSMinor(self): 

239 return self._SMBConnection.get_server_os_minor() 

240 

241 def getServerOSBuild(self): 

242 return self._SMBConnection.get_server_os_build() 

243 

244 def doesSupportNTLMv2(self): 

245 return self._SMBConnection.doesSupportNTLMv2() 

246 

247 def isLoginRequired(self): 

248 return self._SMBConnection.is_login_required() 

249 

250 def isSigningRequired(self): 

251 return self._SMBConnection.is_signing_required() 

252 

253 def getCredentials(self): 

254 return self._SMBConnection.getCredentials() 

255 

256 def getIOCapabilities(self): 

257 return self._SMBConnection.getIOCapabilities() 

258 

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

260 """ 

261 logins into the target system 

262 

263 :param string user: username 

264 :param string password: password for the user 

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

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

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

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

269 

270 :return: None 

271 :raise SessionError: if error 

272 """ 

273 self._ntlmFallback = ntlmFallback 

274 try: 

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

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

277 else: 

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

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

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

281 

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

283 TGS=None, useCache=True): 

284 """ 

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

286 

287 :param string user: username 

288 :param string password: password for the user 

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

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

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

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

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

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

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

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

297 

298 :return: None 

299 :raise SessionError: if error 

300 """ 

301 import os 

302 from impacket.krb5.ccache import CCache 

303 from impacket.krb5.kerberosv5 import KerberosError 

304 from impacket.krb5 import constants 

305 

306 self._kdcHost = kdcHost 

307 self._useCache = useCache 

308 

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

310 useCache = False 

311 

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

313 try: 

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

315 except: 

316 # No cache present 

317 pass 

318 else: 

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

320 # retrieve domain information from CCache file if needed 

321 if domain == '': 

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

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

324 

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

326 creds = ccache.getCredential(principal) 

327 if creds is None: 

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

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

330 creds = ccache.getCredential(principal) 

331 if creds is not None: 

332 TGT = creds.toTGT() 

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

334 else: 

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

336 else: 

337 TGS = creds.toTGS(principal) 

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

339 

340 # retrieve user information from CCache file if needed 

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

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

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

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

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

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

347 

348 while True: 

349 try: 

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

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

352 TGT, TGS) 

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

354 TGS) 

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

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

357 except KerberosError as e: 

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

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

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

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

362 # done, byebye. 

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

364 lmhash = compute_lmhash(password) 

365 nthash = compute_nthash(password) 

366 else: 

367 raise e 

368 else: 

369 raise e 

370 

371 def isGuestSession(self): 

372 try: 

373 return self._SMBConnection.isGuestSession() 

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

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

376 

377 def logoff(self): 

378 try: 

379 return self._SMBConnection.logoff() 

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

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

382 

383 

384 def connectTree(self,share): 

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

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

387 if ntpath.ismount(share) is False: 

388 # Else we build it 

389 share = ntpath.basename(share) 

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

391 try: 

392 return self._SMBConnection.connect_tree(share) 

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

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

395 

396 

397 def disconnectTree(self, treeId): 

398 try: 

399 return self._SMBConnection.disconnect_tree(treeId) 

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

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

402 

403 

404 def listShares(self): 

405 """ 

406 get a list of available shares at the connected target 

407 

408 :return: a list containing dict entries for each share 

409 :raise SessionError: if error 

410 """ 

411 # Get the shares through RPC 

412 from impacket.dcerpc.v5 import transport, srvs 

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

414 smb_connection=self) 

415 dce = rpctransport.get_dce_rpc() 

416 dce.connect() 

417 dce.bind(srvs.MSRPC_UUID_SRVS) 

418 resp = srvs.hNetrShareEnum(dce, 1) 

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

420 

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

422 """ 

423 list the files/directories under shareName/path 

424 

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

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

427 :param string password: the password for the share 

428 

429 :return: a list containing smb.SharedFile items 

430 :raise SessionError: if error 

431 """ 

432 

433 try: 

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

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

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

437 

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

439 shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 

440 creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OVERWRITE_IF, 

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

442 oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None): 

443 """ 

444 Creates a remote file 

445 

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

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

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

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

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

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

452 field already exists. 

453 :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. 

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

455 :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. 

456 :param int oplockLevel: The requested oplock level 

457 :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. 

458 

459 :return: a valid file descriptor 

460 :raise SessionError: if error 

461 """ 

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

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

464 

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

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

467 

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

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

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

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

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

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

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

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

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

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

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

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

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

481 

482 if flags2 & smb.SMB.FLAGS2_UNICODE: 

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

484 

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

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

487 

488 try: 

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

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

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

492 else: 

493 try: 

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

495 creationDisposition, fileAttributes, impersonationLevel, 

496 securityFlags, oplockLevel, createContexts) 

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

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

499 

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

501 creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OPEN, 

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

503 oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None): 

504 """ 

505 opens a remote file 

506 

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

508 :param string pathName: the path name to open 

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

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

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

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

513 field already exists. 

514 :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. 

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

516 :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. 

517 :param int oplockLevel: The requested oplock level 

518 :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. 

519 

520 :return: a valid file descriptor 

521 :raise SessionError: if error 

522 """ 

523 

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

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

526 

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

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

529 

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

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

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

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

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

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

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

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

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

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

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

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

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

543 

544 if flags2 & smb.SMB.FLAGS2_UNICODE: 

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

546 

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

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

549 

550 try: 

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

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

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

554 else: 

555 try: 

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

557 creationDisposition, fileAttributes, impersonationLevel, 

558 securityFlags, oplockLevel, createContexts) 

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

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

561 

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

563 """ 

564 writes data to a file 

565 

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

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

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

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

570 

571 :return: amount of bytes written 

572 :raise SessionError: if error 

573 """ 

574 try: 

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

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

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

578 

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

580 """ 

581 reads data from a file 

582 

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

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

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

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

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

588 

589 :return: the data read. Length of data read is not always bytesToRead 

590 :raise SessionError: if error 

591 """ 

592 finished = False 

593 data = b'' 

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

595 if bytesToRead is None: 

596 bytesToRead = maxReadSize 

597 remainingBytesToRead = bytesToRead 

598 while not finished: 

599 if remainingBytesToRead > maxReadSize: 

600 toRead = maxReadSize 

601 else: 

602 toRead = remainingBytesToRead 

603 try: 

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

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

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

607 toRead = b'' 

608 break 

609 else: 

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

611 

612 data += bytesRead 

613 if len(data) >= bytesToRead: 

614 finished = True 

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

616 # End of the file achieved. 

617 finished = True 

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

619 finished = True 

620 else: 

621 offset += len(bytesRead) 

622 remainingBytesToRead -= len(bytesRead) 

623 

624 return data 

625 

626 def closeFile(self, treeId, fileId): 

627 """ 

628 closes a file handle 

629 

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

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

632 

633 :return: None 

634 :raise SessionError: if error 

635 """ 

636 try: 

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

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

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

640 

641 def deleteFile(self, shareName, pathName): 

642 """ 

643 removes a file 

644 

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

646 :param string pathName: the path name to remove 

647 

648 :return: None 

649 :raise SessionError: if error 

650 """ 

651 try: 

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

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

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

655 

656 def queryInfo(self, treeId, fileId): 

657 """ 

658 queries basic information about an opened file/directory 

659 

660 :param HANDLE treeId: a valid handle for the share where the file is to be queried 

661 :param HANDLE fileId: a valid handle for the file/directory to be queried 

662 

663 :return: a smb.SMBQueryFileStandardInfo structure. 

664 :raise SessionError: if error 

665 """ 

666 try: 

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

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

669 else: 

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

671 return smb.SMBQueryFileStandardInfo(res) 

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

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

674 

675 def createDirectory(self, shareName, pathName ): 

676 """ 

677 creates a directory 

678 

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

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

681 

682 :return: None 

683 :raise SessionError: if error 

684 """ 

685 try: 

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

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

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

689 

690 def deleteDirectory(self, shareName, pathName): 

691 """ 

692 deletes a directory 

693 

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

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

696 

697 :return: None 

698 :raise SessionError: if error 

699 """ 

700 try: 

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

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

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

704 

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

706 """ 

707 waits for a named pipe 

708 

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

710 :param string pipeName: the pipe name to check 

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

712 

713 :return: None 

714 :raise SessionError: if error 

715 """ 

716 try: 

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

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

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

720 

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

722 """ 

723 writes to a named pipe using a transaction command 

724 

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

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

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

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

729 

730 :return: None 

731 :raise SessionError: if error 

732 """ 

733 try: 

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

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

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

737 

738 def transactNamedPipeRecv(self): 

739 """ 

740 reads from a named pipe using a transaction command 

741 

742 :return: data read 

743 :raise SessionError: if error 

744 """ 

745 try: 

746 return self._SMBConnection.TransactNamedPipeRecv() 

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

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

749 

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

751 """ 

752 writes to a named pipe 

753 

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

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

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

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

758 

759 :return: None 

760 :raise SessionError: if error 

761 """ 

762 try: 

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

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

765 else: 

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

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

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

769 

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

771 """ 

772 read from a named pipe 

773 

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

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

776 :param integer bytesToRead: amount of data to read 

777 

778 :return: None 

779 :raise SessionError: if error 

780 """ 

781 

782 try: 

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

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

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

786 

787 

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

789 """ 

790 downloads a file 

791 

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

793 :param string pathName: the path name to retrieve 

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

795 :param int shareAccessMode: 

796 

797 :return: None 

798 :raise SessionError: if error 

799 """ 

800 try: 

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

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

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

804 else: 

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

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

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

808 

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

810 """ 

811 uploads a file 

812 

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

814 :param string pathName: the path name to upload 

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

816 :param int shareAccessMode: 

817 

818 :return: None 

819 :raise SessionError: if error 

820 """ 

821 try: 

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

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

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

825 else: 

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

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

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

829 

830 def listSnapshots(self, tid, path): 

831 """ 

832 lists the snapshots for the given directory 

833 

834 :param int tid: tree id of current connection 

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

836 

837 :raise SessionError: if error 

838 """ 

839 

840 # Verify we're under SMB2+ session 

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

842 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED) 

843 

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

845 fileAttributes=None, creationOption=FILE_SYNCHRONOUS_IO_NONALERT, 

846 shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE) 

847 

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

849 try: 

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

851 flags=SMB2_0_IOCTL_IS_FSCTL, maxOutputResponse=16)) 

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

853 self.closeFile(tid, fid) 

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

855 

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

857 # now send an appropriate sized buffer 

858 try: 

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

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

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

862 self.closeFile(tid, fid) 

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

864 

865 self.closeFile(tid, fid) 

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

867 

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

869 """ 

870 creates a mount point at an existing directory 

871 

872 :param int tid: tree id of current connection 

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

874 :param string target: target address of mount point 

875 

876 :raise SessionError: if error 

877 """ 

878 

879 # Verify we're under SMB2+ session 

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

881 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED) 

882 

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

884 creationOption=FILE_OPEN_REPARSE_POINT) 

885 

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

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

888 else: 

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

890 

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

892 

893 reparseData = MOUNT_POINT_REPARSE_DATA_STRUCTURE() 

894 

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

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

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

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

899 

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

901 inputBlob=reparseData) 

902 

903 self.closeFile(tid, fid) 

904 

905 def removeMountPoint(self, tid, path): 

906 """ 

907 removes a mount point without deleting the underlying directory 

908 

909 :param int tid: tree id of current connection 

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

911 

912 :raise SessionError: if error 

913 """ 

914 

915 # Verify we're under SMB2+ session 

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

917 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED) 

918 

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

920 creationOption=FILE_OPEN_REPARSE_POINT) 

921 

922 reparseData = MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE() 

923 

924 reparseData['DataBuffer'] = b"" 

925 

926 try: 

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

928 inputBlob=reparseData) 

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

930 self.closeFile(tid, fid) 

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

932 

933 self.closeFile(tid, fid) 

934 

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

936 """ 

937 renames a file/directory 

938 

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

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

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

942 

943 :return: True 

944 :raise SessionError: if error 

945 """ 

946 

947 try: 

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

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

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

951 

952 def reconnect(self): 

953 """ 

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

955 manualNegotiate will not be honored. 

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

957 method (Kerberos, PtH, etc) 

958 

959 :return: True 

960 :raise SessionError: if error 

961 """ 

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

963 self.negotiateSession(self._preferredDialect) 

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

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

966 else: 

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

968 

969 return True 

970 

971 def setTimeout(self, timeout): 

972 try: 

973 return self._SMBConnection.set_timeout(timeout) 

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

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

976 

977 def getSessionKey(self): 

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

979 return self._SMBConnection.get_session_key() 

980 else: 

981 return self._SMBConnection.getSessionKey() 

982 

983 def setSessionKey(self, key): 

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

985 return self._SMBConnection.set_session_key(key) 

986 else: 

987 return self._SMBConnection.setSessionKey(key) 

988 

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

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

991 

992 def close(self): 

993 """ 

994 logs off and closes the underlying _NetBIOSSession() 

995 

996 :return: None 

997 """ 

998 try: 

999 self.logoff() 

1000 except: 

1001 pass 

1002 self._SMBConnection.close_session() 

1003 

1004 

1005class SessionError(Exception): 

1006 """ 

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

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

1009 since all SMB versions share the same NETBIOS instances. 

1010 """ 

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

1012 Exception.__init__(self) 

1013 self.error = error 

1014 self.packet = packet 

1015 

1016 def getErrorCode( self ): 

1017 return self.error 

1018 

1019 def getErrorPacket( self ): 

1020 return self.packet 

1021 

1022 def getErrorString( self ): 

1023 return nt_errors.ERROR_MESSAGES[self.error] 

1024 

1025 def __str__( self ): 

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

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

1028 else: 

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