def decrypt_asrep(self, as_rep): def truncate_key(value, keysize): output = b'' currentNum = 0 while len(output) < keysize: currentDigest = hashlib.sha1(bytes([currentNum]) + value).digest() if len(output) + len(currentDigest) > keysize: output += currentDigest[:keysize - len(output)] break output += currentDigest currentNum += 1 return output for pa in as_rep['padata']: if pa['padata-type'] == 17: pkasrep = PA_PK_AS_REP.load(pa['padata-value']).native break else: raise Exception('PA_PK_AS_REP not found!') sd = cms.SignedData.load(pkasrep['dhSignedData']).native keyinfo = sd['encap_content_info'] if keyinfo['content_type'] != '1.3.6.1.5.2.3.2': raise Exception('Keyinfo content type unexpected value') authdata = KDCDHKeyInfo.load(keyinfo['content']).native pubkey = int( ''.join(['1'] + [str(x) for x in authdata['subjectPublicKey']]), 2) pubkey = int.from_bytes(core.BitString( authdata['subjectPublicKey']).dump()[7:], 'big', signed=False) shared_key = self.diffie.exchange(pubkey) server_nonce = pkasrep['serverDHNonce'] fullKey = shared_key + self.diffie.dh_nonce + server_nonce etype = as_rep['enc-part']['etype'] cipher = _enctype_table[etype] if etype == Enctype.AES256: t_key = truncate_key(fullKey, 32) elif etype == Enctype.AES128: t_key = truncate_key(fullKey, 16) elif etype == Enctype.RC4: raise NotImplementedError( 'RC4 key truncation documentation missing. it is different from AES' ) #t_key = truncate_key(fullKey, 16) key = Key(cipher.enctype, t_key) enc_data = as_rep['enc-part']['cipher'] dec_data = cipher.decrypt(key, 3, enc_data) encasrep = EncASRepPart.load(dec_data).native cipher = _enctype_table[int(encasrep['key']['keytype'])] session_key = Key(cipher.enctype, encasrep['key']['keyvalue']) return encasrep, session_key, cipher
def decrypt_pk_dh(data, diffieHellmanExchange): try: rep = SPNEGO_PKINIT_AS_REP.load(bytes.fromhex(data)).native except: krb_message = KerberosResponse.load(bytes.fromhex(data)) raise KerberosError(krb_message) relevantPadata = None for padata in rep['Kerberos']['padata']: if padata['padata-type'] == 17: relevantPadata = PA_PK_AS_REP.load(padata['padata-value']).native break if not relevantPadata: raise Exception('No PAdata found with type 17') keyinfo = SignedData.load( relevantPadata['dhSignedData']).native['encap_content_info'] if keyinfo['content_type'] != '1.3.6.1.5.2.3.2': raise Exception('Keyinfo content type unexpected value') authdata = KDCDHKeyInfo.load(keyinfo['content']).native pubkey = int( ''.join(['1'] + [str(x) for x in authdata['subjectPublicKey']]), 2) pubkey = int.from_bytes(core.BitString( authdata['subjectPublicKey']).dump()[7:], 'big', signed=False) shared_key = diffieHellmanExchange.exchange(pubkey) server_nonce = relevantPadata['serverDHNonce'] fullKey = shared_key + diffieHellmanExchange.dh_nonce + server_nonce etype = rep['Kerberos']['enc-part']['etype'] cipher = _enctype_table[etype] if etype == Enctype.AES256: t_key = truncate(fullKey, 32) elif etype == Enctype.AES128: t_key = truncate(fullKey, 16) elif etype == Enctype.RC4: raise NotImplementedError( 'RC4 key truncation documentation missing. it is different from AES' ) key = Key(cipher.enctype, t_key) enc_data = rep['Kerberos']['enc-part']['cipher'] dec_data = cipher.decrypt(key, 3, enc_data) encasrep = EncASRepPart.load(dec_data).native cipher = _enctype_table[int(encasrep['key']['keytype'])] session_key = Key(cipher.enctype, encasrep['key']['keyvalue']) return session_key, cipher, rep # remove Octet String manualy padata = str(rep['padata'][0]['padata-value']).encode('hex') parsedPadata = decode(padata.decode('hex'), asn1Spec=AS_REP_Padata())[0] decoded = parsedPadata['DHRepInfo']['dhSignedData'] kdcSignedDataResponse = decode(decoded, asn1Spec=SignedData())[0] kdcDHKeyInfo = str(kdcSignedDataResponse['encapContentInfo'] ['id-pkinit-authData-value']).encode('hex') d = decode(kdcDHKeyInfo.decode('hex'), asn1Spec=KDCDHKeyInfo())[0] dcPublicKey = int(encode(d['subjectPublicKey']).encode('hex')[20:], 16) dcPublicNumbers = dh.DHPublicNumbers(dcPublicKey, diffieHellmanExchange[2]) backend = default_backend() dcPublicKey = backend.load_dh_public_numbers(dcPublicNumbers) shared_key = diffieHellmanExchange[1].exchange(dcPublicKey) sharedHexKey = shared_key.encode('hex') clientDHNonce = '6B328FA66EEBDFD3D69ED34E5007776AB30832A2ED1DCB1699781BFE0BEDF87A' serverDHNonce = encode( parsedPadata['DHRepInfo']['encKeyPack']).encode('hex')[8:] fullKey = sharedHexKey + clientDHNonce + serverDHNonce etype = rep['enc-part']['etype'] cipher = _enctype_table[etype] if etype == Enctype.AES256: truncateKey = truncate(fullKey, 32) key = Key(cipher.enctype, truncateKey) elif etype == Enctype.AES128: truncateKey = truncate(fullKey, 16) key = Key(cipher.enctype, truncateKey) elif etype == Enctype.RC4: truncateKey = truncate(fullKey, 16) key = Key(cipher.enctype, truncateKey) cipherText = rep['enc-part']['cipher'].asOctets() plainText = cipher.decrypt(key, 3, cipherText) encASRepPart = decode(plainText, asn1Spec=EncASRepPart())[0] cipher = _enctype_table[int(encASRepPart['key']['keytype'])] session_key = Key(cipher.enctype, encASRepPart['key']['keyvalue'].asOctets()) return session_key, cipher, rep
def get_TGT(self, override_etype = None, decrypt_tgt = True): """ decrypt_tgt: used for asreproast attacks Steps performed: 1. Send and empty (no encrypted timestamp) AS_REQ with all the encryption types we support 2. Depending on the response (either error or AS_REP with TGT) we either send another AS_REQ with the encrypted data or return the TGT (or fail miserably) 3. PROFIT """ logger.debug('[getTGT] Generating initial TGT without authentication data') now = datetime.datetime.now(datetime.timezone.utc) kdc_req_body = {} kdc_req_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','proxiable'])) kdc_req_body['cname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [self.usercreds.username]}) kdc_req_body['realm'] = self.usercreds.domain.upper() kdc_req_body['sname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': ['krbtgt', self.usercreds.domain.upper()]}) kdc_req_body['till'] = (now + datetime.timedelta(days=1)).replace(microsecond=0) kdc_req_body['rtime'] = (now + datetime.timedelta(days=1)).replace(microsecond=0) kdc_req_body['nonce'] = secrets.randbits(31) if override_etype is None: kdc_req_body['etype'] = self.usercreds.get_supported_enctypes() else: kdc_req_body['etype'] = override_etype pa_data_1 = {} pa_data_1['padata-type'] = int(PADATA_TYPE('PA-PAC-REQUEST')) pa_data_1['padata-value'] = PA_PAC_REQUEST({'include-pac': True}).dump() kdc_req = {} kdc_req['pvno'] = krb5_pvno kdc_req['msg-type'] = MESSAGE_TYPE.KRB_AS_REQ.value kdc_req['padata'] = [pa_data_1] kdc_req['req-body'] = KDC_REQ_BODY(kdc_req_body) req = AS_REQ(kdc_req) logger.debug('[getTGT] Sending initial TGT to %s' % self.ksoc.get_addr_str()) rep = self.ksoc.sendrecv(req.dump(), throw = False) if rep.name != 'KRB_ERROR': #user can do kerberos auth without preauthentication! self.kerberos_TGT = rep.native etype = self.kerberos_TGT['enc-part']['etype'] #if we want to roast the asrep (tgt rep) part then we dont even have the proper keys to decrypt #so we just return, the asrep can be extracted from this object anyhow if decrypt_tgt == False: return self.kerberos_cipher = _enctype_table[etype] self.kerberos_cipher_type = etype encryption_type = EncryptionType(self.kerberos_cipher.enctype) enctype = self.usercreds.get_key_for_enctype(encryption_type) self.kerberos_key = Key(self.kerberos_cipher.enctype, enctype) else: if rep.native['error-code'] != KerberosErrorCode.KDC_ERR_PREAUTH_REQUIRED.value: raise KerberosError(rep) rep = rep.native logger.debug('[getTGT] Got reply from server, asking to provide auth data') rep = self.do_preauth(rep) logger.debug('[getTGT] Got valid response from server') rep = rep.native self.kerberos_TGT = rep cipherText = self.kerberos_TGT['enc-part']['cipher'] temp = self.kerberos_cipher.decrypt(self.kerberos_key, 3, cipherText) try: self.kerberos_TGT_encpart = EncASRepPart.load(temp).native except Exception as e: logger.debug('[getTGT] EncAsRepPart load failed, is this linux?') try: self.kerberos_TGT_encpart = EncTGSRepPart.load(temp).native except Exception as e: logger.error('[getTGT] Failed to load decrypted part of the reply!') raise e self.kerberos_session_key = Key(self.kerberos_cipher.enctype, self.kerberos_TGT_encpart['key']['keyvalue']) self.ccache.add_tgt(self.kerberos_TGT, self.kerberos_TGT_encpart, override_pp = True) logger.debug('[getTGT] Got valid TGT') return