Skip to content
Open
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
164 changes: 97 additions & 67 deletions keytabextract.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -49,79 +132,26 @@ 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:
displayhelp()
sys.exit()
else:
ktextract()
pretty_print()