|
35 | 35 | from impacket.dcerpc.v5.dtypes import NULL |
36 | 36 | from impacket.dcerpc.v5.dcomrt import DCOMConnection |
37 | 37 | from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, IWbemLevel1Login |
38 | | -from impacket.smb3structs import FILE_SHARE_WRITE, FILE_SHARE_DELETE, SMB2_0_IOCTL_IS_FSCTL |
| 38 | +from impacket.smb3structs import FILE_READ_DATA, FILE_WRITE_DATA, FILE_SHARE_WRITE, FILE_SHARE_DELETE, SMB2_0_IOCTL_IS_FSCTL |
39 | 39 | from impacket.dcerpc.v5 import tsts as TSTS |
40 | 40 |
|
41 | 41 | from nxc.config import process_secret, host_info_colors, check_guest_account |
@@ -1937,24 +1937,100 @@ def put_file(self): |
1937 | 1937 | for src, dest in self.args.put_file: |
1938 | 1938 | self.put_file_single(src, dest) |
1939 | 1939 |
|
1940 | | - def get_file_single(self, remote_path, download_path): |
| 1940 | + def download_file(self, share_name, remote_path, dest_file, access_mode=FILE_READ_DATA): |
| 1941 | + try: |
| 1942 | + self.logger.debug(f"Getting file from {share_name}:{remote_path} with access mode {access_mode}") |
| 1943 | + self.conn.getFile(share_name, remote_path, dest_file, shareAccessMode=access_mode) |
| 1944 | + return True |
| 1945 | + except SessionError as e: |
| 1946 | + if "STATUS_SHARING_VIOLATION" in str(e): |
| 1947 | + self.logger.debug(f"Sharing violation on {remote_path}: {e}") |
| 1948 | + else: |
| 1949 | + self.logger.debug(f"SessionError when attempting to download file {remote_path}: {e}") |
| 1950 | + return False |
| 1951 | + except Exception as e: |
| 1952 | + self.logger.debug(f"Other error when attempting to download file {remote_path}: {e}") |
| 1953 | + return False |
| 1954 | + |
| 1955 | + def get_file_single(self, remote_path, download_path, silent=False): |
1941 | 1956 | share_name = self.args.share |
1942 | | - self.logger.display(f'Copying "{remote_path}" to "{download_path}"') |
| 1957 | + if not silent: |
| 1958 | + self.logger.display(f"Copying '{remote_path}' to '{download_path}'") |
1943 | 1959 | if self.args.append_host: |
1944 | 1960 | download_path = f"{self.hostname}-{remote_path}" |
1945 | 1961 | with open(download_path, "wb+") as file: |
1946 | | - try: |
1947 | | - self.conn.getFile(share_name, remote_path, file.write) |
1948 | | - self.logger.success(f'File "{remote_path}" was downloaded to "{download_path}"') |
1949 | | - except Exception as e: |
1950 | | - self.logger.fail(f'Error writing file "{remote_path}" from share "{share_name}": {e}') |
1951 | | - if os.path.getsize(download_path) == 0: |
1952 | | - os.remove(download_path) |
| 1962 | + if self.download_file(share_name, remote_path, file.write): |
| 1963 | + if not silent: |
| 1964 | + self.logger.success(f"File '{remote_path}' was downloaded to '{download_path}'") |
| 1965 | + else: |
| 1966 | + self.logger.debug("Opening with READ alone failed, trying to open file with READ/WRITE access") |
| 1967 | + if self.download_file(share_name, remote_path, file.write, FILE_READ_DATA | FILE_WRITE_DATA): |
| 1968 | + if not silent: |
| 1969 | + self.logger.success(f"File '{remote_path}' was downloaded to '{download_path}'") |
| 1970 | + else: |
| 1971 | + if not silent: |
| 1972 | + self.logger.fail(f"Error downloading file '{remote_path}' from share '{share_name}'") |
1953 | 1973 |
|
1954 | 1974 | def get_file(self): |
1955 | 1975 | for src, dest in self.args.get_file: |
1956 | 1976 | self.get_file_single(src, dest) |
1957 | 1977 |
|
| 1978 | + def download_folder(self, folder, dest, recursive=False, silent=False, base_dir=None, ignore_empty=False): |
| 1979 | + self.logger.debug(f"Downloading folder with args: {folder}, {dest}, Recursive: {recursive}, Silent: {silent}, Base dir: {base_dir}, Ignore empty: {ignore_empty}") |
| 1980 | + normalized_folder = ntpath.normpath(folder) |
| 1981 | + base_folder = os.path.basename(normalized_folder) |
| 1982 | + self.logger.debug(f"Base folder: {base_folder}") |
| 1983 | + |
| 1984 | + try: |
| 1985 | + items = self.conn.listPath(self.args.share, ntpath.join(folder, "*")) |
| 1986 | + except SessionError as e: |
| 1987 | + self.logger.error(f"Error listing folder '{folder}': {e}") |
| 1988 | + return |
| 1989 | + self.logger.debug(f"{len(items)} items in folder: {items}") |
| 1990 | + |
| 1991 | + filtered_items = [item for item in items if item.get_longname() not in [".", ".."]] |
| 1992 | + |
| 1993 | + # create local directory structure regardless of content; download empty folders by default |
| 1994 | + # change the Windows path to Linux and then join it with the base directory to get our actual save path |
| 1995 | + relative_path = os.path.join(*folder.replace(base_dir or folder, "").lstrip("\\").split("\\")) |
| 1996 | + local_folder_path = os.path.join(dest, relative_path) |
| 1997 | + |
| 1998 | + if not filtered_items and ignore_empty: |
| 1999 | + self.logger.debug(f"Skipping empty folder '{folder}'") |
| 2000 | + return |
| 2001 | + |
| 2002 | + # create the directory for this folder |
| 2003 | + os.makedirs(local_folder_path, exist_ok=True) |
| 2004 | + if not filtered_items and not silent: |
| 2005 | + self.logger.display(f"Created empty directory '{local_folder_path}'") |
| 2006 | + |
| 2007 | + for item in filtered_items: |
| 2008 | + item_name = item.get_longname() |
| 2009 | + dir_path = ntpath.normpath(ntpath.join(normalized_folder, item_name)) |
| 2010 | + self.logger.debug(f"Parsing item: {item_name}, {dir_path}") |
| 2011 | + |
| 2012 | + if item.is_directory() and recursive: |
| 2013 | + self.logger.debug(f"Found new directory to parse: {dir_path}") |
| 2014 | + self.download_folder(dir_path, dest, recursive, silent, base_dir or folder, ignore_empty) |
| 2015 | + elif not item.is_directory(): |
| 2016 | + remote_file_path = ntpath.join(folder, item_name) |
| 2017 | + local_file_path = os.path.join(local_folder_path, item_name) |
| 2018 | + self.logger.debug(f"{dest=} {remote_file_path=} {relative_path=} {local_folder_path=} {local_file_path=}") |
| 2019 | + |
| 2020 | + try: |
| 2021 | + self.get_file_single(remote_file_path, local_file_path, silent) |
| 2022 | + except FileNotFoundError: |
| 2023 | + self.logger.fail(f"Error downloading file '{remote_file_path}' due to file not found (probably a race condition between listing and downloading)") |
| 2024 | + |
| 2025 | + def get_folder(self): |
| 2026 | + recursive = self.args.recursive |
| 2027 | + ignore_empty = getattr(self.args, "ignore_empty_folders", False) |
| 2028 | + self.logger.debug(f"Recursive option set to {recursive}") |
| 2029 | + self.logger.debug(f"Ignore empty folders option set to {ignore_empty}") |
| 2030 | + for folder, dest in self.args.get_folder: |
| 2031 | + self.download_folder(folder, dest, recursive, False, None, ignore_empty) |
| 2032 | + self.logger.success(f"Folder '{folder}' was downloaded to '{dest}'") |
| 2033 | + |
1958 | 2034 | def enable_remoteops(self, regsecret=False): |
1959 | 2035 | try: |
1960 | 2036 | if regsecret: |
|
0 commit comments