Skip to content
Permalink
bleeding-jumbo
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
executable file 176 lines (134 sloc) 6.15 KB
#!/usr/bin/env python
# AxCrypt 1.x and 2.x encrypted file parser for JtR.
#
# Written in 2016 by Fist0urs <eddy.maaalou at gmail.com>.
#
# This software is Copyright (c) 2016, Fist0urs <eddy.maaalou at gmail.com>,
# Copyright (c) 2018, Dhiru Kholia, and it is hereby released to the general
# public under the following terms:
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted.
#
# PEP 8 fixes, Python 3.x, and AxCrypt 2 support: Dhiru Kholia (August, 2018)
#
# See AxCryptVersion1AlgorithmsandFileFormat.pdf and AxCryptVersion2AlgorithmsandFileFormat.pdf
# for details on the .axx file format and the algorithms involved.
#
# TODO:
# - Detect usage of ciphers other than AES-256 in AxCrypt 2.x files (not an
# issue in practice currently).
import sys
import struct
import binascii
# file begins with a 16 bytes constant header
GUID = b'\xc0\xb9\x07\x2e\x4f\x93\xf1\x46\xa0\x15\x79\x2c\xa1\xd9\xe8\x21'
OFFSET_TYPE = 4
SIZE_KEYDATA = 24 # size of constant in keywrap (0xA6 * 8) + size of DEK (16)
SIZE_SALT = 16
SIZE_ITERATION = 4
StructKeys = []
PY3 = sys.version_info[0] == 3
if not PY3:
reload(sys)
sys.setdefaultencoding("utf8")
def usage():
sys.stderr.write('Usage: %s <axxfile> [KEY-FILE]\n\n' % sys.argv[0])
sys.stderr.write('Script to extract hash from AxCrypt encrypted file or self-decrypting binary\n\n')
sys.stderr.write('Optional arguments:\n KEY-FILE path to optional key-file provided\n')
sys.exit(1)
def DWORD_to_int(string_dword):
return struct.unpack("<I", string_dword)[0]
def parse_PE(axxdata):
i = 0
while axxdata[i:i+16] != GUID:
i += 1
return axxdata[i:]
def parse_axxfile2(axxdata, header_datalen, header_datalen_offset, headertype):
version = 2
SIZE_KEYDATA = 144
SIZE_WRAP_SALT = 64
SIZE_DERIV_SALT = 32
offset_to_keydata = header_datalen_offset + OFFSET_TYPE + 1
offset_to_wrap_iteration = offset_to_keydata + SIZE_KEYDATA + SIZE_WRAP_SALT
offset_to_wrap_salt = offset_to_keydata + SIZE_KEYDATA
dword_str = axxdata[offset_to_wrap_iteration:offset_to_wrap_iteration + SIZE_ITERATION]
wrap_salt = axxdata[offset_to_wrap_salt:offset_to_wrap_salt+SIZE_WRAP_SALT]
wrap_iterations = DWORD_to_int(dword_str)
wrappedkey = axxdata[offset_to_keydata:offset_to_keydata + SIZE_KEYDATA]
offset_to_deriv_salt = offset_to_keydata + SIZE_KEYDATA + SIZE_WRAP_SALT + 4
deriv_salt = axxdata[offset_to_deriv_salt:offset_to_deriv_salt+SIZE_DERIV_SALT]
offset_to_deriv_iteration = offset_to_deriv_salt + SIZE_DERIV_SALT
dword_str = axxdata[offset_to_deriv_iteration:offset_to_deriv_iteration + SIZE_ITERATION]
deriv_iterations = DWORD_to_int(dword_str)
return version, wrappedkey, wrap_salt, wrap_iterations, deriv_iterations, deriv_salt
def parse_axxfile(axxfile):
stream = open(axxfile, 'rb')
axxdata = stream.read()
stream.close()
# if header is 'MZ'
if axxdata[:2] == b'\x4D\x5a':
offset_PE_magic = struct.unpack('<L', axxdata[60:64])[0]
# if 'PE' assume PE
if axxdata[offset_PE_magic:offset_PE_magic+2] == b'\x50\x45':
axxdata = parse_PE(axxdata)
sizeof_file = len(axxdata)
if axxdata[:16] != GUID:
print("Be careful, GUID is different from AxCrypt's one...")
header_datalen_offset = 16
headertype = b'\x02' # first type encountered
version = 1
# headertype of dataencrypted section is 0x3f
while headertype != 63:
if not PY3:
header_datalen = ord(axxdata[header_datalen_offset])
headertype = ord(axxdata[header_datalen_offset + OFFSET_TYPE])
else:
header_datalen = axxdata[header_datalen_offset]
headertype = axxdata[header_datalen_offset + OFFSET_TYPE]
if header_datalen in (252, 253) and headertype == 13: # Header/Trailer. Symmetric Key Wrap -> AxCrypt 2.x
version = 2
return parse_axxfile2(axxdata, header_datalen, header_datalen_offset, headertype)
# probably a StructKey
if (header_datalen == 49 and headertype == 4):
offset_to_keydata = header_datalen_offset + OFFSET_TYPE + 1
offset_to_salt = offset_to_keydata + SIZE_KEYDATA
offset_to_iteration = offset_to_salt + SIZE_SALT
dword_str = axxdata[offset_to_iteration:offset_to_iteration + SIZE_ITERATION]
StructKeys.append({'KeyData': axxdata[offset_to_keydata:offset_to_salt],
'salt': axxdata[offset_to_salt:offset_to_iteration]
,'Iteration': DWORD_to_int(dword_str)})
header_datalen_offset += header_datalen
if header_datalen_offset >= sizeof_file:
print("Could not parse file, exiting...")
sys.exit(0)
return version, StructKeys[0]['KeyData'], StructKeys[0]['salt'], StructKeys[0]['Iteration'], None, None
if __name__ == "__main__":
if (len(sys.argv) != 2 and len(sys.argv) != 3):
usage()
# A_DEK == wrapped_key
version, wrapped_key, salt, nb_iteration, deriv_iterations, deriv_salt = parse_axxfile(sys.argv[1])
keyfile_content = ''
key_file_name = ''
# dummy strip to relative path
axxfile = sys.argv[1][sys.argv[1].rfind("/")+1:]
if len(sys.argv) == 3:
keyfile = open(sys.argv[2], 'rb')
data = binascii.hexlify(keyfile.read())
if PY3:
data = data.decode("ascii")
keyfile_content = '*' + data
key_file_name = '*' + sys.argv[2][sys.argv[2].rfind("/")+1:]
keyfile.close()
salt = binascii.hexlify(salt)
wrapped_key = binascii.hexlify(wrapped_key)
if PY3:
salt = salt.decode("ascii")
wrapped_key = wrapped_key.decode("ascii")
if version == 1:
print(axxfile + key_file_name + ":$axcrypt$" + "*" + str(version) + "*" + str(nb_iteration) + "*" + salt + "*" + wrapped_key + keyfile_content)
elif version == 2:
deriv_salt = binascii.hexlify(deriv_salt)
if PY3:
deriv_salt = deriv_salt.decode("ascii")
print(axxfile + key_file_name + ":$axcrypt$" + "*" + str(version) + "*" + str(nb_iteration) + "*" + salt + "*" + wrapped_key + keyfile_content + "*" + str(deriv_iterations) + "*" + deriv_salt)