diff --git a/keytabextract.py b/keytabextract.py index 4a83d38..1dac81d 100755 --- a/keytabextract.py +++ b/keytabextract.py @@ -1,16 +1,99 @@ #!/usr/bin/env python3 -import binascii,sys +import binascii,sys,datetime # Take argument 1 as keytab file, import and decode the hex ktfile = sys.argv[1] f = open(ktfile, 'rb').read() hex_encoded = binascii.hexlify(f).decode('utf-8') +TYPES = {"0017":"NTLM", "0011":"AES-128", "0012":"AES-256"} +all_data = dict() + + def displayhelp(): print("KeyTabExtract. Extract NTLM Hashes from KeyTab files where RC4-HMAC encryption has been used.") print("Usage : ./keytabextract.py [keytabfile]") print("Example : ./keytabextract.py service.keytab") +def entryextract(pointer): + # Number of counted octet strings representing the realm of the principal + num_components = int(hex_encoded[pointer:pointer+4], 16) + + # convert the + num_realm = int(hex_encoded[pointer+4:pointer+8], 16) + + # calculate the offset for the realm + realm_jump = pointer+8 + (num_realm * 2) + + # Determine the realm for the keytab file + realm = hex_encoded[pointer+8:realm_jump] + realm = bytes.fromhex(realm).decode('utf-8') + + components = [] + comp_start = realm_jump + comp_end = comp_start + for _ in range(num_components): + # Calculate the number of bytes for the realm of components + comp_len = int(hex_encoded[comp_start:comp_start+4], 16) + + # Calculates the realm component (HTTP) + comp_end = comp_start+4 + (comp_len * 2) + components.append(hex_encoded[comp_start+4:comp_end]) + comp_start = comp_end + + components = [bytes.fromhex(x).decode('utf-8') for x in components] + sp = "/".join(components) + + # Calculate typename - 32 bits from previous value + typename_offset = comp_end + 8 + typename = hex_encoded[comp_end:typename_offset] + + # Calculate Timestamp - 32 bit from typename value + timestamp_offset = typename_offset + 8 + timestamp = int(hex_encoded[typename_offset:timestamp_offset], 16) + timestamp_str = datetime.datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M") + + # Calcualte 8 bit VNO Field + vno_offset = timestamp_offset + 2 + vno = hex_encoded[timestamp_offset:vno_offset] + #print("\tVersion No : " + vno) + + # Calculate KeyType - 16 bit value + keytype_offset = vno_offset + 4 + keytype_hex = hex_encoded[vno_offset:keytype_offset] + keytype_dec = int(keytype_hex, 16) + + # Calculate Length of Key Value - 16 bit value + key_val_offset = keytype_offset + 4 + key_val_len = int(hex_encoded[keytype_offset:key_val_offset], 16) + + # Extract Key Value + key_val_start = key_val_offset + key_val_finish = key_val_start + (key_val_len * 2) + key_val = hex_encoded[key_val_start:key_val_finish] + + ignore_bytes = int(hex_encoded[key_val_finish:key_val_finish + 8], 16) + + if not realm in all_data: + all_data[realm] = dict() + if not sp in all_data[realm]: + all_data[realm][sp] = dict() + if not timestamp_str in all_data[realm][sp]: + all_data[realm][sp][timestamp_str] = dict() + if not keytype_hex in all_data[realm][sp][timestamp_str]: + all_data[realm][sp][timestamp_str][keytype_hex] = key_val + + # Direty hack, if you have a better solution please contribute + next_entry = key_val_finish + 8 + if hex_encoded[next_entry:next_entry+4] == "ffff": + next_entry += 8 + while hex_encoded[next_entry:next_entry+2] == "00": + next_entry += 2 + if hex_encoded[next_entry:next_entry+4] == "ffff": + next_entry += 8 + + return next_entry + 2 + def ktextract(): rc4hmac = False aes128 = False @@ -49,75 +132,21 @@ def ktextract(): # 32 bits indicating the size of the array arrLen = int(hex_encoded[4:12], 16) - # Number of counted octet strings representing the realm of the principal - num_components = hex_encoded[12:16] - - # convert the - num_realm = int(hex_encoded[16:20], 16) - - # calculate the offset for the realm - realm_jump = 20 + (num_realm * 2) - - # Determine the realm for the keytab file - realm = hex_encoded[20:realm_jump] - print("\tREALM : " + bytes.fromhex(realm).decode('utf-8')) - - # Calculate the number of bytes for the realm of components - comp_array_calc = realm_jump + 4 - comp_array = int(hex_encoded[realm_jump:comp_array_calc], 16) - - # Calculates the realm component (HTTP) - comp_array_offset = comp_array_calc + (comp_array * 2) - comp_array2 = hex_encoded[comp_array_calc:comp_array_offset] - - # calculate number of bytes for the principal - principal_array_offset = comp_array_offset + 4 - - # extract the principal - principal_array = hex_encoded[comp_array_offset:principal_array_offset] - principal_array_int = (int(principal_array, 16) * 2) - prin_array_start = principal_array_offset - prin_array_finish = prin_array_start + principal_array_int - principal_array_value = hex_encoded[prin_array_start:prin_array_finish] - print("\tSERVICE PRINCIPAL : " + bytes.fromhex(comp_array2).decode('utf-8') + "/" + bytes.fromhex(principal_array_value).decode('utf-8')) - - # Calculate typename - 32 bits from previous value - typename_offset = prin_array_finish + 8 - typename = hex_encoded[prin_array_finish:typename_offset] - - # Calculate Timestamp - 32 bit from typename value - timestamp_offset = typename_offset + 8 - timestamp = hex_encoded[typename_offset:timestamp_offset] - - # Calcualte 8 bit VNO Field - vno_offset = timestamp_offset + 2 - vno = hex_encoded[timestamp_offset:vno_offset] - #print("\tVersion No : " + vno) - - # Calculate KeyType - 16 bit value - keytype_offset = vno_offset + 4 - keytype_hex = hex_encoded[vno_offset:keytype_offset] - keytype_dec = int(keytype_hex, 16) - - # Calculate Length of Key Value - 16 bit value - key_val_offset = keytype_offset + 4 - key_val_len = int(hex_encoded[keytype_offset:key_val_offset], 16) + pointer = 12 - # Extract Key Value - key_val_start = key_val_offset - key_val_finish = key_val_start + (key_val_len * 2) - key_val = hex_encoded[key_val_start:key_val_finish] - if rc4hmac == True: - NTLMHash = hex_encoded.split("00170010")[1] - print("\tNTLM HASH : " + NTLMHash[:32]) + while pointer < len(hex_encoded): + pointer = entryextract(pointer) - if aes256 == True: - aes256hash = hex_encoded.split("00120020")[1] - print("\tAES-256 HASH : " + aes256hash[:64]) +def pretty_print(): + for realm in all_data: + print(f"- Realm: {realm}") + for sp in all_data[realm]: + print(f"\t- Service Principal: {sp}") + for timestamp in sorted(all_data[realm][sp].keys())[::-1]: + print(f"\t\t- Timestamp: {timestamp}") + for enctype in sorted(all_data[realm][sp][timestamp].keys()): + print(f"\t\t\t{TYPES[enctype]}: {all_data[realm][sp][timestamp][enctype]}") - if aes128 == True: - aes128hash = hex_encoded.split("00110010")[1] - print("\tAES-128 HASH : " + aes128hash[:32]) if __name__ == "__main__": if len(sys.argv) == 1: @@ -125,3 +154,4 @@ def ktextract(): sys.exit() else: ktextract() + pretty_print()