diff --git a/README.md b/README.md index 9edbda8..06a35a1 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,10 @@ ![Supported Python versions](https://img.shields.io/badge/python-3.7+-blue.svg) [![Twitter](https://img.shields.io/twitter/follow/skelsec?label=skelsec&style=social)](https://twitter.com/intent/follow?screen_name=skelsec) -:triangular_flag_on_post: This is the public repository of PyPyKatz, for latest version and updates please consider supporting us through https://porchetta.industries/ - # pypykatz Mimikatz implementation in pure Python. At least a part of it :) Runs on all OS's which support python>=3.6 ![pypy_card](https://user-images.githubusercontent.com/19204702/71646030-221fe200-2ce1-11ea-9e2a-e587ea4790d7.jpg) -## :triangular_flag_on_post: Sponsors - -If you want to sponsors this project and have the latest updates on this project, latest issues fixed, latest features, please support us on https://porchetta.industries/ - ## Official Discord Channel Come hang out on Discord! diff --git a/pypykatz/_version.py b/pypykatz/_version.py index d246450..9f49872 100644 --- a/pypykatz/_version.py +++ b/pypykatz/_version.py @@ -1,5 +1,5 @@ -__version__ = "0.6.8" +__version__ = "0.6.9" __banner__ = \ """ # pypyKatz %s diff --git a/pypykatz/lsadecryptor/package_commons.py b/pypykatz/lsadecryptor/package_commons.py index 3e7d621..cf3da53 100644 --- a/pypykatz/lsadecryptor/package_commons.py +++ b/pypykatz/lsadecryptor/package_commons.py @@ -173,11 +173,16 @@ def walk_list(self, entry_ptr, callback, max_walk = 255, override_ptr = None): max_walk = max_walk self.log_ptr(entry_ptr.value, 'List entry -%s-' % entry_ptr.finaltype.__name__ if not override_ptr else override_ptr.__name__) while True: - if override_ptr: - entry = entry_ptr.read(self.reader, override_ptr) - else: - entry = entry_ptr.read(self.reader) - + try: + if override_ptr: + entry = entry_ptr.read(self.reader, override_ptr) + else: + entry = entry_ptr.read(self.reader) + except Exception as e: + self.log('Exception while reading list entry: %s' % e) + break # uncomment this line to discard the exception and continue parsing + raise + if not entry: break diff --git a/pypykatz/registry/security/acommon.py b/pypykatz/registry/security/acommon.py index 3dc03c5..71ec90c 100644 --- a/pypykatz/registry/security/acommon.py +++ b/pypykatz/registry/security/acommon.py @@ -206,12 +206,13 @@ def __str__(self): return '=== LSA DPAPI secret ===\r\nHistory: %s\r\nMachine key (hex): %s\r\nUser key(hex): %s' % (self.history, self.machine_key.hex(), self.user_key.hex()) class LSADCCSecret: - def __init__(self, version, domain, username, hash_value, iteration = None): + def __init__(self, version, domain, username, hash_value, iteration = 10240, last_write_ts = None): self.version = version self.domain = domain self.username = username self.iteration = iteration self.hash_value = hash_value + self.last_write_ts = last_write_ts def to_dict(self): t = {} @@ -220,7 +221,13 @@ def to_dict(self): t['username'] = self.username t['iteration'] = self.iteration t['hash_value'] = self.hash_value + t['lastwrite'] = self.get_lastwrite() return t + + def get_lastwrite(self, default = None): + if self.last_write_ts is None: + return default + return self.last_write_ts.strftime("%Y-%m-%d %H:%M:%S") def __str__(self): return self.to_lopth() @@ -229,4 +236,4 @@ def to_lopth(self): if self.version == 1: return "%s/%s:%s:%s" % (self.domain, self.username, self.hash_value.hex(), self.username) else: - return "%s/%s:$DCC2$%s#%s#%s" % (self.domain, self.username, self.iteration, self.username, self.hash_value.hex()) \ No newline at end of file + return "%s/%s:*%s*$DCC2$%s#%s#%s" % (self.domain, self.username, self.get_lastwrite(''), self.iteration, self.username, self.hash_value.hex()) \ No newline at end of file diff --git a/pypykatz/registry/security/asecurity.py b/pypykatz/registry/security/asecurity.py index 542b9bf..9dc47ab 100644 --- a/pypykatz/registry/security/asecurity.py +++ b/pypykatz/registry/security/asecurity.py @@ -204,7 +204,7 @@ async def dump_dcc(self): domain = blob.read(record.DnsDomainNameLength).decode('utf-16-le') version = 2 if self.lsa_secret_key_vista_type is True else 1 - secret = LSADCCSecret(version, domain, username, dcc_hash, iteration = self.dcc_iteration_count) + secret = LSADCCSecret(version, domain, username, dcc_hash, iteration = self.dcc_iteration_count, last_write_ts=record.LastWrite) self.dcc_hashes.append(secret) return self.dcc_hashes diff --git a/pypykatz/registry/security/common.py b/pypykatz/registry/security/common.py index 0da73f0..a55a81e 100644 --- a/pypykatz/registry/security/common.py +++ b/pypykatz/registry/security/common.py @@ -206,12 +206,13 @@ def __str__(self): return '=== LSA DPAPI secret ===\r\nHistory: %s\r\nMachine key (hex): %s\r\nUser key(hex): %s' % (self.history, self.machine_key.hex(), self.user_key.hex()) class LSADCCSecret: - def __init__(self, version, domain, username, hash_value, iteration = None): + def __init__(self, version, domain, username, hash_value, iteration = 10240, last_write_ts = None): self.version = version self.domain = domain self.username = username self.iteration = iteration self.hash_value = hash_value + self.last_write_ts = last_write_ts def to_dict(self): t = {} @@ -220,7 +221,13 @@ def to_dict(self): t['username'] = self.username t['iteration'] = self.iteration t['hash_value'] = self.hash_value + t['lastwrite'] = self.get_lastwrite() return t + + def get_lastwrite(self, default = None): + if self.last_write_ts is None: + return default + return self.last_write_ts.strftime("%Y-%m-%d %H:%M:%S") def __str__(self): return self.to_lopth() @@ -229,4 +236,4 @@ def to_lopth(self): if self.version == 1: return "%s/%s:%s:%s" % (self.domain, self.username, self.hash_value.hex(), self.username) else: - return "%s/%s:$DCC2$%s#%s#%s" % (self.domain, self.username, self.iteration, self.username, self.hash_value.hex()) \ No newline at end of file + return "%s/%s:*%s*$DCC2$%s#%s#%s" % (self.domain, self.username, self.get_lastwrite(''), self.iteration, self.username, self.hash_value.hex()) \ No newline at end of file diff --git a/pypykatz/registry/security/security.py b/pypykatz/registry/security/security.py index 6959419..3eb769c 100644 --- a/pypykatz/registry/security/security.py +++ b/pypykatz/registry/security/security.py @@ -198,9 +198,9 @@ def dump_dcc(self): username = blob.read(record.UserLength).decode('utf-16-le') blob.seek(self.__pad(record.UserLength) + self.__pad(record.DomainNameLength)) domain = blob.read(record.DnsDomainNameLength).decode('utf-16-le') - + version = 2 if self.lsa_secret_key_vista_type is True else 1 - secret = LSADCCSecret(version, domain, username, dcc_hash, iteration = self.dcc_iteration_count) + secret = LSADCCSecret(version, domain, username, dcc_hash, iteration = self.dcc_iteration_count, last_write_ts=record.LastWrite) self.dcc_hashes.append(secret) return self.dcc_hashes diff --git a/pypykatz/registry/security/structures.py b/pypykatz/registry/security/structures.py index 48ab3c0..1a3cf3d 100644 --- a/pypykatz/registry/security/structures.py +++ b/pypykatz/registry/security/structures.py @@ -5,6 +5,7 @@ # import enum import io +from pypykatz.commons.filetime import filetime_to_dt class LSA_SECRET_BLOB: def __init__(self): @@ -157,6 +158,11 @@ def from_buffer(buff): nl.IV = buff.read(16) nl.CH = buff.read(16) nl.EncryptedData = buff.read() + + try: + nl.LastWrite = filetime_to_dt(nl.LastWrite) + except Exception as e: + nl.LastWrite = None return nl diff --git a/setup.py b/setup.py index 1e74bca..a7444f1 100644 --- a/setup.py +++ b/setup.py @@ -51,14 +51,14 @@ "Operating System :: OS Independent", ], install_requires=[ - 'unicrypto==0.0.10', - 'minidump==0.0.21', - 'minikerberos==0.4.1', - 'aiowinreg==0.0.10', - 'msldap==0.5.5', - 'winacl==0.1.7', - 'aiosmb==0.4.6', - 'aesedb==0.1.4', + 'unicrypto>=0.0.10,<=0.1.0', + 'minidump>=0.0.21,<=0.1.0', + 'minikerberos>=0.4.1,<=0.5.0', + 'aiowinreg>=0.0.10,<=0.1.0', + 'msldap>=0.5.7,<=0.6.0', + 'winacl>=0.1.7,<=0.2.0', + 'aiosmb>=0.4.8,<=0.5.0', + 'aesedb>=0.1.4,<=0.2.0', 'tqdm', ],