smbclientng.modules.Find

  1#!/usr/bin/env python3
  2# -*- coding: utf-8 -*-
  3# File name          : InteractiveShell.py
  4# Author             : Podalirius (@podalirius_)
  5# Date created       : 23 may 2024
  6
  7
  8import impacket
  9import ntpath
 10import re
 11from smbclientng.core.Module import Module
 12from smbclientng.core.ModuleArgumentParser import ModuleArgumentParser
 13from smbclientng.core.utils import windows_ls_entry
 14
 15
 16class Find(Module):
 17    """
 18    A class to search for files in a directory hierarchy.
 19
 20    This class provides functionality to search for files based on various criteria in a directory hierarchy.
 21    """
 22
 23    name = "find"
 24    description = "Search for files in a directory hierarchy"
 25
 26    def parseArgs(self, arguments):
 27        """
 28        Parses the command line arguments provided to the module.
 29
 30        This method initializes the argument parser with the module's name and description, and defines all the necessary arguments that the module accepts. It then parses the provided command line arguments based on these definitions.
 31
 32        Args:
 33            arguments (str): A string of command line arguments.
 34
 35        Returns:
 36            ModuleArgumentParser.Namespace | None: The parsed arguments as a Namespace object if successful, None if there are no arguments or help is requested.
 37        """
 38
 39        parser = ModuleArgumentParser(prog=self.name, description=self.description)
 40
 41        # Adding positional arguments
 42        parser.add_argument("paths", metavar="PATH", type=str, nargs="*", default=[], help="The starting point(s) for the search.")
 43
 44        # Adding tests, actions, and options for expressions (incomplete for brevity)
 45        parser.add_argument("-name", type=str, help="Base of file name (the path with the leading directories removed).")
 46        parser.add_argument("-iname", type=str, help="Like -name, but the match is case insensitive.")
 47        parser.add_argument("-type", type=str, default=None, help="File type (e.g., f for regular file, d for directory).")
 48        parser.add_argument("-size", type=str, help="File uses n units of space.")
 49        # parser.add_argument("-mtime", type=str, help="File's data was last modified n*24 hours ago")
 50        # parser.add_argument("-ctime", type=str, help="File's status was last changed n*24 hours ago")
 51        # parser.add_argument("-atime", type=str, help="File was last accessed n*24 hours ago")
 52        
 53        # Adding actions
 54        parser.add_argument("-ls", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
 55        parser.add_argument("-download", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
 56
 57        # Other options (incomplete for brevity)
 58        parser.add_argument("-maxdepth", type=int, help="Descend at most levels (a non-negative integer) levels of directories below the command line arguments.")
 59        parser.add_argument("-mindepth", type=int, help="Do not apply any tests or actions at levels less than levels (a non-negative integer).")
 60
 61        if len(arguments.strip()) == 0:
 62            parser.print_help()
 63            return None
 64        else:
 65            self.options = self.processArguments(parser, arguments)
 66
 67        if self.options is not None:
 68            if len(self.options.paths) == 0:
 69                parser.print_help()
 70                self.options = None
 71
 72        return self.options
 73
 74    def __recurse_action(self, paths=[], depth=0):
 75        """
 76        Recursively searches for files in a directory hierarchy and prints the results based on specified criteria.
 77
 78        Args:
 79            base_dir (str): The base directory to start the search from.
 80            paths (list): List of paths to search within the base directory.
 81            depth (int): The current depth level in the directory hierarchy.
 82
 83        Returns:
 84            None
 85        """
 86
 87        next_directories_to_explore = []
 88
 89        for path in paths:
 90            remote_smb_path = ntpath.normpath(self.smbSession.smb_cwd + ntpath.sep + path)
 91
 92            entries = []
 93            try:
 94                entries = self.smbSession.smbClient.listPath(
 95                    shareName=self.smbSession.smb_share, 
 96                    path=(remote_smb_path + ntpath.sep + '*')
 97                )
 98            except impacket.smbconnection.SessionError as err:
 99                continue 
100            # Remove dot names
101            entries = [e for e in entries if e.get_longname() not in [".", ".."]]
102            # Sort the entries ignoring case
103            entries = sorted(entries, key=lambda x:x.get_longname().lower())
104
105            # Match and print results
106            do_print_results = True
107            if self.options.mindepth is not None:
108                if depth < self.options.mindepth:
109                    do_print_results = False
110            if self.options.maxdepth is not None:
111                if depth > self.options.maxdepth:
112                    do_print_results = False
113            
114            if do_print_results:
115                for entry in entries:
116                    do_print_entry = False
117                    # Print directory
118                    if entry.is_directory():
119                        if (self.options.type == 'd' or self.options.type is None):
120                            # No name filtering
121                            if self.options.name is None and self.options.iname is None:
122                                do_print_entry = True
123                            
124                            # Filtering on names case sensitive
125                            elif self.options.name is not None:
126                                if '*' in self.options.name:
127                                    regex = self.options.name
128                                    regex = regex.replace('.', '\\.')
129                                    regex = regex.replace('*', '.*')
130                                    regex = '^' + regex + '$'
131                                    if re.match(regex, entry.get_longname()):
132                                        do_print_entry = True
133                                    else:
134                                        do_print_entry = False
135                                else:
136                                    do_print_entry = (entry.get_longname().lower() == self.options.name.lower())
137                            
138                            # Filtering on names case insensitive  
139                            elif self.options.iname is not None:
140                                if '*' in self.options.iname:
141                                    regex = self.options.iname
142                                    regex = regex.replace('.', '\\.')
143                                    regex = regex.replace('*', '.*')
144                                    regex = '^' + regex + '$'
145                                    if re.match(regex, entry.get_longname(), re.IGNORECASE):
146                                        do_print_entry = True
147                                    else:
148                                        do_print_entry = False
149                                else:
150                                    do_print_entry = (entry.get_longname().lower() == self.options.iname.lower())
151                                    
152                    # Print file
153                    else:
154                        if (self.options.type == 'f' or self.options.type is None):
155                            # No name filtering
156                            if self.options.name is None and self.options.iname is None:
157                                do_print_entry = True
158                            
159                            # Filtering on names case sensitive
160                            elif self.options.name is not None:
161                                if '*' in self.options.name:
162                                    regex = self.options.name
163                                    regex = regex.replace('.', '\\.')
164                                    regex = regex.replace('*', '.*')
165                                    regex = '^' + regex + '$'
166                                    if re.match(regex, entry.get_longname()):
167                                        do_print_entry = True
168                                    else:
169                                        do_print_entry = False
170                                else:
171                                    do_print_entry = (entry.get_longname().lower() == self.options.name.lower())
172                            
173                            # Filtering on names case insensitive
174                            elif self.options.iname is not None:
175                                if '*' in self.options.iname:
176                                    regex = self.options.iname
177                                    regex = regex.replace('.', '\\.')
178                                    regex = regex.replace('*', '.*')
179                                    regex = '^' + regex + '$'
180                                    if re.match(regex, entry.get_longname(), re.IGNORECASE):
181                                        do_print_entry = True
182                                    else:
183                                        do_print_entry = False
184                                else:
185                                    do_print_entry = (entry.get_longname().lower() == self.options.iname.lower())
186
187                    if do_print_entry:
188                        # Actions on matches
189                        if self.options.download:
190                            if entry.is_directory():
191                                self.smbSession.get_file_recursively(path=(path + entry.get_longname() + ntpath.sep))
192                            else:
193                                self.smbSession.get_file(path=(path + entry.get_longname() + ntpath.sep), keepRemotePath=True)
194                        # Output formats
195                        if self.options.ls:
196                            if entry.is_directory():
197                                windows_ls_entry(entry, (path + entry.get_longname() + ntpath.sep))
198                            else:
199                                windows_ls_entry(entry, (path + entry.get_longname()))
200                        else:
201                            if entry.is_directory():
202                                print("%s" % (path + entry.get_longname() + ntpath.sep))
203                            else:
204                                print("%s" % (path + entry.get_longname()))
205
206            # Next directories to explore
207            for entry in entries:
208                if entry.is_directory():
209                    next_directories_to_explore.append(path + entry.get_longname() + ntpath.sep)
210        
211        return next_directories_to_explore
212
213    def run(self, arguments):
214        """
215        This function recursively searches for files in a directory hierarchy and prints the results based on specified criteria.
216
217        Args:
218            base_dir (str): The base directory to start the search from.
219            paths (list): List of paths to search within the base directory.
220            depth (int): The current depth level in the directory hierarchy.
221
222        Returns:
223            None
224        """
225
226        self.options = self.parseArgs(arguments=arguments)
227
228        if self.options is not None:
229            # Entrypoint
230            try:
231                next_directories_to_explore = []
232                for path in list(set(self.options.paths)):
233                    next_directories_to_explore.append(ntpath.normpath(path) + ntpath.sep)
234                next_directories_to_explore = sorted(list(set(next_directories_to_explore)))
235                
236                depth = 0
237                
238                while len(next_directories_to_explore) != 0:
239                    next_directories_to_explore = self.__recurse_action(
240                        paths=next_directories_to_explore,
241                        depth=depth
242                    )
243                    depth = depth + 1
244
245            except (BrokenPipeError, KeyboardInterrupt) as e:
246                print("[!] Interrupted.")
247                self.smbSession.close_smb_session()
248                self.smbSession.init_smb_session()
class Find(smbclientng.core.Module.Module):
 17class Find(Module):
 18    """
 19    A class to search for files in a directory hierarchy.
 20
 21    This class provides functionality to search for files based on various criteria in a directory hierarchy.
 22    """
 23
 24    name = "find"
 25    description = "Search for files in a directory hierarchy"
 26
 27    def parseArgs(self, arguments):
 28        """
 29        Parses the command line arguments provided to the module.
 30
 31        This method initializes the argument parser with the module's name and description, and defines all the necessary arguments that the module accepts. It then parses the provided command line arguments based on these definitions.
 32
 33        Args:
 34            arguments (str): A string of command line arguments.
 35
 36        Returns:
 37            ModuleArgumentParser.Namespace | None: The parsed arguments as a Namespace object if successful, None if there are no arguments or help is requested.
 38        """
 39
 40        parser = ModuleArgumentParser(prog=self.name, description=self.description)
 41
 42        # Adding positional arguments
 43        parser.add_argument("paths", metavar="PATH", type=str, nargs="*", default=[], help="The starting point(s) for the search.")
 44
 45        # Adding tests, actions, and options for expressions (incomplete for brevity)
 46        parser.add_argument("-name", type=str, help="Base of file name (the path with the leading directories removed).")
 47        parser.add_argument("-iname", type=str, help="Like -name, but the match is case insensitive.")
 48        parser.add_argument("-type", type=str, default=None, help="File type (e.g., f for regular file, d for directory).")
 49        parser.add_argument("-size", type=str, help="File uses n units of space.")
 50        # parser.add_argument("-mtime", type=str, help="File's data was last modified n*24 hours ago")
 51        # parser.add_argument("-ctime", type=str, help="File's status was last changed n*24 hours ago")
 52        # parser.add_argument("-atime", type=str, help="File was last accessed n*24 hours ago")
 53        
 54        # Adding actions
 55        parser.add_argument("-ls", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
 56        parser.add_argument("-download", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
 57
 58        # Other options (incomplete for brevity)
 59        parser.add_argument("-maxdepth", type=int, help="Descend at most levels (a non-negative integer) levels of directories below the command line arguments.")
 60        parser.add_argument("-mindepth", type=int, help="Do not apply any tests or actions at levels less than levels (a non-negative integer).")
 61
 62        if len(arguments.strip()) == 0:
 63            parser.print_help()
 64            return None
 65        else:
 66            self.options = self.processArguments(parser, arguments)
 67
 68        if self.options is not None:
 69            if len(self.options.paths) == 0:
 70                parser.print_help()
 71                self.options = None
 72
 73        return self.options
 74
 75    def __recurse_action(self, paths=[], depth=0):
 76        """
 77        Recursively searches for files in a directory hierarchy and prints the results based on specified criteria.
 78
 79        Args:
 80            base_dir (str): The base directory to start the search from.
 81            paths (list): List of paths to search within the base directory.
 82            depth (int): The current depth level in the directory hierarchy.
 83
 84        Returns:
 85            None
 86        """
 87
 88        next_directories_to_explore = []
 89
 90        for path in paths:
 91            remote_smb_path = ntpath.normpath(self.smbSession.smb_cwd + ntpath.sep + path)
 92
 93            entries = []
 94            try:
 95                entries = self.smbSession.smbClient.listPath(
 96                    shareName=self.smbSession.smb_share, 
 97                    path=(remote_smb_path + ntpath.sep + '*')
 98                )
 99            except impacket.smbconnection.SessionError as err:
100                continue 
101            # Remove dot names
102            entries = [e for e in entries if e.get_longname() not in [".", ".."]]
103            # Sort the entries ignoring case
104            entries = sorted(entries, key=lambda x:x.get_longname().lower())
105
106            # Match and print results
107            do_print_results = True
108            if self.options.mindepth is not None:
109                if depth < self.options.mindepth:
110                    do_print_results = False
111            if self.options.maxdepth is not None:
112                if depth > self.options.maxdepth:
113                    do_print_results = False
114            
115            if do_print_results:
116                for entry in entries:
117                    do_print_entry = False
118                    # Print directory
119                    if entry.is_directory():
120                        if (self.options.type == 'd' or self.options.type is None):
121                            # No name filtering
122                            if self.options.name is None and self.options.iname is None:
123                                do_print_entry = True
124                            
125                            # Filtering on names case sensitive
126                            elif self.options.name is not None:
127                                if '*' in self.options.name:
128                                    regex = self.options.name
129                                    regex = regex.replace('.', '\\.')
130                                    regex = regex.replace('*', '.*')
131                                    regex = '^' + regex + '$'
132                                    if re.match(regex, entry.get_longname()):
133                                        do_print_entry = True
134                                    else:
135                                        do_print_entry = False
136                                else:
137                                    do_print_entry = (entry.get_longname().lower() == self.options.name.lower())
138                            
139                            # Filtering on names case insensitive  
140                            elif self.options.iname is not None:
141                                if '*' in self.options.iname:
142                                    regex = self.options.iname
143                                    regex = regex.replace('.', '\\.')
144                                    regex = regex.replace('*', '.*')
145                                    regex = '^' + regex + '$'
146                                    if re.match(regex, entry.get_longname(), re.IGNORECASE):
147                                        do_print_entry = True
148                                    else:
149                                        do_print_entry = False
150                                else:
151                                    do_print_entry = (entry.get_longname().lower() == self.options.iname.lower())
152                                    
153                    # Print file
154                    else:
155                        if (self.options.type == 'f' or self.options.type is None):
156                            # No name filtering
157                            if self.options.name is None and self.options.iname is None:
158                                do_print_entry = True
159                            
160                            # Filtering on names case sensitive
161                            elif self.options.name is not None:
162                                if '*' in self.options.name:
163                                    regex = self.options.name
164                                    regex = regex.replace('.', '\\.')
165                                    regex = regex.replace('*', '.*')
166                                    regex = '^' + regex + '$'
167                                    if re.match(regex, entry.get_longname()):
168                                        do_print_entry = True
169                                    else:
170                                        do_print_entry = False
171                                else:
172                                    do_print_entry = (entry.get_longname().lower() == self.options.name.lower())
173                            
174                            # Filtering on names case insensitive
175                            elif self.options.iname is not None:
176                                if '*' in self.options.iname:
177                                    regex = self.options.iname
178                                    regex = regex.replace('.', '\\.')
179                                    regex = regex.replace('*', '.*')
180                                    regex = '^' + regex + '$'
181                                    if re.match(regex, entry.get_longname(), re.IGNORECASE):
182                                        do_print_entry = True
183                                    else:
184                                        do_print_entry = False
185                                else:
186                                    do_print_entry = (entry.get_longname().lower() == self.options.iname.lower())
187
188                    if do_print_entry:
189                        # Actions on matches
190                        if self.options.download:
191                            if entry.is_directory():
192                                self.smbSession.get_file_recursively(path=(path + entry.get_longname() + ntpath.sep))
193                            else:
194                                self.smbSession.get_file(path=(path + entry.get_longname() + ntpath.sep), keepRemotePath=True)
195                        # Output formats
196                        if self.options.ls:
197                            if entry.is_directory():
198                                windows_ls_entry(entry, (path + entry.get_longname() + ntpath.sep))
199                            else:
200                                windows_ls_entry(entry, (path + entry.get_longname()))
201                        else:
202                            if entry.is_directory():
203                                print("%s" % (path + entry.get_longname() + ntpath.sep))
204                            else:
205                                print("%s" % (path + entry.get_longname()))
206
207            # Next directories to explore
208            for entry in entries:
209                if entry.is_directory():
210                    next_directories_to_explore.append(path + entry.get_longname() + ntpath.sep)
211        
212        return next_directories_to_explore
213
214    def run(self, arguments):
215        """
216        This function recursively searches for files in a directory hierarchy and prints the results based on specified criteria.
217
218        Args:
219            base_dir (str): The base directory to start the search from.
220            paths (list): List of paths to search within the base directory.
221            depth (int): The current depth level in the directory hierarchy.
222
223        Returns:
224            None
225        """
226
227        self.options = self.parseArgs(arguments=arguments)
228
229        if self.options is not None:
230            # Entrypoint
231            try:
232                next_directories_to_explore = []
233                for path in list(set(self.options.paths)):
234                    next_directories_to_explore.append(ntpath.normpath(path) + ntpath.sep)
235                next_directories_to_explore = sorted(list(set(next_directories_to_explore)))
236                
237                depth = 0
238                
239                while len(next_directories_to_explore) != 0:
240                    next_directories_to_explore = self.__recurse_action(
241                        paths=next_directories_to_explore,
242                        depth=depth
243                    )
244                    depth = depth + 1
245
246            except (BrokenPipeError, KeyboardInterrupt) as e:
247                print("[!] Interrupted.")
248                self.smbSession.close_smb_session()
249                self.smbSession.init_smb_session()

A class to search for files in a directory hierarchy.

This class provides functionality to search for files based on various criteria in a directory hierarchy.

name = 'find'
description = 'Search for files in a directory hierarchy'
def parseArgs(self, arguments):
27    def parseArgs(self, arguments):
28        """
29        Parses the command line arguments provided to the module.
30
31        This method initializes the argument parser with the module's name and description, and defines all the necessary arguments that the module accepts. It then parses the provided command line arguments based on these definitions.
32
33        Args:
34            arguments (str): A string of command line arguments.
35
36        Returns:
37            ModuleArgumentParser.Namespace | None: The parsed arguments as a Namespace object if successful, None if there are no arguments or help is requested.
38        """
39
40        parser = ModuleArgumentParser(prog=self.name, description=self.description)
41
42        # Adding positional arguments
43        parser.add_argument("paths", metavar="PATH", type=str, nargs="*", default=[], help="The starting point(s) for the search.")
44
45        # Adding tests, actions, and options for expressions (incomplete for brevity)
46        parser.add_argument("-name", type=str, help="Base of file name (the path with the leading directories removed).")
47        parser.add_argument("-iname", type=str, help="Like -name, but the match is case insensitive.")
48        parser.add_argument("-type", type=str, default=None, help="File type (e.g., f for regular file, d for directory).")
49        parser.add_argument("-size", type=str, help="File uses n units of space.")
50        # parser.add_argument("-mtime", type=str, help="File's data was last modified n*24 hours ago")
51        # parser.add_argument("-ctime", type=str, help="File's status was last changed n*24 hours ago")
52        # parser.add_argument("-atime", type=str, help="File was last accessed n*24 hours ago")
53        
54        # Adding actions
55        parser.add_argument("-ls", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
56        parser.add_argument("-download", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
57
58        # Other options (incomplete for brevity)
59        parser.add_argument("-maxdepth", type=int, help="Descend at most levels (a non-negative integer) levels of directories below the command line arguments.")
60        parser.add_argument("-mindepth", type=int, help="Do not apply any tests or actions at levels less than levels (a non-negative integer).")
61
62        if len(arguments.strip()) == 0:
63            parser.print_help()
64            return None
65        else:
66            self.options = self.processArguments(parser, arguments)
67
68        if self.options is not None:
69            if len(self.options.paths) == 0:
70                parser.print_help()
71                self.options = None
72
73        return self.options

Parses the command line arguments provided to the module.

This method initializes the argument parser with the module's name and description, and defines all the necessary arguments that the module accepts. It then parses the provided command line arguments based on these definitions.

Args: arguments (str): A string of command line arguments.

Returns: ModuleArgumentParser.Namespace | None: The parsed arguments as a Namespace object if successful, None if there are no arguments or help is requested.

def run(self, arguments):
214    def run(self, arguments):
215        """
216        This function recursively searches for files in a directory hierarchy and prints the results based on specified criteria.
217
218        Args:
219            base_dir (str): The base directory to start the search from.
220            paths (list): List of paths to search within the base directory.
221            depth (int): The current depth level in the directory hierarchy.
222
223        Returns:
224            None
225        """
226
227        self.options = self.parseArgs(arguments=arguments)
228
229        if self.options is not None:
230            # Entrypoint
231            try:
232                next_directories_to_explore = []
233                for path in list(set(self.options.paths)):
234                    next_directories_to_explore.append(ntpath.normpath(path) + ntpath.sep)
235                next_directories_to_explore = sorted(list(set(next_directories_to_explore)))
236                
237                depth = 0
238                
239                while len(next_directories_to_explore) != 0:
240                    next_directories_to_explore = self.__recurse_action(
241                        paths=next_directories_to_explore,
242                        depth=depth
243                    )
244                    depth = depth + 1
245
246            except (BrokenPipeError, KeyboardInterrupt) as e:
247                print("[!] Interrupted.")
248                self.smbSession.close_smb_session()
249                self.smbSession.init_smb_session()

This function recursively searches for files in a directory hierarchy and prints the results based on specified criteria.

Args: base_dir (str): The base directory to start the search from. paths (list): List of paths to search within the base directory. depth (int): The current depth level in the directory hierarchy.

Returns: None