Skip to content

Commit

Permalink
kerberos fix 2, adding tests, win2022 IV offsetfix
Browse files Browse the repository at this point in the history
  • Loading branch information
Skelsec committed Mar 4, 2023
1 parent 4b5b4b4 commit faab020
Show file tree
Hide file tree
Showing 22 changed files with 620 additions and 78 deletions.
2 changes: 1 addition & 1 deletion pypykatz/_version.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

__version__ = "0.6.5"
__version__ = "0.6.6"
__banner__ = \
"""
# pypyKatz %s
Expand Down
71 changes: 35 additions & 36 deletions pypykatz/alsadecryptor/lsa_decryptor_nt5.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,76 +16,75 @@ 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))

for x in [self.decryptor_template.feedback_ptr_offset , self.decryptor_template.old_feedback_offset]:
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!')
Expand Down
45 changes: 34 additions & 11 deletions pypykatz/alsadecryptor/lsa_template_nt5.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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'
Expand All @@ -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'
Expand All @@ -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):
Expand Down
3 changes: 2 additions & 1 deletion pypykatz/alsadecryptor/lsa_template_nt6.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions pypykatz/alsadecryptor/packages/kerberos/decryptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand All @@ -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!
Expand Down
19 changes: 9 additions & 10 deletions pypykatz/alsadecryptor/packages/msv/decryptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
22 changes: 15 additions & 7 deletions pypykatz/alsadecryptor/packages/msv/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion pypykatz/apypykatz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pypykatz/commons/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
22 changes: 20 additions & 2 deletions pypykatz/lsadecryptor/cmdhelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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')
Expand Down Expand Up @@ -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':
Expand Down
Loading

0 comments on commit faab020

Please sign in to comment.