44from impacket .dcerpc .v5 .rpcrt import DCERPCException
55from impacket .dcerpc .v5 .rpcrt import DCERPC_v5
66from impacket .nt_errors import STATUS_MORE_ENTRIES
7+ from datetime import datetime , timedelta
78
89
910class UserSamrDump :
@@ -26,6 +27,8 @@ def __init__(self, connection):
2627 self .doKerberos = connection .kerberos
2728 self .protocols = UserSamrDump .KNOWN_PROTOCOLS .keys ()
2829 self .users = []
30+ self .rpc_transport = None
31+ self .dce = None
2932
3033 if self .hash is not None :
3134 if self .hash .find (":" ) != - 1 :
@@ -36,46 +39,37 @@ def __init__(self, connection):
3639 if self .password is None :
3740 self .password = ""
3841
39- def dump (self ):
42+ def dump (self , requested_users = None ):
4043 # Try all requested protocols until one works.
4144 for protocol in self .protocols :
4245 try :
4346 protodef = UserSamrDump .KNOWN_PROTOCOLS [protocol ]
4447 port = protodef [1 ]
4548 except KeyError :
46- self .logger .debug (f"Invalid Protocol '{ protocol } '" )
49+ self .logger .debug (f"Invalid Protocol: { protocol } " )
50+
4751 self .logger .debug (f"Trying protocol { protocol } " )
48- rpctransport = transport .SMBTransport (
49- self .addr ,
50- port ,
51- r"\samr" ,
52- self .username ,
53- self .password ,
54- self .domain ,
55- self .lmhash ,
56- self .nthash ,
57- self .aesKey ,
58- doKerberos = self .doKerberos ,
59- )
52+ self .rpc_transport = transport .SMBTransport (self .addr , port , r"\samr" , self .username , self .password , self .domain , self .lmhash , self .nthash , self .aesKey , doKerberos = self .doKerberos )
53+
6054 try :
61- self .fetchList ( rpctransport )
55+ self .fetch_users ( requested_users )
6256 break
6357 except Exception as e :
64- self .logger .debug (f"Protocol failed: { e } " )
58+ self .logger .debug (f"Connection with protocol { protocol } failed: { e } " )
6559 return self .users
6660
67- def fetchList (self , rpctransport ):
68- dce = DCERPC_v5 (rpctransport )
69- dce .connect ()
70- dce .bind (samr .MSRPC_UUID_SAMR )
61+ def fetch_users (self , requested_users ):
62+ self . dce = DCERPC_v5 (self . rpc_transport )
63+ self . dce .connect ()
64+ self . dce .bind (samr .MSRPC_UUID_SAMR )
7165
7266 # Setup Connection
73- resp = samr .hSamrConnect2 (dce )
67+ resp = samr .hSamrConnect2 (self . dce )
7468 if resp ["ErrorCode" ] != 0 :
7569 raise Exception ("Connect error" )
7670
7771 resp2 = samr .hSamrEnumerateDomainsInSamServer (
78- dce ,
72+ self . dce ,
7973 serverHandle = resp ["ServerHandle" ],
8074 enumerationContext = 0 ,
8175 preferedMaximumLength = 500 ,
@@ -84,15 +78,15 @@ def fetchList(self, rpctransport):
8478 raise Exception ("Connect error" )
8579
8680 resp3 = samr .hSamrLookupDomainInSamServer (
87- dce ,
81+ self . dce ,
8882 serverHandle = resp ["ServerHandle" ],
8983 name = resp2 ["Buffer" ]["Buffer" ][0 ]["Name" ],
9084 )
9185 if resp3 ["ErrorCode" ] != 0 :
9286 raise Exception ("Connect error" )
9387
9488 resp4 = samr .hSamrOpenDomain (
95- dce ,
89+ self . dce ,
9690 serverHandle = resp ["ServerHandle" ],
9791 desiredAccess = samr .MAXIMUM_ALLOWED ,
9892 domainId = resp3 ["DomainId" ],
@@ -101,28 +95,84 @@ def fetchList(self, rpctransport):
10195 raise Exception ("Connect error" )
10296
10397 self .__domains = resp2 ["Buffer" ]["Buffer" ]
104- domainHandle = resp4 ["DomainHandle" ]
98+ domain_handle = resp4 ["DomainHandle" ]
10599 # End Setup
106100
107- status = STATUS_MORE_ENTRIES
108- enumerationContext = 0
109- while status == STATUS_MORE_ENTRIES :
101+ if requested_users :
102+ self .logger .debug (f"Looping through users requested and looking up their information: { requested_users } " )
110103 try :
111- resp = samr .hSamrEnumerateUsersInDomain (dce , domainHandle , enumerationContext = enumerationContext )
104+ names_lookup_resp = samr .hSamrLookupNamesInDomain (self .dce , domain_handle , requested_users )
105+ rids = [r ["Data" ] for r in names_lookup_resp ["RelativeIds" ]["Element" ]]
106+ self .logger .debug (f"Specific RIDs retrieved: { rids } " )
107+ users = self .get_user_info (domain_handle , rids )
112108 except DCERPCException as e :
113- if str (e ).find ("STATUS_MORE_ENTRIES" ) < 0 :
114- self .logger .fail ("Error enumerating domain user(s)" )
115- break
116- resp = e .get_packet ()
117- self .logger .success ("Enumerated domain user(s)" )
118- for user in resp ["Buffer" ]["Buffer" ]:
119- r = samr .hSamrOpenUser (dce , domainHandle , samr .MAXIMUM_ALLOWED , user ["RelativeId" ])
120- info_user = samr .hSamrQueryInformationUser2 (dce , r ["UserHandle" ], samr .USER_INFORMATION_CLASS .UserAllInformation )["Buffer" ]["All" ]["AdminComment" ]
121- self .logger .highlight (f"{ self .domain } \\ { user ['Name' ]:<30} { info_user } " )
122- self .users .append (user ["Name" ])
123- samr .hSamrCloseHandle (dce , r ["UserHandle" ])
124-
125- enumerationContext = resp ["EnumerationContext" ]
126- status = resp ["ErrorCode" ]
127-
128- dce .disconnect ()
109+ self .logger .debug (f"Exception while requesting users in domain: { e } " )
110+ if "STATUS_SOME_NOT_MAPPED" in str (e ):
111+ # which user is not translated correctly isn't returned so we can't tell the user which is failing, which is very annoying
112+ self .logger .fail ("One of the users requested does not exist in the domain, causing a critical failure during translation, re-check the users and try again" )
113+ else :
114+ self .logger .fail (f"Error occurred when looking up users in domain: { e } " )
115+ else :
116+ status = STATUS_MORE_ENTRIES
117+ enumerationContext = 0
118+ while status == STATUS_MORE_ENTRIES :
119+ try :
120+ enumerate_users_resp = samr .hSamrEnumerateUsersInDomain (self .dce , domain_handle , enumerationContext = enumerationContext )
121+ except DCERPCException as e :
122+ if str (e ).find ("STATUS_MORE_ENTRIES" ) < 0 :
123+ self .logger .fail ("Error enumerating domain user(s)" )
124+ break
125+ enumerate_users_resp = e .get_packet ()
126+
127+ rids = [r ["RelativeId" ] for r in enumerate_users_resp ["Buffer" ]["Buffer" ]]
128+ self .logger .debug (f"Full domain RIDs retrieved: { rids } " )
129+ users = self .get_user_info (domain_handle , rids )
130+
131+ # set these for the while loop
132+ enumerationContext = enumerate_users_resp ["EnumerationContext" ]
133+ status = enumerate_users_resp ["ErrorCode" ]
134+ self .print_user_info (users )
135+ self .dce .disconnect ()
136+
137+ def get_user_info (self , domain_handle , user_ids ):
138+ self .logger .debug (f"Getting user info for users: { user_ids } " )
139+ users = []
140+
141+ for user in user_ids :
142+ self .logger .debug (f"Calling hSamrOpenUser for RID { user } " )
143+ open_user_resp = samr .hSamrOpenUser (
144+ self .dce ,
145+ domain_handle ,
146+ samr .MAXIMUM_ALLOWED ,
147+ user
148+ )
149+ info_user_resp = samr .hSamrQueryInformationUser2 (
150+ self .dce ,
151+ open_user_resp ["UserHandle" ],
152+ samr .USER_INFORMATION_CLASS .UserAllInformation
153+ )["Buffer" ]
154+
155+ user_info = info_user_resp ["All" ]
156+ user_name = user_info ["UserName" ]
157+ bad_pwd_count = user_info ["BadPasswordCount" ]
158+ user_description = user_info ["AdminComment" ]
159+ last_pw_set = old_large_int_to_datetime (user_info ["PasswordLastSet" ])
160+ if last_pw_set == "1601-01-01 00:00:00" :
161+ last_pw_set = "<never>"
162+ users .append ({"name" : user_name , "description" : user_description , "bad_pwd_count" : bad_pwd_count , "last_pw_set" : last_pw_set })
163+
164+ samr .hSamrCloseHandle (self .dce , open_user_resp ["UserHandle" ])
165+ return users
166+
167+ def print_user_info (self , users ):
168+ self .logger .highlight (f"{ '-Username-' :<30} { '-Last PW Set-' :<20} { '-BadPW-' :<8} { '-Description-' :<60} " )
169+ for user in users :
170+ self .logger .debug (f"Full user info: { user } " )
171+ self .logger .highlight (f"{ user ['name' ]:<30} { user ['last_pw_set' ]:<20} { user ['bad_pwd_count' ]:<8} { user ['description' ]} " )
172+
173+
174+ def old_large_int_to_datetime (large_int ):
175+ combined = (large_int ["HighPart" ] << 32 ) | large_int ["LowPart" ]
176+ timestamp_seconds = combined / 10 ** 7
177+ start_date = datetime (1601 , 1 , 1 )
178+ return (start_date + timedelta (seconds = timestamp_seconds )).replace (microsecond = 0 ).strftime ("%Y-%m-%d %H:%M:%S" )
0 commit comments