Skip to content

Commit

Permalink
Merge pull request #116 from forense54/master
Browse files Browse the repository at this point in the history
Add SHA1 support to DPAPI prekeys generation
  • Loading branch information
skelsec authored Jan 22, 2024
2 parents b8d91b4 + f1708e1 commit a701a6a
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 30 deletions.
15 changes: 13 additions & 2 deletions pypykatz/dpapi/cmdhelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ def add_args(self, parser, live_parser):
prekey_nt.add_argument('nthash', help='NT hash of the user')
prekey_nt.add_argument('-o', '--out-file', help= 'Key candidates will be stored in this file. Easier to handle this way in the masterkeyfil command.')

prekey_sha1 = dpapi_prekey_subparsers.add_parser('sha1', help = 'Generate prekeys from SHA1 hash')
prekey_sha1.add_argument('sid', help='SID of the user')
prekey_sha1.add_argument('sha1hash', help='SHA1 hash of the user')
prekey_sha1.add_argument('-o', '--out-file', help= 'Key candidates will be stored in this file. Easier to handle this way in the masterkeyfil command.')


prekey_registry = dpapi_prekey_subparsers.add_parser('registry', help = 'Generate prekeys from registry secrets')
prekey_registry.add_argument('system', help='SYSTEM hive')
prekey_registry.add_argument('sam', help='SAM hive')
Expand Down Expand Up @@ -177,13 +183,18 @@ def run(self, args):
pw = getpass.getpass()

dpapi.get_prekeys_from_password(args.sid, password = pw)

elif args.prekey_command == 'nt':
if args.nthash is None or args.sid is None:
raise Exception('NT hash and SID must be specified for generating prekey in this mode')

dpapi.get_prekeys_from_password(args.sid, nt_hash = args.nthash)
dpapi.get_prekeys_from_password(args.sid, nt_hash=args.nthash)

elif args.prekey_command == 'sha1':
if args.sha1hash is None or args.sid is None:
raise Exception('SHA1 hash and SID must be specified for generating prekey in this mode')

dpapi.get_prekeys_from_password(args.sid, sha1_hash=args.sha1hash)

dpapi.dump_pre_keys(args.out_file)

Expand Down
58 changes: 30 additions & 28 deletions pypykatz/dpapi/dpapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def load_masterkeys(self, filename):
self.masterkeys[guid] = bytes.fromhex(data['masterkeys'][guid])


def get_prekeys_from_password(self, sid, password = None, nt_hash = None):
def get_prekeys_from_password(self, sid, password = None, nt_hash = None, sha1_hash=None):
"""
Creates pre-masterkeys from user SID and password of nt hash.
If NT hash is provided the function can only generate 2 out of the 3 possible keys,
Expand All @@ -163,38 +163,40 @@ def get_prekeys_from_password(self, sid, password = None, nt_hash = None):
password: user's password. optional. if not provided, then NT hash must be provided
nt_hash: user's NT hash. optional if not provided, the password must be provided
"""
if password is None and nt_hash is None:
raise Exception('Provide either password or NT hash!')
if password is None and nt_hash is None and sha1_hash is None:
raise Exception('Provide either password, NT hash or SHA1 hash!')

if password is None and nt_hash:
if isinstance(nt_hash, str):
if password is None:
# Will generate two keys, one with SHA1 and another with MD4
if nt_hash and isinstance(nt_hash, str):
nt_hash = bytes.fromhex(nt_hash)
key1 = None
if sha1_hash and isinstance(sha1_hash, str):
sha1_hash = bytes.fromhex(sha1_hash)


key1 = key2 = key3 = key4 = None
if password or password == '':
ctx = MD4(password.encode('utf-16le'))
nt_hash = ctx.digest()

# Will generate two keys, one with SHA1 and another with MD4
key1 = hmac.new(sha1(password.encode('utf-16le')).digest(), (sid + '\0').encode('utf-16le'), sha1).digest()

key2 = hmac.new(nt_hash, (sid + '\0').encode('utf-16le'), sha1).digest()
# For Protected users
tmp_key = pbkdf2_hmac('sha256', nt_hash, sid.encode('utf-16le'), 10000)
tmp_key_2 = pbkdf2_hmac('sha256', tmp_key, sid.encode('utf-16le'), 1)[:16]
key3 = hmac.new(tmp_key_2, (sid + '\0').encode('utf-16le'), sha1).digest()[:20]

if key1 is not None:
self.prekeys[key1] = 1
self.prekeys[key2] = 1
self.prekeys[key3] = 1

if key1 is not None:
logger.debug('Prekey_1 %s %s %s %s' % (sid, password, nt_hash, key1.hex()))
logger.debug('Prekey_2 %s %s %s %s' % (sid, password, nt_hash, key2.hex()))
logger.debug('Prekey_3 %s %s %s %s' % (sid, password, nt_hash, key3.hex()))

return key1, key2, key3
sha1_hash = sha1(password.encode('utf-16le')).digest()
if sha1_hash:
key1 = hmac.new(sha1_hash, (sid + '\0').encode('utf-16le'), sha1).digest()
key4 = sha1_hash
if nt_hash:
key2 = hmac.new(nt_hash, (sid + '\0').encode('utf-16le'), sha1).digest()
# For Protected users
tmp_key = pbkdf2_hmac('sha256', nt_hash, sid.encode('utf-16le'), 10000)
tmp_key_2 = pbkdf2_hmac('sha256', tmp_key, sid.encode('utf-16le'), 1)[:16]
key3 = hmac.new(tmp_key_2, (sid + '\0').encode('utf-16le'), sha1).digest()[:20]

count = 1
for key in [key1, key2, key3, key4]:
if key is not None:
self.prekeys[key] = 1
logger.debug('Prekey_%d %s %s %s %s' % (count, sid, password, nt_hash, key.hex()))
count += 1

return key1, key2, key3, key4

def __get_registry_secrets(self, lr):
"""
Expand All @@ -221,7 +223,7 @@ def __get_registry_secrets(self, lr):
for secret in lr.sam.secrets:
if secret.nt_hash:
sid = '%s-%s' % (lr.sam.machine_sid, secret.rid)
x, key2, key3 = self.get_prekeys_from_password(sid, nt_hash = secret.nt_hash)
x, key2, key3, y = self.get_prekeys_from_password(sid, nt_hash = secret.nt_hash)
logger.debug('[DPAPI] NT hash method. Calculated user key for user %s! Key2: %s Key3: %s' % (sid, key2, key3))
user.append(key2)
user.append(key3)
Expand Down

0 comments on commit a701a6a

Please sign in to comment.