smbclientng.core.SMBSession
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# File name : smbclient-ng.py 4# Author : Podalirius (@podalirius_) 5# Date created : 20 may 2024 6 7 8import impacket.smbconnection 9import ntpath 10import os 11import re 12import traceback 13from smbclientng.core.LocalFileIO import LocalFileIO 14from smbclientng.core.utils import b_filesize, STYPE_MASK 15 16 17class SMBSession(object): 18 """ 19 Class SMBSession is designed to handle the session management for SMB (Server Message Block) protocol connections. 20 It provides functionalities to connect to an SMB server, authenticate using either NTLM or Kerberos, and manage SMB shares. 21 22 Attributes: 23 address (str): The IP address or hostname of the SMB server. 24 domain (str): The domain name for SMB server authentication. 25 username (str): The username for SMB server authentication. 26 password (str): The password for SMB server authentication. 27 lmhash (str): The LM hash of the user's password, if available. 28 nthash (str): The NT hash of the user's password, if available. 29 use_kerberos (bool): A flag to determine whether to use Kerberos for authentication. 30 kdcHost (str): The Key Distribution Center (KDC) host for Kerberos authentication. 31 debug (bool): A flag to enable debug output. 32 smbClient (object): The SMB client object used for the connection. 33 connected (bool): A flag to check the status of the connection. 34 smb_share (str): The current SMB share in use. 35 smb_path (str): The current path within the SMB share. 36 37 Methods: 38 __init__(address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, debug=False): 39 Initializes the SMBSession with the specified parameters. 40 init_smb_session(): 41 Initializes the SMB session by connecting to the server and authenticating using the specified method. 42 """ 43 44 def __init__(self, address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None): 45 super(SMBSession, self).__init__() 46 # Objects 47 self.config = config 48 49 # Target server 50 self.address = address 51 52 # Credentials 53 self.domain = domain 54 self.username = username 55 self.password = password 56 self.lmhash = lmhash 57 self.nthash = nthash 58 self.use_kerberos = use_kerberos 59 self.kdcHost = kdcHost 60 61 self.smbClient = None 62 self.connected = False 63 64 self.available_shares = {} 65 self.smb_share = None 66 self.smb_cwd = "" 67 68 self.list_shares() 69 70 # Connect and disconnect SMB session 71 72 def init_smb_session(self): 73 """ 74 Initializes and establishes a session with the SMB server. 75 76 This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration. 77 It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization. 78 79 The method will print debug information if the `debug` attribute is set to True. Upon successful connection and authentication, it sets the `connected` attribute to True. 80 81 Returns: 82 bool: True if the connection and authentication are successful, False otherwise. 83 """ 84 85 if self.config.debug: 86 print("[debug] [>] Connecting to remote SMB server '%s' ... " % self.address) 87 try: 88 self.smbClient = impacket.smbconnection.SMBConnection( 89 remoteName=self.address, 90 remoteHost=self.address, 91 sess_port=int(445) 92 ) 93 except OSError as err: 94 print("[!] %s" % err) 95 self.smbClient = None 96 97 self.connected = False 98 if self.smbClient is not None: 99 if self.use_kerberos: 100 if self.config.debug: 101 print("[debug] [>] Authenticating as '%s\\%s' with kerberos ... " % (self.domain, self.username)) 102 self.connected = self.smbClient.kerberosLogin( 103 user=self.username, 104 password=self.password, 105 domain=self.domain, 106 lmhash=self.lmhash, 107 nthash=self.nthash, 108 aesKey=self.aesKey, 109 kdcHost=self.kdcHost 110 ) 111 112 else: 113 if self.config.debug: 114 print("[debug] [>] Authenticating as '%s\\%s' with NTLM ... " % (self.domain, self.username)) 115 self.connected = self.smbClient.login( 116 user=self.username, 117 password=self.password, 118 domain=self.domain, 119 lmhash=self.lmhash, 120 nthash=self.nthash 121 ) 122 123 if self.connected: 124 print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username)) 125 else: 126 print("[!] Failed to authenticate to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username)) 127 128 return self.connected 129 130 def close_smb_session(self): 131 """ 132 Closes the current SMB session by disconnecting the SMB client. 133 134 This method ensures that the SMB client connection is properly closed. It checks if the client is connected 135 and if so, it closes the connection and resets the connection status. 136 137 Raises: 138 Exception: If the SMB client is not initialized or if there's an error during the disconnection process. 139 """ 140 141 if self.smbClient is not None: 142 if self.connected: 143 self.smbClient.close() 144 self.connected = False 145 print("[+] SMB connection closed successfully.") 146 else: 147 print("[!] No active SMB connection to close.") 148 else: 149 raise Exception("SMB client is not initialized.") 150 151 # Operations 152 153 def get_file(self, path=None, keepRemotePath=False): 154 """ 155 Retrieves a file from the specified path on the SMB share. 156 157 This method attempts to retrieve a file from the given path within the currently connected SMB share. 158 If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local 159 file object and writing the contents of the remote file to it using the SMB client's getFile method. 160 161 Parameters: 162 path (str): The path of the file to retrieve. If None, uses the current smb_path. 163 164 Returns: 165 None 166 """ 167 168 tmp_file_path = self.smb_cwd + ntpath.sep + path 169 matches = self.smbClient.listPath( 170 shareName=self.smb_share, 171 path=tmp_file_path 172 ) 173 174 for entry in matches: 175 if entry.is_directory(): 176 print("[>] Skipping '%s' because it is a directory." % tmp_file_path) 177 else: 178 try: 179 if ntpath.sep in path: 180 outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname() 181 else: 182 outputfile = entry.get_longname() 183 f = LocalFileIO( 184 mode="wb", 185 path=outputfile, 186 expected_size=entry.get_filesize(), 187 debug=self.config.debug, 188 keepRemotePath=keepRemotePath 189 ) 190 self.smbClient.getFile( 191 shareName=self.smb_share, 192 pathName=tmp_file_path, 193 callback=f.write 194 ) 195 f.close() 196 except (BrokenPipeError, KeyboardInterrupt) as e: 197 f.close() 198 print("\x1b[v\x1b[o\r[!] Interrupted.") 199 self.close_smb_session() 200 self.init_smb_session() 201 202 return None 203 204 def get_file_recursively(self, path=None): 205 """ 206 Recursively retrieves files from a specified path on the SMB share. 207 208 This method navigates through all directories starting from the given path, 209 and downloads all files found. It handles directories recursively, ensuring 210 that all nested files are retrieved. The method skips over directory entries 211 and handles errors gracefully, attempting to continue the operation where possible. 212 213 Parameters: 214 path (str): The initial directory path from which to start the recursive file retrieval. 215 If None, it starts from the root of the configured SMB share. 216 """ 217 218 def recurse_action(base_dir="", path=[]): 219 remote_smb_path = base_dir + ntpath.sep.join(path) 220 entries = self.smbClient.listPath( 221 shareName=self.smb_share, 222 path=remote_smb_path + '\\*' 223 ) 224 if len(entries) != 0: 225 files = [entry for entry in entries if not entry.is_directory()] 226 directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]] 227 228 # Files 229 if len(files) != 0: 230 print("[>] Retrieving files of '%s'" % remote_smb_path) 231 for entry_file in files: 232 if not entry_file.is_directory(): 233 f = LocalFileIO( 234 mode="wb", 235 path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 236 expected_size=entry_file.get_filesize(), 237 debug=self.config.debug 238 ) 239 try: 240 self.smbClient.getFile( 241 shareName=self.smb_share, 242 pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 243 callback=f.write 244 ) 245 f.close() 246 except BrokenPipeError as err: 247 f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err)) 248 f.close(remove=True) 249 break 250 except Exception as err: 251 f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err)) 252 f.close(remove=True) 253 254 # Directories 255 for entry_directory in directories: 256 if entry_directory.is_directory(): 257 recurse_action( 258 base_dir=self.smb_cwd, 259 path=path+[entry_directory.get_longname()] 260 ) 261 # Entrypoint 262 try: 263 recurse_action( 264 base_dir=self.smb_cwd, 265 path=[path] 266 ) 267 except (BrokenPipeError, KeyboardInterrupt) as e: 268 print("\x1b[v\x1b[o\r[!] Interrupted.") 269 self.close_smb_session() 270 self.init_smb_session() 271 272 def info(self, share=True, server=True): 273 """ 274 Displays information about the server and optionally the shares. 275 276 This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the `share` parameter is set to True and a share is currently set, it will also attempt to display information about the share. 277 278 Parameters: 279 share (bool): If True, display information about the current share. 280 server (bool): If True, display information about the server. 281 282 Returns: 283 None 284 """ 285 286 if server: 287 if self.config.no_colors: 288 print("[+] Server:") 289 print(" ├─NetBIOS:") 290 print(" │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName())) 291 print(" │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain())) 292 print(" ├─DNS:") 293 print(" │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName())) 294 print(" │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName())) 295 print(" ├─OS:") 296 print(" │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS())) 297 print(" │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild())) 298 print(" ├─Server:") 299 print(" │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired())) 300 print(" │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired())) 301 print(" │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2())) 302 MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"] 303 print(" │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize))) 304 MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"] 305 print(" │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize))) 306 print(" └─") 307 else: 308 print("[+] Server:") 309 print(" ├─NetBIOS:") 310 print(" │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName())) 311 print(" │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain())) 312 print(" ├─DNS:") 313 print(" │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName())) 314 print(" │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName())) 315 print(" ├─OS:") 316 print(" │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS())) 317 print(" │ └─ \x1b[94mOS Version\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s.%s.%s\x1b[0m" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild())) 318 print(" ├─Server:") 319 print(" │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired())) 320 print(" │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired())) 321 print(" │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2())) 322 MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"] 323 print(" │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize))) 324 MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"] 325 print(" │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize))) 326 print(" └─") 327 328 if share and self.smb_share is not None: 329 share_name = self.available_shares.get(self.smb_share.lower(), "")["name"] 330 share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"] 331 share_type = self.available_shares.get(self.smb_share.lower(), "")["type"] 332 share_type =', '.join([s.replace("STYPE_","") for s in share_type]) 333 share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"] 334 if self.config.no_colors: 335 print("\n[+] Share:") 336 print(" ├─ Name ──────────── : %s" % (share_name)) 337 print(" ├─ Description ───── : %s" % (share_comment)) 338 print(" ├─ Type ──────────── : %s" % (share_type)) 339 print(" └─ Raw type value ── : %s" % (share_rawtype)) 340 else: 341 print("\n[+] Share:") 342 print(" ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name)) 343 print(" ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment)) 344 print(" ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type)) 345 print(" └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype)) 346 347 def list_contents(self, path=None): 348 """ 349 Lists the contents of a specified directory on the SMB share. 350 351 This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path` 352 is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with 353 the long names of the files and directories as keys and their respective SMB entry objects as values. 354 355 Args: 356 shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None. 357 path (str, optional): The directory path to list contents from. Defaults to the current path if None. 358 359 Returns: 360 dict: A dictionary with file and directory names as keys and their SMB entry objects as values. 361 """ 362 363 if path is None or len(path) == 0: 364 path = self.smb_cwd 365 path = path.rstrip(ntpath.sep) + ntpath.sep + "*" 366 367 contents = {} 368 entries = self.smbClient.listPath( 369 shareName=self.smb_share, 370 path=path 371 ) 372 for entry in entries: 373 contents[entry.get_longname()] = entry 374 375 return contents 376 377 def list_shares(self): 378 """ 379 Lists all the shares available on the connected SMB server. 380 381 This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary 382 with key-value pairs where the key is the share name and the value is a dictionary containing details about the share 383 such as its name, type, raw type, and any comments associated with the share. 384 385 Returns: 386 dict: A dictionary containing information about each share available on the server. 387 """ 388 389 self.available_shares = {} 390 391 if self.connected: 392 if self.smbClient is not None: 393 resp = self.smbClient.listShares() 394 395 for share in resp: 396 # SHARE_INFO_1 structure (lmshare.h) 397 # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1 398 sharename = share["shi1_netname"][:-1] 399 sharecomment = share["shi1_remark"][:-1] 400 sharetype = share["shi1_type"] 401 402 self.available_shares[sharename.lower()] = { 403 "name": sharename, 404 "type": STYPE_MASK(sharetype), 405 "rawtype": sharetype, 406 "comment": sharecomment 407 } 408 else: 409 print("[!] Error: SMBSession.smbClient is None.") 410 411 return self.available_shares 412 413 def mkdir(self, path=None): 414 """ 415 Creates a directory at the specified path on the SMB share. 416 417 This method takes a path and attempts to create the directory structure on the SMB share. If the path includes 418 nested directories, it will create each directory in the sequence. If a directory already exists, it will skip 419 the creation for that directory without raising an error. 420 421 Args: 422 path (str, optional): The full path of the directory to create on the SMB share. Defaults to None. 423 424 Note: 425 The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility. 426 """ 427 428 if path is not None: 429 # Prepare path 430 path = path.replace('/',ntpath.sep) 431 if ntpath.sep in path: 432 path = path.strip(ntpath.sep).split(ntpath.sep) 433 else: 434 path = [path] 435 436 # Create each dir in the path 437 for depth in range(1, len(path)+1): 438 tmp_path = ntpath.sep.join(path[:depth]) 439 try: 440 self.smbClient.createDirectory( 441 shareName=self.smb_share, 442 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep) 443 ) 444 except impacket.smbconnection.SessionError as err: 445 if err.getErrorCode() == 0xc0000035: 446 # STATUS_OBJECT_NAME_COLLISION 447 # Remote directory already created, this is normal 448 # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19 449 pass 450 else: 451 print("[!] Failed to create directory '%s': %s" % (tmp_path, err)) 452 if self.config.debug: 453 traceback.print_exc() 454 else: 455 pass 456 457 def path_exists(self, path=None): 458 """ 459 Checks if the specified path exists on the SMB share. 460 461 This method determines if a given path exists on the SMB share by attempting to list the contents of the path. 462 If the path listing is successful and returns one or more entries, the path is considered to exist. 463 464 Args: 465 path (str, optional): The path to check on the SMB share. Defaults to None. 466 467 Returns: 468 bool: True if the path exists, False otherwise or if an error occurs. 469 """ 470 471 if path is not None: 472 path = path.replace('*','') 473 try: 474 contents = self.smbClient.listPath( 475 shareName=self.smb_share, 476 path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep) 477 ) 478 return (len(contents) != 0) 479 except Exception as e: 480 return False 481 else: 482 return False 483 484 def path_isdir(self, pathFromRoot=None): 485 """ 486 Checks if the specified path is a directory on the SMB share. 487 488 This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the 489 contents of the path and filtering for entries that match the basename of the path and are marked as directories. 490 491 Args: 492 path (str, optional): The path to check on the SMB share. Defaults to None. 493 494 Returns: 495 bool: True if the path is a directory, False otherwise or if an error occurs. 496 """ 497 498 if pathFromRoot is not None: 499 # Replace slashes if any 500 path = pathFromRoot.replace('/', ntpath.sep) 501 502 # Strip wildcards to avoid injections 503 path = path.replace('*','') 504 505 # Normalize path and strip leading backslash 506 path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep) 507 508 if path.strip() in ['', '.', '..']: 509 # By defininition they exist on the filesystem 510 return True 511 else: 512 try: 513 contents = self.smbClient.listPath( 514 shareName=self.smb_share, 515 path=path+'*' 516 ) 517 # Filter on directories 518 contents = [ 519 c for c in contents 520 if c.get_longname() == ntpath.basename(path) and c.is_directory() 521 ] 522 return (len(contents) != 0) 523 except Exception as e: 524 return False 525 else: 526 return False 527 528 def path_isfile(self, path=None): 529 """ 530 Checks if the specified path is a file on the SMB share. 531 532 This method determines if a given path corresponds to a file on the SMB share. It does this by listing the 533 contents of the path and filtering for entries that match the basename of the path and are not marked as directories. 534 535 Args: 536 path (str, optional): The path to check on the SMB share. Defaults to None. 537 538 Returns: 539 bool: True if the path is a file, False otherwise or if an error occurs. 540 """ 541 542 if path is not None: 543 path = path.replace('*','') 544 try: 545 contents = self.smbClient.listPath( 546 shareName=self.smb_share, 547 path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep) 548 ) 549 # Filter on files 550 contents = [ 551 c for c in contents 552 if c.get_longname() == ntpath.basename(path) and not c.is_directory() 553 ] 554 return (len(contents) != 0) 555 except Exception as e: 556 return False 557 else: 558 return False 559 560 def ping_smb_session(self): 561 """ 562 Tests the connectivity to the SMB server by sending an echo command. 563 564 This method attempts to send an echo command to the SMB server to check if the session is still active. 565 It updates the `connected` attribute of the class based on the success or failure of the echo command. 566 567 Returns: 568 bool: True if the echo command succeeds (indicating the session is active), False otherwise. 569 """ 570 571 try: 572 self.smbClient.getSMBServer().echo() 573 self.connected = True 574 except Exception as e: 575 self.connected = False 576 return self.connected 577 578 def put_file(self, localpath=None): 579 """ 580 Uploads a single file to the SMB share. 581 582 This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path. 583 It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session. 584 General exceptions are caught and logged, with a traceback provided if debugging is enabled. 585 586 Args: 587 localpath (str, optional): The local file path of the file to be uploaded. Defaults to None. 588 """ 589 590 if os.path.exists(localpath): 591 if os.path.isfile(localpath): 592 try: 593 localfile = os.path.basename(localpath) 594 f = LocalFileIO( 595 mode="rb", 596 path=localpath, 597 debug=self.config.debug 598 ) 599 self.smbClient.putFile( 600 shareName=self.smb_share, 601 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 602 callback=f.read 603 ) 604 f.close() 605 except (BrokenPipeError, KeyboardInterrupt) as err: 606 print("[!] Interrupted.") 607 self.close_smb_session() 608 self.init_smb_session() 609 except Exception as err: 610 print("[!] Failed to upload '%s': %s" % (localfile, err)) 611 if self.config.debug: 612 traceback.print_exc() 613 else: 614 print("[!] The specified localpath is a directory. Use 'put -r <directory>' instead.") 615 else: 616 print("[!] The specified localpath does not exist.") 617 618 def put_file_recursively(self, localpath=None): 619 """ 620 Recursively uploads files from a specified local directory to the SMB share. 621 622 This method walks through the given local directory and all its subdirectories, uploading each file to the 623 corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is, 624 it iterates over all files and directories within the local path, creating necessary directories on the SMB share 625 and uploading files. If the local path is not a directory, it prints an error message. 626 627 Args: 628 localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None. 629 """ 630 631 if os.path.exists(localpath): 632 if os.path.isfile(localpath): 633 # Iterate over all files and directories within the local path 634 local_files = {} 635 for root, dirs, files in os.walk(localpath): 636 if len(files) != 0: 637 local_files[root] = files 638 639 # Iterate over the found files 640 for local_dir_path in sorted(local_files.keys()): 641 print("[>] Putting files of '%s'" % local_dir_path) 642 643 # Create remote directory 644 remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep) 645 self.mkdir( 646 path=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep) 647 ) 648 649 for local_file_path in local_files[local_dir_path]: 650 try: 651 f = LocalFileIO( 652 mode="rb", 653 path=local_dir_path + os.path.sep + local_file_path, 654 debug=self.config.debug 655 ) 656 self.smbClient.putFile( 657 shareName=self.smb_share, 658 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 659 callback=f.read 660 ) 661 f.close() 662 663 except BrokenPipeError as err: 664 f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err)) 665 f.close(remove=True) 666 break 667 except Exception as err: 668 f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err)) 669 f.close(remove=True) 670 else: 671 print("[!] The specified localpath is a file. Use 'put <file>' instead.") 672 else: 673 print("[!] The specified localpath does not exist.") 674 675 def rmdir(self, path=None): 676 """ 677 Removes a directory from the SMB share at the specified path. 678 679 This method attempts to delete a directory located at the given path on the SMB share. If the operation fails, 680 it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints 681 the stack trace of the exception. 682 683 Args: 684 path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None. 685 """ 686 try: 687 self.smbClient.deleteDirectory( 688 shareName=self.smb_share, 689 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 690 ) 691 except Exception as err: 692 print("[!] Failed to remove directory '%s': %s" % (path, err)) 693 if self.config.debug: 694 traceback.print_exc() 695 696 def rm(self, path=None): 697 """ 698 Removes a file from the SMB share at the specified path. 699 700 This method attempts to delete a file located at the given path on the SMB share. If the operation fails, 701 it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints 702 the stack trace of the exception. 703 704 Args: 705 path (str, optional): The path of the file to be removed on the SMB share. Defaults to None. 706 """ 707 try: 708 self.smbClient.deleteFile( 709 shareName=self.smb_share, 710 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 711 ) 712 except Exception as err: 713 print("[!] Failed to remove file '%s': %s" % (path, err)) 714 if self.config.debug: 715 traceback.print_exc() 716 717 def tree(self, path=None): 718 """ 719 Recursively lists the directory structure of the SMB share starting from the specified path. 720 721 This function prints a visual representation of the directory tree of the remote SMB share. It uses 722 recursion to navigate through directories and lists all files and subdirectories in each directory. 723 The output is color-coded and formatted to enhance readability, with directories highlighted in cyan. 724 725 Args: 726 path (str, optional): The starting path on the SMB share from which to begin listing the tree. 727 Defaults to the root of the current share. 728 """ 729 730 def recurse_action(base_dir="", path=[], prompt=[]): 731 bars = ["│ ", "├── ", "└── "] 732 733 remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path)) 734 735 entries = [] 736 try: 737 entries = self.smbClient.listPath( 738 shareName=self.smb_share, 739 path=remote_smb_path+'\\*' 740 ) 741 except impacket.smbconnection.SessionError as err: 742 code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1] 743 errmsg = "Error 0x%08x (%s): %s" % (code, const, text) 744 if self.config.no_colors: 745 print("%s%s" % (''.join(prompt+[bars[2]]), errmsg)) 746 else: 747 print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg)) 748 return 749 750 entries = [e for e in entries if e.get_longname() not in [".", ".."]] 751 entries = sorted(entries, key=lambda x:x.get_longname()) 752 753 # 754 if len(entries) > 1: 755 index = 0 756 for entry in entries: 757 index += 1 758 # This is the first entry 759 if index == 0: 760 if entry.is_directory(): 761 if self.config.no_colors: 762 print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 763 else: 764 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 765 recurse_action( 766 base_dir=base_dir, 767 path=path+[entry.get_longname()], 768 prompt=prompt+["│ "] 769 ) 770 else: 771 if self.config.no_colors: 772 print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname())) 773 else: 774 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname())) 775 776 # This is the last entry 777 elif index == len(entries): 778 if entry.is_directory(): 779 if self.config.no_colors: 780 print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 781 else: 782 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 783 recurse_action( 784 base_dir=base_dir, 785 path=path+[entry.get_longname()], 786 prompt=prompt+[" "] 787 ) 788 else: 789 if self.config.no_colors: 790 print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname())) 791 else: 792 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname())) 793 794 # These are entries in the middle 795 else: 796 if entry.is_directory(): 797 if self.config.no_colors: 798 print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 799 else: 800 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 801 recurse_action( 802 base_dir=base_dir, 803 path=path+[entry.get_longname()], 804 prompt=prompt+["│ "] 805 ) 806 else: 807 if self.config.no_colors: 808 print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname())) 809 else: 810 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname())) 811 812 # 813 elif len(entries) == 1: 814 entry = entries[0] 815 if entry.is_directory(): 816 if self.config.no_colors: 817 print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 818 else: 819 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 820 recurse_action( 821 base_dir=base_dir, 822 path=path+[entry.get_longname()], 823 prompt=prompt+[" "] 824 ) 825 else: 826 if self.config.no_colors: 827 print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname())) 828 else: 829 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname())) 830 831 # Entrypoint 832 try: 833 if self.config.no_colors: 834 print("%s\\" % path) 835 else: 836 print("\x1b[1;96m%s\x1b[0m\\" % path) 837 recurse_action( 838 base_dir=self.smb_cwd, 839 path=[path], 840 prompt=[""] 841 ) 842 except (BrokenPipeError, KeyboardInterrupt) as e: 843 print("[!] Interrupted.") 844 self.close_smb_session() 845 self.init_smb_session() 846 847 # Setter / Getter 848 849 def set_share(self, shareName): 850 """ 851 Sets the current SMB share to the specified share name. 852 853 This method updates the SMB session to use the specified share name. It checks if the share name is valid 854 and updates the smb_share attribute of the SMBSession instance. 855 856 Parameters: 857 shareName (str): The name of the share to set as the current SMB share. 858 859 Raises: 860 ValueError: If the shareName is None or an empty string. 861 """ 862 863 if shareName is not None: 864 self.smb_share = shareName 865 866 def set_cwd(self, path=None): 867 """ 868 Sets the current working directory on the SMB share to the specified path. 869 870 This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory. 871 If the specified path is not a directory, the cwd remains unchanged. 872 873 Parameters: 874 path (str): The path to set as the current working directory. 875 876 Raises: 877 ValueError: If the specified path is not a directory. 878 """ 879 880 if path is not None: 881 # Set path separators to ntpath sep 882 if '/' in path: 883 path = path.replace('/', ntpath.sep) 884 885 if path.startswith(ntpath.sep): 886 # Absolute path 887 path = path + ntpath.sep 888 else: 889 # Relative path to the CWD 890 if len(self.smb_cwd) == 0: 891 path = path + ntpath.sep 892 else: 893 path = self.smb_cwd + ntpath.sep + path 894 895 # Path normalization 896 path = ntpath.normpath(path) 897 path = re.sub(r'\\+', r'\\', path) 898 899 if path in ["", ".", ".."]: 900 self.smb_cwd = "" 901 else: 902 if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)): 903 # Path exists on the remote 904 self.smb_cwd = ntpath.normpath(path) 905 else: 906 # Path does not exists or is not a directory on the remote 907 print("[!] Remote directory '%s' does not exist." % path)
18class SMBSession(object): 19 """ 20 Class SMBSession is designed to handle the session management for SMB (Server Message Block) protocol connections. 21 It provides functionalities to connect to an SMB server, authenticate using either NTLM or Kerberos, and manage SMB shares. 22 23 Attributes: 24 address (str): The IP address or hostname of the SMB server. 25 domain (str): The domain name for SMB server authentication. 26 username (str): The username for SMB server authentication. 27 password (str): The password for SMB server authentication. 28 lmhash (str): The LM hash of the user's password, if available. 29 nthash (str): The NT hash of the user's password, if available. 30 use_kerberos (bool): A flag to determine whether to use Kerberos for authentication. 31 kdcHost (str): The Key Distribution Center (KDC) host for Kerberos authentication. 32 debug (bool): A flag to enable debug output. 33 smbClient (object): The SMB client object used for the connection. 34 connected (bool): A flag to check the status of the connection. 35 smb_share (str): The current SMB share in use. 36 smb_path (str): The current path within the SMB share. 37 38 Methods: 39 __init__(address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, debug=False): 40 Initializes the SMBSession with the specified parameters. 41 init_smb_session(): 42 Initializes the SMB session by connecting to the server and authenticating using the specified method. 43 """ 44 45 def __init__(self, address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None): 46 super(SMBSession, self).__init__() 47 # Objects 48 self.config = config 49 50 # Target server 51 self.address = address 52 53 # Credentials 54 self.domain = domain 55 self.username = username 56 self.password = password 57 self.lmhash = lmhash 58 self.nthash = nthash 59 self.use_kerberos = use_kerberos 60 self.kdcHost = kdcHost 61 62 self.smbClient = None 63 self.connected = False 64 65 self.available_shares = {} 66 self.smb_share = None 67 self.smb_cwd = "" 68 69 self.list_shares() 70 71 # Connect and disconnect SMB session 72 73 def init_smb_session(self): 74 """ 75 Initializes and establishes a session with the SMB server. 76 77 This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration. 78 It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization. 79 80 The method will print debug information if the `debug` attribute is set to True. Upon successful connection and authentication, it sets the `connected` attribute to True. 81 82 Returns: 83 bool: True if the connection and authentication are successful, False otherwise. 84 """ 85 86 if self.config.debug: 87 print("[debug] [>] Connecting to remote SMB server '%s' ... " % self.address) 88 try: 89 self.smbClient = impacket.smbconnection.SMBConnection( 90 remoteName=self.address, 91 remoteHost=self.address, 92 sess_port=int(445) 93 ) 94 except OSError as err: 95 print("[!] %s" % err) 96 self.smbClient = None 97 98 self.connected = False 99 if self.smbClient is not None: 100 if self.use_kerberos: 101 if self.config.debug: 102 print("[debug] [>] Authenticating as '%s\\%s' with kerberos ... " % (self.domain, self.username)) 103 self.connected = self.smbClient.kerberosLogin( 104 user=self.username, 105 password=self.password, 106 domain=self.domain, 107 lmhash=self.lmhash, 108 nthash=self.nthash, 109 aesKey=self.aesKey, 110 kdcHost=self.kdcHost 111 ) 112 113 else: 114 if self.config.debug: 115 print("[debug] [>] Authenticating as '%s\\%s' with NTLM ... " % (self.domain, self.username)) 116 self.connected = self.smbClient.login( 117 user=self.username, 118 password=self.password, 119 domain=self.domain, 120 lmhash=self.lmhash, 121 nthash=self.nthash 122 ) 123 124 if self.connected: 125 print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username)) 126 else: 127 print("[!] Failed to authenticate to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username)) 128 129 return self.connected 130 131 def close_smb_session(self): 132 """ 133 Closes the current SMB session by disconnecting the SMB client. 134 135 This method ensures that the SMB client connection is properly closed. It checks if the client is connected 136 and if so, it closes the connection and resets the connection status. 137 138 Raises: 139 Exception: If the SMB client is not initialized or if there's an error during the disconnection process. 140 """ 141 142 if self.smbClient is not None: 143 if self.connected: 144 self.smbClient.close() 145 self.connected = False 146 print("[+] SMB connection closed successfully.") 147 else: 148 print("[!] No active SMB connection to close.") 149 else: 150 raise Exception("SMB client is not initialized.") 151 152 # Operations 153 154 def get_file(self, path=None, keepRemotePath=False): 155 """ 156 Retrieves a file from the specified path on the SMB share. 157 158 This method attempts to retrieve a file from the given path within the currently connected SMB share. 159 If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local 160 file object and writing the contents of the remote file to it using the SMB client's getFile method. 161 162 Parameters: 163 path (str): The path of the file to retrieve. If None, uses the current smb_path. 164 165 Returns: 166 None 167 """ 168 169 tmp_file_path = self.smb_cwd + ntpath.sep + path 170 matches = self.smbClient.listPath( 171 shareName=self.smb_share, 172 path=tmp_file_path 173 ) 174 175 for entry in matches: 176 if entry.is_directory(): 177 print("[>] Skipping '%s' because it is a directory." % tmp_file_path) 178 else: 179 try: 180 if ntpath.sep in path: 181 outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname() 182 else: 183 outputfile = entry.get_longname() 184 f = LocalFileIO( 185 mode="wb", 186 path=outputfile, 187 expected_size=entry.get_filesize(), 188 debug=self.config.debug, 189 keepRemotePath=keepRemotePath 190 ) 191 self.smbClient.getFile( 192 shareName=self.smb_share, 193 pathName=tmp_file_path, 194 callback=f.write 195 ) 196 f.close() 197 except (BrokenPipeError, KeyboardInterrupt) as e: 198 f.close() 199 print("\x1b[v\x1b[o\r[!] Interrupted.") 200 self.close_smb_session() 201 self.init_smb_session() 202 203 return None 204 205 def get_file_recursively(self, path=None): 206 """ 207 Recursively retrieves files from a specified path on the SMB share. 208 209 This method navigates through all directories starting from the given path, 210 and downloads all files found. It handles directories recursively, ensuring 211 that all nested files are retrieved. The method skips over directory entries 212 and handles errors gracefully, attempting to continue the operation where possible. 213 214 Parameters: 215 path (str): The initial directory path from which to start the recursive file retrieval. 216 If None, it starts from the root of the configured SMB share. 217 """ 218 219 def recurse_action(base_dir="", path=[]): 220 remote_smb_path = base_dir + ntpath.sep.join(path) 221 entries = self.smbClient.listPath( 222 shareName=self.smb_share, 223 path=remote_smb_path + '\\*' 224 ) 225 if len(entries) != 0: 226 files = [entry for entry in entries if not entry.is_directory()] 227 directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]] 228 229 # Files 230 if len(files) != 0: 231 print("[>] Retrieving files of '%s'" % remote_smb_path) 232 for entry_file in files: 233 if not entry_file.is_directory(): 234 f = LocalFileIO( 235 mode="wb", 236 path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 237 expected_size=entry_file.get_filesize(), 238 debug=self.config.debug 239 ) 240 try: 241 self.smbClient.getFile( 242 shareName=self.smb_share, 243 pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 244 callback=f.write 245 ) 246 f.close() 247 except BrokenPipeError as err: 248 f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err)) 249 f.close(remove=True) 250 break 251 except Exception as err: 252 f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err)) 253 f.close(remove=True) 254 255 # Directories 256 for entry_directory in directories: 257 if entry_directory.is_directory(): 258 recurse_action( 259 base_dir=self.smb_cwd, 260 path=path+[entry_directory.get_longname()] 261 ) 262 # Entrypoint 263 try: 264 recurse_action( 265 base_dir=self.smb_cwd, 266 path=[path] 267 ) 268 except (BrokenPipeError, KeyboardInterrupt) as e: 269 print("\x1b[v\x1b[o\r[!] Interrupted.") 270 self.close_smb_session() 271 self.init_smb_session() 272 273 def info(self, share=True, server=True): 274 """ 275 Displays information about the server and optionally the shares. 276 277 This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the `share` parameter is set to True and a share is currently set, it will also attempt to display information about the share. 278 279 Parameters: 280 share (bool): If True, display information about the current share. 281 server (bool): If True, display information about the server. 282 283 Returns: 284 None 285 """ 286 287 if server: 288 if self.config.no_colors: 289 print("[+] Server:") 290 print(" ├─NetBIOS:") 291 print(" │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName())) 292 print(" │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain())) 293 print(" ├─DNS:") 294 print(" │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName())) 295 print(" │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName())) 296 print(" ├─OS:") 297 print(" │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS())) 298 print(" │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild())) 299 print(" ├─Server:") 300 print(" │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired())) 301 print(" │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired())) 302 print(" │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2())) 303 MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"] 304 print(" │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize))) 305 MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"] 306 print(" │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize))) 307 print(" └─") 308 else: 309 print("[+] Server:") 310 print(" ├─NetBIOS:") 311 print(" │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName())) 312 print(" │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain())) 313 print(" ├─DNS:") 314 print(" │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName())) 315 print(" │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName())) 316 print(" ├─OS:") 317 print(" │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS())) 318 print(" │ └─ \x1b[94mOS Version\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s.%s.%s\x1b[0m" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild())) 319 print(" ├─Server:") 320 print(" │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired())) 321 print(" │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired())) 322 print(" │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2())) 323 MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"] 324 print(" │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize))) 325 MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"] 326 print(" │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize))) 327 print(" └─") 328 329 if share and self.smb_share is not None: 330 share_name = self.available_shares.get(self.smb_share.lower(), "")["name"] 331 share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"] 332 share_type = self.available_shares.get(self.smb_share.lower(), "")["type"] 333 share_type =', '.join([s.replace("STYPE_","") for s in share_type]) 334 share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"] 335 if self.config.no_colors: 336 print("\n[+] Share:") 337 print(" ├─ Name ──────────── : %s" % (share_name)) 338 print(" ├─ Description ───── : %s" % (share_comment)) 339 print(" ├─ Type ──────────── : %s" % (share_type)) 340 print(" └─ Raw type value ── : %s" % (share_rawtype)) 341 else: 342 print("\n[+] Share:") 343 print(" ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name)) 344 print(" ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment)) 345 print(" ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type)) 346 print(" └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype)) 347 348 def list_contents(self, path=None): 349 """ 350 Lists the contents of a specified directory on the SMB share. 351 352 This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path` 353 is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with 354 the long names of the files and directories as keys and their respective SMB entry objects as values. 355 356 Args: 357 shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None. 358 path (str, optional): The directory path to list contents from. Defaults to the current path if None. 359 360 Returns: 361 dict: A dictionary with file and directory names as keys and their SMB entry objects as values. 362 """ 363 364 if path is None or len(path) == 0: 365 path = self.smb_cwd 366 path = path.rstrip(ntpath.sep) + ntpath.sep + "*" 367 368 contents = {} 369 entries = self.smbClient.listPath( 370 shareName=self.smb_share, 371 path=path 372 ) 373 for entry in entries: 374 contents[entry.get_longname()] = entry 375 376 return contents 377 378 def list_shares(self): 379 """ 380 Lists all the shares available on the connected SMB server. 381 382 This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary 383 with key-value pairs where the key is the share name and the value is a dictionary containing details about the share 384 such as its name, type, raw type, and any comments associated with the share. 385 386 Returns: 387 dict: A dictionary containing information about each share available on the server. 388 """ 389 390 self.available_shares = {} 391 392 if self.connected: 393 if self.smbClient is not None: 394 resp = self.smbClient.listShares() 395 396 for share in resp: 397 # SHARE_INFO_1 structure (lmshare.h) 398 # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1 399 sharename = share["shi1_netname"][:-1] 400 sharecomment = share["shi1_remark"][:-1] 401 sharetype = share["shi1_type"] 402 403 self.available_shares[sharename.lower()] = { 404 "name": sharename, 405 "type": STYPE_MASK(sharetype), 406 "rawtype": sharetype, 407 "comment": sharecomment 408 } 409 else: 410 print("[!] Error: SMBSession.smbClient is None.") 411 412 return self.available_shares 413 414 def mkdir(self, path=None): 415 """ 416 Creates a directory at the specified path on the SMB share. 417 418 This method takes a path and attempts to create the directory structure on the SMB share. If the path includes 419 nested directories, it will create each directory in the sequence. If a directory already exists, it will skip 420 the creation for that directory without raising an error. 421 422 Args: 423 path (str, optional): The full path of the directory to create on the SMB share. Defaults to None. 424 425 Note: 426 The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility. 427 """ 428 429 if path is not None: 430 # Prepare path 431 path = path.replace('/',ntpath.sep) 432 if ntpath.sep in path: 433 path = path.strip(ntpath.sep).split(ntpath.sep) 434 else: 435 path = [path] 436 437 # Create each dir in the path 438 for depth in range(1, len(path)+1): 439 tmp_path = ntpath.sep.join(path[:depth]) 440 try: 441 self.smbClient.createDirectory( 442 shareName=self.smb_share, 443 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep) 444 ) 445 except impacket.smbconnection.SessionError as err: 446 if err.getErrorCode() == 0xc0000035: 447 # STATUS_OBJECT_NAME_COLLISION 448 # Remote directory already created, this is normal 449 # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19 450 pass 451 else: 452 print("[!] Failed to create directory '%s': %s" % (tmp_path, err)) 453 if self.config.debug: 454 traceback.print_exc() 455 else: 456 pass 457 458 def path_exists(self, path=None): 459 """ 460 Checks if the specified path exists on the SMB share. 461 462 This method determines if a given path exists on the SMB share by attempting to list the contents of the path. 463 If the path listing is successful and returns one or more entries, the path is considered to exist. 464 465 Args: 466 path (str, optional): The path to check on the SMB share. Defaults to None. 467 468 Returns: 469 bool: True if the path exists, False otherwise or if an error occurs. 470 """ 471 472 if path is not None: 473 path = path.replace('*','') 474 try: 475 contents = self.smbClient.listPath( 476 shareName=self.smb_share, 477 path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep) 478 ) 479 return (len(contents) != 0) 480 except Exception as e: 481 return False 482 else: 483 return False 484 485 def path_isdir(self, pathFromRoot=None): 486 """ 487 Checks if the specified path is a directory on the SMB share. 488 489 This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the 490 contents of the path and filtering for entries that match the basename of the path and are marked as directories. 491 492 Args: 493 path (str, optional): The path to check on the SMB share. Defaults to None. 494 495 Returns: 496 bool: True if the path is a directory, False otherwise or if an error occurs. 497 """ 498 499 if pathFromRoot is not None: 500 # Replace slashes if any 501 path = pathFromRoot.replace('/', ntpath.sep) 502 503 # Strip wildcards to avoid injections 504 path = path.replace('*','') 505 506 # Normalize path and strip leading backslash 507 path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep) 508 509 if path.strip() in ['', '.', '..']: 510 # By defininition they exist on the filesystem 511 return True 512 else: 513 try: 514 contents = self.smbClient.listPath( 515 shareName=self.smb_share, 516 path=path+'*' 517 ) 518 # Filter on directories 519 contents = [ 520 c for c in contents 521 if c.get_longname() == ntpath.basename(path) and c.is_directory() 522 ] 523 return (len(contents) != 0) 524 except Exception as e: 525 return False 526 else: 527 return False 528 529 def path_isfile(self, path=None): 530 """ 531 Checks if the specified path is a file on the SMB share. 532 533 This method determines if a given path corresponds to a file on the SMB share. It does this by listing the 534 contents of the path and filtering for entries that match the basename of the path and are not marked as directories. 535 536 Args: 537 path (str, optional): The path to check on the SMB share. Defaults to None. 538 539 Returns: 540 bool: True if the path is a file, False otherwise or if an error occurs. 541 """ 542 543 if path is not None: 544 path = path.replace('*','') 545 try: 546 contents = self.smbClient.listPath( 547 shareName=self.smb_share, 548 path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep) 549 ) 550 # Filter on files 551 contents = [ 552 c for c in contents 553 if c.get_longname() == ntpath.basename(path) and not c.is_directory() 554 ] 555 return (len(contents) != 0) 556 except Exception as e: 557 return False 558 else: 559 return False 560 561 def ping_smb_session(self): 562 """ 563 Tests the connectivity to the SMB server by sending an echo command. 564 565 This method attempts to send an echo command to the SMB server to check if the session is still active. 566 It updates the `connected` attribute of the class based on the success or failure of the echo command. 567 568 Returns: 569 bool: True if the echo command succeeds (indicating the session is active), False otherwise. 570 """ 571 572 try: 573 self.smbClient.getSMBServer().echo() 574 self.connected = True 575 except Exception as e: 576 self.connected = False 577 return self.connected 578 579 def put_file(self, localpath=None): 580 """ 581 Uploads a single file to the SMB share. 582 583 This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path. 584 It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session. 585 General exceptions are caught and logged, with a traceback provided if debugging is enabled. 586 587 Args: 588 localpath (str, optional): The local file path of the file to be uploaded. Defaults to None. 589 """ 590 591 if os.path.exists(localpath): 592 if os.path.isfile(localpath): 593 try: 594 localfile = os.path.basename(localpath) 595 f = LocalFileIO( 596 mode="rb", 597 path=localpath, 598 debug=self.config.debug 599 ) 600 self.smbClient.putFile( 601 shareName=self.smb_share, 602 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 603 callback=f.read 604 ) 605 f.close() 606 except (BrokenPipeError, KeyboardInterrupt) as err: 607 print("[!] Interrupted.") 608 self.close_smb_session() 609 self.init_smb_session() 610 except Exception as err: 611 print("[!] Failed to upload '%s': %s" % (localfile, err)) 612 if self.config.debug: 613 traceback.print_exc() 614 else: 615 print("[!] The specified localpath is a directory. Use 'put -r <directory>' instead.") 616 else: 617 print("[!] The specified localpath does not exist.") 618 619 def put_file_recursively(self, localpath=None): 620 """ 621 Recursively uploads files from a specified local directory to the SMB share. 622 623 This method walks through the given local directory and all its subdirectories, uploading each file to the 624 corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is, 625 it iterates over all files and directories within the local path, creating necessary directories on the SMB share 626 and uploading files. If the local path is not a directory, it prints an error message. 627 628 Args: 629 localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None. 630 """ 631 632 if os.path.exists(localpath): 633 if os.path.isfile(localpath): 634 # Iterate over all files and directories within the local path 635 local_files = {} 636 for root, dirs, files in os.walk(localpath): 637 if len(files) != 0: 638 local_files[root] = files 639 640 # Iterate over the found files 641 for local_dir_path in sorted(local_files.keys()): 642 print("[>] Putting files of '%s'" % local_dir_path) 643 644 # Create remote directory 645 remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep) 646 self.mkdir( 647 path=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep) 648 ) 649 650 for local_file_path in local_files[local_dir_path]: 651 try: 652 f = LocalFileIO( 653 mode="rb", 654 path=local_dir_path + os.path.sep + local_file_path, 655 debug=self.config.debug 656 ) 657 self.smbClient.putFile( 658 shareName=self.smb_share, 659 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 660 callback=f.read 661 ) 662 f.close() 663 664 except BrokenPipeError as err: 665 f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err)) 666 f.close(remove=True) 667 break 668 except Exception as err: 669 f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err)) 670 f.close(remove=True) 671 else: 672 print("[!] The specified localpath is a file. Use 'put <file>' instead.") 673 else: 674 print("[!] The specified localpath does not exist.") 675 676 def rmdir(self, path=None): 677 """ 678 Removes a directory from the SMB share at the specified path. 679 680 This method attempts to delete a directory located at the given path on the SMB share. If the operation fails, 681 it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints 682 the stack trace of the exception. 683 684 Args: 685 path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None. 686 """ 687 try: 688 self.smbClient.deleteDirectory( 689 shareName=self.smb_share, 690 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 691 ) 692 except Exception as err: 693 print("[!] Failed to remove directory '%s': %s" % (path, err)) 694 if self.config.debug: 695 traceback.print_exc() 696 697 def rm(self, path=None): 698 """ 699 Removes a file from the SMB share at the specified path. 700 701 This method attempts to delete a file located at the given path on the SMB share. If the operation fails, 702 it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints 703 the stack trace of the exception. 704 705 Args: 706 path (str, optional): The path of the file to be removed on the SMB share. Defaults to None. 707 """ 708 try: 709 self.smbClient.deleteFile( 710 shareName=self.smb_share, 711 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 712 ) 713 except Exception as err: 714 print("[!] Failed to remove file '%s': %s" % (path, err)) 715 if self.config.debug: 716 traceback.print_exc() 717 718 def tree(self, path=None): 719 """ 720 Recursively lists the directory structure of the SMB share starting from the specified path. 721 722 This function prints a visual representation of the directory tree of the remote SMB share. It uses 723 recursion to navigate through directories and lists all files and subdirectories in each directory. 724 The output is color-coded and formatted to enhance readability, with directories highlighted in cyan. 725 726 Args: 727 path (str, optional): The starting path on the SMB share from which to begin listing the tree. 728 Defaults to the root of the current share. 729 """ 730 731 def recurse_action(base_dir="", path=[], prompt=[]): 732 bars = ["│ ", "├── ", "└── "] 733 734 remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path)) 735 736 entries = [] 737 try: 738 entries = self.smbClient.listPath( 739 shareName=self.smb_share, 740 path=remote_smb_path+'\\*' 741 ) 742 except impacket.smbconnection.SessionError as err: 743 code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1] 744 errmsg = "Error 0x%08x (%s): %s" % (code, const, text) 745 if self.config.no_colors: 746 print("%s%s" % (''.join(prompt+[bars[2]]), errmsg)) 747 else: 748 print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg)) 749 return 750 751 entries = [e for e in entries if e.get_longname() not in [".", ".."]] 752 entries = sorted(entries, key=lambda x:x.get_longname()) 753 754 # 755 if len(entries) > 1: 756 index = 0 757 for entry in entries: 758 index += 1 759 # This is the first entry 760 if index == 0: 761 if entry.is_directory(): 762 if self.config.no_colors: 763 print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 764 else: 765 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 766 recurse_action( 767 base_dir=base_dir, 768 path=path+[entry.get_longname()], 769 prompt=prompt+["│ "] 770 ) 771 else: 772 if self.config.no_colors: 773 print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname())) 774 else: 775 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname())) 776 777 # This is the last entry 778 elif index == len(entries): 779 if entry.is_directory(): 780 if self.config.no_colors: 781 print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 782 else: 783 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 784 recurse_action( 785 base_dir=base_dir, 786 path=path+[entry.get_longname()], 787 prompt=prompt+[" "] 788 ) 789 else: 790 if self.config.no_colors: 791 print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname())) 792 else: 793 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname())) 794 795 # These are entries in the middle 796 else: 797 if entry.is_directory(): 798 if self.config.no_colors: 799 print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 800 else: 801 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 802 recurse_action( 803 base_dir=base_dir, 804 path=path+[entry.get_longname()], 805 prompt=prompt+["│ "] 806 ) 807 else: 808 if self.config.no_colors: 809 print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname())) 810 else: 811 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname())) 812 813 # 814 elif len(entries) == 1: 815 entry = entries[0] 816 if entry.is_directory(): 817 if self.config.no_colors: 818 print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 819 else: 820 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 821 recurse_action( 822 base_dir=base_dir, 823 path=path+[entry.get_longname()], 824 prompt=prompt+[" "] 825 ) 826 else: 827 if self.config.no_colors: 828 print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname())) 829 else: 830 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname())) 831 832 # Entrypoint 833 try: 834 if self.config.no_colors: 835 print("%s\\" % path) 836 else: 837 print("\x1b[1;96m%s\x1b[0m\\" % path) 838 recurse_action( 839 base_dir=self.smb_cwd, 840 path=[path], 841 prompt=[""] 842 ) 843 except (BrokenPipeError, KeyboardInterrupt) as e: 844 print("[!] Interrupted.") 845 self.close_smb_session() 846 self.init_smb_session() 847 848 # Setter / Getter 849 850 def set_share(self, shareName): 851 """ 852 Sets the current SMB share to the specified share name. 853 854 This method updates the SMB session to use the specified share name. It checks if the share name is valid 855 and updates the smb_share attribute of the SMBSession instance. 856 857 Parameters: 858 shareName (str): The name of the share to set as the current SMB share. 859 860 Raises: 861 ValueError: If the shareName is None or an empty string. 862 """ 863 864 if shareName is not None: 865 self.smb_share = shareName 866 867 def set_cwd(self, path=None): 868 """ 869 Sets the current working directory on the SMB share to the specified path. 870 871 This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory. 872 If the specified path is not a directory, the cwd remains unchanged. 873 874 Parameters: 875 path (str): The path to set as the current working directory. 876 877 Raises: 878 ValueError: If the specified path is not a directory. 879 """ 880 881 if path is not None: 882 # Set path separators to ntpath sep 883 if '/' in path: 884 path = path.replace('/', ntpath.sep) 885 886 if path.startswith(ntpath.sep): 887 # Absolute path 888 path = path + ntpath.sep 889 else: 890 # Relative path to the CWD 891 if len(self.smb_cwd) == 0: 892 path = path + ntpath.sep 893 else: 894 path = self.smb_cwd + ntpath.sep + path 895 896 # Path normalization 897 path = ntpath.normpath(path) 898 path = re.sub(r'\\+', r'\\', path) 899 900 if path in ["", ".", ".."]: 901 self.smb_cwd = "" 902 else: 903 if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)): 904 # Path exists on the remote 905 self.smb_cwd = ntpath.normpath(path) 906 else: 907 # Path does not exists or is not a directory on the remote 908 print("[!] Remote directory '%s' does not exist." % path)
Class SMBSession is designed to handle the session management for SMB (Server Message Block) protocol connections. It provides functionalities to connect to an SMB server, authenticate using either NTLM or Kerberos, and manage SMB shares.
Attributes: address (str): The IP address or hostname of the SMB server. domain (str): The domain name for SMB server authentication. username (str): The username for SMB server authentication. password (str): The password for SMB server authentication. lmhash (str): The LM hash of the user's password, if available. nthash (str): The NT hash of the user's password, if available. use_kerberos (bool): A flag to determine whether to use Kerberos for authentication. kdcHost (str): The Key Distribution Center (KDC) host for Kerberos authentication. debug (bool): A flag to enable debug output. smbClient (object): The SMB client object used for the connection. connected (bool): A flag to check the status of the connection. smb_share (str): The current SMB share in use. smb_path (str): The current path within the SMB share.
Methods: __init__(address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, debug=False): Initializes the SMBSession with the specified parameters. init_smb_session(): Initializes the SMB session by connecting to the server and authenticating using the specified method.
45 def __init__(self, address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None): 46 super(SMBSession, self).__init__() 47 # Objects 48 self.config = config 49 50 # Target server 51 self.address = address 52 53 # Credentials 54 self.domain = domain 55 self.username = username 56 self.password = password 57 self.lmhash = lmhash 58 self.nthash = nthash 59 self.use_kerberos = use_kerberos 60 self.kdcHost = kdcHost 61 62 self.smbClient = None 63 self.connected = False 64 65 self.available_shares = {} 66 self.smb_share = None 67 self.smb_cwd = "" 68 69 self.list_shares()
73 def init_smb_session(self): 74 """ 75 Initializes and establishes a session with the SMB server. 76 77 This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration. 78 It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization. 79 80 The method will print debug information if the `debug` attribute is set to True. Upon successful connection and authentication, it sets the `connected` attribute to True. 81 82 Returns: 83 bool: True if the connection and authentication are successful, False otherwise. 84 """ 85 86 if self.config.debug: 87 print("[debug] [>] Connecting to remote SMB server '%s' ... " % self.address) 88 try: 89 self.smbClient = impacket.smbconnection.SMBConnection( 90 remoteName=self.address, 91 remoteHost=self.address, 92 sess_port=int(445) 93 ) 94 except OSError as err: 95 print("[!] %s" % err) 96 self.smbClient = None 97 98 self.connected = False 99 if self.smbClient is not None: 100 if self.use_kerberos: 101 if self.config.debug: 102 print("[debug] [>] Authenticating as '%s\\%s' with kerberos ... " % (self.domain, self.username)) 103 self.connected = self.smbClient.kerberosLogin( 104 user=self.username, 105 password=self.password, 106 domain=self.domain, 107 lmhash=self.lmhash, 108 nthash=self.nthash, 109 aesKey=self.aesKey, 110 kdcHost=self.kdcHost 111 ) 112 113 else: 114 if self.config.debug: 115 print("[debug] [>] Authenticating as '%s\\%s' with NTLM ... " % (self.domain, self.username)) 116 self.connected = self.smbClient.login( 117 user=self.username, 118 password=self.password, 119 domain=self.domain, 120 lmhash=self.lmhash, 121 nthash=self.nthash 122 ) 123 124 if self.connected: 125 print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username)) 126 else: 127 print("[!] Failed to authenticate to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username)) 128 129 return self.connected
Initializes and establishes a session with the SMB server.
This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration.
It attempts to connect to the SMB server specified by the address attribute and authenticate using the credentials provided during the object's initialization.
The method will print debug information if the debug attribute is set to True. Upon successful connection and authentication, it sets the connected attribute to True.
Returns: bool: True if the connection and authentication are successful, False otherwise.
131 def close_smb_session(self): 132 """ 133 Closes the current SMB session by disconnecting the SMB client. 134 135 This method ensures that the SMB client connection is properly closed. It checks if the client is connected 136 and if so, it closes the connection and resets the connection status. 137 138 Raises: 139 Exception: If the SMB client is not initialized or if there's an error during the disconnection process. 140 """ 141 142 if self.smbClient is not None: 143 if self.connected: 144 self.smbClient.close() 145 self.connected = False 146 print("[+] SMB connection closed successfully.") 147 else: 148 print("[!] No active SMB connection to close.") 149 else: 150 raise Exception("SMB client is not initialized.")
Closes the current SMB session by disconnecting the SMB client.
This method ensures that the SMB client connection is properly closed. It checks if the client is connected and if so, it closes the connection and resets the connection status.
Raises: Exception: If the SMB client is not initialized or if there's an error during the disconnection process.
154 def get_file(self, path=None, keepRemotePath=False): 155 """ 156 Retrieves a file from the specified path on the SMB share. 157 158 This method attempts to retrieve a file from the given path within the currently connected SMB share. 159 If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local 160 file object and writing the contents of the remote file to it using the SMB client's getFile method. 161 162 Parameters: 163 path (str): The path of the file to retrieve. If None, uses the current smb_path. 164 165 Returns: 166 None 167 """ 168 169 tmp_file_path = self.smb_cwd + ntpath.sep + path 170 matches = self.smbClient.listPath( 171 shareName=self.smb_share, 172 path=tmp_file_path 173 ) 174 175 for entry in matches: 176 if entry.is_directory(): 177 print("[>] Skipping '%s' because it is a directory." % tmp_file_path) 178 else: 179 try: 180 if ntpath.sep in path: 181 outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname() 182 else: 183 outputfile = entry.get_longname() 184 f = LocalFileIO( 185 mode="wb", 186 path=outputfile, 187 expected_size=entry.get_filesize(), 188 debug=self.config.debug, 189 keepRemotePath=keepRemotePath 190 ) 191 self.smbClient.getFile( 192 shareName=self.smb_share, 193 pathName=tmp_file_path, 194 callback=f.write 195 ) 196 f.close() 197 except (BrokenPipeError, KeyboardInterrupt) as e: 198 f.close() 199 print("\x1b[v\x1b[o\r[!] Interrupted.") 200 self.close_smb_session() 201 self.init_smb_session() 202 203 return None
Retrieves a file from the specified path on the SMB share.
This method attempts to retrieve a file from the given path within the currently connected SMB share. If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local file object and writing the contents of the remote file to it using the SMB client's getFile method.
Parameters: path (str): The path of the file to retrieve. If None, uses the current smb_path.
Returns: None
205 def get_file_recursively(self, path=None): 206 """ 207 Recursively retrieves files from a specified path on the SMB share. 208 209 This method navigates through all directories starting from the given path, 210 and downloads all files found. It handles directories recursively, ensuring 211 that all nested files are retrieved. The method skips over directory entries 212 and handles errors gracefully, attempting to continue the operation where possible. 213 214 Parameters: 215 path (str): The initial directory path from which to start the recursive file retrieval. 216 If None, it starts from the root of the configured SMB share. 217 """ 218 219 def recurse_action(base_dir="", path=[]): 220 remote_smb_path = base_dir + ntpath.sep.join(path) 221 entries = self.smbClient.listPath( 222 shareName=self.smb_share, 223 path=remote_smb_path + '\\*' 224 ) 225 if len(entries) != 0: 226 files = [entry for entry in entries if not entry.is_directory()] 227 directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]] 228 229 # Files 230 if len(files) != 0: 231 print("[>] Retrieving files of '%s'" % remote_smb_path) 232 for entry_file in files: 233 if not entry_file.is_directory(): 234 f = LocalFileIO( 235 mode="wb", 236 path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 237 expected_size=entry_file.get_filesize(), 238 debug=self.config.debug 239 ) 240 try: 241 self.smbClient.getFile( 242 shareName=self.smb_share, 243 pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 244 callback=f.write 245 ) 246 f.close() 247 except BrokenPipeError as err: 248 f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err)) 249 f.close(remove=True) 250 break 251 except Exception as err: 252 f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err)) 253 f.close(remove=True) 254 255 # Directories 256 for entry_directory in directories: 257 if entry_directory.is_directory(): 258 recurse_action( 259 base_dir=self.smb_cwd, 260 path=path+[entry_directory.get_longname()] 261 ) 262 # Entrypoint 263 try: 264 recurse_action( 265 base_dir=self.smb_cwd, 266 path=[path] 267 ) 268 except (BrokenPipeError, KeyboardInterrupt) as e: 269 print("\x1b[v\x1b[o\r[!] Interrupted.") 270 self.close_smb_session() 271 self.init_smb_session()
Recursively retrieves files from a specified path on the SMB share.
This method navigates through all directories starting from the given path, and downloads all files found. It handles directories recursively, ensuring that all nested files are retrieved. The method skips over directory entries and handles errors gracefully, attempting to continue the operation where possible.
Parameters: path (str): The initial directory path from which to start the recursive file retrieval. If None, it starts from the root of the configured SMB share.
273 def info(self, share=True, server=True): 274 """ 275 Displays information about the server and optionally the shares. 276 277 This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the `share` parameter is set to True and a share is currently set, it will also attempt to display information about the share. 278 279 Parameters: 280 share (bool): If True, display information about the current share. 281 server (bool): If True, display information about the server. 282 283 Returns: 284 None 285 """ 286 287 if server: 288 if self.config.no_colors: 289 print("[+] Server:") 290 print(" ├─NetBIOS:") 291 print(" │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName())) 292 print(" │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain())) 293 print(" ├─DNS:") 294 print(" │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName())) 295 print(" │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName())) 296 print(" ├─OS:") 297 print(" │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS())) 298 print(" │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild())) 299 print(" ├─Server:") 300 print(" │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired())) 301 print(" │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired())) 302 print(" │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2())) 303 MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"] 304 print(" │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize))) 305 MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"] 306 print(" │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize))) 307 print(" └─") 308 else: 309 print("[+] Server:") 310 print(" ├─NetBIOS:") 311 print(" │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName())) 312 print(" │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain())) 313 print(" ├─DNS:") 314 print(" │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName())) 315 print(" │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName())) 316 print(" ├─OS:") 317 print(" │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS())) 318 print(" │ └─ \x1b[94mOS Version\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s.%s.%s\x1b[0m" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild())) 319 print(" ├─Server:") 320 print(" │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired())) 321 print(" │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired())) 322 print(" │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2())) 323 MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"] 324 print(" │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize))) 325 MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"] 326 print(" │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize))) 327 print(" └─") 328 329 if share and self.smb_share is not None: 330 share_name = self.available_shares.get(self.smb_share.lower(), "")["name"] 331 share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"] 332 share_type = self.available_shares.get(self.smb_share.lower(), "")["type"] 333 share_type =', '.join([s.replace("STYPE_","") for s in share_type]) 334 share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"] 335 if self.config.no_colors: 336 print("\n[+] Share:") 337 print(" ├─ Name ──────────── : %s" % (share_name)) 338 print(" ├─ Description ───── : %s" % (share_comment)) 339 print(" ├─ Type ──────────── : %s" % (share_type)) 340 print(" └─ Raw type value ── : %s" % (share_rawtype)) 341 else: 342 print("\n[+] Share:") 343 print(" ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name)) 344 print(" ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment)) 345 print(" ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type)) 346 print(" └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype))
Displays information about the server and optionally the shares.
This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the share parameter is set to True and a share is currently set, it will also attempt to display information about the share.
Parameters: share (bool): If True, display information about the current share. server (bool): If True, display information about the server.
Returns: None
348 def list_contents(self, path=None): 349 """ 350 Lists the contents of a specified directory on the SMB share. 351 352 This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path` 353 is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with 354 the long names of the files and directories as keys and their respective SMB entry objects as values. 355 356 Args: 357 shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None. 358 path (str, optional): The directory path to list contents from. Defaults to the current path if None. 359 360 Returns: 361 dict: A dictionary with file and directory names as keys and their SMB entry objects as values. 362 """ 363 364 if path is None or len(path) == 0: 365 path = self.smb_cwd 366 path = path.rstrip(ntpath.sep) + ntpath.sep + "*" 367 368 contents = {} 369 entries = self.smbClient.listPath( 370 shareName=self.smb_share, 371 path=path 372 ) 373 for entry in entries: 374 contents[entry.get_longname()] = entry 375 376 return contents
Lists the contents of a specified directory on the SMB share.
This method retrieves the contents of a directory specified by shareName and path. If shareName or path
is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with
the long names of the files and directories as keys and their respective SMB entry objects as values.
Args: shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None. path (str, optional): The directory path to list contents from. Defaults to the current path if None.
Returns: dict: A dictionary with file and directory names as keys and their SMB entry objects as values.
414 def mkdir(self, path=None): 415 """ 416 Creates a directory at the specified path on the SMB share. 417 418 This method takes a path and attempts to create the directory structure on the SMB share. If the path includes 419 nested directories, it will create each directory in the sequence. If a directory already exists, it will skip 420 the creation for that directory without raising an error. 421 422 Args: 423 path (str, optional): The full path of the directory to create on the SMB share. Defaults to None. 424 425 Note: 426 The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility. 427 """ 428 429 if path is not None: 430 # Prepare path 431 path = path.replace('/',ntpath.sep) 432 if ntpath.sep in path: 433 path = path.strip(ntpath.sep).split(ntpath.sep) 434 else: 435 path = [path] 436 437 # Create each dir in the path 438 for depth in range(1, len(path)+1): 439 tmp_path = ntpath.sep.join(path[:depth]) 440 try: 441 self.smbClient.createDirectory( 442 shareName=self.smb_share, 443 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep) 444 ) 445 except impacket.smbconnection.SessionError as err: 446 if err.getErrorCode() == 0xc0000035: 447 # STATUS_OBJECT_NAME_COLLISION 448 # Remote directory already created, this is normal 449 # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19 450 pass 451 else: 452 print("[!] Failed to create directory '%s': %s" % (tmp_path, err)) 453 if self.config.debug: 454 traceback.print_exc() 455 else: 456 pass
Creates a directory at the specified path on the SMB share.
This method takes a path and attempts to create the directory structure on the SMB share. If the path includes nested directories, it will create each directory in the sequence. If a directory already exists, it will skip the creation for that directory without raising an error.
Args: path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.
Note: The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.
458 def path_exists(self, path=None): 459 """ 460 Checks if the specified path exists on the SMB share. 461 462 This method determines if a given path exists on the SMB share by attempting to list the contents of the path. 463 If the path listing is successful and returns one or more entries, the path is considered to exist. 464 465 Args: 466 path (str, optional): The path to check on the SMB share. Defaults to None. 467 468 Returns: 469 bool: True if the path exists, False otherwise or if an error occurs. 470 """ 471 472 if path is not None: 473 path = path.replace('*','') 474 try: 475 contents = self.smbClient.listPath( 476 shareName=self.smb_share, 477 path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep) 478 ) 479 return (len(contents) != 0) 480 except Exception as e: 481 return False 482 else: 483 return False
Checks if the specified path exists on the SMB share.
This method determines if a given path exists on the SMB share by attempting to list the contents of the path. If the path listing is successful and returns one or more entries, the path is considered to exist.
Args: path (str, optional): The path to check on the SMB share. Defaults to None.
Returns: bool: True if the path exists, False otherwise or if an error occurs.
485 def path_isdir(self, pathFromRoot=None): 486 """ 487 Checks if the specified path is a directory on the SMB share. 488 489 This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the 490 contents of the path and filtering for entries that match the basename of the path and are marked as directories. 491 492 Args: 493 path (str, optional): The path to check on the SMB share. Defaults to None. 494 495 Returns: 496 bool: True if the path is a directory, False otherwise or if an error occurs. 497 """ 498 499 if pathFromRoot is not None: 500 # Replace slashes if any 501 path = pathFromRoot.replace('/', ntpath.sep) 502 503 # Strip wildcards to avoid injections 504 path = path.replace('*','') 505 506 # Normalize path and strip leading backslash 507 path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep) 508 509 if path.strip() in ['', '.', '..']: 510 # By defininition they exist on the filesystem 511 return True 512 else: 513 try: 514 contents = self.smbClient.listPath( 515 shareName=self.smb_share, 516 path=path+'*' 517 ) 518 # Filter on directories 519 contents = [ 520 c for c in contents 521 if c.get_longname() == ntpath.basename(path) and c.is_directory() 522 ] 523 return (len(contents) != 0) 524 except Exception as e: 525 return False 526 else: 527 return False
Checks if the specified path is a directory on the SMB share.
This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the contents of the path and filtering for entries that match the basename of the path and are marked as directories.
Args: path (str, optional): The path to check on the SMB share. Defaults to None.
Returns: bool: True if the path is a directory, False otherwise or if an error occurs.
529 def path_isfile(self, path=None): 530 """ 531 Checks if the specified path is a file on the SMB share. 532 533 This method determines if a given path corresponds to a file on the SMB share. It does this by listing the 534 contents of the path and filtering for entries that match the basename of the path and are not marked as directories. 535 536 Args: 537 path (str, optional): The path to check on the SMB share. Defaults to None. 538 539 Returns: 540 bool: True if the path is a file, False otherwise or if an error occurs. 541 """ 542 543 if path is not None: 544 path = path.replace('*','') 545 try: 546 contents = self.smbClient.listPath( 547 shareName=self.smb_share, 548 path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep) 549 ) 550 # Filter on files 551 contents = [ 552 c for c in contents 553 if c.get_longname() == ntpath.basename(path) and not c.is_directory() 554 ] 555 return (len(contents) != 0) 556 except Exception as e: 557 return False 558 else: 559 return False
Checks if the specified path is a file on the SMB share.
This method determines if a given path corresponds to a file on the SMB share. It does this by listing the contents of the path and filtering for entries that match the basename of the path and are not marked as directories.
Args: path (str, optional): The path to check on the SMB share. Defaults to None.
Returns: bool: True if the path is a file, False otherwise or if an error occurs.
561 def ping_smb_session(self): 562 """ 563 Tests the connectivity to the SMB server by sending an echo command. 564 565 This method attempts to send an echo command to the SMB server to check if the session is still active. 566 It updates the `connected` attribute of the class based on the success or failure of the echo command. 567 568 Returns: 569 bool: True if the echo command succeeds (indicating the session is active), False otherwise. 570 """ 571 572 try: 573 self.smbClient.getSMBServer().echo() 574 self.connected = True 575 except Exception as e: 576 self.connected = False 577 return self.connected
Tests the connectivity to the SMB server by sending an echo command.
This method attempts to send an echo command to the SMB server to check if the session is still active.
It updates the connected attribute of the class based on the success or failure of the echo command.
Returns: bool: True if the echo command succeeds (indicating the session is active), False otherwise.
579 def put_file(self, localpath=None): 580 """ 581 Uploads a single file to the SMB share. 582 583 This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path. 584 It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session. 585 General exceptions are caught and logged, with a traceback provided if debugging is enabled. 586 587 Args: 588 localpath (str, optional): The local file path of the file to be uploaded. Defaults to None. 589 """ 590 591 if os.path.exists(localpath): 592 if os.path.isfile(localpath): 593 try: 594 localfile = os.path.basename(localpath) 595 f = LocalFileIO( 596 mode="rb", 597 path=localpath, 598 debug=self.config.debug 599 ) 600 self.smbClient.putFile( 601 shareName=self.smb_share, 602 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 603 callback=f.read 604 ) 605 f.close() 606 except (BrokenPipeError, KeyboardInterrupt) as err: 607 print("[!] Interrupted.") 608 self.close_smb_session() 609 self.init_smb_session() 610 except Exception as err: 611 print("[!] Failed to upload '%s': %s" % (localfile, err)) 612 if self.config.debug: 613 traceback.print_exc() 614 else: 615 print("[!] The specified localpath is a directory. Use 'put -r <directory>' instead.") 616 else: 617 print("[!] The specified localpath does not exist.")
Uploads a single file to the SMB share.
This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path. It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session. General exceptions are caught and logged, with a traceback provided if debugging is enabled.
Args: localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.
619 def put_file_recursively(self, localpath=None): 620 """ 621 Recursively uploads files from a specified local directory to the SMB share. 622 623 This method walks through the given local directory and all its subdirectories, uploading each file to the 624 corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is, 625 it iterates over all files and directories within the local path, creating necessary directories on the SMB share 626 and uploading files. If the local path is not a directory, it prints an error message. 627 628 Args: 629 localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None. 630 """ 631 632 if os.path.exists(localpath): 633 if os.path.isfile(localpath): 634 # Iterate over all files and directories within the local path 635 local_files = {} 636 for root, dirs, files in os.walk(localpath): 637 if len(files) != 0: 638 local_files[root] = files 639 640 # Iterate over the found files 641 for local_dir_path in sorted(local_files.keys()): 642 print("[>] Putting files of '%s'" % local_dir_path) 643 644 # Create remote directory 645 remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep) 646 self.mkdir( 647 path=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep) 648 ) 649 650 for local_file_path in local_files[local_dir_path]: 651 try: 652 f = LocalFileIO( 653 mode="rb", 654 path=local_dir_path + os.path.sep + local_file_path, 655 debug=self.config.debug 656 ) 657 self.smbClient.putFile( 658 shareName=self.smb_share, 659 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 660 callback=f.read 661 ) 662 f.close() 663 664 except BrokenPipeError as err: 665 f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err)) 666 f.close(remove=True) 667 break 668 except Exception as err: 669 f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err)) 670 f.close(remove=True) 671 else: 672 print("[!] The specified localpath is a file. Use 'put <file>' instead.") 673 else: 674 print("[!] The specified localpath does not exist.")
Recursively uploads files from a specified local directory to the SMB share.
This method walks through the given local directory and all its subdirectories, uploading each file to the corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is, it iterates over all files and directories within the local path, creating necessary directories on the SMB share and uploading files. If the local path is not a directory, it prints an error message.
Args: localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.
676 def rmdir(self, path=None): 677 """ 678 Removes a directory from the SMB share at the specified path. 679 680 This method attempts to delete a directory located at the given path on the SMB share. If the operation fails, 681 it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints 682 the stack trace of the exception. 683 684 Args: 685 path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None. 686 """ 687 try: 688 self.smbClient.deleteDirectory( 689 shareName=self.smb_share, 690 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 691 ) 692 except Exception as err: 693 print("[!] Failed to remove directory '%s': %s" % (path, err)) 694 if self.config.debug: 695 traceback.print_exc()
Removes a directory from the SMB share at the specified path.
This method attempts to delete a directory located at the given path on the SMB share. If the operation fails, it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints the stack trace of the exception.
Args: path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.
697 def rm(self, path=None): 698 """ 699 Removes a file from the SMB share at the specified path. 700 701 This method attempts to delete a file located at the given path on the SMB share. If the operation fails, 702 it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints 703 the stack trace of the exception. 704 705 Args: 706 path (str, optional): The path of the file to be removed on the SMB share. Defaults to None. 707 """ 708 try: 709 self.smbClient.deleteFile( 710 shareName=self.smb_share, 711 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 712 ) 713 except Exception as err: 714 print("[!] Failed to remove file '%s': %s" % (path, err)) 715 if self.config.debug: 716 traceback.print_exc()
Removes a file from the SMB share at the specified path.
This method attempts to delete a file located at the given path on the SMB share. If the operation fails, it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints the stack trace of the exception.
Args: path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.
718 def tree(self, path=None): 719 """ 720 Recursively lists the directory structure of the SMB share starting from the specified path. 721 722 This function prints a visual representation of the directory tree of the remote SMB share. It uses 723 recursion to navigate through directories and lists all files and subdirectories in each directory. 724 The output is color-coded and formatted to enhance readability, with directories highlighted in cyan. 725 726 Args: 727 path (str, optional): The starting path on the SMB share from which to begin listing the tree. 728 Defaults to the root of the current share. 729 """ 730 731 def recurse_action(base_dir="", path=[], prompt=[]): 732 bars = ["│ ", "├── ", "└── "] 733 734 remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path)) 735 736 entries = [] 737 try: 738 entries = self.smbClient.listPath( 739 shareName=self.smb_share, 740 path=remote_smb_path+'\\*' 741 ) 742 except impacket.smbconnection.SessionError as err: 743 code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1] 744 errmsg = "Error 0x%08x (%s): %s" % (code, const, text) 745 if self.config.no_colors: 746 print("%s%s" % (''.join(prompt+[bars[2]]), errmsg)) 747 else: 748 print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg)) 749 return 750 751 entries = [e for e in entries if e.get_longname() not in [".", ".."]] 752 entries = sorted(entries, key=lambda x:x.get_longname()) 753 754 # 755 if len(entries) > 1: 756 index = 0 757 for entry in entries: 758 index += 1 759 # This is the first entry 760 if index == 0: 761 if entry.is_directory(): 762 if self.config.no_colors: 763 print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 764 else: 765 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 766 recurse_action( 767 base_dir=base_dir, 768 path=path+[entry.get_longname()], 769 prompt=prompt+["│ "] 770 ) 771 else: 772 if self.config.no_colors: 773 print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname())) 774 else: 775 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname())) 776 777 # This is the last entry 778 elif index == len(entries): 779 if entry.is_directory(): 780 if self.config.no_colors: 781 print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 782 else: 783 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 784 recurse_action( 785 base_dir=base_dir, 786 path=path+[entry.get_longname()], 787 prompt=prompt+[" "] 788 ) 789 else: 790 if self.config.no_colors: 791 print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname())) 792 else: 793 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname())) 794 795 # These are entries in the middle 796 else: 797 if entry.is_directory(): 798 if self.config.no_colors: 799 print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 800 else: 801 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) 802 recurse_action( 803 base_dir=base_dir, 804 path=path+[entry.get_longname()], 805 prompt=prompt+["│ "] 806 ) 807 else: 808 if self.config.no_colors: 809 print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname())) 810 else: 811 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname())) 812 813 # 814 elif len(entries) == 1: 815 entry = entries[0] 816 if entry.is_directory(): 817 if self.config.no_colors: 818 print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 819 else: 820 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) 821 recurse_action( 822 base_dir=base_dir, 823 path=path+[entry.get_longname()], 824 prompt=prompt+[" "] 825 ) 826 else: 827 if self.config.no_colors: 828 print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname())) 829 else: 830 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname())) 831 832 # Entrypoint 833 try: 834 if self.config.no_colors: 835 print("%s\\" % path) 836 else: 837 print("\x1b[1;96m%s\x1b[0m\\" % path) 838 recurse_action( 839 base_dir=self.smb_cwd, 840 path=[path], 841 prompt=[""] 842 ) 843 except (BrokenPipeError, KeyboardInterrupt) as e: 844 print("[!] Interrupted.") 845 self.close_smb_session() 846 self.init_smb_session()
Recursively lists the directory structure of the SMB share starting from the specified path.
This function prints a visual representation of the directory tree of the remote SMB share. It uses recursion to navigate through directories and lists all files and subdirectories in each directory. The output is color-coded and formatted to enhance readability, with directories highlighted in cyan.
Args: path (str, optional): The starting path on the SMB share from which to begin listing the tree. Defaults to the root of the current share.
867 def set_cwd(self, path=None): 868 """ 869 Sets the current working directory on the SMB share to the specified path. 870 871 This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory. 872 If the specified path is not a directory, the cwd remains unchanged. 873 874 Parameters: 875 path (str): The path to set as the current working directory. 876 877 Raises: 878 ValueError: If the specified path is not a directory. 879 """ 880 881 if path is not None: 882 # Set path separators to ntpath sep 883 if '/' in path: 884 path = path.replace('/', ntpath.sep) 885 886 if path.startswith(ntpath.sep): 887 # Absolute path 888 path = path + ntpath.sep 889 else: 890 # Relative path to the CWD 891 if len(self.smb_cwd) == 0: 892 path = path + ntpath.sep 893 else: 894 path = self.smb_cwd + ntpath.sep + path 895 896 # Path normalization 897 path = ntpath.normpath(path) 898 path = re.sub(r'\\+', r'\\', path) 899 900 if path in ["", ".", ".."]: 901 self.smb_cwd = "" 902 else: 903 if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)): 904 # Path exists on the remote 905 self.smb_cwd = ntpath.normpath(path) 906 else: 907 # Path does not exists or is not a directory on the remote 908 print("[!] Remote directory '%s' does not exist." % path)
Sets the current working directory on the SMB share to the specified path.
This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory. If the specified path is not a directory, the cwd remains unchanged.
Parameters: path (str): The path to set as the current working directory.
Raises: ValueError: If the specified path is not a directory.