Skip to content

Commit

Permalink
new version, lot of features
Browse files Browse the repository at this point in the history
  • Loading branch information
skelsec committed Nov 30, 2019
1 parent 7dff233 commit 352c4dd
Show file tree
Hide file tree
Showing 18 changed files with 1,276 additions and 394 deletions.
17 changes: 17 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
clean:
rm -f -r build/
rm -f -r dist/
rm -f -r *.egg-info
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +

publish: clean
python3.7 setup.py sdist bdist_wheel
python3.7 -m twine upload dist/*

rebuild: clean
python3.7 setup.py install

build:
python3.7 setup.py install
6 changes: 4 additions & 2 deletions pypykatz/commons/kerberosticket.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class KerberosSessionKey:
def __init__(self):
self.keydata = None
self.sessionkey = None


@staticmethod
def parse(key_struct, sysinfo):
ksk = KerberosSessionKey()
ksk.keydata = key_struct.Data
Expand Down Expand Up @@ -121,7 +122,8 @@ def to_asn1(self):
krbcred['enc-part'] = EncryptedData({'etype': EncryptionType.NULL.value, 'cipher': EncKrbCredPart(enc_krbcred).dump()})

return KRBCRED(krbcred)


@staticmethod
def parse(kerberos_ticket, reader, sysinfo, type = None):
kt = KerberosTicket()
kt.type = type
Expand Down
9 changes: 8 additions & 1 deletion pypykatz/crypto/des.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,13 @@ def __create_sub_keys(self):

i += 1

def replace_K(self, K):
#print(self.Kn)
#input('above the original')
#print(K)
#input('above the replacement')
self.Kn = K

# Main part of the encryption algorithm, the number cruncher :)
def __des_crypt(self, block, crypt_type):
"""Crypt the block of data through DES bit-manipulation"""
Expand Down Expand Up @@ -864,4 +871,4 @@ def expand_DES_key(key):
s += (((key[4] & 0x1f) << 2 | ((key[5] >> 6) & 0x03)) << 1).to_bytes(1, byteorder = 'big')
s += (((key[5] & 0x3f) << 1 | ((key[6] >> 7) & 0x01)) << 1).to_bytes(1, byteorder = 'big')
s += ( (key[6] & 0x7f) << 1).to_bytes(1, byteorder = 'big')
return s
return s
6 changes: 6 additions & 0 deletions pypykatz/lsadecryptor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
#!/usr/bin/env python3
#
# Author:
# Tamas Jos (@skelsec)
#

from .lsa_templates import *
from .lsa_decryptor import *
from .packages import *
21 changes: 18 additions & 3 deletions pypykatz/lsadecryptor/cmdhelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pypykatz import logging
from pypykatz.pypykatz import pypykatz
from pypykatz.commons.common import UniversalEncoder
from pypykatz.lsadecryptor.packages.msv.decryptor import LogonSession



Expand All @@ -38,6 +39,7 @@ def add_args(self, parser, live_parser):
group.add_argument('-k', '--kerberos-dir', help = 'Save kerberos tickets to a directory.')
group.add_argument('-r', '--recursive', action='store_true', help = 'Recursive parsing')
group.add_argument('-d', '--directory', action='store_true', help = 'Parse all dump files in a folder')
group.add_argument('-g', '--grep', action='store_true', help = 'Print credentials in greppable format')

def execute(self, args):
if len(self.keywords) > 0 and args.command in self.keywords:
Expand All @@ -50,7 +52,15 @@ def process_results(self, results, files_with_error, args):
if args.outfile and args.json:
with open(args.outfile, 'w') as f:
json.dump(results, f, cls = UniversalEncoder, indent=4, sort_keys=True)


elif args.outfile and args.grep:
with open(args.outfile, 'w', newline = '') as f:
f.write(':'.join(LogonSession.grep_header) + '\r\n')
for result in results:
for luid in results[result].logon_sessions:
for row in results[result].logon_sessions[luid].to_grep_rows():
f.write(':'.join(row) + '\r\n')

elif args.outfile:
with open(args.outfile, 'w') as f:
for result in results:
Expand All @@ -71,8 +81,13 @@ def process_results(self, results, files_with_error, args):

elif args.json:
print(json.dumps(results, cls = UniversalEncoder, indent=4, sort_keys=True))



elif args.grep:
print(':'.join(LogonSession.grep_header))
for result in results:
for luid in results[result].logon_sessions:
for row in results[result].logon_sessions[luid].to_grep_rows():
print(':'.join(row))
else:
for result in results:
print('FILE: ======== %s =======' % result)
Expand Down
115 changes: 15 additions & 100 deletions pypykatz/lsadecryptor/lsa_decryptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,104 +3,19 @@
# Author:
# Tamas Jos (@skelsec)
#
import io
import logging
from pypykatz.commons.common import *
from pypykatz.crypto.des import *
from pypykatz.crypto.aes import AESModeOfOperationCBC
from pypykatz.lsadecryptor.lsa_templates import *
from pypykatz.lsadecryptor.lsa_template_nt5 import LsaTemplate_NT5
from pypykatz.lsadecryptor.lsa_template_nt6 import LsaTemplate_NT6
from pypykatz.lsadecryptor.lsa_decryptor_nt6 import LsaDecryptor_NT6
from pypykatz.lsadecryptor.lsa_decryptor_nt5 import LsaDecryptor_NT5

class LsaDecryptor(PackageDecryptor):
def __init__(self, reader, decryptor_template, sysinfo):
super().__init__('LsaDecryptor', None, sysinfo, reader)
self.decryptor_template = decryptor_template
self.iv = None
self.aes_key = None
self.des_key = None

self.acquire_crypto_material()

def acquire_crypto_material(self):
self.log('Acquireing crypto stuff...')
sigpos = self.find_signature()
self.reader.move(sigpos)
data = self.reader.peek(0x50)
self.log('Memory looks like this around the signature\n%s' % hexdump(data, start = sigpos))
self.iv = self.get_IV(sigpos)
self.des_key = self.get_des_key(sigpos)
self.aes_key = self.get_aes_key(sigpos)

def get_des_key(self, pos):
self.log('Acquireing DES key...')
return self.get_key(pos, self.decryptor_template.key_pattern.offset_to_DES_key_ptr)

def get_aes_key(self, pos):
self.log('Acquireing AES key...')
return self.get_key(pos, self.decryptor_template.key_pattern.offset_to_AES_key_ptr)

def find_signature(self):
self.log('Looking for main struct signature in memory...')
fl = self.reader.find_in_module('lsasrv.dll', self.decryptor_template.key_pattern.signature)
if len(fl) == 0:
logging.warning('signature not found! %s' % self.decryptor_template.key_pattern.signature.hex())
raise Exception('LSA signature not found!')

self.log('Found candidates on the following positions: %s' % ' '.join(hex(x) for x in fl))
self.log('Selecting first one @ 0x%08x' % fl[0])
return fl[0]

def get_IV(self, pos):
self.log('Reading IV')
#print('Offset to IV: %s' % hex(self.decryptor_template.key_pattern.offset_to_IV_ptr))
ptr_iv = self.reader.get_ptr_with_offset(pos + self.decryptor_template.key_pattern.offset_to_IV_ptr)
self.log('IV pointer takes us to 0x%08x' % ptr_iv)
self.reader.move(ptr_iv)
data = self.reader.read(self.decryptor_template.key_pattern.IV_length)
self.log('IV data: %s' % hexdump(data))
return data

def get_key(self, pos, key_offset):
ptr_key = self.reader.get_ptr_with_offset(pos + key_offset)
self.log('key handle pointer is @ 0x%08x' % ptr_key)
ptr_key = self.reader.get_ptr(ptr_key)
self.log('key handle is @ 0x%08x' % ptr_key)
self.reader.move(ptr_key)
data = self.reader.peek(0x50)
self.log('BCRYPT_HANLE_KEY_DATA\n%s' % hexdump(data, start = ptr_key))
kbhk = self.decryptor_template.key_handle_struct(self.reader)
if kbhk.verify():
ptr_key = kbhk.ptr_key.value
self.reader.move(ptr_key)
data = self.reader.peek(0x50)
self.log('BCRYPT_KEY_DATA\n%s' % hexdump(data, start = ptr_key))
kbk = kbhk.ptr_key.read(self.reader, self.decryptor_template.key_struct)
self.log('HARD_KEY SIZE: 0x%x' % kbk.size)
if kbk.verify():
self.log('HARD_KEY data:\n%s' % hexdump(kbk.hardkey.data))
return kbk.hardkey.data

def decrypt(self, encrypted):
# TODO: NT version specific, move from here in subclasses.
cleartext = b''
size = len(encrypted)
if size:
if size % 8:
if not self.aes_key or not self.iv:
return cleartext
cipher = AESModeOfOperationCBC(self.aes_key, iv = self.iv)
n = 16
for block in [encrypted[i:i+n] for i in range(0, len(encrypted), n)]: #terrible, terrible workaround
cleartext += cipher.decrypt(block)
else:
if not self.des_key or not self.iv:
return cleartext
#cipher = DES3.new(self.des_key, DES3.MODE_CBC, self.iv[:8])
cipher = triple_des(self.des_key, CBC, self.iv[:8])
cleartext = cipher.decrypt(encrypted)
return cleartext

def dump(self):
self.log('Recovered LSA encryption keys\n')
self.log('IV ({}): {}'.format(len(self.iv), self.iv.hex()))
self.log('DES_KEY ({}): {}'.format(len(self.des_key), self.des_key.hex()))
self.log('AES_KEY ({}): {}'.format(len(self.aes_key), self.aes_key.hex()))
class LsaDecryptor:
def __init__(self):
pass

@staticmethod
def choose(reader, decryptor_template, sysinfo):
if isinstance(decryptor_template, LsaTemplate_NT5):
raise Exception('Windows NT5 (XP, 2003) is not yet supported!')
#return LsaDecryptor_NT5(reader, decryptor_template, sysinfo)
else:
return LsaDecryptor_NT6(reader, decryptor_template, sysinfo)
Loading

0 comments on commit 352c4dd

Please sign in to comment.