Skip to content

Commit 9829b08

Browse files
authored
Merge pull request Pennyw0rth#463 from Dfte/SMB]-Add-the-recyclebin-module
Add option to --spider and add recyclebin.py module
2 parents 4f028ea + 0391e81 commit 9829b08

6 files changed

Lines changed: 144 additions & 36 deletions

File tree

nxc/modules/get_netconnections.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class NXCModule:
1717
multiple_hosts = True
1818

1919
def options(self, context, module_options):
20-
"""No options"""
20+
"""No options available"""
2121

2222
def on_admin_login(self, context, connection):
2323
data = []

nxc/modules/recyclebin.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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()

nxc/modules/veeam.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def __init__(self):
2525
self.psScriptPostgresql = psFile.read()
2626

2727
def options(self, context, module_options):
28-
"""No options"""
28+
"""No options available"""
2929

3030
def checkVeeamInstalled(self, context, connection):
3131
context.log.display("Looking for Veeam installation...")

nxc/protocols/smb.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,7 @@ def spider(
13761376
depth=None,
13771377
content=False,
13781378
only_files=True,
1379+
silent=True
13791380
):
13801381
if exclude_dirs is None:
13811382
exclude_dirs = []
@@ -1384,8 +1385,8 @@ def spider(
13841385
if pattern is None:
13851386
pattern = []
13861387
spider = SMBSpider(self.conn, self.logger)
1387-
1388-
self.logger.display("Started spidering")
1388+
if not silent:
1389+
self.logger.display("Started spidering")
13891390
start_time = time()
13901391
if not share:
13911392
spider.spider(
@@ -1397,11 +1398,12 @@ def spider(
13971398
self.args.depth,
13981399
self.args.content,
13991400
self.args.only_files,
1401+
self.args.silent
14001402
)
14011403
else:
1402-
spider.spider(share, folder, pattern, regex, exclude_dirs, depth, content, only_files)
1403-
1404-
self.logger.display(f"Done spidering (Completed in {time() - start_time})")
1404+
spider.spider(share, folder, pattern, regex, exclude_dirs, depth, content, only_files, silent)
1405+
if not silent:
1406+
self.logger.display(f"Done spidering (Completed in {time() - start_time})")
14051407

14061408
return spider.results
14071409

nxc/protocols/smb/proto_args.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ def proto_args(parser, parents):
6767
spidering_group.add_argument("--exclude-dirs", type=str, metavar="DIR_LIST", default="", help="directories to exclude from spidering")
6868
spidering_group.add_argument("--depth", type=int, help="max spider recursion depth")
6969
spidering_group.add_argument("--only-files", action="store_true", help="only spider files")
70+
spidering_group.add_argument("--silent", action="store_true", help="Do not print found files/directories", default=False)
7071
segroup = spidering_group.add_mutually_exclusive_group()
7172
segroup.add_argument("--pattern", nargs="+", help="pattern(s) to search for in folders, filenames and file content")
7273
segroup.add_argument("--regex", nargs="+", help="regex(s) to search for in folders, filenames and file content")

nxc/protocols/smb/smbspider.py

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def __init__(self, smbconnection, logger):
1919
self.onlyfiles = True
2020
self.content = False
2121
self.results = []
22+
self.silent = False
2223

2324
def spider(
2425
self,
@@ -30,6 +31,7 @@ def spider(
3031
depth=None,
3132
content=False,
3233
onlyfiles=True,
34+
silent=False
3335
):
3436
if exclude_dirs is None:
3537
exclude_dirs = []
@@ -48,6 +50,7 @@ def spider(
4850
self.exclude_dirs = exclude_dirs
4951
self.content = content
5052
self.onlyfiles = onlyfiles
53+
self.silent = silent
5154

5255
if share == "*":
5356
self.logger.display("Enumerating shares for spidering")
@@ -66,7 +69,8 @@ def spider(
6669
self.logger.fail(f"Error enumerating shares: {e}")
6770
else:
6871
self.share = share
69-
self.logger.display(f"Spidering {folder}")
72+
if not self.silent:
73+
self.logger.display(f"Spidering {folder}")
7074
self._spider(folder, depth)
7175

7276
return self.results
@@ -114,9 +118,9 @@ def dir_list(self, files, path):
114118
if self.pattern:
115119
for pattern in self.pattern:
116120
if bytes(result.get_longname().lower(), "utf8").find(bytes(pattern.lower(), "utf8")) != -1:
117-
if not self.onlyfiles and result.is_directory():
121+
if not self.onlyfiles and result.is_directory() and not self.silent:
118122
self.logger.highlight(f"//{self.smbconnection.getRemoteHost()}/{self.share}/{path}{result.get_longname()} [dir]")
119-
else:
123+
elif not self.silent:
120124
self.logger.highlight(
121125
"//{}/{}/{}{} [lastm:'{}' size:{}]".format(
122126
self.smbconnection.getRemoteHost(),
@@ -131,9 +135,9 @@ def dir_list(self, files, path):
131135
if self.regex:
132136
for regex in self.regex:
133137
if regex.findall(bytes(result.get_longname(), "utf8")):
134-
if not self.onlyfiles and result.is_directory():
138+
if not self.onlyfiles and result.is_directory() and not self.silent:
135139
self.logger.highlight(f"//{self.smbconnection.getRemoteHost()}/{self.share}/{path}{result.get_longname()} [dir]")
136-
else:
140+
elif not self.silent:
137141
self.logger.highlight(
138142
"//{}/{}/{}{} [lastm:'{}' size:{}]".format(
139143
self.smbconnection.getRemoteHost(),
@@ -149,7 +153,6 @@ def dir_list(self, files, path):
149153
if self.content and not result.is_directory():
150154
self.search_content(path, result)
151155

152-
153156
def search_content(self, path, result):
154157
path = path.replace("*", "")
155158
try:
@@ -176,34 +179,36 @@ def search_content(self, path, result):
176179
if self.pattern:
177180
for pattern in self.pattern:
178181
if contents.lower().find(bytes(pattern.lower(), "utf8")) != -1:
179-
self.logger.highlight(
180-
"//{}/{}/{}{} [lastm:'{}' size:{} offset:{} pattern:'{}']".format(
181-
self.smbconnection.getRemoteHost(),
182-
self.share,
183-
path,
184-
result.get_longname(),
185-
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
186-
result.get_filesize(),
187-
rfile.tell(),
188-
pattern,
182+
if not self.silent:
183+
self.logger.highlight(
184+
"//{}/{}/{}{} [lastm:'{}' size:{} offset:{} pattern:'{}']".format(
185+
self.smbconnection.getRemoteHost(),
186+
self.share,
187+
path,
188+
result.get_longname(),
189+
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
190+
result.get_filesize(),
191+
rfile.tell(),
192+
pattern,
193+
)
189194
)
190-
)
191195
self.results.append(f"{path}{result.get_longname()}")
192196
if self.regex:
193197
for regex in self.regex:
194198
if regex.findall(contents):
195-
self.logger.highlight(
196-
"//{}/{}/{}{} [lastm:'{}' size:{} offset:{} regex:'{}']".format(
197-
self.smbconnection.getRemoteHost(),
198-
self.share,
199-
path,
200-
result.get_longname(),
201-
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
202-
result.get_filesize(),
203-
rfile.tell(),
204-
regex.pattern,
199+
if not self.silent:
200+
self.logger.highlight(
201+
"//{}/{}/{}{} [lastm:'{}' size:{} offset:{} regex:'{}']".format(
202+
self.smbconnection.getRemoteHost(),
203+
self.share,
204+
path,
205+
result.get_longname(),
206+
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
207+
result.get_filesize(),
208+
rfile.tell(),
209+
regex.pattern,
210+
)
205211
)
206-
)
207212
self.results.append(f"{path}{result.get_longname()}")
208213

209214
rfile.close()
@@ -219,4 +224,3 @@ def search_content(self, path, result):
219224
def get_lastm_time(self, result_obj):
220225
with contextlib.suppress(Exception):
221226
return strftime("%Y-%m-%d %H:%M", localtime(result_obj.get_mtime_epoch()))
222-

0 commit comments

Comments
 (0)