smbclientng.core.SMBSession

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

Class SMBSession is designed to handle the session management for SMB (Server Message Block) protocol connections. It provides functionalities to connect to an SMB server, authenticate using either NTLM or Kerberos, and manage SMB shares.

Attributes: address (str): The IP address or hostname of the SMB server. domain (str): The domain name for SMB server authentication. username (str): The username for SMB server authentication. password (str): The password for SMB server authentication. lmhash (str): The LM hash of the user's password, if available. nthash (str): The NT hash of the user's password, if available. use_kerberos (bool): A flag to determine whether to use Kerberos for authentication. kdcHost (str): The Key Distribution Center (KDC) host for Kerberos authentication. debug (bool): A flag to enable debug output. smbClient (object): The SMB client object used for the connection. connected (bool): A flag to check the status of the connection. smb_share (str): The current SMB share in use. smb_path (str): The current path within the SMB share.

Methods: __init__(address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, debug=False): Initializes the SMBSession with the specified parameters. init_smb_session(): Initializes the SMB session by connecting to the server and authenticating using the specified method.

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

Initializes and establishes a session with the SMB server.

This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration. It attempts to connect to the SMB server specified by the address attribute and authenticate using the credentials provided during the object's initialization.

The method will print debug information if the debug attribute is set to True. Upon successful connection and authentication, it sets the connected attribute to True.

Returns: bool: True if the connection and authentication are successful, False otherwise.

def close_smb_session(self):
131    def close_smb_session(self):
132        """
133        Closes the current SMB session by disconnecting the SMB client.
134
135        This method ensures that the SMB client connection is properly closed. It checks if the client is connected
136        and if so, it closes the connection and resets the connection status.
137
138        Raises:
139            Exception: If the SMB client is not initialized or if there's an error during the disconnection process.
140        """
141
142        if self.smbClient is not None:
143            if self.connected:
144                self.smbClient.close()
145                self.connected = False
146                print("[+] SMB connection closed successfully.")
147            else:
148                print("[!] No active SMB connection to close.")
149        else:
150            raise Exception("SMB client is not initialized.")

Closes the current SMB session by disconnecting the SMB client.

This method ensures that the SMB client connection is properly closed. It checks if the client is connected and if so, it closes the connection and resets the connection status.

Raises: Exception: If the SMB client is not initialized or if there's an error during the disconnection process.

def get_file(self, path=None, keepRemotePath=False):
154    def get_file(self, path=None, keepRemotePath=False):
155        """
156        Retrieves a file from the specified path on the SMB share.
157
158        This method attempts to retrieve a file from the given path within the currently connected SMB share.
159        If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local
160        file object and writing the contents of the remote file to it using the SMB client's getFile method.
161
162        Parameters:
163            path (str): The path of the file to retrieve. If None, uses the current smb_path.
164
165        Returns:
166            None
167        """
168
169        tmp_file_path = self.smb_cwd + ntpath.sep + path
170        matches = self.smbClient.listPath(
171            shareName=self.smb_share, 
172            path=tmp_file_path
173        )
174        
175        for entry in matches:
176            if entry.is_directory():
177                print("[>] Skipping '%s' because it is a directory." % tmp_file_path)
178            else:
179                try:
180                    if ntpath.sep in path:
181                        outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname()
182                    else:
183                        outputfile = entry.get_longname()
184                    f = LocalFileIO(
185                        mode="wb", 
186                        path=outputfile,
187                        expected_size=entry.get_filesize(), 
188                        debug=self.config.debug,
189                        keepRemotePath=keepRemotePath
190                    )
191                    self.smbClient.getFile(
192                        shareName=self.smb_share, 
193                        pathName=tmp_file_path, 
194                        callback=f.write
195                    )
196                    f.close()
197                except (BrokenPipeError, KeyboardInterrupt) as e:
198                    f.close()
199                    print("\x1b[v\x1b[o\r[!] Interrupted.")
200                    self.close_smb_session()
201                    self.init_smb_session()
202                        
203        return None

Retrieves a file from the specified path on the SMB share.

This method attempts to retrieve a file from the given path within the currently connected SMB share. If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local file object and writing the contents of the remote file to it using the SMB client's getFile method.

