Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dcshadowdev #45

Merged
merged 18 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions impacket/krb5/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from binascii import unhexlify
from functools import reduce
from os import urandom

# XXX current status:
# * Done and tested
# - AES encryption, checksum, string2key, prf
Expand All @@ -60,7 +61,6 @@
from Cryptodome.Util.number import GCD as gcd
from six import b, PY3, indexbytes, binary_type


def get_random_bytes(lenBytes):
# We don't really need super strong randomness here to use PyCrypto.Random
return urandom(lenBytes)
Expand Down Expand Up @@ -210,17 +210,23 @@ def derive(cls, key, constant):
return cls.random_to_key(rndseed[0:cls.seedsize])

@classmethod
def encrypt(cls, key, keyusage, plaintext, confounder):
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))
if confounder is None:
confounder = get_random_bytes(cls.blocksize)
basic_plaintext = confounder + _zeropad(plaintext, cls.padsize)
hmac = HMAC.new(ki.contents, basic_plaintext, cls.hashmod).digest()
return cls.basic_encrypt(ke, basic_plaintext) + hmac[:cls.macsize]
# 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]
return enc

@classmethod
def decrypt(cls, key, keyusage, ciphertext):
def decrypt(cls, key, keyusage, ciphertext, ignore_integrity=False, wrap_token_len=None, filler_len=None, dce_rpc_header=None, auth_data_header=None):
ki = cls.derive(key, pack('>IB', keyusage, 0x55))
ke = cls.derive(key, pack('>IB', keyusage, 0xAA))
if len(ciphertext) < cls.blocksize + cls.macsize:
Expand All @@ -229,12 +235,22 @@ def decrypt(cls, key, keyusage, ciphertext):
if len(basic_ctext) % cls.padsize != 0:
raise ValueError('ciphertext does not meet padding requirement')
basic_plaintext = cls.basic_decrypt(ke, bytes(basic_ctext))
hmac = bytearray(HMAC.new(ki.contents, basic_plaintext, cls.hashmod).digest())
plaintext, confounder = 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 dce_rpc_header is not None and auth_data_header is not None:
plaintext_data, plaintext_filler_and_wrap_token_header = plaintext[:-filler_len-wrap_token_len], plaintext[-filler_len-wrap_token_len:] # FIXME dynamically set the filler size
integrity_blob = confounder + dce_rpc_header + plaintext_data + auth_data_header + plaintext_filler_and_wrap_token_header
hmac = bytearray(HMAC.new(ki.contents, integrity_blob, SHA).digest())
else:
hmac = bytearray(HMAC.new(ki.contents, basic_plaintext, SHA).digest())
expmac = hmac[:cls.macsize]
if not _mac_equal(mac, expmac):
if not _mac_equal(mac, expmac) and not ignore_integrity:
raise InvalidChecksum('ciphertext integrity failure')
# Discard the confounder.
return bytes(basic_plaintext[cls.blocksize:])
return bytes(basic_plaintext[cls.blocksize:]), bytes(basic_plaintext[:cls.blocksize])

@classmethod
def prf(cls, key, string):
Expand Down
71 changes: 54 additions & 17 deletions impacket/krb5/gssapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import struct
import random
import string

from six import b

from Cryptodome.Hash import HMAC, MD5
Expand Down Expand Up @@ -123,7 +124,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 @@ -246,27 +247,50 @@ def unrotate(self, data, numBytes):
numBytes %= len(data)
result = data[numBytes:] + data[:numBytes]
return result

def get_filler(self, data):
cipher = self.cipherType()
length = (cipher.blocksize - (len(data) % cipher.blocksize)) or 16
filler = b'\x00' * length
return filler


def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True):
def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction='init', encrypt=True, acceptorSubkey=False, keyUsage=KG_USAGE_INITIATOR_SEAL, confounder=None, dce_rpc_header=None, auth_data_header=None):
token = self.WRAP()

cipher = self.cipherType()

# Let's pad the data
pad = (cipher.blocksize - (len(data) % cipher.blocksize)) & 15
padStr = b'\xFF' * pad
data += padStr
if confounder is None:
confounder = b(''.join([rand.choice(string.ascii_letters) for _ in range(16)]))

# The RRC field ([RFC4121] section 4.2.5) is 12 if no encryption is requested or 28 if encryption
# 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

token['Flags'] = 6
token['EC'] = pad
token['Flags'] = 0
if acceptorSubkey:
token['Flags'] |= 4 # AcceptorSubKey flag (0x00100)
if encrypt:
token['Flags'] |= 2 # Sealed flag (0x00010)
if direction != 'init':
token['Flags'] |= 1 # SendByAcceptor flag (0x00001)

token['EC'] = len(self.get_filler(data))
# (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)

# 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 + self.get_filler(data) + token.getData()

cipherText = cipher.encrypt(sessionKey, KG_USAGE_INITIATOR_SEAL, data + token.getData(), None)
data += self.get_filler(data)

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 @@ -277,18 +301,31 @@ def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt

return ret1, ret2

def GSS_Unwrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, authData=None):
def GSS_Unwrap(self, sessionKey, data, sequenceNumber, direction='init', encrypt=True, authData=None, keyUsage=KG_USAGE_ACCEPTOR_SEAL, dce_rpc_header=None, auth_data_header=None, ignore_integrity=False):
# implementation as per [MS-KILE] Section 4.3
from impacket.dcerpc.v5.rpcrt import SEC_TRAILER

cipher = self.cipherType()
token = self.WRAP(authData[len(SEC_TRAILER()):])

rotated = authData[len(self.WRAP())+len(SEC_TRAILER()):] + data

cipherText = self.unrotate(rotated, token['RRC'] + token['EC'])
plainText = cipher.decrypt(sessionKey, KG_USAGE_ACCEPTOR_SEAL, cipherText)
enc1 = authData[len(self.WRAP()) + len(SEC_TRAILER()):]
enc2 = data

cipherText = self.unrotate(enc1 + enc2, 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,
dce_rpc_header=dce_rpc_header, auth_data_header=auth_data_header, ignore_integrity=ignore_integrity,
wrap_token_len=len(self.WRAP()), filler_len=token['EC']
)

encdata, enchdr = plainText[:-len(self.WRAP())], plainText[-len(self.WRAP()):]
encdata, filler = encdata[:-token['EC']], encdata[-token['EC']:]

return plainText[:-(token['EC']+len(self.WRAP()))], None
return encdata, confounder

class GSSAPI_AES256(GSSAPI_AES):
checkSumProfile = crypto._SHA1AES256
Expand Down
Loading