Skip to content

Commit e7138b6

Browse files
committed
Add extraction of scheduled tasks
1 parent 9a11512 commit e7138b6

1 file changed

Lines changed: 75 additions & 11 deletions

File tree

nxc/modules/presence.py

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from impacket.dcerpc.v5 import samr, transport
1+
import re
2+
from impacket.dcerpc.v5 import samr, tsch, transport
23
from impacket.dcerpc.v5 import tsts as TSTS
3-
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
4+
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
45
from contextlib import suppress
56
import traceback
67

@@ -27,9 +28,10 @@ def on_admin_login(self, context, connection):
2728
context.log.fail("No admin users found.")
2829
return
2930

30-
# Update user objects to check if they are in tasklist or users directory
31+
# Update user objects to check if they are in tasklist, users directory or in scheduled tasks
3132
self.check_users_directory(context, connection, admin_users)
3233
self.check_tasklist(context, connection, admin_users)
34+
self.check_scheduled_tasks(context, connection, admin_users)
3335

3436
# print grouped/logged results nicely
3537
self.print_grouped_results(context, admin_users)
@@ -62,9 +64,9 @@ def enumerate_admin_users(self, context, connection):
6264
server_handle = samr.hSamrConnect2(dce)["ServerHandle"]
6365
domain = samr.hSamrEnumerateDomainsInSamServer(dce, server_handle)["Buffer"]["Buffer"][0]["Name"]
6466
resp = samr.hSamrLookupDomainInSamServer(dce, server_handle, domain)
65-
domain_sid = resp["DomainId"].formatCanonical()
67+
self.domain_sid = resp["DomainId"].formatCanonical()
6668
domain_handle = samr.hSamrOpenDomain(dce, server_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_LIST_ACCOUNTS, resp["DomainId"])["DomainHandle"]
67-
context.log.debug(f"Resolved domain SID for {domain}: {domain_sid}")
69+
context.log.debug(f"Resolved domain SID for {domain}: {self.domain_sid}")
6870
except Exception as e:
6971
context.log.fail(f"Failed to open domain {domain}: {e!s}")
7072
context.log.debug(traceback.format_exc())
@@ -89,11 +91,11 @@ def enumerate_admin_users(self, context, connection):
8991
username = samr.hSamrQueryInformationUser2(dce, user_handle, samr.USER_INFORMATION_CLASS.UserAllInformation)["Buffer"]["All"]["UserName"]
9092

9193
# If user already exists, append group name
92-
if any(u["sid"] == f"{domain_sid}-{rid}" for u in admin_users):
93-
user = next(u for u in admin_users if u["sid"] == f"{domain_sid}-{rid}")
94+
if any(u["sid"] == f"{self.domain_sid}-{rid}" for u in admin_users):
95+
user = next(u for u in admin_users if u["sid"] == f"{self.domain_sid}-{rid}")
9496
user["group"].append(group_name)
9597
else:
96-
admin_users.append({"username": username, "sid": f"{domain_sid}-{rid}", "domain": domain, "group": [group_name], "in_tasks": False, "in_directory": False})
98+
admin_users.append({"username": username, "sid": f"{self.domain_sid}-{rid}", "domain": domain, "group": [group_name], "in_tasks": False, "in_directory": False, "in_scheduled_tasks": False})
9799
context.log.debug(f"Found user: {username} with RID {rid} in group {group_name}")
98100
except Exception as e:
99101
context.log.debug(f"Failed to get user info for RID {rid}: {e!s}")
@@ -155,23 +157,85 @@ def check_tasklist(self, context, connection, admin_users):
155157
user["in_tasks"] = True
156158
context.log.info(f"Matched process {process['ImageName']} with user {user['username']}")
157159

160+
def check_scheduled_tasks(self, context, connection, admin_users):
161+
"""Checks scheduled tasks over rpc."""
162+
target = connection.hostname if connection.kerberos else connection.host
163+
stringbinding = f"ncacn_np:{target}[\\pipe\\atsvc]"
164+
rpctransport = transport.DCERPCTransportFactory(stringbinding)
165+
rpctransport.setRemoteHost(connection.hostname)
166+
rpctransport.set_credentials(
167+
connection.username,
168+
connection.password,
169+
connection.domain,
170+
connection.lmhash,
171+
connection.nthash,
172+
aesKey=connection.aesKey,
173+
)
174+
rpctransport.set_kerberos(connection.kerberos, connection.kdcHost)
175+
176+
try:
177+
dce = rpctransport.get_dce_rpc()
178+
if connection.kerberos:
179+
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
180+
dce.set_credentials(*rpctransport.get_credentials())
181+
dce.connect()
182+
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
183+
dce.bind(tsch.MSRPC_UUID_TSCHS)
184+
185+
# Also extract non admins where we can get the password
186+
self.non_admin_sids = []
187+
188+
tasks = tsch.hSchRpcEnumTasks(dce, "\\")["pNames"]
189+
for task in tasks:
190+
xml = tsch.hSchRpcRetrieveTask(dce, task["Data"])["pXml"]
191+
# Extract SID and LogonType from the XML, if LogonType is "Password" we should be able to extract the password
192+
sid = re.search(fr"<UserId>({self.domain_sid}-\d{{3,}})</UserId>", xml)
193+
logon_type = re.search(r"<LogonType>(\w+)</LogonType>", xml)
194+
195+
# Check if SID and LogonType are found, then check if SID matches any admin user
196+
if sid and logon_type and logon_type.group(1) == "Password":
197+
context.log.debug(f"Found scheduled task '{task['Data']}' with SID {sid} and LogonType {logon_type.group(1)}")
198+
if any(user["sid"] == sid.group(1) for user in admin_users):
199+
user = next(user for user in admin_users if user["sid"] == sid.group(1))
200+
user["in_scheduled_tasks"] = True
201+
else:
202+
# If not an admin user, add to non_admin_sids for further processing
203+
self.non_admin_sids.append(sid.group(1))
204+
205+
except Exception as e:
206+
context.log.fail(f"Failed to enumerate scheduled tasks: {e}")
207+
context.log.debug(traceback.format_exc())
208+
158209
def print_grouped_results(self, context, admin_users):
159210
"""Logs all results grouped per host in order"""
160211
# Make less verbose for scanning large ranges
161212
context.log.info(f"Identified Admin Users: {', '.join([user['username'] for user in admin_users])}")
162213

214+
# Print directory users
163215
dir_users = [user for user in admin_users if user["in_directory"]]
164216
if dir_users:
165-
context.log.success("Found users in directories:")
217+
context.log.success("Found admins in directories:")
166218
for user in dir_users:
167219
context.log.highlight(f"{user['username']} ({', '.join(user['group'])})")
168220

221+
# Print tasklist users
169222
tasklist_users = [user for user in admin_users if user["in_tasks"]]
170223
if tasklist_users:
171-
context.log.success("Found users in tasklist:")
224+
context.log.success("Found admins in tasklist:")
172225
for user in tasklist_users:
173226
context.log.highlight(f"{user['username']} ({', '.join(user['group'])})")
174227

228+
# Print scheduled tasks users
229+
scheduled_tasks_users = [user for user in admin_users if user["in_scheduled_tasks"]]
230+
if scheduled_tasks_users:
231+
context.log.success("Found admins in scheduled tasks:")
232+
for user in scheduled_tasks_users:
233+
context.log.highlight(f"{user['username']} ({', '.join(user['group'])})")
234+
if self.non_admin_sids:
235+
context.log.success(f"Found {len(self.non_admin_sids)} non-admin scheduled tasks with passwords stored in dpapi:")
236+
for sid in self.non_admin_sids:
237+
context.log.highlight(sid)
238+
175239
# Making this less verbose to better scan large ranges
176240
if not dir_users and not tasklist_users:
177-
context.log.info("No matches found in users directory or tasklist.")
241+
context.log.info("No matches found in users directory, tasklist or scheduled tasks.")

0 commit comments

Comments
 (0)