Created
May 31, 2016 23:57
-
-
Save hasherezade/c78c15eb8acb54e1f13dfc5d9bec6af8 to your computer and use it in GitHub Desktop.
Decoder for 7even-HONE$T ransomware - variant: 08a53eb5d54c6829cf6ea29bd61ea161
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# CC-BY: hasherezade | |
"""Finds R5A key for 7even-HONE$T ransomware - variant: 08a53eb5d54c6829cf6ea29bd61ea161""" | |
import argparse | |
import os | |
PREFIX = 'M' | |
SUFFIX = '*' | |
#hardcoded keys for particular variant of 7ev3n HONE$T: | |
FNAME_KEY_R5A = "777EJfngmfiob5mvX5i4omgfogkthmgA7Jergth3ng5m896h65f34f55gtiyht4jfkdfvb5iy76h544uekdfi5otjg5k0i6h595f" | |
def decode(data, key, offset=0): | |
maxlen = len(data) | |
keylen = len(key) | |
j = 0 #key index | |
decoded = bytearray() | |
for i in range(offset, maxlen): | |
dec = data[i] ^ key[j % keylen] | |
j += 1 | |
decoded.append(dec) | |
return decoded | |
def search_suffix(filep): | |
filep.seek(0, os.SEEK_END) | |
size = filep.tell() | |
pos = size - 1 | |
filep.seek(pos, os.SEEK_SET) | |
data = filep.read(1) | |
if data != '\x0a': | |
return None | |
pos -= 1 | |
buffer = "" | |
prev_data = None | |
while pos > 0: | |
filep.seek(pos, os.SEEK_SET) | |
data = filep.read(1) | |
pos -= 1 | |
if data == SUFFIX and prev_data == SUFFIX: | |
break | |
if data == SUFFIX: | |
prev_data = SUFFIX | |
continue | |
else: | |
if prev_data == SUFFIX: | |
buffer = prev_data + buffer | |
prev_data = None | |
buffer = data + buffer | |
return buffer | |
def merge_win_path(path, filename): | |
if path.endswith('\\') or path.endswith('/'): | |
path = path[:len(path)-1] | |
return path + '\\' + filename | |
def extend_key(key, out_len): | |
while len(key) < out_len: | |
key = key + key | |
return key[:out_len] | |
class R5A_decoder(): | |
"""Decoder for R5A algorithm""" | |
def __init__(self, f_path, data): | |
self.f_path = f_path | |
self.data = bytearray(data) | |
self.size = len(data) | |
self.half_size = len(data) >> 1 | |
self.quarter_size = self.half_size >> 1 | |
def decode(self): | |
for i in (0,1): | |
self.loop2(i) | |
self.loop1(i) | |
return self.data | |
def loop2(self, index2): | |
#process quarter of the content | |
my_quarter = self.half_size * index2 + self.quarter_size | |
end_quarter = my_quarter + self.quarter_size | |
for i in range(my_quarter, my_quarter + self.quarter_size): | |
dx = i % 255 | |
self.data[i] = self.data[i] ^ dx | |
return self.data | |
def loop1(self, index2): | |
#process quarter of the content | |
my_quarter = self.half_size * index2 | |
other_quarter = my_quarter + self.quarter_size | |
for i in range(0, self.quarter_size): | |
other_val = self.data[other_quarter + i] | |
my_val = self.data[my_quarter + i] | |
self.data[my_quarter + i] = my_val ^ other_val | |
return self.data | |
def decode_content(fp, orig_file_name): | |
suffix_len = len(orig_file_name) + len('**') + len('\x0a') + 1 | |
data = read_encrypted(fp, suffix_len) | |
r5a_decoder = R5A_decoder(orig_file_name, data) | |
return r5a_decoder.decode() | |
def read_encrypted(filep, suffix_len): | |
filep.seek(0, os.SEEK_END) | |
size = filep.tell() | |
if size < suffix_len: | |
return None | |
filep.seek(0, os.SEEK_SET) | |
data = filep.read(1) | |
if data != PREFIX: | |
print "encrypted not found" | |
return None | |
return filep.read(size - suffix_len) | |
def check_key_len(keydata, suggested_len): | |
if len(keydata) < suggested_len * 2: | |
print "[-] Those files are too short to recover keys. Choose pair of bigger files" | |
return False | |
key1 = keydata[:suggested_len] | |
start2 = suggested_len | |
key2 = keydata[start2: start2 + suggested_len] | |
if key1 == key2: | |
return True | |
return False | |
def find_key_len(keydata, min_keylen=10): | |
if keydata == None or len(keydata) < min_keylen: | |
print "[-] Invalid key data" | |
return None | |
start_len = 4 | |
while (start_len < len(keydata)): | |
key_start = keydata[:start_len] | |
next_occur = keydata.find(key_start, start_len) | |
if check_key_len(keydata, next_occur) == True: | |
return next_occur | |
start_len = next_occur | |
if next_occur == -1: | |
return None | |
return None | |
def main(): | |
parser = argparse.ArgumentParser(description="Recover R5A key") | |
parser.add_argument('--encfile', dest="encfile", default=None, help="Encrypted file", required=True) | |
parser.add_argument('--origfile', dest="origfile", default=None, help="Original file", required=True) | |
args = parser.parse_args() | |
if args.encfile.endswith("R5A"): | |
fname_key = FNAME_KEY_R5A | |
else: | |
print "[-] Invalid arg: %s is not a R5A file!" % args.encfile | |
return (-1) | |
fp = open(args.encfile, 'rb') | |
fname = search_suffix(fp) | |
if fname == None or len(fname) == 0: | |
print "Failed to recover file name" | |
exit (-1) | |
orig_file_name = decode(bytearray(fname), bytearray(fname_key)) | |
print "[+] Original name: " + orig_file_name | |
if not args.origfile.endswith(orig_file_name): | |
print "[-] ERROR: Names mismatch. Supply the original file corresponding to the encrypted file: " + orig_file_name | |
exit (-1) | |
outdata = decode_content(fp, orig_file_name) | |
if outdata is None: | |
print "[-] Decoding failed" | |
return (-2) | |
fp.close() | |
fp2 = open(args.origfile, 'rb') | |
if fp2 is None: | |
print "[-] Failed to read original file" | |
exit (-1) | |
orig_data = fp2.read() | |
fp2.close() | |
r5a_key = decode(bytearray(outdata), bytearray(orig_data)) | |
keylen = find_key_len(r5a_key) | |
if keylen is None: | |
print "[-] Filed to recover the key! Maybe invalid path or pair of files?" | |
return (-2) | |
if check_key_len(r5a_key, keylen): | |
print "[+] Detected key length: %d" % keylen | |
r5a_key = r5a_key[:keylen] | |
else: | |
print "[-] Key is invalid! Maybe you gave an incorrect pair of files?" | |
return (-2) | |
key_fname = "r5a_key.bin" | |
keyfile = open(key_fname, 'wb') | |
if keyfile is None: | |
print "Error creating file: " + key_fname | |
return (-2) | |
keyfile.write(r5a_key) | |
keyfile.close() | |
print "[+] R5A key saved to: " + key_fname | |
return 0 | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# CC-BY: hasherezade | |
"""Decoder for 7even-HONE$T ransomware - variant: 08a53eb5d54c6829cf6ea29bd61ea161""" | |
import argparse | |
import os | |
PREFIX = 'M' | |
SUFFIX = '*' | |
#hardcoded keys for particular variant of 7ev3n HONE$T: | |
FNAME_KEY_R4A = 'ifgj5906jHg948f8g46hgkf054k6h0igjf45n9g5fj90g64g5' | |
FNAME_KEY_R5A = '777EJfngmfiob5mvX5i4omgfogkthmgA7Jergth3ng5m896h65f34f55gtiyht4jfkdfvb5iy76h544uekdfi5otjg5k0i6h595f' | |
def decode(data, key, offset=0): | |
maxlen = len(data) | |
keylen = len(key) | |
j = 0 #key index | |
decoded = bytearray() | |
for i in range(offset, maxlen): | |
dec = data[i] ^ key[j % keylen] | |
j += 1 | |
decoded.append(dec) | |
return decoded | |
def search_suffix(filep): | |
filep.seek(0, os.SEEK_END) | |
size = filep.tell() | |
pos = size - 1 | |
filep.seek(pos, os.SEEK_SET) | |
data = filep.read(1) | |
if data != '\x0a': | |
return None | |
pos -= 1 | |
buffer = "" | |
prev_data = None | |
while pos > 0: | |
filep.seek(pos, os.SEEK_SET) | |
data = filep.read(1) | |
pos -= 1 | |
if data == SUFFIX and prev_data == SUFFIX: | |
break | |
if data == SUFFIX: | |
prev_data = SUFFIX | |
continue | |
else: | |
if prev_data == SUFFIX: | |
buffer = prev_data + buffer | |
prev_data = None | |
buffer = data + buffer | |
return buffer | |
def merge_win_path(path, filename): | |
if path.endswith('\\') or path.endswith('/'): | |
path = path[:len(path)-1] | |
return path + '\\' + filename | |
def extend_key(key, out_len): | |
while len(key) < out_len: | |
key = key + key | |
return key[:out_len] | |
class R5A_decoder(): | |
"""Decoder for R5A algorithm""" | |
def __init__(self, f_path, data): | |
self.f_path = f_path | |
self.data = bytearray(data) | |
self.size = len(data) | |
self.quarter_size = self.size >> 2 | |
self.half_size = self.quarter_size * 2 | |
def decode(self, r5a_key, key_len): | |
for i in (0,1): | |
self.loop2(i) | |
self.loop1(i) | |
path_key = extend_key(self.f_path, key_len) | |
hard_key = extend_key(r5a_key, key_len) | |
self.data = decode(self.data, bytearray(hard_key)) | |
return self.data | |
def loop2(self, index2): | |
#process quarter of the content | |
my_quarter = self.half_size * index2 + self.quarter_size | |
end_quarter = my_quarter + self.quarter_size | |
for i in range(my_quarter, my_quarter + self.quarter_size): | |
dx = i % 255 | |
self.data[i] = self.data[i] ^ dx | |
return self.data | |
def loop1(self, index2): | |
#process quarter of the content | |
my_quarter = self.half_size * index2 | |
other_quarter = my_quarter + self.quarter_size | |
for i in range(0, self.quarter_size): | |
other_val = self.data[other_quarter + i] | |
my_val = self.data[my_quarter + i] | |
self.data[my_quarter + i] = my_val ^ other_val | |
return self.data | |
def decode_content(fp, is_r4a, fname_key, orig_file_name, r5a_key=None, r5a_keylen=None): | |
suffix_len = len(orig_file_name) + len('**') + len('\x0a') + 1 | |
data = read_encrypted(fp, suffix_len) | |
if is_r4a: | |
return decode(bytearray(data), bytearray(fname_key)) | |
f_path = orig_file_name | |
r5a_decoder = R5A_decoder(f_path, data) | |
return r5a_decoder.decode(r5a_key, r5a_keylen) | |
def read_encrypted(filep, suffix_len): | |
filep.seek(0, os.SEEK_END) | |
size = filep.tell() | |
if size < suffix_len: | |
return None | |
filep.seek(0, os.SEEK_SET) | |
data = filep.read(1) | |
if data != PREFIX: | |
print "encrypted not found" | |
return None | |
return filep.read(size - suffix_len) | |
def main(): | |
parser = argparse.ArgumentParser(description="Data XOR") | |
parser.add_argument('--file', dest="file", default=None, help="Input file", required=True) | |
parser.add_argument('--r5a_key', dest="r5a_key", default="r5a_key.bin", help="File with the R5A key") | |
parser.add_argument('--r5a_keylen', dest="r5a_keylen", type=int, default=None, help="Length of R5A key") | |
args = parser.parse_args() | |
fp = open(args.file, 'rb') | |
fname = search_suffix(fp) | |
if fname == None or len(fname) == 0: | |
print "Failed to recover file name" | |
exit (-1) | |
is_r4a = False | |
if args.file.endswith("R5A"): | |
fname_key = FNAME_KEY_R5A | |
print "R5A" | |
else: | |
fname_key = FNAME_KEY_R4A | |
is_r4a = True | |
print "R4A" | |
orig_file_name = decode(bytearray(fname), bytearray(fname_key)) | |
print "[+] Original name: " + orig_file_name | |
dirname = os.path.dirname(args.file) | |
orig_fname = os.path.join(dirname, orig_file_name) | |
r5a_key = None | |
if args.r5a_key is not None: | |
keyfile = None | |
try: | |
keyfile = open(args.r5a_key, 'rb') | |
except IOError: | |
print "[-] Cannot open file with R5A key. Please supply the valid file as a parameter" | |
return -1 | |
if keyfile is None: | |
print "[-] Cannot open the file with R5A key: %s" % args.r5a_key | |
return -1 | |
r5a_key = keyfile.read() | |
keyfile.close() | |
print "[+] R5A key fetched from file: %s" % args.r5a_key | |
if not is_r4a and r5a_key is None: | |
print "[-] You need to supply R5A key" | |
return -1 | |
r5a_keylen = len(r5a_key) | |
if args.r5a_keylen is not None: | |
r5a_keylen = args.r5a_keylen | |
print "[+] Using R5A key length: %s" % r5a_keylen | |
outdata = decode_content(fp, is_r4a, fname_key, orig_file_name, r5a_key, r5a_keylen) | |
if outdata is None: | |
print "[-] Decoding failed" | |
return (-2) | |
outfile = open(str(orig_fname), 'wb') | |
if outfile is None: | |
print "Error creating file: " + orig_fname | |
return (-2) | |
outfile.write(outdata) | |
outfile.close() | |
print "[+] Decoded to: " + orig_fname | |
return 0 | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment