Skip to content

Commit bc26a1b

Browse files
authored
[SMB] Rework --spider and add recyclebin.py module
1 parent ae4317c commit bc26a1b

4 files changed

Lines changed: 169 additions & 48 deletions

File tree

nxc/modules/recyclebin.py

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

nxc/protocols/smb.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,7 @@ def spider(
13101310
depth=None,
13111311
content=False,
13121312
only_files=True,
1313+
no_print_results=True
13131314
):
13141315
if exclude_dirs is None:
13151316
exclude_dirs = []
@@ -1318,8 +1319,8 @@ def spider(
13181319
if pattern is None:
13191320
pattern = []
13201321
spider = SMBSpider(self.conn, self.logger)
1321-
1322-
self.logger.display("Started spidering")
1322+
if not no_print_results:
1323+
self.logger.display("Started spidering")
13231324
start_time = time()
13241325
if not share:
13251326
spider.spider(
@@ -1331,11 +1332,12 @@ def spider(
13311332
self.args.depth,
13321333
self.args.content,
13331334
self.args.only_files,
1335+
self.args.no_print_results
13341336
)
13351337
else:
1336-
spider.spider(share, folder, pattern, regex, exclude_dirs, depth, content, only_files)
1337-
1338-
self.logger.display(f"Done spidering (Completed in {time() - start_time})")
1338+
spider.spider(share, folder, pattern, regex, exclude_dirs, depth, content, only_files, no_print_results)
1339+
if not no_print_results:
1340+
self.logger.display(f"Done spidering (Completed in {time() - start_time})")
13391341

13401342
return spider.results
13411343

nxc/protocols/smb/proto_args.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def proto_args(parser, parents):
5959
spidering_group.add_argument("--exclude-dirs", type=str, metavar="DIR_LIST", default="", help="directories to exclude from spidering")
6060
spidering_group.add_argument("--depth", type=int, help="max spider recursion depth")
6161
spidering_group.add_argument("--only-files", action="store_true", help="only spider files")
62+
spidering_group.add_argument("--no-print-results", action="store_true", help="Do not print found files/directories", default=False)
6263
segroup = spidering_group.add_mutually_exclusive_group()
6364
segroup.add_argument("--pattern", nargs="+", help="pattern(s) to search for in folders, filenames and file content")
6465
segroup.add_argument("--regex", nargs="+", help="regex(s) to search for in folders, filenames and file content")

nxc/protocols/smb/smbspider.py

Lines changed: 53 additions & 43 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.no_print_results = False
2223

2324
def spider(
2425
self,
@@ -30,6 +31,7 @@ def spider(
3031
depth=None,
3132
content=False,
3233
onlyfiles=True,
34+
no_print_results = 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.no_print_results = no_print_results
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.no_print_results:
73+
self.logger.display(f"Spidering {folder}")
7074
self._spider(folder, depth)
7175

7276
return self.results
@@ -115,35 +119,39 @@ def dir_list(self, files, path):
115119
for pattern in self.pattern:
116120
if bytes(result.get_longname().lower(), "utf8").find(bytes(pattern.lower(), "utf8")) != -1:
117121
if not self.onlyfiles and result.is_directory():
118-
self.logger.highlight(f"//{self.smbconnection.getRemoteHost()}/{self.share}/{path}{result.get_longname()} [dir]")
122+
if not self.no_print_results:
123+
self.logger.highlight(f"//{self.smbconnection.getRemoteHost()}/{self.share}/{path}{result.get_longname()} [dir]")
119124
else:
120-
self.logger.highlight(
121-
"//{}/{}/{}{} [lastm:'{}' size:{}]".format(
122-
self.smbconnection.getRemoteHost(),
123-
self.share,
124-
path,
125-
result.get_longname(),
126-
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
127-
result.get_filesize(),
125+
if not self.no_print_results:
126+
self.logger.highlight(
127+
"//{}/{}/{}{} [lastm:'{}' size:{}]".format(
128+
self.smbconnection.getRemoteHost(),
129+
self.share,
130+
path,
131+
result.get_longname(),
132+
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
133+
result.get_filesize(),
134+
)
128135
)
129-
)
130136
self.results.append(f"{path}{result.get_longname()}")
131137
if self.regex:
132138
for regex in self.regex:
133139
if regex.findall(bytes(result.get_longname(), "utf8")):
134140
if not self.onlyfiles and result.is_directory():
135-
self.logger.highlight(f"//{self.smbconnection.getRemoteHost()}/{self.share}/{path}{result.get_longname()} [dir]")
141+
if not self.no_print_results:
142+
self.logger.highlight(f"//{self.smbconnection.getRemoteHost()}/{self.share}/{path}{result.get_longname()} [dir]")
136143
else:
137-
self.logger.highlight(
138-
"//{}/{}/{}{} [lastm:'{}' size:{}]".format(
139-
self.smbconnection.getRemoteHost(),
140-
self.share,
141-
path,
142-
result.get_longname(),
143-
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
144-
result.get_filesize(),
144+
if not self.no_print_results:
145+
self.logger.highlight(
146+
"//{}/{}/{}{} [lastm:'{}' size:{}]".format(
147+
self.smbconnection.getRemoteHost(),
148+
self.share,
149+
path,
150+
result.get_longname(),
151+
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
152+
result.get_filesize(),
153+
)
145154
)
146-
)
147155
self.results.append(f"{path}{result.get_longname()}")
148156

149157
if self.content and not result.is_directory():
@@ -176,34 +184,36 @@ def search_content(self, path, result):
176184
if self.pattern:
177185
for pattern in self.pattern:
178186
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,
187+
if not self.no_print_results:
188+
self.logger.highlight(
189+
"//{}/{}/{}{} [lastm:'{}' size:{} offset:{} pattern:'{}']".format(
190+
self.smbconnection.getRemoteHost(),
191+
self.share,
192+
path,
193+
result.get_longname(),
194+
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
195+
result.get_filesize(),
196+
rfile.tell(),
197+
pattern,
198+
)
189199
)
190-
)
191200
self.results.append(f"{path}{result.get_longname()}")
192201
if self.regex:
193202
for regex in self.regex:
194203
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,
204+
if not self.no_print_results:
205+
self.logger.highlight(
206+
"//{}/{}/{}{} [lastm:'{}' size:{} offset:{} regex:'{}']".format(
207+
self.smbconnection.getRemoteHost(),
208+
self.share,
209+
path,
210+
result.get_longname(),
211+
"n\\a" if not self.get_lastm_time(result) else self.get_lastm_time(result),
212+
result.get_filesize(),
213+
rfile.tell(),
214+
regex.pattern,
215+
)
205216
)
206-
)
207217
self.results.append(f"{path}{result.get_longname()}")
208218

209219
rfile.close()

0 commit comments

Comments
 (0)