mirror of
https://github.com/login-securite/DonPAPI
synced 2025-03-11 06:58:49 +00:00
remove dead code and unused deps
This commit is contained in:
parent
5be83bd047
commit
c07dd6c01d
@ -1,306 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ############################################################################
|
||||
## ##
|
||||
## This file is part of DPAPIck ##
|
||||
## Windows DPAPI decryption & forensic toolkit ##
|
||||
## ##
|
||||
## ##
|
||||
## Copyright (C) 2010, 2011 Cassidian SAS. All rights reserved. ##
|
||||
## This document is the property of Cassidian SAS, it may not be copied or ##
|
||||
## circulated without prior licence ##
|
||||
## ##
|
||||
## Author: Jean-Michel Picod <jmichel.p@gmail.com> ##
|
||||
## ##
|
||||
## This program is distributed under GPLv3 licence (see LICENCE.txt) ##
|
||||
## ##
|
||||
#############################################################################
|
||||
import binascii
|
||||
import struct
|
||||
import hmac
|
||||
import hashlib
|
||||
from donpapi.lib.dpapi_pick import crypto
|
||||
from donpapi.lib.dpapi_pick import eater
|
||||
'''
|
||||
crypto_CryptoAlgo={
|
||||
0x6601: {"name":"DES", "keyLength":64/8, "blockLength":64/8, "IVLength":64/8, "module":"des", "keyFixup":"des_set_odd_parity"},
|
||||
0x6603: {"name":"DES3", "keyLength":192/8, "blockLength":64/8, "IVLength":64/8, "module":"triple_des", "keyFixup":"des_set_odd_parity"},
|
||||
0x6611: {"name":"AES", "keyLength":128/8, "blockLength":128/8, "IVLength":128/8},
|
||||
0x660e: {"name":"AES-128", "keyLength":128/8, "blockLength":128/8, "IVLength":128/8},
|
||||
0x660f: {"name":"AES-192", "keyLength":192/8, "blockLength":128/8, "IVLength":128/8},
|
||||
0x6610: {"name":"AES-256", "keyLength":256/8, "blockLength":128/8, "IVLength":128/8},
|
||||
0x8009: {"name":"HMAC", "digestLength":160/8, "blockLength":512/8},
|
||||
0x8003: {"name":"md5", "digestLength":128/8, "blockLength":512/8},
|
||||
0x8004: {"name":"sha1", "digestLength":160/8, "blockLength":512/8},
|
||||
0x800c: {"name":"sha256", "digestLength":256/8, "blockLength":512/8},
|
||||
0x800d: {"name":"sha384", "digestLength":384/8, "blockLength":1024/8},
|
||||
0x800e: {"name":"sha512", "digestLength":512/8, "blockLength":1024/8}
|
||||
}'''
|
||||
|
||||
class RPC_SID(eater.DataStruct):
|
||||
"""Represents a RPC_SID structure. See MSDN for documentation"""
|
||||
def __init__(self, raw=None):
|
||||
self.version = None
|
||||
self.idAuth = None
|
||||
self.subAuth = None
|
||||
eater.DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
#print('parse rpc')
|
||||
self.version = data.eat("B")
|
||||
n = data.eat("B")
|
||||
self.idAuth = struct.unpack(">Q", b"\0\0" + data.eat("6s"))[0]
|
||||
self.subAuth = data.eat("%dL" % n)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
s = ["S-%d-%d" % (self.version, self.idAuth)]
|
||||
s += ["%d" % x for x in self.subAuth]
|
||||
return "-".join(s)
|
||||
|
||||
def __repr__(self):
|
||||
return """RPC_SID(%s):
|
||||
revision = %d
|
||||
identifier-authority = %r
|
||||
subAuthorities = %r""" % (self, self.version, self.idAuth, self.subAuth)
|
||||
|
||||
|
||||
class CredSystem(eater.DataStruct):
|
||||
"""This represents the DPAPI_SYSTEM token which is stored as an LSA
|
||||
secret.
|
||||
|
||||
Sets 2 properties:
|
||||
self.machine
|
||||
self.user
|
||||
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.machine = None
|
||||
self.user = None
|
||||
self.revision = None
|
||||
eater.DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
self.revision = data.eat("L")
|
||||
self.machine = data.eat("20s")
|
||||
self.user = data.eat("20s")
|
||||
|
||||
def __repr__(self):
|
||||
s = ["DPAPI_SYSTEM:"]
|
||||
if self.user is not None:
|
||||
s.append("\tUser Credential : %s" % self.user.encode('hex'))
|
||||
if self.machine is not None:
|
||||
s.append("\tMachine Credential: %s" % self.machine.encode('hex'))
|
||||
return "\n".join(s)
|
||||
|
||||
|
||||
class CredhistEntry(eater.DataStruct):
|
||||
"""Represents an entry in the Credhist file"""
|
||||
def __init__(self, raw=None):
|
||||
self.pwdhash = None
|
||||
self.hmac = None
|
||||
self.revision = None
|
||||
self.hashAlgo = None
|
||||
self.rounds = None
|
||||
self.cipherAlgo = None
|
||||
self.shaHashLen = None
|
||||
self.ntHashLen = None
|
||||
self.iv = None
|
||||
self.userSID = None
|
||||
self.encrypted = None
|
||||
self.revision2 = None
|
||||
self.guid = None
|
||||
self.ntlm = None
|
||||
eater.DataStruct.__init__(self, raw)
|
||||
|
||||
def __getstate__(self):
|
||||
d = dict(self.__dict__)
|
||||
for k in ["cipherAlgo", "hashAlgo"]:
|
||||
if k in d:
|
||||
d[k] = d[k].algnum
|
||||
return d
|
||||
|
||||
def __setstate__(self, d):
|
||||
for k in ["cipherAlgo", "hashAlgo"]:
|
||||
if k in d:
|
||||
d[k] = crypto.CryptoAlgo(d[k])#'''crypto_CryptoAlgo[d[k]]'''
|
||||
self.__dict__.update(d)
|
||||
|
||||
def parse(self, data):
|
||||
#print('parse2')
|
||||
self.revision = data.eat("L")
|
||||
#print('parse3')
|
||||
self.hashAlgo = crypto.CryptoAlgo(data.eat("L"))#'''crypto_CryptoAlgo[data.eat("L")]'''
|
||||
self.rounds = data.eat("L")
|
||||
data.eat("L")
|
||||
#print('parse4')
|
||||
self.cipherAlgo = crypto.CryptoAlgo(data.eat("L"))#'''crypto_CryptoAlgo'''
|
||||
self.shaHashLen = data.eat("L")
|
||||
self.ntHashLen = data.eat("L")
|
||||
self.iv = data.eat("16s")
|
||||
#print('parse5')
|
||||
self.userSID = RPC_SID()
|
||||
self.userSID.parse(data)
|
||||
n = self.shaHashLen + self.ntHashLen
|
||||
n += -n % self.cipherAlgo.blockSize#self.cipherAlgo['blockLength'] #blockSize
|
||||
self.encrypted = data.eat_string(n)
|
||||
#print('parse6')
|
||||
self.revision2 = data.eat("L")
|
||||
self.guid = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B")
|
||||
#print(self.__repr__)
|
||||
|
||||
def decryptWithKey(self, enckey):
|
||||
"""Decrypts this credhist entry using the given encryption key."""
|
||||
#print(f'using key {enckey}')
|
||||
cleartxt = crypto.dataDecrypt(self.cipherAlgo, self.hashAlgo, self.encrypted,
|
||||
enckey, self.iv, self.rounds)
|
||||
#print(f'cleartext {cleartxt}')
|
||||
self.pwdhash = cleartxt[:self.shaHashLen]
|
||||
self.ntlm = cleartxt[self.shaHashLen:self.shaHashLen + self.ntHashLen].rstrip(b"\x00")
|
||||
if len(self.ntlm) != 16:
|
||||
self.ntlm = None
|
||||
|
||||
def decryptWithHash(self, pwdhash):
|
||||
"""Decrypts this credhist entry with the given user's password hash.
|
||||
Simply computes the encryption key with the given hash then calls
|
||||
self.decryptWithKey() to finish the decryption.
|
||||
|
||||
"""
|
||||
self.decryptWithKey(crypto.derivePwdHash(pwdhash, str(self.userSID)))#self.crypto ?
|
||||
|
||||
def derivePwdHash(pwdhash, sid, digest='sha1'):
|
||||
"""
|
||||
Internal use. Computes the encryption key from a user's password hash
|
||||
"""
|
||||
return hmac.new(pwdhash, (sid + "\0").encode("UTF-16LE"), digestmod=lambda: hashlib.new(digest)).digest()
|
||||
|
||||
def decryptWithPassword(self, password):
|
||||
"""Decrypts this credhist entry with the given user's password.
|
||||
Simply computes the password hash then calls self.decryptWithHash()
|
||||
|
||||
"""
|
||||
return self.decryptWithHash(hashlib.sha1(password.encode("UTF-16LE")).digest())
|
||||
|
||||
'''
|
||||
def jtr_shadow(self):
|
||||
"""Returns a string that can be passed to John the Ripper to crack this
|
||||
CREDHIST entry. Requires to use a recent jumbo version of JtR plus
|
||||
the configuration snipplet in the "3rdparty" directory of DPAPIck.
|
||||
|
||||
Unless you know what you are doing, you shall not call this function
|
||||
yourself. Instead, use the method provided by CredHistPool object.
|
||||
"""
|
||||
rv = []
|
||||
if self.pwdhash is not None:
|
||||
rv.append("%s:$dynamic_1400$%s" % (self.userSID, self.pwdhash.encode('hex')))
|
||||
if self.ntlm is not None:
|
||||
rv.append("%s:$NT$%s" % (self.userSID, self.ntlm.encode('hex')))
|
||||
return "\n".join(rv)
|
||||
'''
|
||||
def __repr__(self):
|
||||
s = ["CredHist entry",
|
||||
"\trevision = %x\n" % self.revision,
|
||||
"\thash = %r" % self.hashAlgo,
|
||||
"\trounds = %i" % self.rounds,
|
||||
"\tcipher = %r" % self.cipherAlgo,
|
||||
"\tshaHashLen = %i" % self.shaHashLen,
|
||||
"\tntHashLen = %i" % self.ntHashLen,
|
||||
"\tuserSID = %s" % self.userSID,
|
||||
"\tguid = %s" % self.guid,
|
||||
"\tiv = %s" % binascii.hexlify(self.iv)]#.encode("hex")
|
||||
if self.pwdhash is not None:
|
||||
s.append("\tpwdhash = %s" % binascii.hexlify(self.pwdhash))#.encode("hex")
|
||||
if self.ntlm is not None:
|
||||
s.append("\tNTLM = %s" % binascii.hexlify(self.ntlm))#.encode("hex")
|
||||
return "\n".join(s)
|
||||
|
||||
|
||||
class CredHistFile(eater.DataStruct):
|
||||
"""Represents a CREDHIST file.
|
||||
Be aware that currently, it is not possible to check whether the decryption
|
||||
succeeded or not. To circumvent that and optimize a little bit crypto
|
||||
operations, once a credhist entry successfully decrypts a masterkey, the
|
||||
whole CredHistFile is flagged as valid. Then, no further decryption occurs.
|
||||
|
||||
"""
|
||||
def __init__(self, raw=None):
|
||||
self.entries_list = []
|
||||
self.entries = {}
|
||||
self.valid = False
|
||||
self.footmagic = None
|
||||
self.curr_guid = None
|
||||
eater.DataStruct.__init__(self, raw)
|
||||
|
||||
def parse(self, data):
|
||||
while True:
|
||||
l = data.pop("L")
|
||||
if l == 0:
|
||||
break
|
||||
self.addEntry(data.pop_string(l - 4))
|
||||
|
||||
self.footmagic = data.eat("L")
|
||||
self.curr_guid = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B")
|
||||
#print(f'GUID:{self.curr_guid}')
|
||||
|
||||
def addEntry(self, blob):
|
||||
"""Creates a CredhistEntry object with blob then adds it to the store"""
|
||||
x = CredhistEntry(blob)
|
||||
self.entries[x.guid] = x
|
||||
self.entries_list.append(x)
|
||||
|
||||
def validate(self):
|
||||
"""Simply flags a file as successfully decrypted. See the class
|
||||
documentation for information.
|
||||
|
||||
"""
|
||||
self.valid = True
|
||||
|
||||
def decryptWithHash(self, h):
|
||||
"""Try to decrypt each entry with the given hash"""
|
||||
if self.valid:
|
||||
return
|
||||
curhash = h
|
||||
for entry in self.entries_list:
|
||||
try:
|
||||
entry.decryptWithHash(curhash)
|
||||
curhash = entry.pwdhash
|
||||
except Exception as ex:
|
||||
#print('erro Decrypting')
|
||||
#print(ex)
|
||||
continue
|
||||
def decryptWithPassword(self, pwd):
|
||||
"""Try to decrypt each entry with the given password.
|
||||
This function simply computes the SHA-1 hash with the password, then
|
||||
calls self.decryptWithHash()
|
||||
|
||||
"""
|
||||
#print(f'password : {pwd}')
|
||||
return self.decryptWithHash(hashlib.sha1(pwd.encode("UTF-16LE")).digest())
|
||||
|
||||
def jtr_shadow(self, validonly=False):
|
||||
"""Returns a string that can be passed to John the Ripper to crack the
|
||||
CREDHIST entries. Requires to use a recent jumbo version of JtR plus
|
||||
the configuration snipplet in the "3rdparty" directory of DPAPIck.
|
||||
|
||||
If validonly is set to True, will only extract CREDHIST entries
|
||||
that are known to have sucessfully decrypted a masterkey.
|
||||
|
||||
"""
|
||||
if validonly and not self.valid:
|
||||
return ""
|
||||
s = []
|
||||
for e in self.entries.itervalues():
|
||||
s.append(e.jtr_shadow())
|
||||
return "\n".join(s)
|
||||
|
||||
def __repr__(self):
|
||||
s = ["CredHistPool: %s" % self.curr_guid]
|
||||
for e in self.entries:
|
||||
s.append("---")
|
||||
s.append(repr(self.entries[e]))
|
||||
s.append("====")
|
||||
return "\n".join(s)
|
||||
|
||||
|
||||
# vim:ts=4:expandtab:sw=4
|
@ -1,339 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ############################################################################
|
||||
## ##
|
||||
## This file is part of DPAPIck ##
|
||||
## Windows DPAPI decryption & forensic toolkit ##
|
||||
## ##
|
||||
## ##
|
||||
## Copyright (C) 2010, 2011 Cassidian SAS. All rights reserved. ##
|
||||
## This document is the property of Cassidian SAS, it may not be copied or ##
|
||||
## circulated without prior licence ##
|
||||
## ##
|
||||
## Author: Jean-Michel Picod <jmichel.p@gmail.com> ##
|
||||
## ##
|
||||
## This program is distributed under GPLv3 licence (see LICENCE.txt) ##
|
||||
## ##
|
||||
#############################################################################
|
||||
|
||||
import hashlib
|
||||
import struct
|
||||
import array
|
||||
import M2Crypto
|
||||
|
||||
|
||||
try:
|
||||
xrange
|
||||
except NameError:
|
||||
xrange = range
|
||||
|
||||
AES_BLOCK_SIZE = 16
|
||||
|
||||
class CryptoAlgo(object):
|
||||
"""This class is used to wrap Microsoft algorithm IDs with M2Crypto"""
|
||||
|
||||
class Algo(object):
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in self.data:
|
||||
return self.data[attr]
|
||||
raise AttributeError(attr)
|
||||
|
||||
_crypto_data = { }
|
||||
|
||||
@classmethod
|
||||
def add_algo(cls, algnum, **kargs):
|
||||
cls._crypto_data[algnum] = cls.Algo(kargs)
|
||||
|
||||
@classmethod
|
||||
def get_algo(cls, algnum):
|
||||
return cls._crypto_data[algnum]
|
||||
|
||||
def __init__(self, i):
|
||||
self.algnum = i
|
||||
self.algo = CryptoAlgo.get_algo(i)
|
||||
|
||||
name = property(lambda self: self.algo.name)
|
||||
m2name = property(lambda self: self.algo.m2)
|
||||
keyLength = property(lambda self: int(self.algo.keyLength / 8))
|
||||
ivLength = property(lambda self: int(self.algo.IVLength / 8))
|
||||
blockSize = property(lambda self: int(self.algo.blockLength / 8))
|
||||
digestLength = property(lambda self: int(self.algo.digestLength / 8))
|
||||
|
||||
def do_fixup_key(self, key):
|
||||
try:
|
||||
return self.algo.keyFixup.__call__(key)
|
||||
except AttributeError:
|
||||
return key
|
||||
|
||||
def __repr__(self):
|
||||
return "%s [%#x]" % (self.algo.name, self.algnum)
|
||||
|
||||
|
||||
def des_set_odd_parity(key):
|
||||
_lut = [1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 16, 16, 19,
|
||||
19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 32, 32, 35, 35, 37,
|
||||
37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 49, 49, 50, 50, 52, 52, 55,
|
||||
55, 56, 56, 59, 59, 61, 61, 62, 62, 64, 64, 67, 67, 69, 69, 70, 70, 73,
|
||||
73, 74, 74, 76, 76, 79, 79, 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91,
|
||||
91, 93, 93, 94, 94, 97, 97, 98, 98, 100, 100, 103, 103, 104, 104, 107,
|
||||
107, 109, 109, 110, 110, 112, 112, 115, 115, 117, 117, 118, 118, 121,
|
||||
121, 122, 122, 124, 124, 127, 127, 128, 128, 131, 131, 133, 133, 134,
|
||||
134, 137, 137, 138, 138, 140, 140, 143, 143, 145, 145, 146, 146, 148,
|
||||
148, 151, 151, 152, 152, 155, 155, 157, 157, 158, 158, 161, 161, 162,
|
||||
162, 164, 164, 167, 167, 168, 168, 171, 171, 173, 173, 174, 174, 176,
|
||||
176, 179, 179, 181, 181, 182, 182, 185, 185, 186, 186, 188, 188, 191,
|
||||
191, 193, 193, 194, 194, 196, 196, 199, 199, 200, 200, 203, 203, 205,
|
||||
205, 206, 206, 208, 208, 211, 211, 213, 213, 214, 214, 217, 217, 218,
|
||||
218, 220, 220, 223, 223, 224, 224, 227, 227, 229, 229, 230, 230, 233,
|
||||
233, 234, 234, 236, 236, 239, 239, 241, 241, 242, 242, 244, 244, 247,
|
||||
247, 248, 248, 251, 251, 253, 253, 254, 254]
|
||||
tmp = array.array("B")
|
||||
tmp.fromstring(key)
|
||||
for i, v in enumerate(tmp):
|
||||
tmp[i] = _lut[v]
|
||||
return tmp.tostring()
|
||||
|
||||
|
||||
CryptoAlgo.add_algo(0x6603, name="DES3", keyLength=192, IVLength=64, blockLength=64, m2="des_ede3_cbc",
|
||||
keyFixup=des_set_odd_parity)
|
||||
CryptoAlgo.add_algo(0x6609, name="DES2", keyLength=128, IVLength=64, blockLength=64, m2="des_ede_cbc",
|
||||
keyFixup=des_set_odd_parity)
|
||||
CryptoAlgo.add_algo(0x6611, name="AES", keyLength=128, IVLength=128, blockLength=128, m2="aes_128_cbc")
|
||||
CryptoAlgo.add_algo(0x660e, name="AES-128", keyLength=128, IVLength=128, blockLength=128, m2="aes_128_cbc")
|
||||
CryptoAlgo.add_algo(0x660f, name="AES-192", keyLength=192, IVLength=128, blockLength=128, m2="aes_192_cbc")
|
||||
CryptoAlgo.add_algo(0x6610, name="AES-256", keyLength=256, IVLength=128, blockLength=128, m2="aes_256_cbc")
|
||||
CryptoAlgo.add_algo(0x6601, name="DES", keyLength=64, IVLength=64, blockLength=64, m2="des_cbc",
|
||||
keyFixup=des_set_odd_parity)
|
||||
|
||||
CryptoAlgo.add_algo(0x8009, name="HMAC", digestLength=160, blockLength=512)
|
||||
|
||||
CryptoAlgo.add_algo(0x8001, name="md2", digestLength=128, blockLength=128)
|
||||
CryptoAlgo.add_algo(0x8002, name="md4", digestLength=128, blockLength=512)
|
||||
CryptoAlgo.add_algo(0x8003, name="md5", digestLength=128, blockLength=512)
|
||||
|
||||
CryptoAlgo.add_algo(0x8004, name="sha1", digestLength=160, blockLength=512)
|
||||
CryptoAlgo.add_algo(0x800c, name="sha256", digestLength=256, blockLength=512)
|
||||
CryptoAlgo.add_algo(0x800d, name="sha384", digestLength=384, blockLength=1024)
|
||||
CryptoAlgo.add_algo(0x800e, name="sha512", digestLength=512, blockLength=1024)
|
||||
|
||||
|
||||
def CryptSessionKeyXP(masterkey, nonce, hashAlgo, entropy=None, strongPassword=None):
|
||||
"""Computes the decryption key for XP DPAPI blob, given the masterkey and optional information.
|
||||
|
||||
This implementation relies on a faulty implementation from Microsoft that does not respect the HMAC RFC.
|
||||
Instead of updating the inner pad, we update the outer pad...
|
||||
This algorithm is also used when checking the HMAC for integrity after decryption
|
||||
|
||||
:param masterkey: decrypted masterkey (should be 64 bytes long)
|
||||
:param nonce: this is the nonce contained in the blob or the HMAC in the blob (integrity check)
|
||||
:param entropy: this is the optional entropy from CryptProtectData() API
|
||||
:param strongPassword: optional password used for decryption or the blob itself (integrity check)
|
||||
:returns: decryption key
|
||||
:rtype : str
|
||||
"""
|
||||
if len(masterkey) > 20:
|
||||
masterkey = hashlib.sha1(masterkey).digest()
|
||||
|
||||
masterkey += "\x00" * hashAlgo.blockSize
|
||||
ipad = "".join(chr(ord(masterkey[i]) ^ 0x36) for i in range(hashAlgo.blockSize))
|
||||
opad = "".join(chr(ord(masterkey[i]) ^ 0x5c) for i in range(hashAlgo.blockSize))
|
||||
digest = hashlib.new(hashAlgo.name)
|
||||
digest.update(ipad)
|
||||
digest.update(nonce)
|
||||
tmp = digest.digest()
|
||||
|
||||
digest = hashlib.new(hashAlgo.name)
|
||||
digest.update(opad)
|
||||
digest.update(tmp)
|
||||
if entropy is not None:
|
||||
digest.update(entropy)
|
||||
if strongPassword is not None:
|
||||
digest.update(strongPassword)
|
||||
return digest.digest()
|
||||
|
||||
|
||||
def CryptSessionKeyWin7(masterkey, nonce, hashAlgo, entropy=None, strongPassword=None):
|
||||
"""Computes the decryption key for XP DPAPI blob, given the masterkey and optional information.
|
||||
|
||||
This implementation relies on an RFC compliant HMAC implementation
|
||||
This algorithm is also used when checking the HMAC for integrity after decryption
|
||||
|
||||
:param masterkey: decrypted masterkey (should be 64 bytes long)
|
||||
:param nonce: this is the nonce contained in the blob or the HMAC in the blob (integrity check)
|
||||
:param entropy: this is the optional entropy from CryptProtectData() API
|
||||
:param strongPassword: optional password used for decryption or the blob itself (integrity check)
|
||||
:returns: decryption key
|
||||
:rtype : str
|
||||
"""
|
||||
if len(masterkey) > 20:
|
||||
masterkey = hashlib.sha1(masterkey).digest()
|
||||
|
||||
digest = M2Crypto.EVP.HMAC(masterkey, hashAlgo.name)
|
||||
digest.update(nonce)
|
||||
if entropy is not None:
|
||||
digest.update(entropy)
|
||||
if strongPassword is not None:
|
||||
digest.update(strongPassword)
|
||||
return digest.final()
|
||||
|
||||
|
||||
def CryptDeriveKey(h, cipherAlgo, hashAlgo):
|
||||
"""Internal use. Mimics the corresponding native Microsoft function"""
|
||||
if len(h) > hashAlgo.blockSize:
|
||||
h = hashlib.new(hashAlgo.name, h).digest()
|
||||
if len(h) >= cipherAlgo.keyLength:
|
||||
return h
|
||||
h += "\x00" * hashAlgo.blockSize
|
||||
ipad = "".join(chr(ord(h[i]) ^ 0x36) for i in range(hashAlgo.blockSize))
|
||||
opad = "".join(chr(ord(h[i]) ^ 0x5c) for i in range(hashAlgo.blockSize))
|
||||
k = hashlib.new(hashAlgo.name, ipad).digest() + hashlib.new(hashAlgo.name, opad).digest()
|
||||
k = cipherAlgo.do_fixup_key(k)
|
||||
return k
|
||||
|
||||
|
||||
def decrypt_lsa_key_nt5(lsakey, syskey):
|
||||
"""This function decrypts the LSA key using the syskey"""
|
||||
dg = hashlib.md5()
|
||||
dg.update(syskey)
|
||||
for i in xrange(1000):
|
||||
dg.update(lsakey[60:76])
|
||||
arcfour = M2Crypto.RC4.RC4(dg.digest())
|
||||
deskey = arcfour.update(lsakey[12:60]) + arcfour.final()
|
||||
return [deskey[16 * x:16 * (x + 1)] for x in xrange(3)]
|
||||
|
||||
|
||||
def decrypt_lsa_key_nt6(lsakey, syskey):
|
||||
"""This function decrypts the LSA keys using the syskey"""
|
||||
dg = hashlib.sha256()
|
||||
dg.update(syskey)
|
||||
for i in xrange(1000):
|
||||
dg.update(lsakey[28:60])
|
||||
c = M2Crypto.EVP.Cipher(alg="aes_256_ecb", key=dg.digest(), iv="", op=M2Crypto.decrypt)
|
||||
c.set_padding(0)
|
||||
keys = c.update(lsakey[60:]) + c.final()
|
||||
size = struct.unpack_from("<L", keys)[0]
|
||||
keys = keys[16:16 + size]
|
||||
currentkey = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", keys[4:20])
|
||||
nb = struct.unpack("<L", keys[24:28])[0]
|
||||
off = 28
|
||||
kd = {}
|
||||
for i in xrange(nb):
|
||||
g = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", keys[off:off + 16])
|
||||
t, l = struct.unpack_from("<2L", keys[off + 16:])
|
||||
k = keys[off + 24:off + 24 + l]
|
||||
kd[g] = {"type": t, "key": k}
|
||||
off += 24 + l
|
||||
return (currentkey, kd)
|
||||
|
||||
|
||||
def SystemFunction005(secret, key):
|
||||
"""This function is used to decrypt LSA secrets.
|
||||
Reproduces the corresponding Windows internal function.
|
||||
Taken from creddump project https://code.google.com/p/creddump/
|
||||
"""
|
||||
decrypted_data = ''
|
||||
j = 0
|
||||
algo = CryptoAlgo(0x6603)
|
||||
for i in range(0, len(secret), 8):
|
||||
enc_block = secret[i:i + 8]
|
||||
block_key = key[j:j + 7]
|
||||
des_key = []
|
||||
des_key.append(ord(block_key[0]) >> 1)
|
||||
des_key.append(((ord(block_key[0]) & 0x01) << 6) | (ord(block_key[1]) >> 2))
|
||||
des_key.append(((ord(block_key[1]) & 0x03) << 5) | (ord(block_key[2]) >> 3))
|
||||
des_key.append(((ord(block_key[2]) & 0x07) << 4) | (ord(block_key[3]) >> 4))
|
||||
des_key.append(((ord(block_key[3]) & 0x0F) << 3) | (ord(block_key[4]) >> 5))
|
||||
des_key.append(((ord(block_key[4]) & 0x1F) << 2) | (ord(block_key[5]) >> 6))
|
||||
des_key.append(((ord(block_key[5]) & 0x3F) << 1) | (ord(block_key[6]) >> 7))
|
||||
des_key.append(ord(block_key[6]) & 0x7F)
|
||||
des_key = algo.do_fixup_key("".join([chr(x << 1) for x in des_key]))
|
||||
|
||||
cipher = M2Crypto.EVP.Cipher(alg="des_ecb", key=des_key, iv="", op=M2Crypto.decrypt)
|
||||
cipher.set_padding(0)
|
||||
decrypted_data += cipher.update(enc_block) + cipher.final()
|
||||
j += 7
|
||||
if len(key[j:j + 7]) < 7:
|
||||
j = len(key[j:j + 7])
|
||||
dec_data_len = struct.unpack("<L", decrypted_data[:4])[0]
|
||||
return decrypted_data[8:8 + dec_data_len]
|
||||
|
||||
|
||||
def decrypt_lsa_secret(secret, lsa_keys):
|
||||
"""This function replaces SystemFunction005 for newer Windows"""
|
||||
keyid = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", secret[4:20])
|
||||
if keyid not in lsa_keys:
|
||||
return None
|
||||
algo = struct.unpack("<L", secret[20:24])[0]
|
||||
dg = hashlib.sha256()
|
||||
dg.update(lsa_keys[keyid]["key"])
|
||||
for i in xrange(1000):
|
||||
dg.update(secret[28:60])
|
||||
c = M2Crypto.EVP.Cipher(alg="aes_256_ecb", key=dg.digest(), iv="", op=M2Crypto.decrypt)
|
||||
c.set_padding(0)
|
||||
clear = c.update(secret[60:]) + c.final()
|
||||
size = struct.unpack_from("<L", clear)[0]
|
||||
return clear[16:16 + size]
|
||||
|
||||
|
||||
def pbkdf2(passphrase, salt, keylen, iterations, digest='sha1'):
|
||||
"""Implementation of PBKDF2 that allows specifying digest algorithm.
|
||||
|
||||
Returns the corresponding expanded key which is keylen long.
|
||||
"""
|
||||
print(f'PKBF of {passphrase} -- {salt} -- {keylen} -- {iterations} -- {digest}')
|
||||
buff = b""
|
||||
i = 1
|
||||
while len(buff) < keylen:
|
||||
U = salt + struct.pack("!L", i)
|
||||
i += 1
|
||||
derived = M2Crypto.EVP.hmac(passphrase, U, digest)
|
||||
for r in xrange(iterations - 1):
|
||||
actual = M2Crypto.EVP.hmac(passphrase, derived, digest)
|
||||
#print(f'R:{r} :{len(derived)} {derived} : {len(actual)} {actual}')
|
||||
#derived = ''.join([chr(ord(x) ^ ord(y)) for (x, y) in zip(derived, actual)])
|
||||
derived = ''.join([chr(x ^ y) for (x, y) in zip(derived, actual)]).encode('utf8')
|
||||
#print(f'r{r} - {len(derived)} : {derived}')
|
||||
buff += derived
|
||||
#print('ret')
|
||||
return buff[:int(keylen)]
|
||||
|
||||
|
||||
def derivePwdHash(pwdhash, userSID, digest='sha1'):
|
||||
"""Internal use. Computes the encryption key from a user's password hash"""
|
||||
return M2Crypto.EVP.hmac(pwdhash, (userSID + "\0").encode("UTF-16LE"), digest)
|
||||
|
||||
|
||||
def dataDecrypt(cipherAlgo, hashAlgo, raw, encKey, iv, rounds):
|
||||
"""Internal use. Decrypts data stored in DPAPI structures."""
|
||||
|
||||
hname = {"HMAC": "sha1"}.get(hashAlgo.name, hashAlgo.name)
|
||||
print(hname)
|
||||
derived = pbkdf2(encKey, iv, cipherAlgo.keyLength + cipherAlgo.ivLength, rounds, hname)
|
||||
print(f'derived {derived}')
|
||||
key, iv = derived[:cipherAlgo.keyLength], derived[cipherAlgo.keyLength:]
|
||||
key = key[:cipherAlgo.keyLength]
|
||||
iv = iv[:cipherAlgo.ivLength]
|
||||
print(key)
|
||||
print(iv)
|
||||
cipher = M2Crypto.EVP.Cipher(cipherAlgo.m2name, key, iv, M2Crypto.decrypt, 0)
|
||||
cipher.set_padding(0)
|
||||
print(cipher)
|
||||
cleartxt = cipher.update(raw) + cipher.final()
|
||||
return cleartxt
|
||||
|
||||
|
||||
def DPAPIHmac(hashAlgo, pwdhash, hmacSalt, value):
|
||||
"""Internal function used to compute HMACs of DPAPI structures"""
|
||||
hname = {"HMAC": "sha1"}.get(hashAlgo.name, hashAlgo.name)
|
||||
encKey = M2Crypto.EVP.HMAC(pwdhash, hname)
|
||||
encKey.update(hmacSalt)
|
||||
encKey = encKey.final()
|
||||
rv = M2Crypto.EVP.HMAC(encKey, hname)
|
||||
rv.update(value)
|
||||
return rv.final()
|
||||
|
||||
# vim:ts=4:expandtab:sw=4
|
@ -1,128 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#############################################################################
|
||||
## ##
|
||||
## This file is part of DPAPIck ##
|
||||
## Windows DPAPI decryption & forensic toolkit ##
|
||||
## ##
|
||||
## ##
|
||||
## Copyright (C) 2010, 2011 Cassidian SAS. All rights reserved. ##
|
||||
## This document is the property of Cassidian SAS, it may not be copied or ##
|
||||
## circulated without prior licence ##
|
||||
## ##
|
||||
## Author: Jean-Michel Picod <jmichel.p@gmail.com> ##
|
||||
## ##
|
||||
## This program is distributed under GPLv3 licence (see LICENCE.txt) ##
|
||||
## ##
|
||||
#############################################################################
|
||||
|
||||
import struct
|
||||
|
||||
|
||||
class Eater(object):
|
||||
"""This class is a helper for parsing binary structures."""
|
||||
|
||||
def __init__(self, raw, offset=0, end=None, endianness="<"):
|
||||
self.raw = raw
|
||||
self.ofs = offset
|
||||
if end is None:
|
||||
end = len(raw)
|
||||
self.end = end
|
||||
self.endianness = endianness
|
||||
|
||||
def prepare_fmt(self, fmt):
|
||||
"""Internal use. Prepend endianness to the given format if it is not
|
||||
already specified.
|
||||
|
||||
fmt is a format string for struct.unpack()
|
||||
|
||||
Returns a tuple of the format string and the corresponding data size.
|
||||
|
||||
"""
|
||||
if fmt[0] not in ["<", ">", "!", "@"]:
|
||||
fmt = self.endianness+fmt
|
||||
return fmt, struct.calcsize(fmt)
|
||||
|
||||
def read(self, fmt):
|
||||
"""Parses data with the given format string without taking away bytes.
|
||||
|
||||
Returns an array of elements or just one element depending on fmt.
|
||||
|
||||
"""
|
||||
fmt, sz = self.prepare_fmt(fmt)
|
||||
v = struct.unpack_from(fmt, self.raw, self.ofs)
|
||||
if len(v) == 1:
|
||||
v = v[0]
|
||||
return v
|
||||
|
||||
def eat(self, fmt):
|
||||
"""Parses data with the given format string.
|
||||
|
||||
Returns an array of elements or just one element depending on fmt.
|
||||
|
||||
"""
|
||||
fmt, sz = self.prepare_fmt(fmt)
|
||||
v = struct.unpack_from(fmt, self.raw, self.ofs)
|
||||
if len(v) == 1:
|
||||
v = v[0]
|
||||
self.ofs += sz
|
||||
return v
|
||||
|
||||
def eat_string(self, length):
|
||||
"""Eats and returns a string of length characters"""
|
||||
return self.eat("%us" % length)
|
||||
|
||||
def eat_length_and_string(self, fmt):
|
||||
"""Eats and returns a string which length is obtained after eating
|
||||
an integer represented by fmt
|
||||
|
||||
"""
|
||||
l = self.eat(fmt)
|
||||
return self.eat_string(l)
|
||||
|
||||
def pop(self, fmt):
|
||||
"""Eats a structure represented by fmt from the end of raw data"""
|
||||
fmt, sz = self.prepare_fmt(fmt)
|
||||
self.end -= sz
|
||||
v = struct.unpack_from(fmt, self.raw, self.end)
|
||||
if len(v) == 1:
|
||||
v = v[0]
|
||||
return v
|
||||
|
||||
def pop_string(self, length):
|
||||
"""Pops and returns a string of length characters"""
|
||||
return self.pop("%us" % length)
|
||||
|
||||
def pop_length_and_string(self, fmt):
|
||||
"""Pops and returns a string which length is obtained after poping an
|
||||
integer represented by fmt.
|
||||
|
||||
"""
|
||||
l = self.pop(fmt)
|
||||
return self.pop_string(l)
|
||||
|
||||
def remain(self):
|
||||
"""Returns all the bytes that have not been eated nor poped yet."""
|
||||
return self.raw[self.ofs:self.end]
|
||||
|
||||
def eat_sub(self, length):
|
||||
"""Eats a sub-structure that is contained in the next length bytes"""
|
||||
sub = self.__class__(self.raw[self.ofs:self.ofs+length], endianness=self.endianness)
|
||||
self.ofs += length
|
||||
return sub
|
||||
|
||||
def __nonzero__(self):
|
||||
return self.ofs < self.end
|
||||
|
||||
|
||||
class DataStruct(object):
|
||||
"""Don't use this class unless you know what you are doing!"""
|
||||
|
||||
def __init__(self, raw=None):
|
||||
if raw is not None:
|
||||
self.parse(Eater(raw, endianness="<"))
|
||||
|
||||
def parse(self, eater_obj):
|
||||
raise NotImplementedError("This function must be implemented in subclasses")
|
||||
|
@ -1673,8 +1673,6 @@ class MySeatBelt:
|
||||
f"[{self.options.target_ip}] Found Directory %s -> doing nothing" % longname)
|
||||
else:
|
||||
self.logging.debug(f"[{self.options.target_ip}] Found file %s" % longname)
|
||||
if "CREDHIST" in longname:
|
||||
self.download_credhist(user, tmp_pwd, longname, type='USER')
|
||||
except Exception as ex:
|
||||
self.logging.debug(
|
||||
f"[{self.options.target_ip}] {bcolors.WARNING}Exception in get_masterkeys for {longname}{bcolors.ENDC}")
|
||||
@ -1736,8 +1734,6 @@ class MySeatBelt:
|
||||
self.logging.debug("Found (not SID) Directory %s" % longname)
|
||||
else:
|
||||
self.logging.debug("Found file %s" % longname)
|
||||
if "CREDHIST" in longname:
|
||||
self.download_credhist(user, tmp_pwd, longname, type='MACHINE')
|
||||
|
||||
except Exception as ex:
|
||||
self.logging.error(
|
||||
@ -1746,32 +1742,6 @@ class MySeatBelt:
|
||||
self.logging.debug(
|
||||
f"[{self.options.target_ip}] {bcolors.OKBLUE}[-] Gathered Masterkeys for {len(self.users)} users{bcolors.ENDC}")
|
||||
|
||||
def download_credhist(self, user, tmp_pwd, longname, type='MACHINE'):
|
||||
# Downloading file
|
||||
try:
|
||||
|
||||
self.logging.debug(
|
||||
f"[{self.options.target_ip}] [...] Downloading CREDHIST {user.username} {tmp_pwd} {longname}")
|
||||
# from DonPAPI.lib.dpapi_pick.credhist import CredHistFile
|
||||
# localfile = self.myfileops.get_file(ntpath.join(tmp_pwd, longname))
|
||||
'''f=open(localfile,'rb')
|
||||
credhistdata = f.read()
|
||||
f.close()
|
||||
myCredhistfile = CredHistFile(raw=credhistdata)
|
||||
|
||||
print(repr(myCredhistfile))
|
||||
#myCredhistfile = CredHistFile(raw=credhistdata)
|
||||
for username in self.options.credz:
|
||||
if username in user.username: # pour fonctionner aussi avec le .domain ou les sessions multiple citrix en user.domain.001 ?
|
||||
self.logging.debug(f"[{self.options.target_ip}] [...] Testing {len(self.options.credz[username])} credz for user {user.username} CREDHIST")
|
||||
for password in self.options.credz[username]:
|
||||
ret=myCredhistfile.decryptWithPassword(password)
|
||||
print(ret)
|
||||
'''
|
||||
except Exception as ex:
|
||||
self.logging.error(f"[{self.options.target_ip}] {bcolors.FAIL}Error in Decrypting Credhist{bcolors.ENDC}")
|
||||
self.logging.debug(ex)
|
||||
|
||||
def download_masterkey(self, user, path, guid, type):
|
||||
guid = guid.lower()
|
||||
if is_guid(guid):
|
||||
|
@ -29,10 +29,7 @@ python = "^3.9"
|
||||
impacket = "^0.9.23"
|
||||
pyasn = "^1.6.1"
|
||||
LnkParse3 = "^1.2.0"
|
||||
wheel = "^0.37.0"
|
||||
M2Crypto = "^0.38.0"
|
||||
pycryptodome = "^3.11.0"
|
||||
swig = "^4.1.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user