Parameters: path (str): The path of the file to retrieve. If None, uses the current smb_path.

Returns: None

def get_file_recursively(self, path=None):
205    def get_file_recursively(self, path=None):
206        """
207        Recursively retrieves files from a specified path on the SMB share.
208
209        This method navigates through all directories starting from the given path,
210        and downloads all files found. It handles directories recursively, ensuring
211        that all nested files are retrieved. The method skips over directory entries
212        and handles errors gracefully, attempting to continue the operation where possible.
213
214        Parameters:
215            path (str): The initial directory path from which to start the recursive file retrieval.
216                        If None, it starts from the root of the configured SMB share.
217        """
218        
219        def recurse_action(base_dir="", path=[]):
220            remote_smb_path = base_dir + ntpath.sep.join(path)
221            entries = self.smbClient.listPath(
222                shareName=self.smb_share, 
223                path=remote_smb_path + '\\*'
224            )
225            if len(entries) != 0:
226                files = [entry for entry in entries if not entry.is_directory()]
227                directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]]
228
229                # Files
230                if len(files) != 0:
231                    print("[>] Retrieving files of '%s'" % remote_smb_path)
232                for entry_file in files:
233                    if not entry_file.is_directory():
234                        f = LocalFileIO(
235                            mode="wb",
236                            path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
237                            expected_size=entry_file.get_filesize(),
238                            debug=self.config.debug
239                        )
240                        try:
241                            self.smbClient.getFile(
242                                shareName=self.smb_share, 
243                                pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
244                                callback=f.write
245                            )
246                            f.close()
247                        except BrokenPipeError as err:
248                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
249                            f.close(remove=True)
250                            break
251                        except Exception as err:
252                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
253                            f.close(remove=True)
254                
255                # Directories
256                for entry_directory in directories:
257                    if entry_directory.is_directory():
258                        recurse_action(
259                            base_dir=self.smb_cwd, 
260                            path=path+[entry_directory.get_longname()]
261                        )                   
262        # Entrypoint
263        try:
264            recurse_action(
265                base_dir=self.smb_cwd, 
266                path=[path]
267            )
268        except (BrokenPipeError, KeyboardInterrupt) as e:
269            print("\x1b[v\x1b[o\r[!] Interrupted.")
270            self.close_smb_session()
271            self.init_smb_session()

Recursively retrieves files from a specified path on the SMB share.

This method navigates through all directories starting from the given path, and downloads all files found. It handles directories recursively, ensuring that all nested files are retrieved. The method skips over directory entries and handles errors gracefully, attempting to continue the operation where possible.

Parameters: path (str): The initial directory path from which to start the recursive file retrieval. If None, it starts from the root of the configured SMB share.

