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

SMBSession( address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None)
47    def __init__(self, address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None):
48        super(SMBSession, self).__init__()
49        # Objects
50        self.config = config
51
52        # Target server
53        self.address = address
54
55        # Credentials
56        self.domain = domain
57        self.username = username
58        self.password = password 
59        self.lmhash = lmhash
60        self.nthash = nthash
61        self.use_kerberos = use_kerberos
62        self.kdcHost = kdcHost
63
64        self.smbClient = None
65        self.connected = False
66
67        self.available_shares = {}
68        self.smb_share = None
69        self.smb_cwd = ""
70        self.smb_tree_id = None
71
72        self.list_shares()
config
address
domain
username
password
lmhash
nthash
use_kerberos
kdcHost
smbClient
connected
available_shares
smb_share
smb_cwd
smb_tree_id
def init_smb_session(self):
 76    def init_smb_session(self):
 77        """
 78        Initializes and establishes a session with the SMB server.
 79
 80        This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration.
 81        It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization.
 82
 83        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.
 84
 85        Returns:
 86            bool: True if the connection and authentication are successful, False otherwise.
 87        """
 88
 89        self.connected = False
 90
 91        if self.config.debug:
 92            print("[debug] [>] Connecting to remote SMB server '%s' ... " % self.address)
 93        try:
 94            self.smbClient = impacket.smbconnection.SMBConnection(
 95                remoteName=self.address,
 96                remoteHost=self.address,
 97                sess_port=int(445)
 98            )
 99        except OSError as err:
