diff --git a/pypykatz/_version.py b/pypykatz/_version.py index c8a06cf..2fda288 100644 --- a/pypykatz/_version.py +++ b/pypykatz/_version.py @@ -1,5 +1,5 @@ -__version__ = "0.6.5" +__version__ = "0.6.6" __banner__ = \ """ # pypyKatz %s diff --git a/pypykatz/alsadecryptor/lsa_decryptor_nt5.py b/pypykatz/alsadecryptor/lsa_decryptor_nt5.py index 8a8a299..7071cf9 100644 --- a/pypykatz/alsadecryptor/lsa_decryptor_nt5.py +++ b/pypykatz/alsadecryptor/lsa_decryptor_nt5.py @@ -16,12 +16,11 @@ def __init__(self, reader, decryptor_template, sysinfo): self.feedback_offset = None self.des_key = None self.random_key = None - self.acquire_crypto_material() - def acquire_crypto_material(self): + async def acquire_crypto_material(self): self.log('Acquireing crypto stuff...') - sigpos = self.find_signature() - self.reader.move(sigpos) + sigpos = await self.find_signature() + await self.reader.move(sigpos) #data = self.reader.peek(0x50) #self.log('Memory looks like this around the signature\n%s' % hexdump(data, start = sigpos)) @@ -29,63 +28,63 @@ def acquire_crypto_material(self): self.feedback_offset = x try: - self.feedback = self.get_feedback(sigpos) + self.feedback = await self.get_feedback(sigpos) #self.log('Feedback bytes:\n%s' % hexdump(self.feedback, start = 0)) - self.des_key = self.get_key(sigpos) - self.random_key = self.get_random(sigpos) + self.des_key = await self.get_key(sigpos) + self.random_key = await self.get_random(sigpos) #self.log('randomkey bytes:\n%s' % hexdump(self.random_key, start = 0)) except: import traceback traceback.print_exc() - input() + #input() else: break - def get_feedback(self, sigpos): + async def get_feedback(self, sigpos): if self.decryptor_template.arch == 'x86': - new_ptr = self.reader.get_ptr_with_offset(sigpos + self.feedback_offset) - self.reader.move(new_ptr) - return self.reader.read(8) + new_ptr = await self.reader.get_ptr_with_offset(sigpos + self.feedback_offset) + await self.reader.move(new_ptr) + return await self.reader.read(8) else: - self.reader.move(sigpos + self.feedback_offset) - offset = LONG(self.reader).value + await self.reader.move(sigpos + self.feedback_offset) + offset = await LONG.loadvalue(self.reader) newpos = sigpos + self.feedback_offset + 4 + offset - self.reader.move(newpos) - return self.reader.read(8) + await self.reader.move(newpos) + return await self.reader.read(8) - def get_key(self, sigpos): + async def get_key(self, sigpos): if self.decryptor_template.arch == 'x86': - new_ptr = self.reader.get_ptr_with_offset(sigpos + self.decryptor_template.desx_key_ptr_offset) - self.reader.move(new_ptr) - des_key_ptr = self.decryptor_template.key_struct_ptr(self.reader) - des_key = des_key_ptr.read(self.reader) + new_ptr = await self.reader.get_ptr_with_offset(sigpos + self.decryptor_template.desx_key_ptr_offset) + await self.reader.move(new_ptr) + des_key_ptr = await self.decryptor_template.key_struct_ptr.load(self.reader) + des_key = await des_key_ptr.read(self.reader) else: - self.reader.move(sigpos + self.decryptor_template.desx_key_ptr_offset) - offset = LONG(self.reader).value + await self.reader.move(sigpos + self.decryptor_template.desx_key_ptr_offset) + offset = await LONG.loadvalue(self.reader) newpos = sigpos + self.decryptor_template.desx_key_ptr_offset + 4 + offset - self.reader.move(newpos) - des_key_ptr = self.decryptor_template.key_struct_ptr(self.reader) - des_key = des_key_ptr.read(self.reader) + await self.reader.move(newpos) + des_key_ptr = await self.decryptor_template.key_struct_ptr.load(self.reader) + des_key = await des_key_ptr.read(self.reader) return des_key - def get_random(self, sigpos): + async def get_random(self, sigpos): if self.decryptor_template.arch == 'x86': - random_key_ptr = self.reader.get_ptr_with_offset(sigpos + self.decryptor_template.randomkey_ptr_offset) - random_key_ptr = self.reader.get_ptr_with_offset(random_key_ptr) - self.reader.move(random_key_ptr) + random_key_ptr = await self.reader.get_ptr_with_offset(sigpos + self.decryptor_template.randomkey_ptr_offset) + random_key_ptr = await self.reader.get_ptr_with_offset(random_key_ptr) + await self.reader.move(random_key_ptr) else: - self.reader.move(sigpos + self.decryptor_template.randomkey_ptr_offset) - offset = LONG(self.reader).value + await self.reader.move(sigpos + self.decryptor_template.randomkey_ptr_offset) + offset = await LONG.loadvalue(self.reader) newpos = sigpos + self.decryptor_template.desx_key_ptr_offset + 4 + offset - self.reader.move(newpos) + await self.reader.move(newpos) - return self.reader.read(256) + return await self.reader.read(256) - def find_signature(self): + async def find_signature(self): self.log('Looking for main struct signature in memory...') - fl = self.reader.find_in_module('lsasrv.dll', self.decryptor_template.signature) + fl = await self.reader.find_in_module('lsasrv.dll', self.decryptor_template.signature) if len(fl) == 0: self.logger.log('signature not found! %s' % self.decryptor_template.signature.hex()) raise Exception('LSA signature not found!') diff --git a/pypykatz/alsadecryptor/lsa_template_nt5.py b/pypykatz/alsadecryptor/lsa_template_nt5.py index d5074f3..f7f6fdb 100644 --- a/pypykatz/alsadecryptor/lsa_template_nt5.py +++ b/pypykatz/alsadecryptor/lsa_template_nt5.py @@ -4,7 +4,7 @@ # Tamas Jos (@skelsec) # -from pypykatz.alsadecryptor.win_datatypes import POINTER +from pypykatz.alsadecryptor.win_datatypes import ULONG, PVOID, POINTER from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild from pypykatz.alsadecryptor.package_commons import PackageTemplate @@ -37,12 +37,19 @@ def get_template(sysinfo): raise Exception('NT 6 is in another castle!') class SYMCRYPT_NT5_DES_EXPANDED_KEY: - def __init__(self, reader): + def __init__(self): self.roundKey = [] + + @staticmethod + async def load(reader): + s = SYMCRYPT_NT5_DES_EXPANDED_KEY() for _ in range(16): - r = int.from_bytes(reader.read(4), 'little', signed = False) - l = int.from_bytes(reader.read(4), 'little', signed = False) - self.roundKey.append([r, l]) + x = await reader.read(4) + r = int.from_bytes(x, 'little', signed = False) + x = await reader.read(4) + l = int.from_bytes(x, 'little', signed = False) + s.roundKey.append([r, l]) + return s def __str__(self): t = 'SYMCRYPT_NT5_DES_EXPANDED_KEY\r\n' @@ -51,10 +58,18 @@ def __str__(self): return t class SYMCRYPT_NT5_DESX_EXPANDED_KEY: - def __init__(self, reader): - self.inputWhitening = reader.read(8) - self.outputWhitening = reader.read(8) - self.desKey = SYMCRYPT_NT5_DES_EXPANDED_KEY(reader) + def __init__(self): + self.inputWhitening = None + self.outputWhitening = None + self.desKey = None + + @staticmethod + async def load(reader): + s = SYMCRYPT_NT5_DESX_EXPANDED_KEY() + s.inputWhitening = await reader.read(8) + s.outputWhitening = await reader.read(8) + s.desKey = await SYMCRYPT_NT5_DES_EXPANDED_KEY.load(reader) + return s def __str__(self): t = 'SYMCRYPT_NT5_DESX_EXPANDED_KEY\r\n' @@ -64,8 +79,16 @@ def __str__(self): return t class PSYMCRYPT_NT5_DESX_EXPANDED_KEY(POINTER): - def __init__(self, reader): - super().__init__(reader, SYMCRYPT_NT5_DESX_EXPANDED_KEY) + def __init__(self): + super().__init__() + + @staticmethod + async def load(reader): + p = PVOID() + p.location = reader.tell() + p.value = await reader.read_uint() + p.finaltype = SYMCRYPT_NT5_DESX_EXPANDED_KEY + return p class LSA_x64_nt5_1(LsaTemplate_NT5): def __init__(self): diff --git a/pypykatz/alsadecryptor/lsa_template_nt6.py b/pypykatz/alsadecryptor/lsa_template_nt6.py index ae89829..2cccbca 100644 --- a/pypykatz/alsadecryptor/lsa_template_nt6.py +++ b/pypykatz/alsadecryptor/lsa_template_nt6.py @@ -408,7 +408,8 @@ def __init__(self): self.key_pattern = LSADecyptorKeyPattern() self.key_pattern.signature = b'\x83\x64\x24\x30\x00\x48\x8d\x45\xe0\x44\x8b\x4d\xd8\x48\x8d\x15' self.key_pattern.IV_length = 16 - self.key_pattern.offset_to_IV_ptr = 71 + #self.key_pattern.offset_to_IV_ptr = 71 + self.key_pattern.offset_to_IV_ptr = 58 self.key_pattern.offset_to_DES_key_ptr = -89 self.key_pattern.offset_to_AES_key_ptr = 16 diff --git a/pypykatz/alsadecryptor/packages/kerberos/decryptor.py b/pypykatz/alsadecryptor/packages/kerberos/decryptor.py index 55339e2..f38f8f7 100644 --- a/pypykatz/alsadecryptor/packages/kerberos/decryptor.py +++ b/pypykatz/alsadecryptor/packages/kerberos/decryptor.py @@ -8,6 +8,7 @@ from pypykatz.alsadecryptor.package_commons import PackageDecryptor from pypykatz.alsadecryptor.win_datatypes import PLIST_ENTRY, PRTL_AVL_TABLE from pypykatz.commons.common import WindowsMinBuild +from pypykatz.commons.common import hexdump class KerberosCredential: def __init__(self): @@ -97,6 +98,8 @@ async def start(self): return if self.sysinfo.buildnumber < WindowsMinBuild.WIN_VISTA.value: + #TODO: fix this + return await self.reader.move(entry_ptr_loc) entry_ptr = await PLIST_ENTRY.load(self.reader) await self.walk_list(entry_ptr, self.process_session_elist) @@ -114,6 +117,8 @@ async def start(self): await self.process_session(kerberos_logon_session) async def process_session_elist(self, elist): + #TODO: fix this + return await self.reader.move(elist.location) await self.reader.read_uint() #Flink do not remove this line! await self.reader.read_uint() #Blink do not remove this line! diff --git a/pypykatz/alsadecryptor/packages/msv/decryptor.py b/pypykatz/alsadecryptor/packages/msv/decryptor.py index d3d418b..f413d82 100644 --- a/pypykatz/alsadecryptor/packages/msv/decryptor.py +++ b/pypykatz/alsadecryptor/packages/msv/decryptor.py @@ -6,7 +6,7 @@ import io import json import base64 -from pypykatz.commons.common import WindowsMinBuild, KatzSystemArchitecture, AGenericReader, UniversalEncoder, hexdump +from pypykatz.commons.common import WindowsBuild, WindowsMinBuild, KatzSystemArchitecture, AGenericReader, UniversalEncoder, hexdump from pypykatz.commons.filetime import filetime_to_dt from pypykatz.alsadecryptor.packages.msv.templates import MSV1_0_PRIMARY_CREDENTIAL_STRANGE_DEC from pypykatz.alsadecryptor.packages.credman.templates import KIWI_CREDMAN_LIST_STARTER, KIWI_CREDMAN_SET_LIST_ENTRY @@ -32,7 +32,7 @@ def to_dict(self): t['LMHash'] = self.LMHash t['SHAHash'] = self.SHAHash t['DPAPI'] = self.DPAPI - t['isoProt'] = self.isoProt + #t['isoProt'] = self.isoProt # removed this because it's not implemented fully and messes up the testcases return t def to_json(self): @@ -288,15 +288,14 @@ def __init__(self, reader, decryptor_template, lsa_decryptor, credman_template, async def find_first_entry(self): #finding signature position = await self.find_signature('lsasrv.dll',self.decryptor_template.signature) - #getting logon session count - if self.sysinfo.architecture == KatzSystemArchitecture.X64 and self.sysinfo.buildnumber > WindowsMinBuild.WIN_BLUE.value: - ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.offset2) - await self.reader.move(ptr_entry_loc) - t = await self.reader.read(1) - self.logon_session_count = int.from_bytes(t, byteorder = 'big', signed = False) - else: - self.logon_session_count = 1 + self.logon_session_count = 1 + if self.sysinfo.architecture == KatzSystemArchitecture.X64: + if self.sysinfo.buildnumber >= WindowsBuild.WIN_8.value or (WindowsMinBuild.WIN_8.value <= self.sysinfo.buildnumber < WindowsMinBuild.WIN_BLUE.value and self.sysinfo.msv_dll_timestamp > 0x60000000): + ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.offset2) + await self.reader.move(ptr_entry_loc) + t = await self.reader.read(1) + self.logon_session_count = int.from_bytes(t, byteorder = 'big', signed = False) #getting logon session ptr ptr_entry_loc = await self.reader.get_ptr_with_offset(position + self.decryptor_template.first_entry_offset) diff --git a/pypykatz/alsadecryptor/packages/msv/templates.py b/pypykatz/alsadecryptor/packages/msv/templates.py index ed58285..e33d341 100644 --- a/pypykatz/alsadecryptor/packages/msv/templates.py +++ b/pypykatz/alsadecryptor/packages/msv/templates.py @@ -3,7 +3,7 @@ # Author: # Tamas Jos (@skelsec) # -import io +from pypykatz import logger from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild, WindowsBuild from pypykatz.alsadecryptor.win_datatypes import BOOLEAN, HANDLE, USHORT, ULONG, LSA_UNICODE_STRING, LSAISO_DATA_BLOB, \ BYTE, PVOID, WORD, DWORD, POINTER, LUID, PSID, ANSI_STRING @@ -24,6 +24,8 @@ def __init__(self): @staticmethod def get_template(sysinfo): + logger.debug('buildnumber: %s' % sysinfo.buildnumber) + logger.debug('msv_dll_timestamp: %s' % sysinfo.msv_dll_timestamp) template = MsvTemplate() template.encrypted_credentials_list_struct = KIWI_MSV1_0_CREDENTIAL_LIST template.log_template('encrypted_credentials_list_struct', template.encrypted_credentials_list_struct) @@ -66,8 +68,7 @@ def get_template(sysinfo): else: template.decrypted_credential_struct = MSV1_0_PRIMARY_CREDENTIAL_10_1607_DEC - template.log_template('decrypted_credential_struct', template.decrypted_credential_struct) - + template.log_template('decrypted_credential_struct', template.decrypted_credential_struct) if sysinfo.architecture == KatzSystemArchitecture.X64: if WindowsMinBuild.WIN_XP.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_2K3.value: template.signature = b'\x4c\x8b\xdf\x49\xc1\xe3\x04\x48\x8b\xcb\x4c\x03\xd8' @@ -90,9 +91,16 @@ def get_template(sysinfo): template.offset2 = -4 elif WindowsMinBuild.WIN_8.value <= sysinfo.buildnumber < WindowsMinBuild.WIN_BLUE.value: - template.signature = b'\x33\xff\x41\x89\x37\x4c\x8b\xf3\x45\x85\xc0\x74' - template.first_entry_offset = 16 - template.offset2 = -4 + if sysinfo.msv_dll_timestamp > 0x60000000: + # new win2012 update weirdness + template.list_entry = PKIWI_MSV1_0_LIST_63 + template.signature = b'\x8b\xde\x48\x8d\x0c\x5b\x48\xc1\xe1\x05\x48\x8d\x05' + template.first_entry_offset = 34 + template.offset2 = -6 + else: + template.signature = b'\x33\xff\x41\x89\x37\x4c\x8b\xf3\x45\x85\xc0\x74' + template.first_entry_offset = 16 + template.offset2 = -4 elif WindowsMinBuild.WIN_BLUE.value <= sysinfo.buildnumber < WindowsBuild.WIN_10_1507.value: template.signature = b'\x8b\xde\x48\x8d\x0c\x5b\x48\xc1\xe1\x05\x48\x8d\x05' @@ -121,7 +129,7 @@ def get_template(sysinfo): template.signature = b'\x33\xff\x41\x89\x37\x4c\x8b\xf3\x45\x85\xc0\x74' template.first_entry_offset = 23 template.offset2 = -4 - + elif WindowsBuild.WIN_11_2022.value <= sysinfo.buildnumber < WindowsBuild.WIN_11_2023.value: #20348 template.signature = b'\x45\x89\x34\x24\x4c\x8b\xff\x8b\xf3\x45\x85\xc0\x74' template.first_entry_offset = 24 diff --git a/pypykatz/apypykatz.py b/pypykatz/apypykatz.py index a011dea..17121c8 100644 --- a/pypykatz/apypykatz.py +++ b/pypykatz/apypykatz.py @@ -22,6 +22,7 @@ from pypykatz.commons.common import UniversalEncoder from minidump.aminidumpfile import AMinidumpFile from minikerberos.common.ccache import CCACHE +from minikerberos.common.kirbi import Kirbi from pypykatz._version import __version__ class apypykatz: @@ -245,7 +246,7 @@ async def get_kerberos(self, with_tickets = True): for cred in dec.credentials: for ticket in cred.tickets: for fn in ticket.kirbi_data: - self.kerberos_ccache.add_kirbi(ticket.kirbi_data[fn].native) + self.kerberos_ccache.add_kirbi(Kirbi(ticket.kirbi_data[fn])) if cred.luid in self.logon_sessions: self.logon_sessions[cred.luid].kerberos_creds.append(cred) diff --git a/pypykatz/commons/common.py b/pypykatz/commons/common.py index 02172ae..7700bf6 100644 --- a/pypykatz/commons/common.py +++ b/pypykatz/commons/common.py @@ -457,7 +457,7 @@ def __init__(self): self.major_version = 6 def __str__(self): - return '%s %s' % (self.architecture.name, self.buildnumber) + return 'ARCH:%s BUILD:%s MSV_TS:%s OS(guess): %s' % (self.architecture.name, self.buildnumber, self.msv_dll_timestamp, self.operating_system) @staticmethod def from_live_reader(lr): diff --git a/pypykatz/lsadecryptor/cmdhelper.py b/pypykatz/lsadecryptor/cmdhelper.py index 5afd828..4eab8cd 100644 --- a/pypykatz/lsadecryptor/cmdhelper.py +++ b/pypykatz/lsadecryptor/cmdhelper.py @@ -15,7 +15,8 @@ from pypykatz.pypykatz import pypykatz from pypykatz.commons.common import UniversalEncoder from pypykatz.lsadecryptor.packages.msv.decryptor import LogonSession - +from minidump.minidumpfile import MinidumpFile +from pypykatz.commons.common import KatzSystemInfo class LSACMDHelper: @@ -35,7 +36,7 @@ def add_args(self, parser, live_parser): group = parser.add_parser('lsa', help='Get secrets from memory dump') - group.add_argument('cmd', choices=['minidump','rekall']) + group.add_argument('cmd', choices=['minidump','rekall','info']) group.add_argument('memoryfile', help='path to the dump file') group.add_argument('-t','--timestamp_override', type=int, help='enforces msv timestamp override (0=normal, 1=anti_mimikatz)') group.add_argument('--json', action='store_true',help = 'Print credentials in JSON format') @@ -205,6 +206,23 @@ def run(self, args): args.packages.append('ktickets') mimi = pypykatz.parse_memory_dump_rekall(args.memoryfile, args.timestamp_override, packages=args.packages) results['rekall'] = mimi + + elif args.cmd == 'info': + if args.directory: + dir_fullpath = os.path.abspath(args.memoryfile) + file_pattern = '*.dmp' + if args.recursive == True: + globdata = os.path.join(dir_fullpath, '**', file_pattern) + else: + globdata = os.path.join(dir_fullpath, file_pattern) + else: + globdata = args.memoryfile + + for filename in glob.glob(globdata, recursive=args.recursive): + minidump = MinidumpFile.parse(filename) + sysinfo = KatzSystemInfo.from_minidump(minidump) + print('[%s] %s' % (filename, sysinfo)) + ###### Minidump elif args.cmd == 'minidump': diff --git a/pypykatz/lsadecryptor/lsa_decryptor_nt5.py b/pypykatz/lsadecryptor/lsa_decryptor_nt5.py index d1cb2e8..b2d9483 100644 --- a/pypykatz/lsadecryptor/lsa_decryptor_nt5.py +++ b/pypykatz/lsadecryptor/lsa_decryptor_nt5.py @@ -37,7 +37,7 @@ def acquire_crypto_material(self): except: import traceback traceback.print_exc() - input() + #input() else: break diff --git a/pypykatz/lsadecryptor/lsa_decryptor_nt6.py b/pypykatz/lsadecryptor/lsa_decryptor_nt6.py index 6e614b5..ddc69a4 100644 --- a/pypykatz/lsadecryptor/lsa_decryptor_nt6.py +++ b/pypykatz/lsadecryptor/lsa_decryptor_nt6.py @@ -94,11 +94,13 @@ def decrypt(self, encrypted): size = len(encrypted) if size: if size % 8: + logger.debug('AES-CFB') if not self.aes_key or not self.iv: return cleartext cipher = AES(self.aes_key, MODE_CFB, IV = self.iv, segment_size=128) cleartext = cipher.decrypt(encrypted) else: + logger.debug('TDES') if not self.des_key or not self.iv: return cleartext cipher = TDES(self.des_key, MODE_CBC, self.iv[:8]) diff --git a/pypykatz/lsadecryptor/lsa_template_nt6.py b/pypykatz/lsadecryptor/lsa_template_nt6.py index 8c5d378..8df0e38 100644 --- a/pypykatz/lsadecryptor/lsa_template_nt6.py +++ b/pypykatz/lsadecryptor/lsa_template_nt6.py @@ -320,7 +320,8 @@ def __init__(self): self.key_pattern = LSADecyptorKeyPattern() self.key_pattern.signature = b'\x83\x64\x24\x30\x00\x48\x8d\x45\xe0\x44\x8b\x4d\xd8\x48\x8d\x15' self.key_pattern.IV_length = 16 - self.key_pattern.offset_to_IV_ptr = 71 + #self.key_pattern.offset_to_IV_ptr = 71 + self.key_pattern.offset_to_IV_ptr = 58 self.key_pattern.offset_to_DES_key_ptr = -89 self.key_pattern.offset_to_AES_key_ptr = 16 diff --git a/pypykatz/lsadecryptor/packages/kerberos/decryptor.py b/pypykatz/lsadecryptor/packages/kerberos/decryptor.py index a02764b..e54afed 100644 --- a/pypykatz/lsadecryptor/packages/kerberos/decryptor.py +++ b/pypykatz/lsadecryptor/packages/kerberos/decryptor.py @@ -4,9 +4,6 @@ # Tamas Jos (@skelsec) # from typing import List -#from pypykatz.commons.common import * -#from pypykatz.commons.filetime import * -#from .templates import * from pypykatz.commons.kerberosticket import KerberosTicket, KerberosTicketType from pypykatz.lsadecryptor.package_commons import PackageDecryptor from pypykatz.commons.win_datatypes import PLIST_ENTRY, PRTL_AVL_TABLE diff --git a/pypykatz/lsadecryptor/packages/msv/templates.py b/pypykatz/lsadecryptor/packages/msv/templates.py index c8698fd..6f6c74a 100644 --- a/pypykatz/lsadecryptor/packages/msv/templates.py +++ b/pypykatz/lsadecryptor/packages/msv/templates.py @@ -3,7 +3,6 @@ # Author: # Tamas Jos (@skelsec) # -import io from pypykatz import logger from minidump.win_datatypes import BOOLEAN, HANDLE from pypykatz.commons.common import KatzSystemArchitecture, WindowsMinBuild, WindowsBuild @@ -132,7 +131,7 @@ def get_template(sysinfo): template.signature = b'\x33\xff\x41\x89\x37\x4c\x8b\xf3\x45\x85\xc0\x74' template.first_entry_offset = 23 template.offset2 = -4 - + elif WindowsBuild.WIN_11_2022.value <= sysinfo.buildnumber < WindowsBuild.WIN_11_2023.value: #20348 template.signature = b'\x45\x89\x34\x24\x4c\x8b\xff\x8b\xf3\x45\x85\xc0\x74' template.first_entry_offset = 24 diff --git a/setup.py b/setup.py index 97222c6..a905444 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ author_email="info@skelsecprojects.com", # Packages - packages=find_packages(), + packages=find_packages(exclude=["tests*"]), # Include additional files into the package include_package_data=True, diff --git a/tests/.coveragerc b/tests/.coveragerc new file mode 100644 index 0000000..5e9900a --- /dev/null +++ b/tests/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = ../tests/*, ../pypykatz/commons/winapi/*, ../pypykatz/commons/readers/local/*, ../pypykatz/kerberos/functiondefs/* \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/config.py b/tests/config.py new file mode 100644 index 0000000..4c135f4 --- /dev/null +++ b/tests/config.py @@ -0,0 +1,12 @@ +import json + +TESTFILES_DIR = '/mnt/hgfs/!SHARED/test_dumps/ok' + +def compare_jsons(json1, json2): + """ + Compare two jsons and return True if they are equal, False otherwise + :param json1: json string + :param json2: json string + :return: True if equal, False otherwise + """ + return json.loads(json1) == json.loads(json2) \ No newline at end of file diff --git a/tests/coverage.sh b/tests/coverage.sh new file mode 100755 index 0000000..306b392 --- /dev/null +++ b/tests/coverage.sh @@ -0,0 +1 @@ +pytest --cov-config=.coveragerc --cov-report html --cov=pypykatz \ No newline at end of file diff --git a/tests/test_async_lsassdecrypt.py b/tests/test_async_lsassdecrypt.py new file mode 100644 index 0000000..220ede5 --- /dev/null +++ b/tests/test_async_lsassdecrypt.py @@ -0,0 +1,252 @@ +import os +from pypykatz.apypykatz import apypykatz +from .config import TESTFILES_DIR, compare_jsons +import pytest + + +@pytest.mark.asyncio +async def test_x64_win7_7601(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win7') + with open(os.path.join(basedir, '7601.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '7601.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win7_7601_1(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win7') + with open(os.path.join(basedir, '7601_1.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '7601_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win10_10240(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '10240.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '10240.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win10_10240_2(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '10240_2.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '10240_2.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win10_10240_3(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '10240_3.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '10240_3.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win10_10240_4(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '10240_4.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '10240_4.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win10_15063(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '15063.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '15063.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win10_16299(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '16299.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '16299.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win10_18362(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '18362.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '18362.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win10_19041(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '19041.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '19041.dmp')) + print(res.to_json()) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win10_19044(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '19044.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '19044.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win81_9600(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win81') + with open(os.path.join(basedir, '9600.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '9600.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win81_9600_1(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win81') + with open(os.path.join(basedir, '9600_1.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '9600_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win2008R2_7601(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2008R2') + with open(os.path.join(basedir, '7601.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '7601.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win2012_9600(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2012') + with open(os.path.join(basedir, '9600.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '9600.dmp')) + print(res.to_json()) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win2016_14393(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2016') + with open(os.path.join(basedir, '14393.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '14393.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win2016_14393_1(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2016') + with open(os.path.join(basedir, '14393_1.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '14393_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win2016_14393_2(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2016') + with open(os.path.join(basedir, '14393_2.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '14393_2.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win2016_14393_3(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2016') + with open(os.path.join(basedir, '14393_3.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '14393_3.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win2019_17763(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2019') + with open(os.path.join(basedir, '17763.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '17763.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win2019_17763_1(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2019') + with open(os.path.join(basedir, '17763_1.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '17763_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win2022_20348(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2022') + with open(os.path.join(basedir, '20348.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '20348.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_win2022_20348_1(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2022') + with open(os.path.join(basedir, '20348_1.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '20348_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x64_xp_3790(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'xp') + with open(os.path.join(basedir, '3790.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '3790.dmp')) + print(res.to_json()) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x86_vista_6002(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'vista') + with open(os.path.join(basedir, '6002.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '6002.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x86_win7_7601(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'win7') + with open(os.path.join(basedir, '7601.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '7601.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x86_win7_7601_1(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'win7') + with open(os.path.join(basedir, '7601_1.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '7601_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x86_win10_19043(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'win10') + with open(os.path.join(basedir, '19043.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '19043.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x86_win10_14393(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'win10') + with open(os.path.join(basedir, '14393.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '14393.dmp')) + assert compare_jsons(res.to_json(), expected) + +@pytest.mark.asyncio +async def test_x86_win10_14393_1(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'win10') + with open(os.path.join(basedir, '14393_1.json'), 'r') as f: + expected = f.read() + res = await apypykatz.parse_minidump_file(os.path.join(basedir, '14393_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +if __name__ == '__main__': + import asyncio + asyncio.run(test_x64_xp_3790()) \ No newline at end of file diff --git a/tests/test_lsassdecrypt.py b/tests/test_lsassdecrypt.py new file mode 100644 index 0000000..9ed304b --- /dev/null +++ b/tests/test_lsassdecrypt.py @@ -0,0 +1,222 @@ +import os +import json +from pypykatz.pypykatz import pypykatz +from .config import TESTFILES_DIR, compare_jsons + + +def test_x64_win7_7601(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win7') + with open(os.path.join(basedir, '7601.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '7601.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win7_7601_1(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win7') + with open(os.path.join(basedir, '7601_1.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '7601_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win10_10240(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '10240.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '10240.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win10_10240_2(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '10240_2.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '10240_2.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win10_10240_3(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '10240_3.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '10240_3.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win10_10240_4(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '10240_4.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '10240_4.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win10_15063(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '15063.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '15063.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win10_16299(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '16299.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '16299.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win10_18362(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '18362.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '18362.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win10_19041(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '19041.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '19041.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win10_19044(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win10') + with open(os.path.join(basedir, '19044.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '19044.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win81_9600(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win81') + with open(os.path.join(basedir, '9600.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '9600.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win81_9600_1(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win81') + with open(os.path.join(basedir, '9600_1.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '9600_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win2008R2_7601(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2008R2') + with open(os.path.join(basedir, '7601.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '7601.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win2012_9600(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2012') + with open(os.path.join(basedir, '9600.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '9600.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win2016_14393(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2016') + with open(os.path.join(basedir, '14393.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '14393.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win2016_14393_1(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2016') + with open(os.path.join(basedir, '14393_1.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '14393_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win2016_14393_2(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2016') + with open(os.path.join(basedir, '14393_2.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '14393_2.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win2016_14393_3(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2016') + with open(os.path.join(basedir, '14393_3.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '14393_3.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win2019_17763(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2019') + with open(os.path.join(basedir, '17763.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '17763.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win2019_17763_1(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2019') + with open(os.path.join(basedir, '17763_1.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '17763_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win2022_20348(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2022') + with open(os.path.join(basedir, '20348.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '20348.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_win2022_20348_1(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'win2022') + with open(os.path.join(basedir, '20348_1.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '20348_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x64_xp_3790(): + basedir = os.path.join(TESTFILES_DIR, 'x64', 'xp') + with open(os.path.join(basedir, '3790.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '3790.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x86_vista_6002(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'vista') + with open(os.path.join(basedir, '6002.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '6002.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x86_win7_7601(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'win7') + with open(os.path.join(basedir, '7601.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '7601.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x86_win7_7601_1(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'win7') + with open(os.path.join(basedir, '7601_1.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '7601_1.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x86_win10_19043(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'win10') + with open(os.path.join(basedir, '19043.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '19043.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x86_win10_14393(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'win10') + with open(os.path.join(basedir, '14393.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '14393.dmp')) + assert compare_jsons(res.to_json(), expected) + +def test_x86_win10_14393_1(): + basedir = os.path.join(TESTFILES_DIR, 'x86', 'win10') + with open(os.path.join(basedir, '14393_1.json'), 'r') as f: + expected = f.read() + res = pypykatz.parse_minidump_file(os.path.join(basedir, '14393_1.dmp')) + assert compare_jsons(res.to_json(), expected) + + + + + +if __name__ == '__main__': + test_x64_win7_7601_1() \ No newline at end of file