diff --git a/impacket/krb5/crypto.py b/impacket/krb5/crypto.py index 2668e6b6f..ec55ba7e2 100644 --- a/impacket/krb5/crypto.py +++ b/impacket/krb5/crypto.py @@ -39,7 +39,6 @@ from binascii import unhexlify from functools import reduce from os import urandom -import hexdump # XXX current status: # * Done and tested @@ -212,104 +211,41 @@ def derive(cls, key, constant): return cls.random_to_key(rndseed[0:cls.seedsize]) @classmethod - def encrypt(cls, key, keyusage, plaintext, confounder): - from R2Log import logger + def encrypt(cls, key, keyusage, plaintext, confounder, integrity_blob=None): ki = cls.derive(key, pack('>IB', keyusage, 0x55)) ke = cls.derive(key, pack('>IB', keyusage, 0xAA)) - logger.info("in encrypt") if confounder is None: confounder = get_random_bytes(cls.blocksize) - logger.info(f"confounder len={len(confounder)}") - hexdump.hexdump(confounder) - logger.info(f"cls.padsize = {cls.padsize}") - logger.info(f"cls.macsize = {cls.macsize}") basic_plaintext = confounder + _zeropad(plaintext, cls.padsize) - logger.info(f"basic_plaintext len={len(basic_plaintext)}") - hexdump.hexdump(basic_plaintext) - hmac = HMAC.new(ki.contents, basic_plaintext, cls.hashmod).digest() - logger.info(f"hmac len={len(hmac)}") - hexdump.hexdump(hmac) + # For Kerberos SSP: blob for HMAC calculation is different from blob for encryption. + # This is undocumented, and was identified by reversing cryptodll.dll + if integrity_blob is not None: + hmac = HMAC.new(ki.contents, integrity_blob, cls.hashmod).digest() + else: + hmac = HMAC.new(ki.contents, basic_plaintext, cls.hashmod).digest() enc = cls.basic_encrypt(ke, basic_plaintext) + hmac[:cls.macsize] - logger.info("returning from encrypt") return enc @classmethod - def decrypt(cls, key, keyusage, ciphertext): - from R2Log import logger + def decrypt(cls, key, keyusage, ciphertext, ignore_integrity=False): ki = cls.derive(key, pack('>IB', keyusage, 0x55)) ke = cls.derive(key, pack('>IB', keyusage, 0xAA)) - - logger.info("in decrypt") - logger.info(f"ciphertext len={len(ciphertext)}") - dumpciphertext = None - if type(ciphertext) != type(bytearray) and type(ciphertext) != type(bytes): - dumpciphertext = bytes(ciphertext) - else: - dumpciphertext = ciphertext - hexdump.hexdump(dumpciphertext) - if len(ciphertext) < cls.blocksize + cls.macsize: raise ValueError('ciphertext too short') - logger.info(f"cls.maacsize = {cls.macsize}") - basic_ctext, mac = bytearray(ciphertext[:-cls.macsize]), bytearray(ciphertext[-cls.macsize:]) - logger.info(f"basic_ctext len={len(basic_ctext)}") - hexdump.hexdump(basic_ctext) if len(basic_ctext) % cls.padsize != 0: raise ValueError('ciphertext does not meet padding requirement') - basic_plaintext = cls.basic_decrypt(ke, bytes(basic_ctext)) - - logger.info("basic_plaintext") - hexdump.hexdump(basic_plaintext) - - logger.info("basic_plaintext without confounder") - hexdump.hexdump(basic_plaintext[cls.blocksize:]) - - # tmp = basic_plaintext[cls.blocksize:] - # padding = tmp[-16-16:-16] - # print("") - # print("padding") - # for i in padding: - # print(f"{hex(i)},",end="") - # print("") - # basic_plaintext = basic_plaintext.replace(padding,b"\xff"*len(padding)) - - # hmac = bytearray(HMAC.new(ki.contents, basic_plaintext, cls.hashmod).digest()) - import Cryptodome hmac = bytearray(HMAC.new(ki.contents, basic_plaintext, SHA).digest()) expmac = hmac[:cls.macsize] - # logger.info(f"hmac len={len(hmac)} (computed mac)") - # hexdump.hexdump(hmac) - logger.info(f"expmac len={len(expmac)} (computed mac)") - hexdump.hexdump(expmac) - logger.info(f"real mac len={len(mac)}") - hexdump.hexdump(mac) - - # logger.info("Bruteforcing mac to find the right params until we match the real mac") - # for msg in [basic_plaintext[cls.blocksize:], basic_plaintext]: - # for i in range(32): - # msg = msg[:-i] - # for mod in [SHA, MD5, Cryptodome.Hash.SHA1]: - # for kusage in range(0,30): - # for constant in [0x55, 0x99, 0xAA]: - # ki2 = cls.derive(key, pack('>IB', kusage, constant)) - # calcmac = bytearray(HMAC.new(ki2.contents, msg, mod).digest())[:cls.macsize] - # if calcmac == mac: - # logger.success("found a match") - # logger.success(mod) - # logger.success(kusage) - # logger.success(constant) - # exit(0) - # hexdump.hexdump(calcmac) - if not _mac_equal(mac, expmac): - print('ciphertext integrity failure') - else: - print('ciphertext integrity correct') - # Discalogger.inford the confounder. - logger.info(f"cls.blocksize = {cls.blocksize}") - logger.info('returning from decrypt') - return bytes(basic_plaintext[cls.blocksize:]),bytes(basic_plaintext[:cls.blocksize]) + # For Kerberos SSP: blob for HMAC calculation is different from blob for encryption. + # This is undocumented, and was identified by reversing cryptodll.dll + # in that case, integrity_blob to send to HMAC + # = confounder + dce_rpc_header + data + auth_data_header + filler + wrap_token + # no need to implement, assuming the other client is legit + if not _mac_equal(mac, expmac) and not ignore_integrity: + raise InvalidChecksum('ciphertext integrity failure') + return bytes(basic_plaintext[cls.blocksize:]), bytes(basic_plaintext[:cls.blocksize]) @classmethod def prf(cls, key, string): diff --git a/impacket/krb5/gssapi.py b/impacket/krb5/gssapi.py index 86d5f2e6b..25167c238 100644 --- a/impacket/krb5/gssapi.py +++ b/impacket/krb5/gssapi.py @@ -125,7 +125,7 @@ def GSS_GetMIC(self, sessionKey, data, sequenceNumber, direction = 'init'): finalData = GSS_GETMIC_HEADER + token.getData() return finalData - def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, authData=None): + def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction='init', encrypt=True, authData=None): # Damn inacurate RFC, useful info from here # https://social.msdn.microsoft.com/Forums/en-US/fb98e8f4-e697-4652-bcb7-604e027e14cc/gsswrap-token-size-kerberos-and-rc4hmac?forum=os_windowsprotocols # and here @@ -249,46 +249,43 @@ def unrotate(self, data, numBytes): result = data[numBytes:] + data[:numBytes] return result - def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True,keyUsage=KG_USAGE_INITIATOR_SEAL,confounder=None, padding=None): + def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction='init', encrypt=True, keyUsage=KG_USAGE_INITIATOR_SEAL, confounder=None, padding=None, dce_rpc_header=None, auth_data_header=None): token = self.WRAP() - print("in GSS_Wrap") + cipher = self.cipherType() - print(f"data len={len(data)}") if padding: padStr = padding pad = len(padding) - data += padStr - else: #Let's pad the data - pad = (cipher.blocksize - (len(data) % cipher.blocksize)) & 16 + pad = (cipher.blocksize - (len(data) % cipher.blocksize)) & 15 padStr = b'\xFF' * pad - data += padStr - print(f"pad: {pad}") - print(f"cipher.blocksize: {cipher.blocksize}") - print(f"len(data) % cipher.blocksize): {len(data) % cipher.blocksize}") - print(f"padding") - hexdump.hexdump(padStr) # The RRC field ([RFC4121] section 4.2.5) is 12 if no encryption is requested or 28 if encryption # is requested. The RRC field is chosen such that all the data can be encrypted in place. rrc = 28 + if direction != 'init': token['Flags'] = 7 else: token['Flags'] = 6 token['EC'] = pad + # (RFC4121 section 4.2.4) the RRC field (as + # defined in section 4.2.5) in the to-be-encrypted header contains the + # hex value 00 00. token['RRC'] = 0 - token['SND_SEQ'] = struct.pack('>Q',sequenceNumber) + token['SND_SEQ'] = struct.pack('>Q', sequenceNumber) - print(f"token.getData() len={len(token.getData())}") - hexdump.hexdump(token.getData()) - print(f"data (plaintext to encrypt) len={len(data)}") - hexdump.hexdump(data) - cipherText = cipher.encrypt(sessionKey, keyUsage, data + token.getData(), confounder) - print(f"cipherText len={len(cipherText)}") - hexdump.hexdump(cipherText) + # For Kerberos SSP: blob for HMAC calculation is different from blob for encryption. + # This is undocumented, and was identified by reversing cryptodll.dll + integrity_blob = None + if dce_rpc_header is not None and auth_data_header is not None: + integrity_blob = confounder + dce_rpc_header + data + auth_data_header + padStr + token.getData() + + data += padStr + + cipherText = cipher.encrypt(key=sessionKey, keyusage=keyUsage, plaintext=data + token.getData(), confounder=confounder, integrity_blob=integrity_blob) token['RRC'] = rrc cipherText = self.rotate(cipherText, token['RRC'] + token['EC']) @@ -299,55 +296,26 @@ def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt return ret1, ret2 - def GSS_Unwrap(self, sessionKey, enc2, sequenceNumber, direction ='init', encrypt=True, authData=None, keyUsage=KG_USAGE_ACCEPTOR_SEAL): + def GSS_Unwrap(self, sessionKey, data, sequenceNumber, direction='init', encrypt=True, authData=None, keyUsage=KG_USAGE_ACCEPTOR_SEAL, dcerpcheader=None): + # implementation as per [MS-KILE] Section 4.3 from impacket.dcerpc.v5.rpcrt import SEC_TRAILER - from R2Log import logger - logger.info("in GSSAPI_Unwrap") cipher = self.cipherType() token = self.WRAP(authData[len(SEC_TRAILER()):]) - logger.info(f"WRAP TOKEN (len={len(token)})") - print(token.fields) enc1 = authData[len(self.WRAP()) + len(SEC_TRAILER()):] - logger.info(f"Wrap token's Data = krb5_sgn_cksum (len={len(enc1)})") - hexdump.hexdump(enc1) - - logger.info("Rotated = krb5_sgn_cksum + PDUData") - rotated = enc1 + enc2 - hexdump.hexdump(rotated) + enc2 = data - cipherText = self.unrotate(rotated, token['RRC'] + token['EC']) + cipherText = self.unrotate(enc1 + enc2, token['RRC'] + token['EC']) - # rotated = authData[len(self.WRAP())+len(SEC_TRAILER()):] - # cipherText = data + self.unrotate(rotated, token['RRC'] + token['EC']) + # For Kerberos SSP: blob for HMAC calculation is different from blob for encryption. + # This is undocumented, and was identified by reversing cryptodll.dll + # no need to implement, assuming the other client is legit + plainText, confounder = cipher.decrypt(sessionKey, keyUsage, cipherText, ignore_integrity=True) - logger.info(f"cipherText dump len={len(cipherText)}") - hexdump.hexdump(cipherText) - - logger.info("decrypting.....") - plainText, confounder = cipher.decrypt(sessionKey, keyUsage, cipherText) - - logger.info("again in GSS_Unwrap") - - logger.info(f"confounder len={len(confounder)}") - hexdump.hexdump(confounder) encdata, enchdr = plainText[:-len(self.WRAP())], plainText[-len(self.WRAP()):] encdata, filler = encdata[:-token['EC']], encdata[-token['EC']:] - logger.info(f"encdata (len={len(encdata)})") - hexdump.hexdump(encdata) - - logger.info(f"filler (len={len(filler)})") - hexdump.hexdump(filler) - signedtoken = self.WRAP(enchdr) - - logger.info(f"enchdr (len={len(enchdr)})") - hexdump.hexdump(enchdr) - print(signedtoken.fields) - - logger.info(f"SND_SEQ: {int.from_bytes(signedtoken['SND_SEQ'], byteorder='big')}") - return encdata, confounder class GSSAPI_AES256(GSSAPI_AES):