100            print("[!] %s" % err)
101            self.smbClient = None
102
103        if self.smbClient is not None:
104            if self.use_kerberos:
105                if self.config.debug:
106                    print("[debug] [>] Authenticating as '%s\\%s' with kerberos ... " % (self.domain, self.username))
107                try:
108                    self.connected = self.smbClient.kerberosLogin(
109                        user=self.username,
110                        password=self.password,
111                        domain=self.domain,
112                        lmhash=self.lmhash,
113                        nthash=self.nthash,
114                        aesKey=self.aesKey,
115                        kdcHost=self.kdcHost
116                    )
117                except impacket.smbconnection.SessionError as err:
118                    if self.config.debug:
119                        traceback.print_exc()
120                    print("[!] Could not login: %s" % err)
121                    self.connected = False
122
123            else:
124                if self.config.debug:
125                    print("[debug] [>] Authenticating as '%s\\%s' with NTLM ... " % (self.domain, self.username))
126                try:
127                    self.connected = self.smbClient.login(
128                        user=self.username,
129                        password=self.password,
130                        domain=self.domain,
131                        lmhash=self.lmhash,
132                        nthash=self.nthash
133                    )
134                except impacket.smbconnection.SessionError as err:
135                    if self.config.debug:
136                        traceback.print_exc()
137                    print("[!] Could not login: %s" % err)
138                    self.connected = False
139
140            if self.connected:
141                print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
142            else:
143                print("[!] Failed to authenticate to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
144
145        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.

def close_smb_session(self):
147    def close_smb_session(self):
148        """
149        Closes the current SMB session by disconnecting the SMB client.
150
151        This method ensures that the SMB client connection is properly closed. It checks if the client is connected
152        and if so, it closes the connection and resets the connection status.
153
154        Raises:
155            Exception: If the SMB client is not initialized or if there's an error during the disconnection process.
156        """
157
158        if self.smbClient is not None:
159            if self.connected:
160                self.smbClient.close()
161                self.connected = False
162                if self.config.debug:
163                    print("[+] SMB connection closed successfully.")
164            else:
165                if self.config.debug:
166                    print("[!] No active SMB connection to close.")
167        else:
168            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.

def read_file(self, path=None):
172    def read_file(self, path=None):
173        if self.path_isfile(path=path):
174            tmp_file_path = self.smb_cwd + ntpath.sep + path
175            matches = self.smbClient.listPath(
176                shareName=self.smb_share, 
177                path=tmp_file_path
178            )
179
180            fh = io.BytesIO()
181            try:
182                # opening the files in streams instead of mounting shares allows 
183                # for running the script from unprivileged containers
184                self.smbClient.getFile(self.smb_share, tmp_file_path, fh.write)
185            except impacket.smbconnection.SessionError as e:
186                return None
187            rawdata = fh.getvalue()
188            fh.close()
189            return rawdata
190        else:
191            print("[!] Remote path '%s' is not a file." % path)
def find(self, paths=[], callback=None):
193    def find(self, paths=[], callback=None):
194        def recurse_action(paths=[], depth=0, callback=None):
195            if callback is None:
196                return []
197            
198            next_directories_to_explore = []
199
200            for path in paths:
201                remote_smb_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path)
202                entries = []
203                
204                try:
205                    entries = self.smbClient.listPath(
206                        shareName=self.smb_share, 
207                        path=(remote_smb_path + ntpath.sep + '*')
208                    )
209                except impacket.smbconnection.SessionError as err:
210                    continue 
211                # Remove dot names
212                entries = [e for e in entries if e.get_longname() not in [".", ".."]]
213                # Sort the entries ignoring case
214                entries = sorted(entries, key=lambda x:x.get_longname().lower())
215                
216                for entry in entries:
217                    if entry.is_directory():
218                        callback(entry, path + ntpath.sep + entry.get_longname() + ntpath.sep, depth)
219                    else:
220                        callback(entry, path + ntpath.sep + entry.get_longname(), depth)
221
222                # Next directories to explore
223                for entry in entries:
224                    if entry.is_directory():
225                        next_directories_to_explore.append(path + ntpath.sep + entry.get_longname() + ntpath.sep)
226            
227            return next_directories_to_explore
228        # 
229        if callback is not None:
230            depth = 0
231            while len(paths) != 0:
232                paths = recurse_action(
233                    paths=paths,
234                    depth=depth,
235                    callback=callback
236                )
237                depth = depth + 1
238        else:
239            print("[!] SMBSession.find(), callback function cannot be None.")
def get_file(self, path=None, keepRemotePath=False):
241    def get_file(self, path=None, keepRemotePath=False):
242        """
243        Retrieves a file from the specified path on the SMB share.
244
245        This method attempts to retrieve a file from the given path within the currently connected SMB share.
246        If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local
247        file object and writing the contents of the remote file to it using the SMB client's getFile method.
248
249        Parameters:
250            path (str): The path of the file to retrieve. If None, uses the current smb_path.
251
252        Returns:
253            None
254        """
255
256        tmp_file_path = self.smb_cwd + ntpath.sep + path
257        matches = self.smbClient.listPath(
258            shareName=self.smb_share, 
259            path=tmp_file_path
260        )
261        
262        for entry in matches:
263            if entry.is_directory():
264                print("[>] Skipping '%s' because it is a directory." % tmp_file_path)
265            else:
266                try:
267                    if ntpath.sep in path:
268                        outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname()
269                    else:
270                        outputfile = entry.get_longname()
271                    f = LocalFileIO(
272                        mode="wb", 
273                        path=outputfile,
274                        expected_size=entry.get_filesize(), 
275                        debug=self.config.debug,
276                        keepRemotePath=keepRemotePath
277                    )
278                    self.smbClient.getFile(
279                        shareName=self.smb_share, 
280                        pathName=tmp_file_path, 
281                        callback=f.write
282                    )
283                    f.close()
284                except (BrokenPipeError, KeyboardInterrupt) as e:
285                    f.close()
286                    print("\x1b[v\x1b[o\r[!] Interrupted.")
287                    self.close_smb_session()
288                    self.init_smb_session()
289                        
290        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

def get_file_recursively(self, path=None):
292    def get_file_recursively(self, path=None):
293        """
294        Recursively retrieves files from a specified path on the SMB share.
295
296        This method navigates through all directories starting from the given path,
297        and downloads all files found. It handles directories recursively, ensuring
298        that all nested files are retrieved. The method skips over directory entries
299        and handles errors gracefully, attempting to continue the operation where possible.
300
301        Parameters:
302            path (str): The initial directory path from which to start the recursive file retrieval.
303                        If None, it starts from the root of the configured SMB share.
304        """
305        
306        def recurse_action(base_dir="", path=[]):
307            if len(base_dir) == 0:
308                remote_smb_path = ntpath.sep.join(path)
309            else:
310                remote_smb_path = base_dir + ntpath.sep + ntpath.sep.join(path)
311            remote_smb_path = ntpath.normpath(remote_smb_path)
312
313            entries = self.smbClient.listPath(
314                shareName=self.smb_share, 
315                path=remote_smb_path + '\\*'
316            )
317            if len(entries) != 0:
318                files = [entry for entry in entries if not entry.is_directory()]
319                directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]]
320
321                # Files
322                if len(files) != 0:
323                    print("[>] Retrieving files of '%s'" % remote_smb_path)
324                for entry_file in files:
325                    if not entry_file.is_directory():
326                        f = LocalFileIO(
327                            mode="wb",
328                            path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
329                            expected_size=entry_file.get_filesize(),
330                            keepRemotePath=True,
331                            debug=self.config.debug
332                        )
333                        try:
334                            self.smbClient.getFile(
335                                shareName=self.smb_share, 
336                                pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
337                                callback=f.write
338                            )
339                            f.close()
340                        except BrokenPipeError as err:
341                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
342                            f.close(remove=True)
343                            break
344                        except Exception as err:
345                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
346                            f.close(remove=True)
347                
348                # Directories
349                for entry_directory in directories:
350                    if entry_directory.is_directory():
351                        recurse_action(
352                            base_dir=self.smb_cwd, 
353                            path=path+[entry_directory.get_longname()]
354                        )                   
355        # Entrypoint
356        try:
357            recurse_action(
358                base_dir=self.smb_cwd, 
359                path=[path]
360            )
361        except (BrokenPipeError, KeyboardInterrupt) as e:
362            print("\x1b[v\x1b[o\r[!] Interrupted.")
363            self.close_smb_session()
364            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.

def get_entry(self, path=None):
366    def get_entry(self, path=None):
367        """
368        Retrieves information about a specific entry located at the provided path on the SMB share.
369
370        This method checks if the specified path exists on the SMB share. If the path exists, it retrieves the details of the entry at that path, including the directory name and file name. If the entry is found, it returns the entry object; otherwise, it returns None.
371
372        Args:
373            path (str): The path of the entry to retrieve information about.
374
375        Returns:
376            Entry: An object representing the entry at the specified path, or None if the entry is not found.
377        """
378
379        if self.path_exists(path=path):
380            matches = self.smbClient.listPath(shareName=self.smb_share, path=path)
381
382            if len(matches) == 1:
383                return matches[0]
384            else:
385                return None
386            
387        else:
388            return None 

Retrieves information about a specific entry located at the provided path on the SMB share.

This method checks if the specified path exists on the SMB share. If the path exists, it retrieves the details of the entry at that path, including the directory name and file name. If the entry is found, it returns the entry object; otherwise, it returns None.

Args: path (str): The path of the entry to retrieve information about.

Returns: Entry: An object representing the entry at the specified path, or None if the entry is not found.

def info(self, share=True, server=True):
390    def info(self, share=True, server=True):
391        """
392        Displays information about the server and optionally the shares.
393
394        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.
395
396        Parameters:
397            share (bool): If True, display information about the current share.
398            server (bool): If True, display information about the server.
399
400        Returns:
401            None
402        """
403
404        if server:
405            if self.config.no_colors:
406                print("[+] Server:")
407                print("  ├─NetBIOS:")
408                print("  │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName()))
409                print("  │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain()))
410                print("  ├─DNS:")
411                print("  │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName()))
412                print("  │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName()))
413                print("  ├─OS:")
414                print("  │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS()))
415                print("  │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
416                print("  ├─Server:")
417                print("  │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired()))
418                print("  │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired()))
419                print("  │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2()))
420                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
421                print("  │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize)))
422                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
423                print("  │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize)))
424                print("  └─")
425            else:
426                print("[+] Server:")
427                print("  ├─NetBIOS:")
428                print("  │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName()))
429                print("  │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain()))
430                print("  ├─DNS:")
431                print("  │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName()))
432                print("  │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName()))
433                print("  ├─OS:")
434                print("  │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS()))
435                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()))
436                print("  ├─Server:")
437                print("  │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired()))
438                print("  │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired()))
439                print("  │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2()))
440                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
441                print("  │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize)))
442                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
443                print("  │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize)))
444                print("  └─")
445
446        if share and self.smb_share is not None:
447            share_name = self.available_shares.get(self.smb_share.lower(), "")["name"]
448            share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"]
449            share_type = self.available_shares.get(self.smb_share.lower(), "")["type"]
450            share_type =', '.join([s.replace("STYPE_","") for s in share_type])
451            share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"]
452            if self.config.no_colors:
453                print("\n[+] Share:")
454                print("  ├─ Name ──────────── : %s" % (share_name))
455                print("  ├─ Description ───── : %s" % (share_comment))
456                print("  ├─ Type ──────────── : %s" % (share_type))
457                print("  └─ Raw type value ── : %s" % (share_rawtype))
458            else:
459                print("\n[+] Share:")
460                print("  ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name))
461                print("  ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment))
462                print("  ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type))
463                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

def list_contents(self, path=None):
465    def list_contents(self, path=None):
466        """
467        Lists the contents of a specified directory on the SMB share.
468
469        This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path`
470        is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with
471        the long names of the files and directories as keys and their respective SMB entry objects as values.
472
473        Args:
474            shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None.
475            path (str, optional): The directory path to list contents from. Defaults to the current path if None.
476
477        Returns:
478            dict: A dictionary with file and directory names as keys and their SMB entry objects as values.
479        """
480        
481        dest_path = [self.smb_cwd.rstrip(ntpath.sep),]
482        if path is not None and len(path) > 0:
483            dest_path.append(path.rstrip(ntpath.sep))
484        dest_path.append('*')
485        path = ntpath.sep.join(dest_path)
486
487        contents = {}
488        entries = self.smbClient.listPath(
489            shareName=self.smb_share, 
490            path=path
491        )
492        for entry in entries:
493            contents[entry.get_longname()] = entry
494
495        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.

def list_shares(self):
497    def list_shares(self):
498        """
499        Lists all the shares available on the connected SMB server.
500
501        This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary
502        with key-value pairs where the key is the share name and the value is a dictionary containing details about the share
503        such as its name, type, raw type, and any comments associated with the share.
504
505        Returns:
506            dict: A dictionary containing information about each share available on the server.
507        """
508
509        self.available_shares = {}
510
511        if self.connected:
512            if self.smbClient is not None:
513                resp = self.smbClient.listShares()
514
515                for share in resp:
516                    # SHARE_INFO_1 structure (lmshare.h)
517                    # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1
518                    sharename = share["shi1_netname"][:-1]
519                    sharecomment = share["shi1_remark"][:-1]
520                    sharetype = share["shi1_type"]
521
522                    self.available_shares[sharename.lower()] = {
523                        "name": sharename, 
524                        "type": STYPE_MASK(sharetype), 
525                        "rawtype": sharetype, 
526                        "comment": sharecomment
527                    }
528            else:
529                print("[!] Error: SMBSession.smbClient is None.")
530
531        return self.available_shares

Lists all the shares available on the connected SMB server.

This method queries the SMB server to retrieve a list of all available shares. It populates the shares dictionary with key-value pairs where the key is the share name and the value is a dictionary containing details about the share such as its name, type, raw type, and any comments associated with the share.

Returns: dict: A dictionary containing information about each share available on the server.

def mkdir(self, path=None):
533    def mkdir(self, path=None):
534        """
535        Creates a directory at the specified path on the SMB share.
536
537        This method takes a path and attempts to create the directory structure on the SMB share. If the path includes
538        nested directories, it will create each directory in the sequence. If a directory already exists, it will skip
539        the creation for that directory without raising an error.
540
541        Args:
542            path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.
543
544        Note:
545            The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.
546        """
547
548        if path is not None:
549            # Prepare path
550            path = path.replace('/',ntpath.sep)
551            if ntpath.sep in path:
552                path = path.strip(ntpath.sep).split(ntpath.sep)
553            else:
554                path = [path]
555
556            # Create each dir in the path
557            for depth in range(1, len(path)+1):
558                tmp_path = ntpath.sep.join(path[:depth])
559                try:
560                    self.smbClient.createDirectory(
561                        shareName=self.smb_share, 
562                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep)
563                    )
564                except impacket.smbconnection.SessionError as err:
565                    if err.getErrorCode() == 0xc0000035:
566                        # STATUS_OBJECT_NAME_COLLISION
567                        # Remote directory already created, this is normal
568                        # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19
569                        pass
570                    else:
571                        print("[!] Failed to create directory '%s': %s" % (tmp_path, err))
572                        if self.config.debug:
573                            traceback.print_exc()
574        else:
575            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.

def mount(self, local_mount_point, remote_path):
577    def mount(self, local_mount_point, remote_path):
578
579        if not os.path.exists(local_mount_point):
580            pass
581
582        if sys.platform.startswith('win'):
583            remote_path = remote_path.replace('/',ntpath.sep)
584            command = f"net use {local_mount_point} \\\\{self.address}\\{self.smb_share}\\{remote_path}"
585        
586        elif sys.platform.startswith('linux'):
587            remote_path = remote_path.replace(ntpath.sep,'/')
588            command = f"mount -t cifs //{self.address}/{self.smb_share}/{remote_path} {local_mount_point} -o username={self.username},password={self.password}"
589        
590        elif sys.platform.startswith('darwin'):
591            remote_path = remote_path.replace(ntpath.sep,'/')
592            command = f"mount_smbfs //{self.username}:{self.password}@{self.address}/{self.smb_share}/{remote_path} {local_mount_point}"
593        
594        else:
595            command = None
596            print("[!] Unsupported platform for mounting SMB share.")
597        
598        if command is not None:
599            if self.config.debug:
600                print("[debug] Executing: %s" % command)
601            os.system(command)
def path_exists(self, path=None):
603    def path_exists(self, path=None):
604        """
605        Checks if the specified path exists on the SMB share.
606
607        This method determines if a given path exists on the SMB share by attempting to list the contents of the path.
608        If the path listing is successful and returns one or more entries, the path is considered to exist.
609
610        Args:
611            path (str, optional): The path to check on the SMB share. Defaults to None.
612
613        Returns:
614            bool: True if the path exists, False otherwise or if an error occurs.
615        """
616
617        if path is not None:
618            path = path.replace('*','')
619            try:
620                contents = self.smbClient.listPath(
621                    shareName=self.smb_share,
622                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
623                )
624                return (len(contents) != 0)
625            except Exception as e:
626                return False
627        else:
628            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.

def path_isdir(self, pathFromRoot=None):
630    def path_isdir(self, pathFromRoot=None):
631        """
632        Checks if the specified path is a directory on the SMB share.
633
634        This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the
635        contents of the path and filtering for entries that match the basename of the path and are marked as directories.
636
637        Args:
638            path (str, optional): The path to check on the SMB share. Defaults to None.
639
640        Returns:
641            bool: True if the path is a directory, False otherwise or if an error occurs.
642        """
643
644        if pathFromRoot is not None:
645            # Replace slashes if any
646            path = pathFromRoot.replace('/', ntpath.sep)
647            
648            # Strip wildcards to avoid injections
649            path = path.replace('*','')
650
651            # Normalize path and strip leading backslash
652            path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep)
653
654            if path.strip() in ['', '.', '..']:
655                # By defininition they exist on the filesystem
656                return True
657            else:
658                try:
659                    contents = self.smbClient.listPath(
660                        shareName=self.smb_share,
661                        path=path+'*'
662                    )
663                    # Filter on directories
664                    contents = [
665                        c for c in contents
666                        if c.get_longname() == ntpath.basename(path) and c.is_directory()
667                    ]
668                    return (len(contents) != 0)
669                except Exception as e:
670                    return False
671        else:
672            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.

def path_isfile(self, path=None):
674    def path_isfile(self, path=None):
675        """
676        Checks if the specified path is a file on the SMB share.
677
678        This method determines if a given path corresponds to a file on the SMB share. It does this by listing the
679        contents of the path and filtering for entries that match the basename of the path and are not marked as directories.
680
681        Args:
682            path (str, optional): The path to check on the SMB share. Defaults to None.
683
684        Returns:
685            bool: True if the path is a file, False otherwise or if an error occurs.
686        """
687
688        if path is not None:
689            path = path.replace('*','')
690            search_dir = ntpath.normpath(self.smb_cwd + ntpath.sep + path)
691            search_dir = ntpath.dirname(search_dir) + ntpath.sep + '*'
692            try:
693                contents = self.smbClient.listPath(
694                    shareName=self.smb_share,
695                    path=search_dir
696                )
697                # Filter on files
698                contents = [
699                    c for c in contents
700                    if c.get_longname() == ntpath.basename(path) and not c.is_directory()
701                ]
702                return (len(contents) != 0)
703            except Exception as e:
704                return False
705        else:
706            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.

def ping_smb_session(self):
708    def ping_smb_session(self):
709        """
710        Tests the connectivity to the SMB server by sending an echo command.
711
712        This method attempts to send an echo command to the SMB server to check if the session is still active.
713        It updates the `connected` attribute of the class based on the success or failure of the echo command.
714
715        Returns:
716            bool: True if the echo command succeeds (indicating the session is active), False otherwise.
717        """
718
719        try:
720            self.smbClient.getSMBServer().echo()
721        except Exception as e:
722            self.connected = False
723        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.

def put_file(self, localpath=None):
725    def put_file(self, localpath=None):
726        """
727        Uploads a single file to the SMB share.
728
729        This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path.
730        It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session.
731        General exceptions are caught and logged, with a traceback provided if debugging is enabled.
732
733        Args:
734            localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.
735        """
736
737        if os.path.exists(localpath):
738            if os.path.isfile(localpath):
739                try:
740                    localfile = os.path.basename(localpath)
741                    f = LocalFileIO(
742                        mode="rb", 
743                        path=localpath, 
744                        debug=self.config.debug
745                    )
746                    self.smbClient.putFile(
747                        shareName=self.smb_share, 
748                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 
749                        callback=f.read
750                    )
751                    f.close()
752                except (BrokenPipeError, KeyboardInterrupt) as err:
753                    print("[!] Interrupted.")
754                    self.close_smb_session()
755                    self.init_smb_session()
756                except Exception as err:
757                    print("[!] Failed to upload '%s': %s" % (localfile, err))
758                    if self.config.debug:
759                        traceback.print_exc()
760            else:
761                print("[!] The specified localpath is a directory. Use 'put -r <directory>' instead.")
762        else:
763            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.

def put_file_recursively(self, localpath=None):
765    def put_file_recursively(self, localpath=None):
766        """
767        Recursively uploads files from a specified local directory to the SMB share.
768
769        This method walks through the given local directory and all its subdirectories, uploading each file to the
770        corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is,
771        it iterates over all files and directories within the local path, creating necessary directories on the SMB share
772        and uploading files. If the local path is not a directory, it prints an error message.
773
774        Args:
775            localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.
776        """
777
778        if os.path.exists(localpath):
779            if os.path.isfile(localpath):
780                # Iterate over all files and directories within the local path
781                local_files = {}
782                for root, dirs, files in os.walk(localpath):
783                    if len(files) != 0:
784                        local_files[root] = files
785
786                # Iterate over the found files
787                for local_dir_path in sorted(local_files.keys()):
788                    print("[>] Putting files of '%s'" % local_dir_path)
789
790                    # Create remote directory
791                    remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep)
792                    self.mkdir(
793                        path=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep)
794                    )
795
796                    for local_file_path in local_files[local_dir_path]:
797                        try:
798                            f = LocalFileIO(
799                                mode="rb", 
800                                path=local_dir_path + os.path.sep + local_file_path, 
801                                debug=self.config.debug
802                            )
803                            self.smbClient.putFile(
804                                shareName=self.smb_share, 
805                                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 
806                                callback=f.read
807                            )
808                            f.close()
809
810                        except BrokenPipeError as err:
811                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
812                            f.close(remove=True)
813                            break
814                        except Exception as err:
815                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
816                            f.close(remove=True)
817                else:
818                    print("[!] The specified localpath is a file. Use 'put <file>' instead.")
819        else:
820            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.

def rmdir(self, path=None):
822    def rmdir(self, path=None):
823        """
824        Removes a directory from the SMB share at the specified path.
825
826        This method attempts to delete a directory located at the given path on the SMB share. If the operation fails,
827        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
828        the stack trace of the exception.
829
830        Args:
831            path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.
832        """
833        try:
834            self.smbClient.deleteDirectory(
835                shareName=self.smb_share, 
836                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
837            )
838        except Exception as err:
839            print("[!] Failed to remove directory '%s': %s" % (path, err))
840            if self.config.debug:
841                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.

def rm(self, path=None):
843    def rm(self, path=None):
844        """
845        Removes a file from the SMB share at the specified path.
846
847        This method attempts to delete a file located at the given path on the SMB share. If the operation fails,
848        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
849        the stack trace of the exception.
850
851        Args:
852            path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.
853        """
854        try:
855            self.smbClient.deleteFile(
856                shareName=self.smb_share, 
857                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
858            )
859        except Exception as err:
860            print("[!] Failed to remove file '%s': %s" % (path, err))
861            if self.config.debug:
862                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.

def tree(self, path=None):
864    def tree(self, path=None):
865        """
866        Recursively lists the directory structure of the SMB share starting from the specified path.
867
868        This function prints a visual representation of the directory tree of the remote SMB share. It uses
869        recursion to navigate through directories and lists all files and subdirectories in each directory.
870        The output is color-coded and formatted to enhance readability, with directories highlighted in cyan.
871
872        Args:
873            path (str, optional): The starting path on the SMB share from which to begin listing the tree.
874                                  Defaults to the root of the current share.
875        """
876        
877        def recurse_action(base_dir="", path=[], prompt=[]):
878            bars = ["│   ", "├── ", "└── "]
879
880            remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path))
881
882            entries = []
883            try:
884                entries = self.smbClient.listPath(
885                    shareName=self.smb_share, 
886                    path=remote_smb_path+'\\*'
887                )
888            except impacket.smbconnection.SessionError as err:
889                code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1]
890                errmsg = "Error 0x%08x (%s): %s" % (code, const, text)
891                if self.config.no_colors:
892                    print("%s%s" % (''.join(prompt+[bars[2]]), errmsg))
893                else:
894                    print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg))
895                return 
896
897            entries = [e for e in entries if e.get_longname() not in [".", ".."]]
898            entries = sorted(entries, key=lambda x:x.get_longname())
899
900            # 
901            if len(entries) > 1:
902                index = 0
903                for entry in entries:
904                    index += 1
905                    # This is the first entry 
906                    if index == 0:
907                        if entry.is_directory():
908                            if self.config.no_colors:
909                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
910                            else:
911                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
912                            recurse_action(
913                                base_dir=base_dir, 
914                                path=path+[entry.get_longname()],
915                                prompt=prompt+["│   "]
916                            )
917                        else:
918                            if self.config.no_colors:
919                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
920                            else:
921                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
922
923                    # This is the last entry
924                    elif index == len(entries):
925                        if entry.is_directory():
926                            if self.config.no_colors:
927                                print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
928                            else:
929                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
930                            recurse_action(
931                                base_dir=base_dir, 
932                                path=path+[entry.get_longname()],
933                                prompt=prompt+["    "]
934                            )
935                        else:
936                            if self.config.no_colors:
937                                print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
938                            else:
939                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
940                        
941                    # These are entries in the middle
942                    else:
943                        if entry.is_directory():
944                            if self.config.no_colors:
945                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
946                            else:
947                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
948                            recurse_action(
949                                base_dir=base_dir, 
950                                path=path+[entry.get_longname()],
951                                prompt=prompt+["│   "]
952                            )
953                        else:
954                            if self.config.no_colors:
955                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
956                            else:
957                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
958
959            # 
960            elif len(entries) == 1:
961                entry = entries[0]
962                if entry.is_directory():
963                    if self.config.no_colors:
964                        print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
965                    else:
966                        print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
967                    recurse_action(
968                        base_dir=base_dir, 
969                        path=path+[entry.get_longname()],
970                        prompt=prompt+["    "]
971                    )
972                else:
973                    if self.config.no_colors:
974                        print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
975                    else:
976                        print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
977
978        # Entrypoint
979        try:
980            if self.config.no_colors:
981                print("%s\\" % path)
982            else:
983                print("\x1b[1;96m%s\x1b[0m\\" % path)
984            recurse_action(
985                base_dir=self.smb_cwd, 
986                path=[path],
987                prompt=[""]
988            )
989        except (BrokenPipeError, KeyboardInterrupt) as e:
990            print("[!] Interrupted.")
991            self.close_smb_session()
992            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.

def umount(self, local_mount_point):
 994    def umount(self, local_mount_point):
 995        if os.path.exists(local_mount_point):
 996            if sys.platform.startswith('win'):
 997                command = f"net use {local_mount_point} /delete"
 998
 999            elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
1000                command = f"umount {local_mount_point}"
1001
1002            else:
1003                command = None
1004                print("[!] Unsupported platform for unmounting SMB share.")
1005        
1006            if command is not None:
1007                if self.config.debug:
1008                    print("[debug] Executing: %s" % command)
1009                os.system(command)
1010        else:
1011            print("[!] Cannot unmount a non existing path.")        
def set_share(self, shareName):
1015    def set_share(self, shareName):
1016        """
1017        Sets the current SMB share to the specified share name.
1018
1019        This method updates the SMB session to use the specified share name. It checks if the share name is valid
1020        and updates the smb_share attribute of the SMBSession instance.
1021
1022        Parameters:
1023            shareName (str): The name of the share to set as the current SMB share.
1024
1025        Raises:
1026            ValueError: If the shareName is None or an empty string.
1027        """
1028
1029        if shareName is not None:
1030            self.list_shares()
1031            if shareName.lower() in self.available_shares.keys():
1032                # Doing this in order to keep the case of the share adevertised by the remote machine
1033                self.smb_share = self.available_shares[shareName.lower()]["name"]
1034                # Connects the tree
1035                self.smb_tree_id = self.smbClient.connectTree(self.smb_share)
1036            else:
1037                print("[!] Could not set share '%s', it does not exist remotely." % shareName)

Sets the current SMB share to the specified share name.

This method updates the SMB session to use the specified share name. It checks if the share name is valid and updates the smb_share attribute of the SMBSession instance.

Parameters: shareName (str): The name of the share to set as the current SMB share.

Raises: ValueError: If the shareName is None or an empty string.

def set_cwd(self, path=None):
1039    def set_cwd(self, path=None):
1040        """
1041        Sets the current working directory on the SMB share to the specified path.
1042
1043        This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory.
1044        If the specified path is not a directory, the cwd remains unchanged.
1045
1046        Parameters:
1047            path (str): The path to set as the current working directory.
1048
1049        Raises:
1050            ValueError: If the specified path is not a directory.
1051        """
1052
1053        if path is not None:
1054            # Set path separators to ntpath sep 
1055            if '/' in path:
1056                path = path.replace('/', ntpath.sep)
1057
1058            if path.startswith(ntpath.sep):
1059                # Absolute path
1060                path = path + ntpath.sep
1061            else:
1062                # Relative path to the CWD
1063                if len(self.smb_cwd) == 0:
1064                    path = path + ntpath.sep
1065                else:
1066                    path = self.smb_cwd + ntpath.sep + path
1067            
1068            # Path normalization
1069            path = ntpath.normpath(path)
1070            path = re.sub(r'\\+', r'\\', path)
1071
1072            if path in ["", ".", ".."]:
1073                self.smb_cwd = ""
1074            else:
1075                if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)):
1076                    # Path exists on the remote 
1077                    self.smb_cwd = ntpath.normpath(path)
1078                else:
1079                    # Path does not exists or is not a directory on the remote 
1080                    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.