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()
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.
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.
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