Last active
July 18, 2023 22:57
-
-
Save monoxgas/b0e3ec74bc3d190f76951ac1ac9b6f2a to your computer and use it in GitHub Desktop.
VoiceCrypt Crypto
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
import sys | |
import struct | |
import binascii | |
from itertools import cycle, zip_longest | |
from operator import itemgetter, xor | |
from collections import Counter | |
import re | |
# Some root key constants from the binary | |
g_key = b'\x34\x8C\x55\x3E\xC7\xFA\x1E\x21\x28\x41\x4F\x6C\xBE\x81\xD0\xC5\xE3\x82\x2C\x7F\x70\xC0\x92\xB7\xAB\xBD\x3B\x25\xD1\xA7\x15\x78\x29\x06\x27\xA2\x71\x26\xAD\x5C\x4A\xDB\xC9\xFF\x2F\xE6\x4E\x2D\xE0\xA1\x86\x75\x68\xD7\x38\x08\x5A\xD4\xCA\x0D\xE2\xEE\x54\x13\xCF\x59\x1B\xE7\x14\x9A\xD5\xAF\xE4\xA4\xD8\x97\x60\x3D\xF2\x56\xA0\x51\x5E\x31\x30\x11\xD2\x93\x17\x7A\xC2\xE9\x12\x4B\x07\x39\x6B\xCD\x8B\xE1\xF6\x0F\x33\x24\x52\xB4\x89\x5D\x87\xFB\x09\x10\x61\x9B\x32\xC6\x72\x57\xC8\xAE\x74\x2A\x23\x16\xCB\xDF\x94\x42\x0C\x44\x3C\xED\x88\xAC\xF1\x45\x7B\xDE\x9C\xC4\xFC\xF9\x53\x99\x76\xB8\xDA\x20\x40\x8D\x0B\x8F\xB6\x2B\x01\x84\xC1\xD6\x69\xBA\xB1\xFD\x0A\x58\x65\x6F\x64\x62\xEF\x47\xF0\x77\x7E\xCC\x5F\x19\xC3\xA5\x91\xAA\xFE\x37\xA9\x63\x80\xB2\xEA\x8E\xA3\x48\x0E\xA8\x5B\x7D\x04\xB3\xEC\x6A\x67\xE5\xA6\xD9\x00\x7C\x18\x9F\xB0\x35\x79\x2E\x98\xE8\x95\xDC\xBB\x22\x46\x36\x83\x1F\xBC\x1A\x1D\x4D\xEB\x9E\x90\xBF\xF5\x05\x1C\xF7\x43\xD3\xCE\x49\xB5\x50\x4C\x85\x9D\x6D\x3F\xDD\x66\x96\xB9\xF4\x03\x3A\xF3\x6E\x02\x8A\xF8\x73' | |
g_key2 = b'\xCA\x9A\xFC\xF8\xC2\xE5\x21\x5E\x37\x6E\xA2\x96\x80\x3B\xBE\x65\x6F\x55\x5C\x3F\x44\x1E\x7B\x58\xCC\xAF\xDD\x42\xE6\xDE\x06\xDB\x93\x07\xD7\x7A\x67\x1B\x25\x22\x08\x20\x79\x99\x12\x2F\xD1\x2C\x54\x53\x72\x66\x00\xCF\xD9\xB5\x36\x5F\xF9\x1A\x82\x4D\x03\xF2\x94\x09\x7F\xE8\x81\x87\xD8\xA9\xBD\xEB\x28\x5D\xEE\xDF\x2E\x0A\xED\x51\x68\x8E\x3E\x02\x4F\x75\xA3\x41\x38\xC0\x27\x6B\x52\xAE\x4C\x70\xA7\xB7\xA6\xA4\xF4\xC6\x34\x9E\xC5\x60\x0B\xF1\xFB\xA5\x14\x24\x74\xFF\x78\x33\x90\xAB\x1F\xD0\x59\x88\xCB\xC1\xAC\x13\xB8\x0D\x11\xDA\x9B\xEF\x32\x6C\x84\x6A\xFD\x62\x01\x95\xBB\x97\xE2\xB2\x16\x57\x7E\xD4\xF5\x4B\xD2\x8F\x45\x71\x8A\xF0\xE1\xCD\x50\x31\x23\xBC\x49\xB1\xC8\x1D\xBF\xB6\xB3\x18\x85\x26\x77\x47\xCE\xA0\xB9\xC3\x69\xEC\x98\x17\x91\xF6\x9F\xD6\xDC\x19\x0C\xE3\x15\x9C\x5A\xB0\x8B\x0F\x73\x04\x76\x2A\x3A\x7C\xAD\x61\xEA\x40\x0E\x1C\x56\xE9\x39\x46\x9D\x35\x4A\xC9\x92\x29\xD5\xF3\x89\x7D\x30\x63\x3C\x10\x48\xC7\x2D\x43\xD3\x5B\xBA\xE0\xC4\x83\x3D\xA8\xAA\x86\x4E\xFA\xF7\xE4\x64\xE7\xFE\x8D\x05\x6D\x8C\xA1\xB4\x2B' | |
KeySize = 32 | |
# Helper functions | |
def keystream_from_rand(seed, length): | |
out = [] | |
for i in range(length): | |
seed = (214013 * seed + 2531011) & 0x7fffffff | |
rand = ((seed >> 16) & 0xff) | |
out.append(((rand >> 31) ^ abs(rand) - (rand >> 31))) | |
return bytes(out) | |
def xor(d, k): | |
return bytearray([a ^ b for a, b in zip(d, cycle(k))]) | |
def xor_with_rand(data, seed): | |
out = [] | |
for b in data: | |
seed = (214013 * seed + 2531011) & 0x7fffffff | |
out.append(g_key[b ^ ((seed >> 16) & 0xff)]) | |
return bytes(out) | |
def reverse_xor_k1(data, key): | |
data = bytearray(data) | |
i = len(data) | |
while i > 0: | |
i -= 1 | |
data[i] = g_key[data[i] ^ key[i % len(key)]] | |
return bytes(data) | |
def reverse_xor_k2(data, key): | |
data = bytearray(data) | |
i = len(data) | |
while i > 0: | |
i -= 1 | |
data[i] = g_key2[data[i] ^ key[i % len(key)]] | |
return bytes(data) | |
def xor_k1(data, key): | |
data = bytearray(data) | |
i = len(data) | |
while i > 0: | |
i -= 1 | |
data[i] = g_key[data[i]] ^ key[i % len(key)] | |
return bytes(data) | |
def xor_k2(data, key): | |
data = bytearray(data) | |
i = len(data) | |
while i > 0: | |
i -= 1 | |
data[i] = g_key2[data[i]] ^ key[i % len(key)] | |
return bytes(data) | |
def chunkinto(d, s): | |
return [d[i:i + s] for i in range(0, len(d), s)] | |
def transpose(l): | |
return list(zip_longest(*l, fillvalue=0)) | |
top_english = {c: i for i, c in enumerate(list('etaoinsrhdlucmfywgpbvkxqjz'))} | |
scoring_factor = 26 | |
def score_english(data): | |
score = 0 | |
try: text = data.decode('ascii').lower() | |
except: return score | |
if re.search(r'[^\x20-\x7e]', text): | |
return score | |
m_common = Counter(text).most_common()[:scoring_factor] | |
for i, data in enumerate(m_common): | |
(char,_) = data | |
if char in top_english.keys(): | |
diff = i - top_english[char] | |
score += (scoring_factor - abs(diff)) | |
return score | |
# --- Start --- | |
raw = open(sys.argv[1], 'rb').read() | |
# First step is to use one of the header values in srand() | |
# to decrypt the file suffix. | |
# XORing each byte with rand() yeilds an offset to g_key | |
# The output is some sort of "suffix", which includes | |
# the original file name, along with the original | |
# first 16 bytes of the encrypted block | |
data_len, data_seed, suffix_lengh, suffix_seed = struct.unpack('iiii', raw[:16]) | |
enc_suffix = raw[data_len:] | |
assert len(enc_suffix) == suffix_lengh | |
suffix = xor_with_rand(enc_suffix, suffix_seed) | |
paths = suffix[72:].split(b'\x00')[:2] | |
paths.reverse() | |
print('\n[+] Original: ' + '\\'.join([p.decode() for p in paths])) | |
# Once we restore the "real" first 16 bytes | |
# we should now have the encrypted block. | |
# The size should match the first 4 bytes | |
# of the file. | |
packed = struct.unpack('IIII', suffix[56:56+16]) | |
enc_data = struct.pack('>IIII', *packed) + raw[16:data_len] | |
assert len(enc_data) == data_len | |
# This encrypted stream is simply our plaintext | |
# XORed against a keystream. We can use known-plaintext | |
# attacks or even english analysis to recover the 32 | |
# byte key | |
blocks = chunkinto(enc_data, KeySize) | |
tblocks = transpose(blocks) | |
real = open(sys.argv[1].replace('vtxt', 'txt'), 'rb').read() | |
rblocks = transpose(chunkinto(real, KeySize)) | |
key = bytearray(KeySize) | |
for kp, tb in enumerate(tblocks): | |
scores = [] | |
for k in range(1, 255): | |
e = bytearray(tb) | |
for i in range(len(e)): | |
e[i] = g_key[e[i] ^ k] | |
if e[:2] == bytearray(rblocks[kp])[:2]: | |
key[kp] = k | |
# score = score_english(e) | |
# scores.append([score, k, e]) | |
# scores = sorted(scores, key=itemgetter(0), reverse=True) | |
# key[kp] = scores[0][1] | |
# print(scores[:3], end="\r\n\r\n") | |
print('[+] Recovered Key : ' + binascii.hexlify(key).decode()) | |
print('\n[+] Dec: ', end='') | |
print(reverse_xor_k1(enc_data, key)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment