Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to --spider and add recyclebin.py module #463

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions nxc/modules/recyclebin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from io import BytesIO
from os import makedirs
from nxc.paths import NXC_PATH
from os.path import join, abspath
from impacket.dcerpc.v5 import rrp
from impacket.dcerpc.v5.rrp import DCERPCSessionError
from impacket.examples.secretsdump import RemoteOperations


class NXCModule:
# Module by @Defte_
# Dumps files from recycle bins

name = "recyclebin"
description = "Lists and exports users' recycle bins"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"]

def options(self, context, module_options):
""""""

def on_admin_login(self, context, connection):
found = 0
try:
remote_ops = RemoteOperations(connection.conn, False)
remote_ops.enableRegistry()
except DCERPCSessionError as e:
context.log.debug(f"Error connecting to RemoteRegistry {e} on host {connection.host}")
finally:
remote_ops.finish()

if remote_ops._RemoteOperations__rrp:
for sid_directory in connection.conn.listPath("C$", "$Recycle.Bin\\*"):
if sid_directory.get_longname() and sid_directory.get_longname() not in self.false_positive:

# Extracts the username from the SID
if remote_ops._RemoteOperations__rrp:
ans = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remote_ops._RemoteOperations__rrp,
reg_handle,
f"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{sid_directory.get_longname()}"
)
key_handle = ans["phkResult"]
_ = username = profileimagepath = None
try:
_, profileimagepath = rrp.hBaseRegQueryValue(
remote_ops._RemoteOperations__rrp,
key_handle,
"ProfileImagePath\x00"
)
# Get username and remove embedded null byte
username = profileimagepath.split("\\")[-1].replace("\x00", "")
except rrp.DCERPCSessionError as e:
context.log.debug(f"Couldn't get username from SID {e} on host {connection.host}")

# Lists for any file or directory in the recycle bin
spider_folder = f"$Recycle.Bin\\{sid_directory.get_longname()}\\"
paths = connection.spider(
"C$",
folder=spider_folder,
regex=[r"(.*)"],
no_print_results=True
)

false_positiv = [".", "..", "desktop.ini"]
filtered_file_paths = [path for path in paths if not path.endswith(tuple(false_positiv))]
if filtered_file_paths:
if username is not None:
context.log.highlight(f"CONTENT FOUND {sid_directory.get_longname()} ({username})")
else:
context.log.highlight(f"CONTENT FOUND {sid_directory.get_longname()}")

for path in filtered_file_paths:
# Returned path look like:
# $Recycle.Bin\S-1-5-21-4140170355-2927207985-2497279808-500\/$I87021Q.txt
# Or
# $Recycle.Bin\S-1-5-21-4140170355-2927207985-2497279808-500\/$R87021Q.txt
# $I files are metadata while $R are actual files so we split the path from the SID
# And check that the filename contains $R only to prevent downloading useless stuff

if "$R" in path.split(sid_directory.get_longname())[1] and not path.endswith(tuple([".", "..", "desktop.ini"])):
try:
buf = BytesIO()
connection.conn.getFile("C$", path, buf.write)
context.log.highlight(f"\t{path}")
found += 1
buf.seek(0)
file_path = path.split('$')[-1].replace("/", "_")
if username:
filename = f"{connection.host}_{username}_recyclebin_{file_path}"
else:
filename = f"{connection.host}_{sid_directory.get_longname()}_recyclebin_{file_path}"
export_path = join(NXC_PATH, "modules", "recyclebin")
path = abspath(join(export_path, filename))
makedirs(export_path, exist_ok=True)
try:
with open(path, "w+") as file:
file.write(buf.read().decode("utf-8", errors="ignore"))
except Exception as e:
context.log.fail(f"Failed to write recyclebin file to {filename}: {e}")
except Exception as e:
# Probably trying to getFile a directory which won't work
context.log.debug(f"Couldn't open {path} because of {e}")
if found >0:
context.log.highlight(f"Recycle bin's content downloaded to {export_path}")
12 changes: 7 additions & 5 deletions nxc/protocols/smb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,7 @@ def spider(
depth=None,
content=False,
only_files=True,
no_print_results=True
):
if exclude_dirs is None:
exclude_dirs = []
Expand All @@ -1318,8 +1319,8 @@ def spider(
if pattern is None:
pattern = []
spider = SMBSpider(self.conn, self.logger)

self.logger.display("Started spidering")
if not no_print_results:
self.logger.display("Started spidering")
start_time = time()
if not share:
spider.spider(
Expand All @@ -1331,11 +1332,12 @@ def spider(
self.args.depth,
self.args.content,
self.args.only_files,
self.args.no_print_results
)
else:
spider.spider(share, folder, pattern, regex, exclude_dirs, depth, content, only_files)

self.logger.display(f"Done spidering (Completed in {time() - start_time})")
spider.spider(share, folder, pattern, regex, exclude_dirs, depth, content, only_files, no_print_results)
if not no_print_results:
self.logger.display(f"Done spidering (Completed in {time() - start_time})")

return spider.results

Expand Down
1 change: 1 addition & 0 deletions nxc/protocols/smb/proto_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def proto_args(parser, parents):
spidering_group.add_argument("--exclude-dirs", type=str, metavar="DIR_LIST", default="", help="directories to exclude from spidering")
spidering_group.add_argument("--depth", type=int, help="max spider recursion depth")
spidering_group.add_argument("--only-files", action="store_true", help="only spider files")
spidering_group.add_argument("--no-print-results", action="store_true", help="Do not print found files/directories", default=False)
segroup = spidering_group.add_mutually_exclusive_group()
segroup.add_argument("--pattern", nargs="+", help="pattern(s) to search for in folders, filenames and file content")
segroup.add_argument("--regex", nargs="+", help="regex(s) to search for in folders, filenames and file content")
Expand Down
96 changes: 53 additions & 43 deletions nxc/protocols/smb/smbspider.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def __init__(self, smbconnection, logger):
self.onlyfiles = True
self.content = False
self.results = []
self.no_print_results = False

def spider(
self,
Expand All @@ -30,6 +31,7 @@ def spider(
depth=None,
content=False,
onlyfiles=True,
no_print_results = False
):
if exclude_dirs is None:
exclude_dirs = []
Expand All @@ -48,6 +50,7 @@ def spider(
self.exclude_dirs = exclude_dirs
self.content = content
self.onlyfiles = onlyfiles
self.no_print_results = no_print_results

if share == "*":
self.logger.display("Enumerating shares for spidering")
Expand All @@ -66,7 +69,8 @@ def spider(
self.logger.fail(f"Error enumerating shares: {e}")
else:
self.share = share
self.logger.display(f"Spidering {folder}")
if not self.no_print_results:
self.logger.display(f"Spidering {folder}")
self._spider(folder, depth)

return self.results
Expand Down Expand Up @@ -115,35 +119,39 @@ def dir_list(self, files, path):
for pattern in self.pattern:
if bytes(result.get_longname().lower(), "utf8").find(bytes(pattern.lower(), "utf8")) != -1:
if not self.onlyfiles and result.is_directory():
self.logger.highlight(f"//{self.smbconnection.getRemoteHost()}/{self.share}/{path}{result.get_longname()} [dir]")
if not self.no_print_results:
self.logger.highlight(f"//{self.smbconnection.getRemoteHost()}/{self.share}/{path}{result.get_longname()} [dir]")
else:
self.logger.highlight(
"//{}/{}/{}{} [lastm:'{}' size:{}]".format(
self.smbconnection.getRemoteHost(),
self.share,
path,
result.get_longname(),
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
result.get_filesize(),
if not self.no_print_results:
self.logger.highlight(
"//{}/{}/{}{} [lastm:'{}' size:{}]".format(
self.smbconnection.getRemoteHost(),
self.share,
path,
result.get_longname(),
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
result.get_filesize(),
)
)
)
self.results.append(f"{path}{result.get_longname()}")
if self.regex:
for regex in self.regex:
if regex.findall(bytes(result.get_longname(), "utf8")):
if not self.onlyfiles and result.is_directory():
self.logger.highlight(f"//{self.smbconnection.getRemoteHost()}/{self.share}/{path}{result.get_longname()} [dir]")
if not self.no_print_results:
self.logger.highlight(f"//{self.smbconnection.getRemoteHost()}/{self.share}/{path}{result.get_longname()} [dir]")
else:
self.logger.highlight(
"//{}/{}/{}{} [lastm:'{}' size:{}]".format(
self.smbconnection.getRemoteHost(),
self.share,
path,
result.get_longname(),
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
result.get_filesize(),
if not self.no_print_results:
self.logger.highlight(
"//{}/{}/{}{} [lastm:'{}' size:{}]".format(
self.smbconnection.getRemoteHost(),
self.share,
path,
result.get_longname(),
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
result.get_filesize(),
)
)
)
self.results.append(f"{path}{result.get_longname()}")

if self.content and not result.is_directory():
Expand Down Expand Up @@ -176,34 +184,36 @@ def search_content(self, path, result):
if self.pattern:
for pattern in self.pattern:
if contents.lower().find(bytes(pattern.lower(), "utf8")) != -1:
self.logger.highlight(
"//{}/{}/{}{} [lastm:'{}' size:{} offset:{} pattern:'{}']".format(
self.smbconnection.getRemoteHost(),
self.share,
path,
result.get_longname(),
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
result.get_filesize(),
rfile.tell(),
pattern,
if not self.no_print_results:
self.logger.highlight(
"//{}/{}/{}{} [lastm:'{}' size:{} offset:{} pattern:'{}']".format(
self.smbconnection.getRemoteHost(),
self.share,
path,
result.get_longname(),
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
result.get_filesize(),
rfile.tell(),
pattern,
)
)
)
self.results.append(f"{path}{result.get_longname()}")
if self.regex:
for regex in self.regex:
if regex.findall(contents):
self.logger.highlight(
"//{}/{}/{}{} [lastm:'{}' size:{} offset:{} regex:'{}']".format(
self.smbconnection.getRemoteHost(),
self.share,
path,
result.get_longname(),
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
result.get_filesize(),
rfile.tell(),
regex.pattern,
if not self.no_print_results:
self.logger.highlight(
"//{}/{}/{}{} [lastm:'{}' size:{} offset:{} regex:'{}']".format(
self.smbconnection.getRemoteHost(),
self.share,
path,
result.get_longname(),
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
result.get_filesize(),
rfile.tell(),
regex.pattern,
)
)
)
self.results.append(f"{path}{result.get_longname()}")

rfile.close()
Expand Down