Exemple #1
0
class NTDSHashes(object):
    NAME_TO_INTERNAL = {
        'uSNCreated': 'ATTq131091',
        'uSNChanged': 'ATTq131192',
        'name': 'ATTm3',
        'objectGUID': 'ATTk589826',
        'objectSid': 'ATTr589970',
        'userAccountControl': 'ATTj589832',
        'primaryGroupID': 'ATTj589922',
        'accountExpires': 'ATTq589983',
        'loggeronCount': 'ATTj589993',
        'sAMAccountName': 'ATTm590045',
        'sAMAccountType': 'ATTj590126',
        'lastloggeronTimestamp': 'ATTq589876',
        'userPrincipalName': 'ATTm590480',
        'unicodePwd': 'ATTk589914',
        'dBCSPwd': 'ATTk589879',
        'ntPwdHistory': 'ATTk589918',
        'lmPwdHistory': 'ATTk589984',
        'pekList': 'ATTk590689',
    }

    INTERNAL_TO_NAME = dict((v, k) for k, v in NAME_TO_INTERNAL.iteritems())

    SAM_NORMAL_USER_ACCOUNT = 0x30000000
    SAM_MACHINE_ACCOUNT = 0x30000001
    SAM_TRUST_ACCOUNT = 0x30000002

    ACCOUNT_TYPES = (SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT,
                     SAM_TRUST_ACCOUNT)

    class PEK_KEY(Structure):
        structure = (
            ('Header', '8s=""'),
            ('KeyMaterial', '16s=""'),
            ('EncryptedPek', '52s=""'),
        )

    class CRYPTED_HASH(Structure):
        structure = (
            ('Header', '8s=""'),
            ('KeyMaterial', '16s=""'),
            ('EncryptedHash', '16s=""'),
        )

    class CRYPTED_HISTORY(Structure):
        structure = (
            ('Header', '8s=""'),
            ('KeyMaterial', '16s=""'),
            ('EncryptedHash', ':'),
        )

    def __init__(self, ntds_file, bootKey, history=False, noLMHash=True):
        self.__bootKey = bootKey
        self.__ntds_file = ntds_file
        self.__history = history
        self.__no_LMhash = noLMHash
        self.__tmpUsers = list()
        self.__PEK = None
        self.__cryptoCommon = CryptoCommon()
        self.__itemsFound = {}

        if not self.__ntds_file:
            return

        self.__ESEDB = ESENT_DB(self.__ntds_file, isRemote=True)
        self.__cursor = self.__ESEDB.openTable('datatable')

    def __getPek(self):
        logger.info('Searching for pekList, be patient')
        pek = None

        while True:
            record = self.__ESEDB.getNextRow(self.__cursor)

            if record is None:
                break
            elif record[self.NAME_TO_INTERNAL['pekList']] is not None:
                pek = record[self.NAME_TO_INTERNAL['pekList']].decode('hex')
                break
            elif record[self.NAME_TO_INTERNAL[
                    'sAMAccountType']] in self.ACCOUNT_TYPES:
                # Okey.. we found some users, but we're not yet ready to process them.
                # Let's just store them in a temp list
                self.__tmpUsers.append(record)

        if pek is not None:
            encryptedPek = self.PEK_KEY(pek)
            md5 = hashlib.new('md5')
            md5.update(self.__bootKey)

            for i in range(1000):
                md5.update(encryptedPek['KeyMaterial'])

            tmpKey = md5.digest()
            rc4 = ARC4.new(tmpKey)
            plainText = rc4.encrypt(encryptedPek['EncryptedPek'])
            self.__PEK = plainText[36:]

    def __removeRC4Layer(self, cryptedHash):
        md5 = hashlib.new('md5')
        md5.update(self.__PEK)
        md5.update(cryptedHash['KeyMaterial'])
        tmpKey = md5.digest()
        rc4 = ARC4.new(tmpKey)
        plainText = rc4.encrypt(cryptedHash['EncryptedHash'])

        return plainText

    def __removeDESLayer(self, cryptedHash, rid):
        Key1, Key2 = self.__cryptoCommon.deriveKey(int(rid))
        Crypt1 = DES.new(Key1, DES.MODE_ECB)
        Crypt2 = DES.new(Key2, DES.MODE_ECB)
        decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt(
            cryptedHash[8:])

        return decryptedHash

    def __decryptHash(self, record):
        logger.debug('Decrypting hash for user: %s' %
                     record[self.NAME_TO_INTERNAL['name']])

        sid = SAMR_RPC_SID(
            record[self.NAME_TO_INTERNAL['objectSid']].decode('hex'))
        rid = sid.formatCanonical().split('-')[-1]

        if record[self.NAME_TO_INTERNAL['dBCSPwd']] is not None:
            encryptedLMHash = self.CRYPTED_HASH(
                record[self.NAME_TO_INTERNAL['dBCSPwd']].decode('hex'))
            tmpLMHash = self.__removeRC4Layer(encryptedLMHash)
            LMHash = self.__removeDESLayer(tmpLMHash, rid)
        else:
            LMHash = ntlm.LMOWFv1('', '')
            encryptedLMHash = None

        if record[self.NAME_TO_INTERNAL['unicodePwd']] is not None:
            encryptedNTHash = self.CRYPTED_HASH(
                record[self.NAME_TO_INTERNAL['unicodePwd']].decode('hex'))
            tmpNTHash = self.__removeRC4Layer(encryptedNTHash)
            NTHash = self.__removeDESLayer(tmpNTHash, rid)
        else:
            NTHash = ntlm.NTOWFv1('', '')
            encryptedNTHash = None

        if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None:
            domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split(
                '@')[-1]
            userName = '******' % (
                domain, record[self.NAME_TO_INTERNAL['sAMAccountName']])
        else:
            userName = '******' % record[self.NAME_TO_INTERNAL['sAMAccountName']]

        answer = "%s:%s:%s:%s:::" % (userName, rid, LMHash.encode('hex'),
                                     NTHash.encode('hex'))
        self.__itemsFound[record[self.NAME_TO_INTERNAL['objectSid']].decode(
            'hex')] = answer
        print answer

        if self.__history:
            LMHistory = []
            NTHistory = []

            if record[self.NAME_TO_INTERNAL['lmPwdHistory']] is not None:
                lmPwdHistory = record[self.NAME_TO_INTERNAL['lmPwdHistory']]
                encryptedLMHistory = self.CRYPTED_HISTORY(record[
                    self.NAME_TO_INTERNAL['lmPwdHistory']].decode('hex'))
                tmpLMHistory = self.__removeRC4Layer(encryptedLMHistory)

                for i in range(0, len(tmpLMHistory) / 16):
                    LMHash = self.__removeDESLayer(
                        tmpLMHistory[i * 16:(i + 1) * 16], rid)
                    LMHistory.append(LMHash)

            if record[self.NAME_TO_INTERNAL['ntPwdHistory']] is not None:
                ntPwdHistory = record[self.NAME_TO_INTERNAL['ntPwdHistory']]
                encryptedNTHistory = self.CRYPTED_HISTORY(record[
                    self.NAME_TO_INTERNAL['ntPwdHistory']].decode('hex'))
                tmpNTHistory = self.__removeRC4Layer(encryptedNTHistory)

                for i in range(0, len(tmpNTHistory) / 16):
                    NTHash = self.__removeDESLayer(
                        tmpNTHistory[i * 16:(i + 1) * 16], rid)
                    NTHistory.append(NTHash)

            for i, (LMHash, NTHash) in enumerate(
                    map(lambda l, n: (l, n)
                        if l else ('', n), LMHistory[1:], NTHistory[1:])):
                if self.__no_LMhash:
                    lmhash = ntlm.LMOWFv1('', '').encode('hex')
                else:
                    lmhash = LMHash.encode('hex')

                answer = "%s_history%d:%s:%s:%s:::" % (
                    userName, i, rid, lmhash, NTHash.encode('hex'))
                self.__itemsFound[
                    record[self.NAME_TO_INTERNAL['objectSid']].decode('hex') +
                    str(i)] = answer
                print answer

    def dumpNTDS(self):
        if not self.__ntds_file:
            return

        logger.info(
            'Dumping domain users\' hashes (DOMAIN\\UID:RID:LMhash:NThash), wait..'
        )

        # We start getting rows from the table aiming at reaching
        # the pekList. If we find users records we stored them
        # in a temp list for later process
        self.__getPek()

        if self.__PEK is not None:
            logger.info('PEK found and decrypted: 0x%s' %
                        self.__PEK.encode('hex'))
            logger.info('Reading and decrypting hashes from %s' %
                        self.__ntds_file)

            # First of all, if we have users already cached, let's decrypt their hashes
            for record in self.__tmpUsers:
                self.__decryptHash(record)

            # Now let's keep moving through the NTDS file and decrypting what we find
            while True:
                try:
                    record = self.__ESEDB.getNextRow(self.__cursor)
                except:
                    logger.error(
                        'Error while calling getNextRow(), trying the next one'
                    )
                    continue

                if record is None:
                    break

                try:
                    if record[self.NAME_TO_INTERNAL[
                            'sAMAccountType']] in self.ACCOUNT_TYPES:
                        self.__decryptHash(record)
                except Exception, e:
                    try:
                        logger.error('Error while processing row for user %s' %
                                     record[self.NAME_TO_INTERNAL['name']])
                        logger.error(str(e))
                    except:
                        logger.error('Error while processing row!')
                        logger.error(str(e))
