Skip to content

Commit

Permalink
updating gssapi and crypto for aes-cts wrap, unwrap, encryp, decrypt
Browse files Browse the repository at this point in the history
  • Loading branch information
ShutdownRepo committed Mar 18, 2024
1 parent 7e94c83 commit 2da6e17
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 138 deletions.
96 changes: 16 additions & 80 deletions impacket/krb5/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
from binascii import unhexlify
from functools import reduce
from os import urandom
import hexdump

# XXX current status:
# * Done and tested
Expand Down Expand Up @@ -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):
Expand Down
84 changes: 26 additions & 58 deletions impacket/krb5/gssapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'])
Expand All @@ -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):
Expand Down

0 comments on commit 2da6e17

Please sign in to comment.