From b72b48c92322b58eb164aa4593ff70e2c6bec309 Mon Sep 17 00:00:00 2001 From: "Remi GASCOU (Podalirius)" <79218792+p0dalirius@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:14:37 +0200 Subject: [PATCH 1/2] Module extract --- pyproject.toml | 1 + requirements.txt | 4 +- smbclientng/core/InteractiveShell.py | 4 +- smbclientng/core/LocalFileIO.py | 14 +-- smbclientng/core/SMBSession.py | 39 +++++---- smbclientng/modules/Extract.py | 126 +++++++++++++++++++++++++++ smbclientng/modules/Users.py | 3 - 7 files changed, 162 insertions(+), 29 deletions(-) create mode 100644 smbclientng/modules/Extract.py diff --git a/pyproject.toml b/pyproject.toml index b17ce42..d3c13b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ python = "^3.7" impacket = "^0.10.0" rich = "^13.0.0" charset-normalizer = "^3.3.2" +pefile = "2023.2.7" [tool.poetry.dev-dependencies] diff --git a/requirements.txt b/requirements.txt index 871cc6f..59c47b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ impacket rich -charset-normalizer \ No newline at end of file +charset-normalizer +pefile +shutil \ No newline at end of file diff --git a/smbclientng/core/InteractiveShell.py b/smbclientng/core/InteractiveShell.py index 6b71b8d..7902d5b 100644 --- a/smbclientng/core/InteractiveShell.py +++ b/smbclientng/core/InteractiveShell.py @@ -1068,8 +1068,10 @@ def __load_modules(self): module_file = import_module('smbclientng.modules.%s' % (file[:-3])) module = module_file.__getattribute__(file[:-3]) self.modules[module.name.lower()] = module - except AttributeError as e: + except AttributeError as err: pass + except ImportError as err: + self.logger.debug("Could not load module '%s': %s" % ((file[:-3]), err)) if self.config.debug: if len(self.modules.keys()) == 0: diff --git a/smbclientng/core/LocalFileIO.py b/smbclientng/core/LocalFileIO.py index 176260d..0527ccc 100644 --- a/smbclientng/core/LocalFileIO.py +++ b/smbclientng/core/LocalFileIO.py @@ -32,23 +32,23 @@ def __init__(self, mode, path=None, expected_size=None, keepRemotePath=False, lo self.logger = logger self.mode = mode # Convert remote path format to local operating system path format - self.path = path.replace(ntpath.sep, os.path.sep) + self.path = os.path.normpath(path.replace(ntpath.sep, os.path.sep)) self.dir = None - self.debug = False self.expected_size = expected_size self.keepRemotePath = keepRemotePath # Write to local (read remote) if self.mode in ["wb"]: - self.dir = '.' + os.path.sep if keepRemotePath: - self.dir += os.path.dirname(self.path) + self.dir = os.path.dirname(self.path) + else: + self.dir = '.' + os.path.sep if not os.path.exists(self.dir): - self.logger.debug("[debug] Creating local directory '%s'" % self.dir) + self.logger.debug("Creating local directory '%s'" % self.dir) os.makedirs(self.dir) - self.logger.debug("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode)) + self.logger.debug("Openning local '%s' with mode '%s'" % (self.path, self.mode)) try: self.fd = open(self.dir + os.path.sep + os.path.basename(self.path), self.mode) @@ -60,7 +60,7 @@ def __init__(self, mode, path=None, expected_size=None, keepRemotePath=False, lo if ntpath.sep in self.path: self.dir = os.path.dirname(self.path) - self.logger.debug("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode)) + self.logger.debug("Openning local '%s' with mode '%s'" % (self.path, self.mode)) try: self.fd = open(self.path, self.mode) diff --git a/smbclientng/core/SMBSession.py b/smbclientng/core/SMBSession.py index 5d67157..ff6972c 100644 --- a/smbclientng/core/SMBSession.py +++ b/smbclientng/core/SMBSession.py @@ -261,7 +261,7 @@ def recurse_action(paths=[], depth=0, callback=None): else: self.logger.error("SMBSession.find(), callback function cannot be None.") - def get_file(self, path=None, keepRemotePath=False): + def get_file(self, path=None, keepRemotePath=False, localDownloadDir="./"): """ Retrieves a file from the specified path on the SMB share. @@ -312,14 +312,14 @@ def get_file(self, path=None, keepRemotePath=False): else: try: if ntpath.sep in path: - outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname() + outputfile = localDownloadDir + os.path.sep + ntpath.dirname(path) + os.path.sep + entry.get_longname() else: - outputfile = entry.get_longname() + outputfile = localDownloadDir + os.path.sep + entry.get_longname() f = LocalFileIO( mode="wb", path=outputfile, expected_size=entry.get_filesize(), - debug=self.config.debug, + logger=self.logger, keepRemotePath=keepRemotePath ) self.smbClient.getFile( @@ -336,7 +336,7 @@ def get_file(self, path=None, keepRemotePath=False): return None - def get_file_recursively(self, path=None): + def get_file_recursively(self, path=None, localDownloadDir="./"): """ Recursively retrieves files from a specified path on the SMB share. @@ -350,16 +350,18 @@ def get_file_recursively(self, path=None): If None, it starts from the root of the configured SMB share. """ - def recurse_action(base_dir="", path=[]): + def recurse_action(base_dir="", path=[], localDownloadDir="./"): if len(base_dir) == 0: - remote_smb_path = ntpath.sep.join(path) + remote_smb_fullpath = ntpath.sep.join(path) else: - remote_smb_path = base_dir + ntpath.sep + ntpath.sep.join(path) - remote_smb_path = ntpath.normpath(remote_smb_path) + remote_smb_fullpath = base_dir + ntpath.sep + ntpath.sep.join(path) + remote_smb_fullpath = ntpath.normpath(remote_smb_fullpath) + + remote_smb_relativepath = ntpath.normpath(ntpath.sep.join(path)) entries = self.smbClient.listPath( shareName=self.smb_share, - path=remote_smb_path + '\\*' + path=remote_smb_fullpath + ntpath.sep + '*' ) if len(entries) != 0: files = [entry for entry in entries if not entry.is_directory()] @@ -367,20 +369,21 @@ def recurse_action(base_dir="", path=[]): # Files if len(files) != 0: - self.logger.print("[>] Retrieving files of '%s'" % remote_smb_path) + self.logger.print("[>] Retrieving files of '%s'" % remote_smb_relativepath) for entry_file in files: if not entry_file.is_directory(): + downloadToPath = localDownloadDir + os.path.sep + remote_smb_relativepath + os.path.sep + entry_file.get_longname() f = LocalFileIO( mode="wb", - path=remote_smb_path + ntpath.sep + entry_file.get_longname(), + path=downloadToPath, expected_size=entry_file.get_filesize(), keepRemotePath=True, - debug=self.config.debug + logger=self.logger ) try: self.smbClient.getFile( shareName=self.smb_share, - pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), + pathName=(remote_smb_fullpath + ntpath.sep + entry_file.get_longname()), callback=f.write ) f.close() @@ -396,14 +399,16 @@ def recurse_action(base_dir="", path=[]): for entry_directory in directories: if entry_directory.is_directory(): recurse_action( - base_dir=self.smb_cwd, - path=path+[entry_directory.get_longname()] + base_dir=self.smb_cwd, + path=path+[entry_directory.get_longname()], + localDownloadDir=localDownloadDir ) # Entrypoint try: recurse_action( base_dir=self.smb_cwd, - path=[path] + path=[path], + localDownloadDir=localDownloadDir ) except (BrokenPipeError, KeyboardInterrupt) as e: print("\x1b[v\x1b[o\r[!] Interrupted.") diff --git a/smbclientng/modules/Extract.py b/smbclientng/modules/Extract.py new file mode 100644 index 0000000..19af6e7 --- /dev/null +++ b/smbclientng/modules/Extract.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# File name : Extract.py +# Author : Podalirius (@podalirius_) +# Date created : 23 may 2024 + +import os +import pefile +import shutil +import tempfile +import zipfile +from smbclientng.core.Module import Module +from smbclientng.core.ModuleArgumentParser import ModuleArgumentParser + + +def pe_get_version(pathtopefile): + data = {"FileVersion": "", "ProductVersion": ""} + p = pefile.PE(pathtopefile) + data["FileVersion"] = "%d.%d.%d.%d" % ( + (p.VS_FIXEDFILEINFO[0].FileVersionMS >> 16) & 0xffff, + (p.VS_FIXEDFILEINFO[0].FileVersionMS >> 0) & 0xffff, + (p.VS_FIXEDFILEINFO[0].FileVersionLS >> 16) & 0xffff, + (p.VS_FIXEDFILEINFO[0].FileVersionLS >> 0) & 0xffff + ) + data["ProductVersion"] = "%d.%d.%d.%d" % ( + (p.VS_FIXEDFILEINFO[0].ProductVersionMS >> 16) & 0xffff, + (p.VS_FIXEDFILEINFO[0].ProductVersionMS >> 0) & 0xff, + (p.VS_FIXEDFILEINFO[0].ProductVersionLS >> 16) & 0xffff, + (p.VS_FIXEDFILEINFO[0].ProductVersionLS >> 0) & 0xffff + ) + return data + + +class Extract(Module): + + name = "extract" + description = "Extracts interesting files of a remote system." + + def parseArgs(self, arguments): + """ + 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. + """ + + parser = ModuleArgumentParser(prog=self.name, description=self.description) + + parser.add_argument("targets", metavar="target", type=str, nargs="*", default=[], help="The path(s) to the file(s) to extract.") + + parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", default=False, help="Verbose mode.") + parser.add_argument("-o", "--outputdir", dest="outputdir", default=os.getcwd(), help="Output directory.") + + self.options = self.processArguments(parser, arguments) + + if self.options is not None: + if len(self.options.targets) == 0: + parser.print_help() + self.options = None + + return self.options + + def saveSpooler(self): + files = [ + r".\spoolss.dll", + r".\spoolsv.exe", + r".\winspool.drv", + r".\en-US\spoolsv.exe.mui", + r".\en-US\winspool.drv.mui" + ] + + # Save old share + old_share = self.smbSession.smb_share + old_pwd = self.smbSession.smb_cwd + + temp_dir = tempfile.mkdtemp() + self.logger.debug("Using temporary local directory '%s'" % temp_dir) + self.smbSession.set_share('C$') + if self.smbSession.path_isdir("/Windows/System32/"): + self.smbSession.set_cwd("/Windows/System32/") + for f in files: + self.smbSession.get_file(path=f, keepRemotePath=True, localDownloadDir=temp_dir) + self.smbSession.get_file_recursively(path="spool/", localDownloadDir=temp_dir) + + # Create a zipfile of the temp_dir + pev = pe_get_version(temp_dir + os.path.sep + "spoolsv.exe") + outputfile = '%s-spooler.zip' % pev["FileVersion"] + zip_file_path = os.path.join(self.options.outputdir, outputfile) + self.logger.info("Zipping files downloaded in '%s'" % temp_dir) + with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + for root, dirs, files in os.walk(temp_dir): + for file in files: + self.logger.print(os.path.join(root, file).replace(temp_dir+os.path.sep, "├──> ", 1)) + zipf.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), temp_dir)) + self.logger.info(f"Backup saved to {zip_file_path}") + + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) + + # Restore old share + self.smbSession.set_share(old_share) + self.smbSession.set_cwd(old_pwd) + + #=[Run]==================================================================== + + def run(self, arguments): + self.options = self.parseArgs(arguments=arguments) + + if self.options is not None: + # Entrypoint + try: + for t in self.options.targets: + if t == "spooler": + self.saveSpooler() + except (BrokenPipeError, KeyboardInterrupt) as e: + print("[!] Interrupted.") + self.smbSession.close_smb_session() + self.smbSession.init_smb_session() + + + diff --git a/smbclientng/modules/Users.py b/smbclientng/modules/Users.py index 671787a..ba3a908 100644 --- a/smbclientng/modules/Users.py +++ b/smbclientng/modules/Users.py @@ -5,11 +5,8 @@ # Date created : 23 may 2024 -import ntpath -import re from smbclientng.core.Module import Module from smbclientng.core.ModuleArgumentParser import ModuleArgumentParser -from smbclientng.core.utils import windows_ls_entry class Users(Module): From b235cfac69c4fb2f32c0bf1d766e45d4aeafde87 Mon Sep 17 00:00:00 2001 From: "Remi GASCOU (Podalirius)" <79218792+p0dalirius@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:16:36 +0200 Subject: [PATCH 2/2] Removed shutil --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 59c47b5..13ddb7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ impacket rich charset-normalizer -pefile -shutil \ No newline at end of file +pefile \ No newline at end of file