Exemple #2
0
class NTDSHashes:
    NAME_TO_INTERNAL = {
        'uSNCreated':'ATTq131091',
        'uSNChanged':'ATTq131192',
        'name':'ATTm3',
        'objectGUID':'ATTk589826',
        'objectSid':'ATTr589970',
        'userAccountControl':'ATTj589832',
        'primaryGroupID':'ATTj589922',
        'accountExpires':'ATTq589983',
        'logonCount':'ATTj589993',
        'sAMAccountName':'ATTm590045',
        'sAMAccountType':'ATTj590126',
        'lastLogonTimestamp':'ATTq589876',
        'userPrincipalName':'ATTm590480',
        'unicodePwd':'ATTk589914',
        'dBCSPwd':'ATTk589879',
        'ntPwdHistory':'ATTk589918',
        'lmPwdHistory':'ATTk589984',
        'pekList':'ATTk590689',
        'supplementalCredentials':'ATTk589949',
        'pwdLastSet':'ATTq589920',
    }

    NAME_TO_ATTRTYP = {
        'userPrincipalName': 0x90290,
        'sAMAccountName': 0x900DD,
        'unicodePwd': 0x9005A,
        'dBCSPwd': 0x90037,
        'ntPwdHistory': 0x9005E,
        'lmPwdHistory': 0x900A0,
        'supplementalCredentials': 0x9007D,
        'objectSid': 0x90092,
    }

    ATTRTYP_TO_ATTID = {
        'userPrincipalName': '1.2.840.113556.1.4.656',
        'sAMAccountName': '1.2.840.113556.1.4.221',
        'unicodePwd': '1.2.840.113556.1.4.90',
        'dBCSPwd': '1.2.840.113556.1.4.55',
        'ntPwdHistory': '1.2.840.113556.1.4.94',
        'lmPwdHistory': '1.2.840.113556.1.4.160',
        'supplementalCredentials': '1.2.840.113556.1.4.125',
        'objectSid': '1.2.840.113556.1.4.146',
        'pwdLastSet': '1.2.840.113556.1.4.96',
    }

    KERBEROS_TYPE = {
        1:'dec-cbc-crc',
        3:'des-cbc-md5',
        17:'aes128-cts-hmac-sha1-96',
        18:'aes256-cts-hmac-sha1-96',
        0xffffff74:'rc4_hmac',
    }

    INTERNAL_TO_NAME = dict((v,k) for k,v in NAME_TO_INTERNAL.iteritems())

    SAM_NORMAL_USER_ACCOUNT = 0x30000000
    SAM_MACHINE_ACCOUNT     = 0x30000001
    SAM_TRUST_ACCOUNT       = 0x30000002

    ACCOUNT_TYPES = ( SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT, SAM_TRUST_ACCOUNT)

    class PEKLIST_ENC(Structure):
        structure = (
            ('Header','8s=""'),
            ('KeyMaterial','16s=""'),
            ('EncryptedPek',':'),
        )

    class PEKLIST_PLAIN(Structure):
        structure = (
            ('Header','32s=""'),
            ('DecryptedPek',':'),
        )

    class PEK_KEY(Structure):
        structure = (
            ('Header','1s=""'),
            ('Padding','3s=""'),
            ('Key','16s=""'),
        )

    class CRYPTED_HASH(Structure):
        structure = (
            ('Header','8s=""'),
            ('KeyMaterial','16s=""'),
            ('EncryptedHash','16s=""'),
        )

    class CRYPTED_HISTORY(Structure):
        structure = (
            ('Header','8s=""'),
            ('KeyMaterial','16s=""'),
            ('EncryptedHash',':'),
        )

    class CRYPTED_BLOB(Structure):
        structure = (
            ('Header','8s=""'),
            ('KeyMaterial','16s=""'),
            ('EncryptedHash',':'),
        )

    def __init__(self, ntdsFile, bootKey, logger, isRemote=False, history=False, noLMHash=True, remoteOps=None,
                 useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None):
        self.__bootKey = bootKey
        self.__logger = logger
        self.__NTDS = ntdsFile
        self.__history = history
        self.__noLMHash = noLMHash
        self.__useVSSMethod = useVSSMethod
        self.__remoteOps = remoteOps
        self.__pwdLastSet = pwdLastSet
        if self.__NTDS is not None:
            self.__ESEDB = ESENT_DB(ntdsFile, isRemote = isRemote)
            self.__cursor = self.__ESEDB.openTable('datatable')
        self.__tmpUsers = list()
        self.__PEK = list() 
        self.__cryptoCommon = CryptoCommon()
        self.__kerberosKeys = OrderedDict()
        self.__clearTextPwds = OrderedDict()
        self.__justNTLM = justNTLM
        self.__savedSessionFile = resumeSession
        self.__resumeSessionFile = None
        self.__outputFileName = outputFileName

    def getResumeSessionFile(self):
        return self.__resumeSessionFile

    def __getPek(self):
        logging.info('Searching for pekList, be patient')
        peklist = None
        while True:
            record = self.__ESEDB.getNextRow(self.__cursor)
            if record is None:
                break
            elif record[self.NAME_TO_INTERNAL['pekList']] is not None:
                peklist =  unhexlify(record[self.NAME_TO_INTERNAL['pekList']])
                break
            elif record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES:
                # Okey.. we found some users, but we're not yet ready to process them.
                # Let's just store them in a temp list
                self.__tmpUsers.append(record)
        
        if peklist is not None:
            encryptedPekList = self.PEKLIST_ENC(peklist)
            md5 = hashlib.new('md5')
            md5.update(self.__bootKey)
            for i in range(1000):
                md5.update(encryptedPekList['KeyMaterial'])
            tmpKey = md5.digest()
            rc4 = ARC4.new(tmpKey)
            decryptedPekList = self.PEKLIST_PLAIN(rc4.encrypt(encryptedPekList['EncryptedPek']))
            PEKLen = len(self.PEK_KEY())
            for i in range(len( decryptedPekList['DecryptedPek'] ) / PEKLen ):
                cursor = i * PEKLen
                pek = self.PEK_KEY(decryptedPekList['DecryptedPek'][cursor:cursor+PEKLen])
                logging.info("PEK # %d found and decrypted: %s", i, hexlify(pek['Key']))
                self.__PEK.append(pek['Key'])

    def __removeRC4Layer(self, cryptedHash):
        md5 = hashlib.new('md5')
        # PEK index can be found on header of each ciphered blob (pos 8-10)
        pekIndex = hexlify(cryptedHash['Header'])
        md5.update(self.__PEK[int(pekIndex[8:10])])
        md5.update(cryptedHash['KeyMaterial'])
        tmpKey = md5.digest()
        rc4 = ARC4.new(tmpKey)
        plainText = rc4.encrypt(cryptedHash['EncryptedHash'])

        return plainText

    def __removeDESLayer(self, cryptedHash, rid):
        Key1,Key2 = self.__cryptoCommon.deriveKey(int(rid))

        Crypt1 = DES.new(Key1, DES.MODE_ECB)
        Crypt2 = DES.new(Key2, DES.MODE_ECB)

        decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt(cryptedHash[8:])

        return decryptedHash

    def __fileTimeToDateTime(self, t):
        t -= 116444736000000000
        t /= 10000000
        if t < 0:
            return 'never'
        else:
            dt = datetime.fromtimestamp(t)
            return dt.strftime("%Y-%m-%d %H:%M")

    def __decryptSupplementalInfo(self, record, prefixTable=None, keysFile=None, clearTextFile=None):
        # This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures
        haveInfo = False
        if self.__useVSSMethod is True:
            if record[self.NAME_TO_INTERNAL['supplementalCredentials']] is not None:
                if len(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']])) > 24:
                    if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None:
                        domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1]
                        userName = '******' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']])
                    else:
                        userName = '******' % record[self.NAME_TO_INTERNAL['sAMAccountName']]
                    cipherText = self.CRYPTED_BLOB(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']]))
                    plainText = self.__removeRC4Layer(cipherText)
                    haveInfo = True
        else:
            domain = None
            userName = None
            for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']:
                try:
                    attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp'])
                    LOOKUP_TABLE = self.ATTRTYP_TO_ATTID
                except Exception, e:
                    logging.debug('Failed to execute OidFromAttid with error %s' % e)
                    # Fallbacking to fixed table and hope for the best
                    attId = attr['attrTyp']
                    LOOKUP_TABLE = self.NAME_TO_ATTRTYP

                if attId == LOOKUP_TABLE['userPrincipalName']:
                    if attr['AttrVal']['valCount'] > 0:
                        try:
                            domain = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le').split('@')[-1]
                        except:
                            domain = None
                    else:
                        domain = None
                elif attId == LOOKUP_TABLE['sAMAccountName']:
                    if attr['AttrVal']['valCount'] > 0:
                        try:
                            userName = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le')
                        except:
                            logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
                            userName = '******'
                    else:
                        logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
                        userName = '******'
                if attId == LOOKUP_TABLE['supplementalCredentials']:
                    if attr['AttrVal']['valCount'] > 0:
                        blob = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
                        plainText = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), blob)
                        if len(plainText) > 24:
                            haveInfo = True
            if domain is not None:
                userName = '******' % (domain, userName)

        if haveInfo is True:
            try:
                userProperties = samr.USER_PROPERTIES(plainText)
            except:
                # On some old w2k3 there might be user properties that don't
                # match [MS-SAMR] structure, discarding them
                return
            propertiesData = userProperties['UserProperties']
            for propertyCount in range(userProperties['PropertyCount']):
                userProperty = samr.USER_PROPERTY(propertiesData)
                propertiesData = propertiesData[len(userProperty):]
                # For now, we will only process Newer Kerberos Keys and CLEARTEXT
                if userProperty['PropertyName'].decode('utf-16le') == 'Primary:Kerberos-Newer-Keys':
                    propertyValueBuffer = unhexlify(userProperty['PropertyValue'])
                    kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer)
                    data = kerbStoredCredentialNew['Buffer']
                    for credential in range(kerbStoredCredentialNew['CredentialCount']):
                        keyDataNew = samr.KERB_KEY_DATA_NEW(data)
                        data = data[len(keyDataNew):]
                        keyValue = propertyValueBuffer[keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']]

                        if  self.KERBEROS_TYPE.has_key(keyDataNew['KeyType']):
                            answer =  "%s:%s:%s" % (userName, self.KERBEROS_TYPE[keyDataNew['KeyType']],hexlify(keyValue))
                        else:
                            answer =  "%s:%s:%s" % (userName, hex(keyDataNew['KeyType']),hexlify(keyValue))
                        # We're just storing the keys, not printing them, to make the output more readable
                        # This is kind of ugly... but it's what I came up with tonight to get an ordered
                        # set :P. Better ideas welcomed ;)
                        self.__kerberosKeys[answer] = None
                        if keysFile is not None:
                            self.__writeOutput(keysFile, answer + '\n')
                elif userProperty['PropertyName'].decode('utf-16le') == 'Primary:CLEARTEXT':
                    # [MS-SAMR] 3.1.1.8.11.5 Primary:CLEARTEXT Property
                    # This credential type is the cleartext password. The value format is the UTF-16 encoded cleartext password.
                    answer = "%s:CLEARTEXT:%s" % (userName, unhexlify(userProperty['PropertyValue']).decode('utf-16le'))
                    self.__clearTextPwds[answer] = None
                    if clearTextFile is not None:
                        self.__writeOutput(clearTextFile, answer + '\n')

            if clearTextFile is not None:
                clearTextFile.flush()
            if keysFile is not None:
                keysFile.flush()
Exemple #3
0
class NTDSHashes:
    NAME_TO_INTERNAL = {
        'uSNCreated': 'ATTq131091',
        'uSNChanged': 'ATTq131192',
        'name': 'ATTm3',
        'objectGUID': 'ATTk589826',
        'objectSid': 'ATTr589970',
        'userAccountControl': 'ATTj589832',
        'primaryGroupID': 'ATTj589922',
        'accountExpires': 'ATTq589983',
        'logonCount': 'ATTj589993',
        'sAMAccountName': 'ATTm590045',
        'sAMAccountType': 'ATTj590126',
        'lastLogonTimestamp': 'ATTq589876',
        'userPrincipalName': 'ATTm590480',
        'unicodePwd': 'ATTk589914',
        'dBCSPwd': 'ATTk589879',
        'ntPwdHistory': 'ATTk589918',
        'lmPwdHistory': 'ATTk589984',
        'pekList': 'ATTk590689',
        'supplementalCredentials': 'ATTk589949',
        'pwdLastSet': 'ATTq589920',
    }

    NAME_TO_ATTRTYP = {
        'userPrincipalName': 0x90290,
        'sAMAccountName': 0x900DD,
        'unicodePwd': 0x9005A,
        'dBCSPwd': 0x90037,
        'ntPwdHistory': 0x9005E,
        'lmPwdHistory': 0x900A0,
        'supplementalCredentials': 0x9007D,
        'objectSid': 0x90092,
    }

    ATTRTYP_TO_ATTID = {
        'userPrincipalName': '1.2.840.113556.1.4.656',
        'sAMAccountName': '1.2.840.113556.1.4.221',
        'unicodePwd': '1.2.840.113556.1.4.90',
        'dBCSPwd': '1.2.840.113556.1.4.55',
        'ntPwdHistory': '1.2.840.113556.1.4.94',
        'lmPwdHistory': '1.2.840.113556.1.4.160',
        'supplementalCredentials': '1.2.840.113556.1.4.125',
        'objectSid': '1.2.840.113556.1.4.146',
        'pwdLastSet': '1.2.840.113556.1.4.96',
    }

    KERBEROS_TYPE = {
        1: 'dec-cbc-crc',
        3: 'des-cbc-md5',
        17: 'aes128-cts-hmac-sha1-96',
        18: 'aes256-cts-hmac-sha1-96',
        0xffffff74: 'rc4_hmac',
    }

    INTERNAL_TO_NAME = dict((v, k) for k, v in NAME_TO_INTERNAL.iteritems())

    SAM_NORMAL_USER_ACCOUNT = 0x30000000
    SAM_MACHINE_ACCOUNT = 0x30000001
    SAM_TRUST_ACCOUNT = 0x30000002

    ACCOUNT_TYPES = (SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT,
                     SAM_TRUST_ACCOUNT)

    class PEKLIST_ENC(Structure):
        structure = (
            ('Header', '8s=""'),
            ('KeyMaterial', '16s=""'),
            ('EncryptedPek', ':'),
        )

    class PEKLIST_PLAIN(Structure):
        structure = (
            ('Header', '32s=""'),
            ('DecryptedPek', ':'),
        )

    class PEK_KEY(Structure):
        structure = (
            ('Header', '1s=""'),
            ('Padding', '3s=""'),
            ('Key', '16s=""'),
        )

    class CRYPTED_HASH(Structure):
        structure = (
            ('Header', '8s=""'),
            ('KeyMaterial', '16s=""'),
            ('EncryptedHash', '16s=""'),
        )

    class CRYPTED_HISTORY(Structure):
        structure = (
            ('Header', '8s=""'),
            ('KeyMaterial', '16s=""'),
            ('EncryptedHash', ':'),
        )

    class CRYPTED_BLOB(Structure):
        structure = (
            ('Header', '8s=""'),
            ('KeyMaterial', '16s=""'),
            ('EncryptedHash', ':'),
        )

    def __init__(self,
                 ntdsFile,
                 bootKey,
                 logger,
                 isRemote=False,
                 history=False,
                 noLMHash=True,
                 remoteOps=None,
                 useVSSMethod=False,
                 justNTLM=False,
                 pwdLastSet=False,
                 resumeSession=None,
                 outputFileName=None):
        self.__bootKey = bootKey
        self.__logger = logger
        self.__NTDS = ntdsFile
        self.__history = history
        self.__noLMHash = noLMHash
        self.__useVSSMethod = useVSSMethod
        self.__remoteOps = remoteOps
        self.__pwdLastSet = pwdLastSet
        if self.__NTDS is not None:
            self.__ESEDB = ESENT_DB(ntdsFile, isRemote=isRemote)
            self.__cursor = self.__ESEDB.openTable('datatable')
        self.__tmpUsers = list()
        self.__PEK = list()
        self.__cryptoCommon = CryptoCommon()
        self.__kerberosKeys = OrderedDict()
        self.__clearTextPwds = OrderedDict()
        self.__justNTLM = justNTLM
        self.__savedSessionFile = resumeSession
        self.__resumeSessionFile = None
        self.__outputFileName = outputFileName

    def getResumeSessionFile(self):
        return self.__resumeSessionFile

    def __getPek(self):
        logging.info('Searching for pekList, be patient')
        peklist = None
        while True:
            record = self.__ESEDB.getNextRow(self.__cursor)
            if record is None:
                break
            elif record[self.NAME_TO_INTERNAL['pekList']] is not None:
                peklist = unhexlify(record[self.NAME_TO_INTERNAL['pekList']])
                break
            elif record[self.NAME_TO_INTERNAL[
                    'sAMAccountType']] in self.ACCOUNT_TYPES:
                # Okey.. we found some users, but we're not yet ready to process them.
                # Let's just store them in a temp list
                self.__tmpUsers.append(record)

        if peklist is not None:
            encryptedPekList = self.PEKLIST_ENC(peklist)
            md5 = hashlib.new('md5')
            md5.update(self.__bootKey)
            for i in range(1000):
                md5.update(encryptedPekList['KeyMaterial'])
            tmpKey = md5.digest()
            rc4 = ARC4.new(tmpKey)
            decryptedPekList = self.PEKLIST_PLAIN(
                rc4.encrypt(encryptedPekList['EncryptedPek']))
            PEKLen = len(self.PEK_KEY())
            for i in range(len(decryptedPekList['DecryptedPek']) / PEKLen):
                cursor = i * PEKLen
                pek = self.PEK_KEY(
                    decryptedPekList['DecryptedPek'][cursor:cursor + PEKLen])
                logging.info("PEK # %d found and decrypted: %s", i,
                             hexlify(pek['Key']))
                self.__PEK.append(pek['Key'])

    def __removeRC4Layer(self, cryptedHash):
        md5 = hashlib.new('md5')
        # PEK index can be found on header of each ciphered blob (pos 8-10)
        pekIndex = hexlify(cryptedHash['Header'])
        md5.update(self.__PEK[int(pekIndex[8:10])])
        md5.update(cryptedHash['KeyMaterial'])
        tmpKey = md5.digest()
        rc4 = ARC4.new(tmpKey)
        plainText = rc4.encrypt(cryptedHash['EncryptedHash'])

        return plainText

    def __removeDESLayer(self, cryptedHash, rid):
        Key1, Key2 = self.__cryptoCommon.deriveKey(int(rid))

        Crypt1 = DES.new(Key1, DES.MODE_ECB)
        Crypt2 = DES.new(Key2, DES.MODE_ECB)

        decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt(
            cryptedHash[8:])

        return decryptedHash

    def __fileTimeToDateTime(self, t):
        t -= 116444736000000000
        t /= 10000000
        if t < 0:
            return 'never'
        else:
            dt = datetime.fromtimestamp(t)
            return dt.strftime("%Y-%m-%d %H:%M")

    def __decryptSupplementalInfo(self,
                                  record,
                                  prefixTable=None,
                                  keysFile=None,
                                  clearTextFile=None):
        # This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures
        haveInfo = False
        if self.__useVSSMethod is True:
            if record[self.
                      NAME_TO_INTERNAL['supplementalCredentials']] is not None:
                if len(
                        unhexlify(record[self.NAME_TO_INTERNAL[
                            'supplementalCredentials']])) > 24:
                    if record[self.NAME_TO_INTERNAL[
                            'userPrincipalName']] is not None:
                        domain = record[self.NAME_TO_INTERNAL[
                            'userPrincipalName']].split('@')[-1]
                        userName = '******' % (domain, record[
                            self.NAME_TO_INTERNAL['sAMAccountName']])
                    else:
                        userName = '******' % record[
                            self.NAME_TO_INTERNAL['sAMAccountName']]
                    cipherText = self.CRYPTED_BLOB(
                        unhexlify(record[
                            self.NAME_TO_INTERNAL['supplementalCredentials']]))
                    plainText = self.__removeRC4Layer(cipherText)
                    haveInfo = True
        else:
            domain = None
            userName = None
            for attr in record['pmsgOut']['V6']['pObjects']['Entinf'][
                    'AttrBlock']['pAttr']:
                try:
                    attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp'])
                    LOOKUP_TABLE = self.ATTRTYP_TO_ATTID
                except Exception, e:
                    logging.debug(
                        'Failed to execute OidFromAttid with error %s' % e)
                    # Fallbacking to fixed table and hope for the best
                    attId = attr['attrTyp']
                    LOOKUP_TABLE = self.NAME_TO_ATTRTYP

                if attId == LOOKUP_TABLE['userPrincipalName']:
                    if attr['AttrVal']['valCount'] > 0:
                        try:
                            domain = ''.join(
                                attr['AttrVal']['pAVal'][0]['pVal']).decode(
                                    'utf-16le').split('@')[-1]
                        except:
                            domain = None
                    else:
                        domain = None
                elif attId == LOOKUP_TABLE['sAMAccountName']:
                    if attr['AttrVal']['valCount'] > 0:
                        try:
                            userName = ''.join(attr['AttrVal']['pAVal'][0]
                                               ['pVal']).decode('utf-16le')
                        except:
                            logging.error('Cannot get sAMAccountName for %s' %
                                          record['pmsgOut']['V6']['pNC']
                                          ['StringName'][:-1])
                            userName = '******'
                    else:
                        logging.error(
                            'Cannot get sAMAccountName for %s' %
                            record['pmsgOut']['V6']['pNC']['StringName'][:-1])
                        userName = '******'
                if attId == LOOKUP_TABLE['supplementalCredentials']:
                    if attr['AttrVal']['valCount'] > 0:
                        blob = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
                        plainText = drsuapi.DecryptAttributeValue(
                            self.__remoteOps.getDrsr(), blob)
                        if len(plainText) > 24:
                            haveInfo = True
            if domain is not None:
                userName = '******' % (domain, userName)

        if haveInfo is True:
            try:
                userProperties = samr.USER_PROPERTIES(plainText)
            except:
                # On some old w2k3 there might be user properties that don't
                # match [MS-SAMR] structure, discarding them
                return
            propertiesData = userProperties['UserProperties']
            for propertyCount in range(userProperties['PropertyCount']):
                userProperty = samr.USER_PROPERTY(propertiesData)
                propertiesData = propertiesData[len(userProperty):]
                # For now, we will only process Newer Kerberos Keys and CLEARTEXT
                if userProperty['PropertyName'].decode(
                        'utf-16le') == 'Primary:Kerberos-Newer-Keys':
                    propertyValueBuffer = unhexlify(
                        userProperty['PropertyValue'])
                    kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(
                        propertyValueBuffer)
                    data = kerbStoredCredentialNew['Buffer']
                    for credential in range(
                            kerbStoredCredentialNew['CredentialCount']):
                        keyDataNew = samr.KERB_KEY_DATA_NEW(data)
                        data = data[len(keyDataNew):]
                        keyValue = propertyValueBuffer[
                            keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']]

                        if self.KERBEROS_TYPE.has_key(keyDataNew['KeyType']):
                            answer = "%s:%s:%s" % (
                                userName,
                                self.KERBEROS_TYPE[keyDataNew['KeyType']],
                                hexlify(keyValue))
                        else:
                            answer = "%s:%s:%s" % (userName,
                                                   hex(keyDataNew['KeyType']),
                                                   hexlify(keyValue))
                        # We're just storing the keys, not printing them, to make the output more readable
                        # This is kind of ugly... but it's what I came up with tonight to get an ordered
                        # set :P. Better ideas welcomed ;)
                        self.__kerberosKeys[answer] = None
                        if keysFile is not None:
                            self.__writeOutput(keysFile, answer + '\n')
                elif userProperty['PropertyName'].decode(
                        'utf-16le') == 'Primary:CLEARTEXT':
                    # [MS-SAMR] 3.1.1.8.11.5 Primary:CLEARTEXT Property
                    # This credential type is the cleartext password. The value format is the UTF-16 encoded cleartext password.
                    answer = "%s:CLEARTEXT:%s" % (
                        userName, unhexlify(
                            userProperty['PropertyValue']).decode('utf-16le'))
                    self.__clearTextPwds[answer] = None
                    if clearTextFile is not None:
                        self.__writeOutput(clearTextFile, answer + '\n')

            if clearTextFile is not None:
                clearTextFile.flush()
            if keysFile is not None:
                keysFile.flush()
Exemple #4
0
class NTDSHashes:
    class SECRET_TYPE:
        NTDS = 0
        NTDS_CLEARTEXT = 1
        NTDS_KERBEROS = 2

    NAME_TO_INTERNAL = {
        'uSNCreated': 'ATTq131091',
        'uSNChanged': 'ATTq131192',
        'name': 'ATTm3',
        'objectGUID': 'ATTk589826',
        'objectSid': 'ATTr589970',
        'userAccountControl': 'ATTj589832',
        'primaryGroupID': 'ATTj589922',
        'accountExpires': 'ATTq589983',
        'logonCount': 'ATTj589993',
        'sAMAccountName': 'ATTm590045',
        'sAMAccountType': 'ATTj590126',
        'lastLogonTimestamp': 'ATTq589876',
        'userPrincipalName': 'ATTm590480',
        'unicodePwd': 'ATTk589914',
        'dBCSPwd': 'ATTk589879',
        'ntPwdHistory': 'ATTk589918',
        'lmPwdHistory': 'ATTk589984',
        'pekList': 'ATTk590689',
        'supplementalCredentials': 'ATTk589949',
        'pwdLastSet': 'ATTq589920',
    }

    INTERNAL_TO_NAME = dict((v, k) for k, v in NAME_TO_INTERNAL.iteritems())

    SAM_NORMAL_USER_ACCOUNT = 0x30000000
    SAM_MACHINE_ACCOUNT = 0x30000001
    SAM_TRUST_ACCOUNT = 0x30000002

    ACCOUNT_TYPES = (SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT,
                     SAM_TRUST_ACCOUNT)

    def __init__(self, ntdsFile, isRemote=False):
        self.__NTDS = ntdsFile
        try:
            if self.__NTDS is not None:
                self.__ESEDB = ESENT_DB(ntdsFile, isRemote=isRemote)
                self.__cursor = self.__ESEDB.openTable('datatable')
        except Exception as e:
            raise e

    def getNextRecord(self):
        record = self.__ESEDB.getNextRow(self.__cursor)
        if record is None:
            return None, False
        elif self.NAME_TO_INTERNAL['sAMAccountType'] not in record:
            raise Exception('InvalidFile')
        try:
            if record[self.NAME_TO_INTERNAL[
                    'sAMAccountType']] in self.ACCOUNT_TYPES:
                if record[self.NAME_TO_INTERNAL['sAMAccountName']] is not None:
                    userName = '******' % record[
                        self.NAME_TO_INTERNAL['sAMAccountName']]
                else:
                    userName = '******'
                    return None, True
                if record[self.NAME_TO_INTERNAL['name']] is not None:
                    displayName = '%s' % record[self.NAME_TO_INTERNAL['name']]
                else:
                    displayName = 'N/A'
                    return None, True
                if record[self.NAME_TO_INTERNAL['objectGUID']] is not None:
                    objectGuid = '%s' % record[
                        self.NAME_TO_INTERNAL['objectGUID']]
                else:
                    objectGuid = 'N/A'
                    return None, True
                fields = "%s:%s:%s" % (displayName, userName, objectGuid)
                return fields, True
            else:
                return None, True
        except Exception as e:
            raise Exception('NextRecordFetchFailed')

    def finish(self):
        if self.__NTDS is not None:
            self.__ESEDB.close()
Exemple #5
0
class NTDSHashes():
	NAME_TO_INTERNAL = { 
		'uSNCreated':'ATTq131091',
		'uSNChanged':'ATTq131192',
		'name':'ATTm3',
		'objectGUID':'ATTk589826',
		'objectSid':'ATTr589970',
		'userAccountControl':'ATTj589832',
		'primaryGroupID':'ATTj589922',
		'accountExpires':'ATTq589983',
		'logonCount':'ATTj589993',
		'sAMAccountName':'ATTm590045',
		'sAMAccountType':'ATTj590126',
		'lastLogonTimestamp':'ATTq589876',
		'userPrincipalName':'ATTm590480',
		'unicodePwd':'ATTk589914',
		'dBCSPwd':'ATTk589879',
		'ntPwdHistory':'ATTk589918',
		'lmPwdHistory':'ATTk589984',
		'pekList':'ATTk590689',
		'supplementalCredentials':'ATTk589949',
	}

	KERBEROS_TYPE = {
		1:'dec-cbc-crc',
		3:'des-cbc-md5',
		17:'aes128-cts-hmac-sha1-96',
		18:'aes256-cts-hmac-sha1-96',
		0xffffff74:'rc4_hmac',
	}

	INTERNAL_TO_NAME = dict((v,k) for k,v in NAME_TO_INTERNAL.iteritems())

	SAM_NORMAL_USER_ACCOUNT = 0x30000000
	SAM_MACHINE_ACCOUNT     = 0x30000001
	SAM_TRUST_ACCOUNT       = 0x30000002

	ACCOUNT_TYPES = ( SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT, SAM_TRUST_ACCOUNT)
	
	class PEK_KEY(Structure):
		structure = (
			('Header','8s=""'),
			('KeyMaterial','16s=""'),
			('EncryptedPek','52s=""'),
		)

	class CRYPTED_HASH(Structure):
		structure = (
			('Header','8s=""'),
			('KeyMaterial','16s=""'),
			('EncryptedHash','16s=""'),
		)

	class CRYPTED_HISTORY(Structure):
		structure = (
			('Header','8s=""'),
			('KeyMaterial','16s=""'),
			('EncryptedHash',':'),
		)

	class CRYPTED_BLOB(Structure):
		structure = (
			('Header','8s=""'),
			('KeyMaterial','16s=""'),
			('EncryptedHash',':'),
		)

	def __init__(self, ntdsFile, bootKey, isRemote = False, history = False, noLMHash = True):
		self.__bootKey = bootKey
		self.__NTDS = ntdsFile
		self.__history = history
		self.__noLMHash = noLMHash
		if self.__NTDS is not None:
			self.__ESEDB = ESENT_DB(ntdsFile, isRemote = isRemote)
			self.__cursor = self.__ESEDB.openTable('datatable')
		self.__tmpUsers = list()
		self.__PEK = None
		self.__cryptoCommon = CryptoCommon()
		self.__hashesFound = {}
		self.__kerberosKeys = OrderedDict()

	def __getPek(self):
		# logging.info('Searching for pekList, be patient')
		pek = None
		while True:
			record = self.__ESEDB.getNextRow(self.__cursor)
			if record is None:
				break
			elif record[self.NAME_TO_INTERNAL['pekList']] is not None:
				pek =  record[self.NAME_TO_INTERNAL['pekList']].decode('hex')
				break
			elif record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES:
				# Okey.. we found some users, but we're not yet ready to process them.
				# Let's just store them in a temp list
				self.__tmpUsers.append(record)

		if pek is not None:
			encryptedPek = self.PEK_KEY(pek)
			md5 = hashlib.new('md5')
			md5.update(self.__bootKey)
			for i in range(1000):
				md5.update(encryptedPek['KeyMaterial'])
			tmpKey = md5.digest()
			rc4 = ARC4.new(tmpKey)
			plainText = rc4.encrypt(encryptedPek['EncryptedPek'])
			self.__PEK = plainText[36:]

	def __removeRC4Layer(self, cryptedHash):
		md5 = hashlib.new('md5')
		md5.update(self.__PEK)
		md5.update(cryptedHash['KeyMaterial'])
		tmpKey = md5.digest()
		rc4 = ARC4.new(tmpKey)
		plainText = rc4.encrypt(cryptedHash['EncryptedHash'])

		return plainText

	def __removeDESLayer(self, cryptedHash, rid):
		Key1,Key2 = self.__cryptoCommon.deriveKey(int(rid))
		Crypt1 = DES.new(Key1, DES.MODE_ECB)
		Crypt2 = DES.new(Key2, DES.MODE_ECB)
		decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt(cryptedHash[8:])
		return decryptedHash

	def __decryptSupplementalInfo(self, record):
		# This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures
		if record[self.NAME_TO_INTERNAL['supplementalCredentials']] is not None:
			if len(record[self.NAME_TO_INTERNAL['supplementalCredentials']].decode('hex')) > 24:
				if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None:
					domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1]
					userName = '******' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']])
				else: 
					userName = '******' % record[self.NAME_TO_INTERNAL['sAMAccountName']]
				cipherText = self.CRYPTED_BLOB(record[self.NAME_TO_INTERNAL['supplementalCredentials']].decode('hex'))
				plainText = self.__removeRC4Layer(cipherText)
				try:
					userProperties = samr.USER_PROPERTIES(plainText)
				except:
					# On some old w2k3 there might be user properties that don't 
					# match [MS-SAMR] structure, discarding them
					return
				propertiesData = userProperties['UserProperties']
				for propertyCount in range(userProperties['PropertyCount']):
					userProperty = samr.USER_PROPERTY(propertiesData)   
					propertiesData = propertiesData[len(userProperty):]
					# For now, we will only process Newer Kerberos Keys. 
					if userProperty['PropertyName'].decode('utf-16le') == 'Primary:Kerberos-Newer-Keys':
						propertyValueBuffer = userProperty['PropertyValue'].decode('hex')
						kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer)
						data = kerbStoredCredentialNew['Buffer']
						for credential in range(kerbStoredCredentialNew['CredentialCount']):
							keyDataNew = samr.KERB_KEY_DATA_NEW(data)
							data = data[len(keyDataNew):]
							keyValue = propertyValueBuffer[keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']]
	
							if  self.KERBEROS_TYPE.has_key(keyDataNew['KeyType']):
								answer =  "%s:%s:%s" % (userName, self.KERBEROS_TYPE[keyDataNew['KeyType']],keyValue.encode('hex'))
							else:
								answer =  "%s:%s:%s" % (userName, hex(keyDataNew['KeyType']),keyValue.encode('hex'))
							# We're just storing the keys, not printing them, to make the output more readable
							# This is kind of ugly... but it's what I came up with tonight to get an ordered
							# set :P. Better ideas welcomed ;)
							self.__kerberosKeys[answer] = None

	def __decryptHash(self, record):
		# logging.debug('Decrypting hash for user: %s' % record[self.NAME_TO_INTERNAL['name']])
		
		sid = SAMR_RPC_SID(record[self.NAME_TO_INTERNAL['objectSid']].decode('hex'))
		rid = sid.formatCanonical().split('-')[-1]

		if record[self.NAME_TO_INTERNAL['dBCSPwd']] is not None:
			encryptedLMHash = self.CRYPTED_HASH(record[self.NAME_TO_INTERNAL['dBCSPwd']].decode('hex'))
			tmpLMHash = self.__removeRC4Layer(encryptedLMHash)
			LMHash = self.__removeDESLayer(tmpLMHash, rid)
		else:
			LMHash = ntlm.LMOWFv1('','')
			encryptedLMHash = None

		if record[self.NAME_TO_INTERNAL['unicodePwd']] is not None:
			encryptedNTHash = self.CRYPTED_HASH(record[self.NAME_TO_INTERNAL['unicodePwd']].decode('hex'))
			tmpNTHash = self.__removeRC4Layer(encryptedNTHash)
			NTHash = self.__removeDESLayer(tmpNTHash, rid)
		else:
			NTHash = ntlm.NTOWFv1('','')
			encryptedNTHash = None

		if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None:
			domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1]
			userName = '******' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']])
		else: 
			userName = '******' % record[self.NAME_TO_INTERNAL['sAMAccountName']]
 
		answer =  "%s:%s:%s:%s:::" % (userName, rid, LMHash.encode('hex'), NTHash.encode('hex'))
		self.__hashesFound[record[self.NAME_TO_INTERNAL['objectSid']].decode('hex')] = answer
		# print answer
	  
		if self.__history:
			LMHistory = []
			NTHistory = []
			if record[self.NAME_TO_INTERNAL['lmPwdHistory']] is not None:
				lmPwdHistory = record[self.NAME_TO_INTERNAL['lmPwdHistory']]
				encryptedLMHistory = self.CRYPTED_HISTORY(record[self.NAME_TO_INTERNAL['lmPwdHistory']].decode('hex'))
				tmpLMHistory = self.__removeRC4Layer(encryptedLMHistory)
				for i in range(0, len(tmpLMHistory)/16):
					LMHash = self.__removeDESLayer(tmpLMHistory[i*16:(i+1)*16], rid)
					LMHistory.append(LMHash)

			if record[self.NAME_TO_INTERNAL['ntPwdHistory']] is not None:
				ntPwdHistory = record[self.NAME_TO_INTERNAL['ntPwdHistory']]
				encryptedNTHistory = self.CRYPTED_HISTORY(record[self.NAME_TO_INTERNAL['ntPwdHistory']].decode('hex'))
				tmpNTHistory = self.__removeRC4Layer(encryptedNTHistory)
				for i in range(0, len(tmpNTHistory)/16):
					NTHash = self.__removeDESLayer(tmpNTHistory[i*16:(i+1)*16], rid)
					NTHistory.append(NTHash)

			for i, (LMHash, NTHash) in enumerate(map(lambda l,n: (l,n) if l else ('',n), LMHistory[1:], NTHistory[1:])):
				if self.__noLMHash:
					lmhash = ntlm.LMOWFv1('','').encode('hex')
				else:
					lmhash = LMHash.encode('hex')
			
				answer =  "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash, NTHash.encode('hex'))
				self.__hashesFound[record[self.NAME_TO_INTERNAL['objectSid']].decode('hex')+str(i)] = answer
				# print answer

	def dump(self):
		if self.__NTDS is None:
			# No NTDS.dit file provided
			return
		# logging.info('Dumping Domain Credentials (domain\\uid:rid:lmhash:nthash)')
		# We start getting rows from the table aiming at reaching
		# the pekList. If we find users records we stored them 
		# in a temp list for later process.
		self.__getPek()
		if self.__PEK is not None:
			# logging.info('Pek found and decrypted: 0x%s' % self.__PEK.encode('hex'))
			# logging.info('Reading and decrypting hashes from %s ' % self.__NTDS)
			# First of all, if we have users already cached, let's decrypt their hashes
			for record in self.__tmpUsers:
				try:
					self.__decryptHash(record)
					self.__decryptSupplementalInfo(record)
				except Exception, e:
					try:
						# logging.error("Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']])
						# logging.error(str(e))
						pass
					except: 
						# logging.error("Error while processing row!")
						# logging.error(str(e))
						pass

			# Now let's keep moving through the NTDS file and decrypting what we find
			while True:
				try:
					record = self.__ESEDB.getNextRow(self.__cursor)
				except: 
					# logging.error('Error while calling getNextRow(), trying the next one')
					continue 

				if record is None:
					break
				try:
					if record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES: 
						self.__decryptHash(record)
						self.__decryptSupplementalInfo(record)
				except Exception, e:
					try:
						# logging.error("Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']])
						# logging.error(str(e))
						pass
					except: 
						# logging.error("Error while processing row!")
						# logging.error(str(e))
						pass
Exemple #6
0
class NTDSHashes():
	NAME_TO_INTERNAL = { 
		'uSNCreated':'ATTq131091',
		'uSNChanged':'ATTq131192',
		'name':'ATTm3',
		'objectGUID':'ATTk589826',
		'objectSid':'ATTr589970',
		'userAccountControl':'ATTj589832',
		'primaryGroupID':'ATTj589922',
		'accountExpires':'ATTq589983',
		'logonCount':'ATTj589993',
		'sAMAccountName':'ATTm590045',
		'sAMAccountType':'ATTj590126',
		'lastLogonTimestamp':'ATTq589876',
		'userPrincipalName':'ATTm590480',
		'unicodePwd':'ATTk589914',
		'dBCSPwd':'ATTk589879',
		'ntPwdHistory':'ATTk589918',
		'lmPwdHistory':'ATTk589984',
		'pekList':'ATTk590689',
		'supplementalCredentials':'ATTk589949',
	}

	KERBEROS_TYPE = {
		1:'dec-cbc-crc',
		3:'des-cbc-md5',
		17:'aes128-cts-hmac-sha1-96',
		18:'aes256-cts-hmac-sha1-96',
		0xffffff74:'rc4_hmac',
	}

	INTERNAL_TO_NAME = dict((v,k) for k,v in NAME_TO_INTERNAL.iteritems())

	SAM_NORMAL_USER_ACCOUNT = 0x30000000
	SAM_MACHINE_ACCOUNT     = 0x30000001
	SAM_TRUST_ACCOUNT       = 0x30000002

	ACCOUNT_TYPES = ( SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT, SAM_TRUST_ACCOUNT)
	
	class PEK_KEY(Structure):
		structure = (
			('Header','8s=""'),
			('KeyMaterial','16s=""'),
			('EncryptedPek','52s=""'),
		)

	class CRYPTED_HASH(Structure):
		structure = (
			('Header','8s=""'),
			('KeyMaterial','16s=""'),
			('EncryptedHash','16s=""'),
		)

	class CRYPTED_HISTORY(Structure):
		structure = (
			('Header','8s=""'),
			('KeyMaterial','16s=""'),
			('EncryptedHash',':'),
		)

	class CRYPTED_BLOB(Structure):
		structure = (
			('Header','8s=""'),
			('KeyMaterial','16s=""'),
			('EncryptedHash',':'),
		)

	def __init__(self, ntdsFile, bootKey, isRemote = False, history = False, noLMHash = True):
		self.__bootKey = bootKey
		self.__NTDS = ntdsFile
		self.__history = history
		self.__noLMHash = noLMHash
		if self.__NTDS is not None:
			self.__ESEDB = ESENT_DB(ntdsFile, isRemote = isRemote)
			self.__cursor = self.__ESEDB.openTable('datatable')
		self.__tmpUsers = list()
		self.__PEK = None
		self.__cryptoCommon = CryptoCommon()
		self.__hashesFound = {}
		self.__kerberosKeys = OrderedDict()

	def __getPek(self):
		# logging.info('Searching for pekList, be patient')
		pek = None
		while True:
			record = self.__ESEDB.getNextRow(self.__cursor)
			if record is None:
				break
			elif record[self.NAME_TO_INTERNAL['pekList']] is not None:
				pek =  record[self.NAME_TO_INTERNAL['pekList']].decode('hex')
				break
			elif record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES:
				# Okey.. we found some users, but we're not yet ready to process them.
				# Let's just store them in a temp list
				self.__tmpUsers.append(record)

		if pek is not None:
			encryptedPek = self.PEK_KEY(pek)
			md5 = hashlib.new('md5')
			md5.update(self.__bootKey)
			for i in range(1000):
				md5.update(encryptedPek['KeyMaterial'])
			tmpKey = md5.digest()
			rc4 = ARC4.new(tmpKey)
			plainText = rc4.encrypt(encryptedPek['EncryptedPek'])
			self.__PEK = plainText[36:]

	def __removeRC4Layer(self, cryptedHash):
		md5 = hashlib.new('md5')
		md5.update(self.__PEK)
		md5.update(cryptedHash['KeyMaterial'])
		tmpKey = md5.digest()
		rc4 = ARC4.new(tmpKey)
		plainText = rc4.encrypt(cryptedHash['EncryptedHash'])

		return plainText

	def __removeDESLayer(self, cryptedHash, rid):
		Key1,Key2 = self.__cryptoCommon.deriveKey(int(rid))
		Crypt1 = DES.new(Key1, DES.MODE_ECB)
		Crypt2 = DES.new(Key2, DES.MODE_ECB)
		decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt(cryptedHash[8:])
		return decryptedHash

	def __decryptSupplementalInfo(self, record):
		# This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures
		if record[self.NAME_TO_INTERNAL['supplementalCredentials']] is not None:
			if len(record[self.NAME_TO_INTERNAL['supplementalCredentials']].decode('hex')) > 24:
				if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None:
					domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1]
					userName = '******' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']])
				else: 
					userName = '******' % record[self.NAME_TO_INTERNAL['sAMAccountName']]
				cipherText = self.CRYPTED_BLOB(record[self.NAME_TO_INTERNAL['supplementalCredentials']].decode('hex'))
				plainText = self.__removeRC4Layer(cipherText)
				try:
					userProperties = samr.USER_PROPERTIES(plainText)
				except:
					# On some old w2k3 there might be user properties that don't 
					# match [MS-SAMR] structure, discarding them
					return
				propertiesData = userProperties['UserProperties']
				for propertyCount in range(userProperties['PropertyCount']):
					userProperty = samr.USER_PROPERTY(propertiesData)   
					propertiesData = propertiesData[len(userProperty):]
					# For now, we will only process Newer Kerberos Keys. 
					if userProperty['PropertyName'].decode('utf-16le') == 'Primary:Kerberos-Newer-Keys':
						propertyValueBuffer = userProperty['PropertyValue'].decode('hex')
						kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer)
						data = kerbStoredCredentialNew['Buffer']
						for credential in range(kerbStoredCredentialNew['CredentialCount']):
							keyDataNew = samr.KERB_KEY_DATA_NEW(data)
							data = data[len(keyDataNew):]
							keyValue = propertyValueBuffer[keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']]
	
							if  self.KERBEROS_TYPE.has_key(keyDataNew['KeyType']):
								answer =  "%s:%s:%s" % (userName, self.KERBEROS_TYPE[keyDataNew['KeyType']],keyValue.encode('hex'))
							else:
								answer =  "%s:%s:%s" % (userName, hex(keyDataNew['KeyType']),keyValue.encode('hex'))
							# We're just storing the keys, not printing them, to make the output more readable
							# This is kind of ugly... but it's what I came up with tonight to get an ordered
							# set :P. Better ideas welcomed ;)
							self.__kerberosKeys[answer] = None

	def __decryptHash(self, record):
		# logging.debug('Decrypting hash for user: %s' % record[self.NAME_TO_INTERNAL['name']])
		
		sid = SAMR_RPC_SID(record[self.NAME_TO_INTERNAL['objectSid']].decode('hex'))
		rid = sid.formatCanonical().split('-')[-1]

		if record[self.NAME_TO_INTERNAL['dBCSPwd']] is not None:
			encryptedLMHash = self.CRYPTED_HASH(record[self.NAME_TO_INTERNAL['dBCSPwd']].decode('hex'))
			tmpLMHash = self.__removeRC4Layer(encryptedLMHash)
			LMHash = self.__removeDESLayer(tmpLMHash, rid)
		else:
			LMHash = ntlm.LMOWFv1('','')
			encryptedLMHash = None

		if record[self.NAME_TO_INTERNAL['unicodePwd']] is not None:
			encryptedNTHash = self.CRYPTED_HASH(record[self.NAME_TO_INTERNAL['unicodePwd']].decode('hex'))
			tmpNTHash = self.__removeRC4Layer(encryptedNTHash)
			NTHash = self.__removeDESLayer(tmpNTHash, rid)
		else:
			NTHash = ntlm.NTOWFv1('','')
			encryptedNTHash = None

		if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None:
			domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1]
			userName = '******' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']])
		else: 
			userName = '******' % record[self.NAME_TO_INTERNAL['sAMAccountName']]
 
		answer =  "%s:%s:%s:%s:::" % (userName, rid, LMHash.encode('hex'), NTHash.encode('hex'))
		self.__hashesFound[record[self.NAME_TO_INTERNAL['objectSid']].decode('hex')] = answer
		# print answer
	  
		if self.__history:
			LMHistory = []
			NTHistory = []
			if record[self.NAME_TO_INTERNAL['lmPwdHistory']] is not None:
				lmPwdHistory = record[self.NAME_TO_INTERNAL['lmPwdHistory']]
				encryptedLMHistory = self.CRYPTED_HISTORY(record[self.NAME_TO_INTERNAL['lmPwdHistory']].decode('hex'))
				tmpLMHistory = self.__removeRC4Layer(encryptedLMHistory)
				for i in range(0, len(tmpLMHistory)/16):
					LMHash = self.__removeDESLayer(tmpLMHistory[i*16:(i+1)*16], rid)
					LMHistory.append(LMHash)

			if record[self.NAME_TO_INTERNAL['ntPwdHistory']] is not None:
				ntPwdHistory = record[self.NAME_TO_INTERNAL['ntPwdHistory']]
				encryptedNTHistory = self.CRYPTED_HISTORY(record[self.NAME_TO_INTERNAL['ntPwdHistory']].decode('hex'))
				tmpNTHistory = self.__removeRC4Layer(encryptedNTHistory)
				for i in range(0, len(tmpNTHistory)/16):
					NTHash = self.__removeDESLayer(tmpNTHistory[i*16:(i+1)*16], rid)
					NTHistory.append(NTHash)

			for i, (LMHash, NTHash) in enumerate(map(lambda l,n: (l,n) if l else ('',n), LMHistory[1:], NTHistory[1:])):
				if self.__noLMHash:
					lmhash = ntlm.LMOWFv1('','').encode('hex')
				else:
					lmhash = LMHash.encode('hex')
			
				answer =  "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash, NTHash.encode('hex'))
				self.__hashesFound[record[self.NAME_TO_INTERNAL['objectSid']].decode('hex')+str(i)] = answer
				# print answer

	def dump(self):
		if self.__NTDS is None:
			# No NTDS.dit file provided
			return
		# logging.info('Dumping Domain Credentials (domain\\uid:rid:lmhash:nthash)')
		# We start getting rows from the table aiming at reaching
		# the pekList. If we find users records we stored them 
		# in a temp list for later process.
		self.__getPek()
		if self.__PEK is not None:
			# logging.info('Pek found and decrypted: 0x%s' % self.__PEK.encode('hex'))
			# logging.info('Reading and decrypting hashes from %s ' % self.__NTDS)
			# First of all, if we have users already cached, let's decrypt their hashes
			for record in self.__tmpUsers:
				try:
					self.__decryptHash(record)
					self.__decryptSupplementalInfo(record)
				except Exception, e:
					try:
						# logging.error("Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']])
						# logging.error(str(e))
						pass
					except: 
						# logging.error("Error while processing row!")
						# logging.error(str(e))
						pass

			# Now let's keep moving through the NTDS file and decrypting what we find
			while True:
				try:
					record = self.__ESEDB.getNextRow(self.__cursor)
				except: 
					# logging.error('Error while calling getNextRow(), trying the next one')
					continue 

				if record is None:
					break
				try:
					if record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES: 
						self.__decryptHash(record)
						self.__decryptSupplementalInfo(record)
				except Exception, e:
					try:
						# logging.error("Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']])
						# logging.error(str(e))
						pass
					except: 
						# logging.error("Error while processing row!")
						# logging.error(str(e))
						pass