def info(self, share=True, server=True):
273    def info(self, share=True, server=True):
274        """
275        Displays information about the server and optionally the shares.
276
277        This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the `share` parameter is set to True and a share is currently set, it will also attempt to display information about the share.
278
279        Parameters:
280            share (bool): If True, display information about the current share.
281            server (bool): If True, display information about the server.
282
283        Returns:
284            None
285        """
286
287        if server:
288            if self.config.no_colors:
289                print("[+] Server:")
290                print("  ├─NetBIOS:")
291                print("  │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName()))
292                print("  │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain()))
293                print("  ├─DNS:")
294                print("  │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName()))
295                print("  │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName()))
296                print("  ├─OS:")
297                print("  │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS()))
298                print("  │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
299                print("  ├─Server:")
300                print("  │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired()))
301                print("  │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired()))
302                print("  │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2()))
303                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
304                print("  │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize)))
305                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
306                print("  │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize)))
307                print("  └─")
308            else:
309                print("[+] Server:")
310                print("  ├─NetBIOS:")
311                print("  │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName()))
312                print("  │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain()))
313                print("  ├─DNS:")
314                print("  │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName()))
315                print("  │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName()))
316                print("  ├─OS:")
317                print("  │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS()))
318                print("  │ └─ \x1b[94mOS Version\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s.%s.%s\x1b[0m" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
319                print("  ├─Server:")
320                print("  │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired()))
321                print("  │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired()))
322                print("  │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2()))
323                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
324                print("  │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize)))
325                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
326                print("  │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize)))
327                print("  └─")
328
329        if share and self.smb_share is not None:
330            share_name = self.available_shares.get(self.smb_share.lower(), "")["name"]
331            share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"]
332            share_type = self.available_shares.get(self.smb_share.lower(), "")["type"]
333            share_type =', '.join([s.replace("STYPE_","") for s in share_type])
334            share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"]
335            if self.config.no_colors:
336                print("\n[+] Share:")
337                print("  ├─ Name ──────────── : %s" % (share_name))
338                print("  ├─ Description ───── : %s" % (share_comment))
339                print("  ├─ Type ──────────── : %s" % (share_type))
340                print("  └─ Raw type value ── : %s" % (share_rawtype))
341            else:
342                print("\n[+] Share:")
343                print("  ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name))
344                print("  ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment))
345                print("  ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type))
346                print("  └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype))

Displays information about the server and optionally the shares.

This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the share parameter is set to True and a share is currently set, it will also attempt to display information about the share.

Parameters: share (bool): If True, display information about the current share. server (bool): If True, display information about the server.

Returns: None

def list_contents(self, path=None):
348    def list_contents(self, path=None):
349        """
350        Lists the contents of a specified directory on the SMB share.
351
352        This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path`
353        is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with
354        the long names of the files and directories as keys and their respective SMB entry objects as values.
355
356        Args:
357            shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None.
358            path (str, optional): The directory path to list contents from. Defaults to the current path if None.
359
360        Returns:
361            dict: A dictionary with file and directory names as keys and their SMB entry objects as values.
362        """
363        
364        if path is None or len(path) == 0:
365            path = self.smb_cwd
366        path = path.rstrip(ntpath.sep) + ntpath.sep + "*"
367
368        contents = {}
369        entries = self.smbClient.listPath(
370            shareName=self.smb_share, 
371            path=path
372        )
373        for entry in entries:
374            contents[entry.get_longname()] = entry
375
376        return contents

Lists the contents of a specified directory on the SMB share.

This method retrieves the contents of a directory specified by shareName and path. If shareName or path is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with the long names of the files and directories as keys and their respective SMB entry objects as values.

Args: shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None. path (str, optional): The directory path to list contents from. Defaults to the current path if None.

Returns: dict: A dictionary with file and directory names as keys and their SMB entry objects as values.

def list_shares(self):
378    def list_shares(self):
379        """
380        Lists all the shares available on the connected SMB server.
381
382        This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary
383        with key-value pairs where the key is the share name and the value is a dictionary containing details about the share
384        such as its name, type, raw type, and any comments associated with the share.
385
386        Returns:
387            dict: A dictionary containing information about each share available on the server.
388        """
389
390        self.available_shares = {}
391
392        if self.connected:
393            if self.smbClient is not None:
394                resp = self.smbClient.listShares()
395
396                for share in resp:
397                    # SHARE_INFO_1 structure (lmshare.h)
398                    # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1
399                    sharename = share["shi1_netname"][:-1]
400                    sharecomment = share["shi1_remark"][:-1]
401                    sharetype = share["shi1_type"]
402
403                    self.available_shares[sharename.lower()] = {
404                        "name": sharename, 
405                        "type": STYPE_MASK(sharetype), 
406                        "rawtype": sharetype, 
407                        "comment": sharecomment
408                    }
409            else:
410                print("[!] Error: SMBSession.smbClient is None.")
411
412        return self.available_shares

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):
414    def mkdir(self, path=None):
415        """
416        Creates a directory at the specified path on the SMB share.
417
418        This method takes a path and attempts to create the directory structure on the SMB share. If the path includes
419        nested directories, it will create each directory in the sequence. If a directory already exists, it will skip
420        the creation for that directory without raising an error.
421
422        Args:
423            path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.
424
425        Note:
426            The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.
427        """
428
429        if path is not None:
430            # Prepare path
431            path = path.replace('/',ntpath.sep)
432            if ntpath.sep in path:
433                path = path.strip(ntpath.sep).split(ntpath.sep)
434            else:
435                path = [path]
436
437            # Create each dir in the path
438            for depth in range(1, len(path)+1):
439                tmp_path = ntpath.sep.join(path[:depth])
440                try:
441                    self.smbClient.createDirectory(
442                        shareName=self.smb_share, 
443                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep)
444                    )
445                except impacket.smbconnection.SessionError as err:
446                    if err.getErrorCode() == 0xc0000035:
447                        # STATUS_OBJECT_NAME_COLLISION
448                        # Remote directory already created, this is normal
449                        # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19
450                        pass
451                    else:
452                        print("[!] Failed to create directory '%s': %s" % (tmp_path, err))
453                        if self.config.debug:
454                            traceback.print_exc()
455        else:
456            pass

Creates a directory at the specified path on the SMB share.

This method takes a path and attempts to create the directory structure on the SMB share. If the path includes nested directories, it will create each directory in the sequence. If a directory already exists, it will skip the creation for that directory without raising an error.

Args: path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.

Note: The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.

def path_exists(self, path=None):
458    def path_exists(self, path=None):
459        """
460        Checks if the specified path exists on the SMB share.
461
462        This method determines if a given path exists on the SMB share by attempting to list the contents of the path.
463        If the path listing is successful and returns one or more entries, the path is considered to exist.
464
465        Args:
466            path (str, optional): The path to check on the SMB share. Defaults to None.
467
468        Returns:
469            bool: True if the path exists, False otherwise or if an error occurs.
470        """
471
472        if path is not None:
473            path = path.replace('*','')
474            try:
475                contents = self.smbClient.listPath(
476                    shareName=self.smb_share,
477                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
478                )
479                return (len(contents) != 0)
480            except Exception as e:
481                return False
482        else:
483            return False

Checks if the specified path exists on the SMB share.

This method determines if a given path exists on the SMB share by attempting to list the contents of the path. If the path listing is successful and returns one or more entries, the path is considered to exist.

Args: path (str, optional): The path to check on the SMB share. Defaults to None.

Returns: bool: True if the path exists, False otherwise or if an error occurs.

def path_isdir(self, pathFromRoot=None):
485    def path_isdir(self, pathFromRoot=None):
486        """
487        Checks if the specified path is a directory on the SMB share.
488
489        This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the
490        contents of the path and filtering for entries that match the basename of the path and are marked as directories.
491
492        Args:
493            path (str, optional): The path to check on the SMB share. Defaults to None.
494
495        Returns:
496            bool: True if the path is a directory, False otherwise or if an error occurs.
497        """
498
499        if pathFromRoot is not None:
500            # Replace slashes if any
501            path = pathFromRoot.replace('/', ntpath.sep)
502            
503            # Strip wildcards to avoid injections
504            path = path.replace('*','')
505
506            # Normalize path and strip leading backslash
507            path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep)
508
509            if path.strip() in ['', '.', '..']:
510                # By defininition they exist on the filesystem
511                return True
512            else:
513                try:
514                    contents = self.smbClient.listPath(
515                        shareName=self.smb_share,
516                        path=path+'*'
517                    )
518                    # Filter on directories
519                    contents = [
520                        c for c in contents
521                        if c.get_longname() == ntpath.basename(path) and c.is_directory()
522                    ]
523                    return (len(contents) != 0)
524                except Exception as e:
525                    return False
526        else:
527            return False

Checks if the specified path is a directory on the SMB share.

This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the contents of the path and filtering for entries that match the basename of the path and are marked as directories.

Args: path (str, optional): The path to check on the SMB share. Defaults to None.

Returns: bool: True if the path is a directory, False otherwise or if an error occurs.

def path_isfile(self, path=None):
529    def path_isfile(self, path=None):
530        """
531        Checks if the specified path is a file on the SMB share.
532
533        This method determines if a given path corresponds to a file on the SMB share. It does this by listing the
534        contents of the path and filtering for entries that match the basename of the path and are not marked as directories.
535
536        Args:
537            path (str, optional): The path to check on the SMB share. Defaults to None.
538
539        Returns:
540            bool: True if the path is a file, False otherwise or if an error occurs.
541        """
542
543        if path is not None:
544            path = path.replace('*','')
545            try:
546                contents = self.smbClient.listPath(
547                    shareName=self.smb_share,
548                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
549                )
550                # Filter on files
551                contents = [
552                    c for c in contents
553                    if c.get_longname() == ntpath.basename(path) and not c.is_directory()
554                ]
555                return (len(contents) != 0)
556            except Exception as e:
557                return False
558        else:
559            return False

Checks if the specified path is a file on the SMB share.

This method determines if a given path corresponds to a file on the SMB share. It does this by listing the contents of the path and filtering for entries that match the basename of the path and are not marked as directories.

Args: path (str, optional): The path to check on the SMB share. Defaults to None.

Returns: bool: True if the path is a file, False otherwise or if an error occurs.

def ping_smb_session(self):
561    def ping_smb_session(self):
562        """
563        Tests the connectivity to the SMB server by sending an echo command.
564
565        This method attempts to send an echo command to the SMB server to check if the session is still active.
566        It updates the `connected` attribute of the class based on the success or failure of the echo command.
567
568        Returns:
569            bool: True if the echo command succeeds (indicating the session is active), False otherwise.
570        """
571
572        try:
573            self.smbClient.getSMBServer().echo()
574            self.connected = True
575        except Exception as e:
576            self.connected = False
577        return self.connected

Tests the connectivity to the SMB server by sending an echo command.

This method attempts to send an echo command to the SMB server to check if the session is still active. It updates the connected attribute of the class based on the success or failure of the echo command.

Returns: bool: True if the echo command succeeds (indicating the session is active), False otherwise.

def put_file(self, localpath=None):
579    def put_file(self, localpath=None):
580        """
581        Uploads a single file to the SMB share.
582
583        This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path.
584        It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session.
585        General exceptions are caught and logged, with a traceback provided if debugging is enabled.
586
587        Args:
588            localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.
589        """
590
591        if os.path.exists(localpath):
592            if os.path.isfile(localpath):
593                try:
594                    localfile = os.path.basename(localpath)
595                    f = LocalFileIO(
596                        mode="rb", 
597                        path=localpath, 
598                        debug=self.config.debug
599                    )
600                    self.smbClient.putFile(
601                        shareName=self.smb_share, 
602                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 
603                        callback=f.read
604                    )
605                    f.close()
606                except (BrokenPipeError, KeyboardInterrupt) as err:
607                    print("[!] Interrupted.")
608                    self.close_smb_session()
609                    self.init_smb_session()
610                except Exception as err:
611                    print("[!] Failed to upload '%s': %s" % (localfile, err))
612                    if self.config.debug:
613                        traceback.print_exc()
614            else:
615                print("[!] The specified localpath is a directory. Use 'put -r <directory>' instead.")
616        else:
617            print("[!] The specified localpath does not exist.")

Uploads a single file to the SMB share.

This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path. It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session. General exceptions are caught and logged, with a traceback provided if debugging is enabled.

Args: localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.

def put_file_recursively(self, localpath=None):
619    def put_file_recursively(self, localpath=None):
620        """
621        Recursively uploads files from a specified local directory to the SMB share.
622
623        This method walks through the given local directory and all its subdirectories, uploading each file to the
624        corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is,
625        it iterates over all files and directories within the local path, creating necessary directories on the SMB share
626        and uploading files. If the local path is not a directory, it prints an error message.
627
628        Args:
629            localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.
630        """
631
632        if os.path.exists(localpath):
633            if os.path.isfile(localpath):
634                # Iterate over all files and directories within the local path
635                local_files = {}
636                for root, dirs, files in os.walk(localpath):
637                    if len(files) != 0:
638                        local_files[root] = files
639
640                # Iterate over the found files
641                for local_dir_path in sorted(local_files.keys()):
642                    print("[>] Putting files of '%s'" % local_dir_path)
643
644                    # Create remote directory
645                    remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep)
646                    self.mkdir(
647                        path=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep)
648                    )
649
650                    for local_file_path in local_files[local_dir_path]:
651                        try:
652                            f = LocalFileIO(
653                                mode="rb", 
654                                path=local_dir_path + os.path.sep + local_file_path, 
655                                debug=self.config.debug
656                            )
657                            self.smbClient.putFile(
658                                shareName=self.smb_share, 
659                                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 
660                                callback=f.read
661                            )
662                            f.close()
663
664                        except BrokenPipeError as err:
665                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
666                            f.close(remove=True)
667                            break
668                        except Exception as err:
669                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
670                            f.close(remove=True)
671                else:
672                    print("[!] The specified localpath is a file. Use 'put <file>' instead.")
673        else:
674            print("[!] The specified localpath does not exist.")

Recursively uploads files from a specified local directory to the SMB share.

This method walks through the given local directory and all its subdirectories, uploading each file to the corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is, it iterates over all files and directories within the local path, creating necessary directories on the SMB share and uploading files. If the local path is not a directory, it prints an error message.

Args: localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.

def rmdir(self, path=None):
676    def rmdir(self, path=None):
677        """
678        Removes a directory from the SMB share at the specified path.
679
680        This method attempts to delete a directory located at the given path on the SMB share. If the operation fails,
681        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
682        the stack trace of the exception.
683
684        Args:
685            path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.
686        """
687        try:
688            self.smbClient.deleteDirectory(
689                shareName=self.smb_share, 
690                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
691            )
692        except Exception as err:
693            print("[!] Failed to remove directory '%s': %s" % (path, err))
694            if self.config.debug:
695                traceback.print_exc()

Removes a directory from the SMB share at the specified path.

This method attempts to delete a directory located at the given path on the SMB share. If the operation fails, it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints the stack trace of the exception.

Args: path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.

def rm(self, path=None):
697    def rm(self, path=None):
698        """
699        Removes a file from the SMB share at the specified path.
700
701        This method attempts to delete a file located at the given path on the SMB share. If the operation fails,
702        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
703        the stack trace of the exception.
704
705        Args:
706            path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.
707        """
708        try:
709            self.smbClient.deleteFile(
710                shareName=self.smb_share, 
711                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
712            )
713        except Exception as err:
714            print("[!] Failed to remove file '%s': %s" % (path, err))
715            if self.config.debug:
716                traceback.print_exc()

Removes a file from the SMB share at the specified path.

This method attempts to delete a file located at the given path on the SMB share. If the operation fails, it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints the stack trace of the exception.

Args: path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.

def tree(self, path=None):
718    def tree(self, path=None):
719        """
720        Recursively lists the directory structure of the SMB share starting from the specified path.
721
722        This function prints a visual representation of the directory tree of the remote SMB share. It uses
723        recursion to navigate through directories and lists all files and subdirectories in each directory.
724        The output is color-coded and formatted to enhance readability, with directories highlighted in cyan.
725
726        Args:
727            path (str, optional): The starting path on the SMB share from which to begin listing the tree.
728                                  Defaults to the root of the current share.
729        """
730        
731        def recurse_action(base_dir="", path=[], prompt=[]):
732            bars = ["│   ", "├── ", "└── "]
733
734            remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path))
735
736            entries = []
737            try:
738                entries = self.smbClient.listPath(
739                    shareName=self.smb_share, 
740                    path=remote_smb_path+'\\*'
741                )
742            except impacket.smbconnection.SessionError as err:
743                code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1]
744                errmsg = "Error 0x%08x (%s): %s" % (code, const, text)
745                if self.config.no_colors:
746                    print("%s%s" % (''.join(prompt+[bars[2]]), errmsg))
747                else:
748                    print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg))
749                return 
750
751            entries = [e for e in entries if e.get_longname() not in [".", ".."]]
752            entries = sorted(entries, key=lambda x:x.get_longname())
753
754            # 
755            if len(entries) > 1:
756                index = 0
757                for entry in entries:
758                    index += 1
759                    # This is the first entry 
760                    if index == 0:
761                        if entry.is_directory():
762                            if self.config.no_colors:
763                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
764                            else:
765                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
766                            recurse_action(
767                                base_dir=base_dir, 
768                                path=path+[entry.get_longname()],
769                                prompt=prompt+["│   "]
770                            )
771                        else:
772                            if self.config.no_colors:
773                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
774                            else:
775                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
776
777                    # This is the last entry
778                    elif index == len(entries):
779                        if entry.is_directory():
780                            if self.config.no_colors:
781                                print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
782                            else:
783                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
784                            recurse_action(
785                                base_dir=base_dir, 
786                                path=path+[entry.get_longname()],
787                                prompt=prompt+["    "]
788                            )
789                        else:
790                            if self.config.no_colors:
791                                print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
792                            else:
793                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
794                        
795                    # These are entries in the middle
796                    else:
797                        if entry.is_directory():
798                            if self.config.no_colors:
799                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
800                            else:
801                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
802                            recurse_action(
803                                base_dir=base_dir, 
804                                path=path+[entry.get_longname()],
805                                prompt=prompt+["│   "]
806                            )
807                        else:
808                            if self.config.no_colors:
809                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
810                            else:
811                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
812
813            # 
814            elif len(entries) == 1:
815                entry = entries[0]
816                if entry.is_directory():
817                    if self.config.no_colors:
818                        print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
819                    else:
820                        print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
821                    recurse_action(
822                        base_dir=base_dir, 
823                        path=path+[entry.get_longname()],
824                        prompt=prompt+["    "]
825                    )
826                else:
827                    if self.config.no_colors:
828                        print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
829                    else:
830                        print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
831
832        # Entrypoint
833        try:
834            if self.config.no_colors:
835                print("%s\\" % path)
836            else:
837                print("\x1b[1;96m%s\x1b[0m\\" % path)
838            recurse_action(
839                base_dir=self.smb_cwd, 
840                path=[path],
841                prompt=[""]
842            )
843        except (BrokenPipeError, KeyboardInterrupt) as e:
844            print("[!] Interrupted.")
845            self.close_smb_session()
846            self.init_smb_session()

Recursively lists the directory structure of the SMB share starting from the specified path.

This function prints a visual representation of the directory tree of the remote SMB share. It uses recursion to navigate through directories and lists all files and subdirectories in each directory. The output is color-coded and formatted to enhance readability, with directories highlighted in cyan.

Args: path (str, optional): The starting path on the SMB share from which to begin listing the tree. Defaults to the root of the current share.

def set_share(self, shareName):
850    def set_share(self, shareName):
851        """
852        Sets the current SMB share to the specified share name.
853
854        This method updates the SMB session to use the specified share name. It checks if the share name is valid
855        and updates the smb_share attribute of the SMBSession instance.
856
857        Parameters:
858            shareName (str): The name of the share to set as the current SMB share.
859
860        Raises:
861            ValueError: If the shareName is None or an empty string.
862        """
863
864        if shareName is not None:
865            self.smb_share = shareName

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):
867    def set_cwd(self, path=None):
868        """
869        Sets the current working directory on the SMB share to the specified path.
870
871        This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory.
872        If the specified path is not a directory, the cwd remains unchanged.
873
874        Parameters:
875            path (str): The path to set as the current working directory.
876
877        Raises:
878            ValueError: If the specified path is not a directory.
879        """
880
881        if path is not None:
882            # Set path separators to ntpath sep 
883            if '/' in path:
884                path = path.replace('/', ntpath.sep)
885
886            if path.startswith(ntpath.sep):
887                # Absolute path
888                path = path + ntpath.sep
889            else:
890                # Relative path to the CWD
891                if len(self.smb_cwd) == 0:
892                    path = path + ntpath.sep
893                else:
894                    path = self.smb_cwd + ntpath.sep + path
895            
896            # Path normalization
897            path = ntpath.normpath(path)
898            path = re.sub(r'\\+', r'\\', path)
899
900            if path in ["", ".", ".."]:
901                self.smb_cwd = ""
902            else:
903                if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)):
904                    # Path exists on the remote 
905                    self.smb_cwd = ntpath.normpath(path)
906                else:
907                    # Path does not exists or is not a directory on the remote 
908                    print("[!] Remote directory '%s' does not exist." % path)

Sets the current working directory on the SMB share to the specified path.

This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory. If the specified path is not a directory, the cwd remains unchanged.

Parameters: path (str): The path to set as the current working directory.

Raises: ValueError: If the specified path is not a directory.