1- from impacket .dcerpc .v5 import samr , transport
1+ import re
2+ from impacket .dcerpc .v5 import samr , tsch , transport
23from 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
45from contextlib import suppress
56import 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