commit
920228e75b
9 changed files with 321 additions and 0 deletions
@ -0,0 +1,7 @@
|
||||
[submodule "ext/sx"] |
||||
path = ext/sx |
||||
url = https://weeaboo.software/Shiz/sx |
||||
branch = feat/sxplore |
||||
[submodule "ext/atwork"] |
||||
path = ext/atwork |
||||
url = https://github.com/Shizmob/atwork |
@ -0,0 +1,3 @@
|
||||
pycryptodome |
||||
-e git+https://github.com/Shizmob/atwork#egg=atwork |
||||
-e git+https://weeaboo.software/Shiz/sx@feat/sxplore#egg=sx |
@ -0,0 +1,20 @@
|
||||
from setuptools import setup |
||||
|
||||
setup( |
||||
name='truedecrypt', |
||||
version='0.1.0', |
||||
author='Shiz <hi@shiz.me>', |
||||
url='https://weeaboo.software/Shiz/truedecrypt', |
||||
description='Truecrypt version 4.1 - 4.3 volume decrypter', |
||||
|
||||
py_modules=['truedecrypt'], |
||||
install_requires=["atwork", "sx", "pycryptodome"], |
||||
dependency_links=[ |
||||
"https://github.com/Shizmob/atwork/archive/refs/heads/master.tar.gz#egg=atwork", |
||||
"https://weeaboo.software/Shiz/sx/archive/feat/sxplore.tar.gz#egg=sx", |
||||
], |
||||
entry_points={ |
||||
'console_scripts': ['truedecrypt=truedecrypt:main'], |
||||
}, |
||||
zip_safe=True, |
||||
) |
@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env python3 |
||||
from __future__ import annotations |
||||
import enum |
||||
import zlib |
||||
import hashlib |
||||
import itertools |
||||
from sx import Struct, parse |
||||
|
||||
from Crypto.Cipher import AES |
||||
|
||||
|
||||
IV_LEN = 32 |
||||
|
||||
def prf_ripemd160(key: bytes, salt: bytes, nout: int) -> bytes: |
||||
return hashlib.pbkdf2_hmac('ripemd160', key, salt, 2000, nout) |
||||
|
||||
def prf_sha512(key: bytes, salt: bytes, nout: int) -> bytes: |
||||
return hashlib.pbkdf2_hmac('sha512', key, salt, 1000, nout) |
||||
|
||||
def prf_sha1(key: bytes, salt: bytes, nout: int) -> bytes: |
||||
return hashlib.pbkdf2_hmac('sha1', key, salt, 1000, nout) |
||||
|
||||
def prf_whirlpool(key: bytes, salt: bytes, nout: int) -> bytes: |
||||
return hashlib.pbkdf2_hmac('whirlpool', key, salt, 1000, nout) |
||||
|
||||
def prf_single(key: bytes, salt: bytes, nout: int, method) -> tuple[bytes, bytes]: |
||||
r = method(key, salt, nout + IV_LEN) |
||||
return r[IV_LEN:], r[:IV_LEN] |
||||
|
||||
|
||||
PRF_METHODS = [prf_ripemd160, prf_sha512, prf_sha1, prf_whirlpool] |
||||
|
||||
def prf_all(key: bytes, salt: bytes, nout: int) -> list[tuple[bytes, bytes]]: |
||||
d = [] |
||||
for method in PRF_METHODS: |
||||
key, iv = prf_single(key, salt, nout, method) |
||||
yield (method, key, iv) |
||||
|
||||
|
||||
def gf2_add(a: int, b: int): |
||||
""" Add two numbers in GF(2 ^ n) """ |
||||
return a ^ b |
||||
|
||||
def gf2_mul(a, b, mod): |
||||
""" Multiply two numbers in GF(2^n) with a given polynomial - adapted from http://www.bjrn.se/code/pytruecrypt/lrwpy.txt """ |
||||
def xor_mod(n, mod): |
||||
while True: |
||||
x = n.bit_length() - mod.bit_length() |
||||
|
||||
if x == 0: |
||||
n = n ^ mod |
||||
if x <= 0: |
||||
break |
||||
lower = n & ((1 << x) - 1) |
||||
n = (((n >> x) ^ mod) << x) | lower |
||||
return n |
||||
|
||||
# Naively mutiply two polynomials together. Lets say a is x^8+x^3+1 |
||||
# and b is x^4+x^2, then we can write this as the following pseudo code: |
||||
res = 0 |
||||
a_cnt = 0 |
||||
# for each term in [x^8, x^3, 1]: |
||||
while a: |
||||
b2 = b |
||||
b_cnt = 0 |
||||
if a & 1: |
||||
# for each term in [x^4, x^2]: |
||||
while b2: |
||||
if b2 & 1: |
||||
# 1 << (a_cnt + b_cnt) constructs the new term |
||||
# and the xor adds it to the result modulo 2. |
||||
res ^= 1 << (a_cnt + b_cnt) |
||||
b2 >>= 1 |
||||
b_cnt += 1 |
||||
a >>= 1 |
||||
a_cnt += 1 |
||||
|
||||
return xor_mod(res, mod) |
||||
|
||||
def gf2b_add(a: bytes, b: bytes) -> bytes: |
||||
n = len(a) |
||||
return gf2_add( |
||||
int.from_bytes(a, byteorder='big'), |
||||
int.from_bytes(b, byteorder='big'), |
||||
).to_bytes(n, byteorder='big') |
||||
|
||||
def gf2b_mul(a: bytes, b: bytes, poly: int) -> bytes: |
||||
n = len(a) |
||||
return gf2_mul( |
||||
int.from_bytes(a, byteorder='big'), |
||||
int.from_bytes(b, byteorder='big'), |
||||
poly, |
||||
).to_bytes(n, byteorder='big') |
||||
|
||||
def gf2b_128_mul(a: bytes, b: bytes): |
||||
return gf2b_mul(a, b, 0x100000000000000000000000000000087) # x^128+x^7+x^2+x+1 |
||||
|
||||
|
||||
class LRW: |
||||
""" LRW cipher mode. """ |
||||
def __init__(self, cipher, tweak: bytes): |
||||
self.cipher = cipher |
||||
self.block_size = 16 |
||||
self.tweak = tweak[:self.block_size] |
||||
|
||||
def get_tweak(self, offset: int) -> bytes: |
||||
return gf2b_128_mul( |
||||
self.tweak, |
||||
(offset // self.block_size).to_bytes(self.block_size, byteorder='big'), |
||||
) |
||||
|
||||
def encrypt(self, input: bytes, offset: int) -> bytes: |
||||
output = b'' |
||||
for i in range(0, len(input), self.block_size): |
||||
tweak = self.get_tweak(offset + i) |
||||
inblk = gf2b_add(tweak, input[i:i + self.block_size]) |
||||
output += gf2b_add(tweak, self.cipher.encrypt(inblk)) |
||||
return output |
||||
|
||||
def decrypt(self, input: bytes, offset: int) -> bytes: |
||||
output = b'' |
||||
for i in range(0, len(input), self.block_size): |
||||
tweak = self.get_tweak(offset + i) |
||||
inblk = gf2b_add(tweak, input[i:i + self.block_size]) |
||||
output += gf2b_add(tweak, self.cipher.decrypt(inblk)) |
||||
return output |
||||
|
||||
def cipher_mode_lrw(cipher, iv: bytes): |
||||
return LRW(cipher, iv) |
||||
|
||||
def cipher_algo_aes(key: bytes): |
||||
return AES.new(key[:32], AES.MODE_ECB) |
||||
|
||||
|
||||
CIPHER_ALGOS = [cipher_algo_aes] |
||||
CIPHER_MODES = [cipher_mode_lrw] |
||||
|
||||
def encrypt_single(key: bytes, iv: bytes, block: bytes, offset: int, cipher_algo, cipher_mode): |
||||
cipher = cipher_mode(cipher_algo(key), iv) |
||||
return cipher.encrypt(block, offset) |
||||
|
||||
def decrypt_single(key: bytes, iv: bytes, block: bytes, offset: int, cipher_algo, cipher_mode): |
||||
cipher = cipher_mode(cipher_algo(key), iv) |
||||
return cipher.decrypt(block, offset) |
||||
|
||||
def decrypt_all(key: bytes, iv: bytes, block: bytes, offset: int): |
||||
d = [] |
||||
for (algo, mode) in itertools.product(CIPHER_ALGOS, CIPHER_MODES): |
||||
dec = decrypt_single(key, iv, block, offset, algo, mode) |
||||
yield (algo, mode, dec) |
||||
|
||||
|
||||
def crc32p(b: bytes, v: int) -> int: |
||||
return (~zlib.crc32(b, (~v) & 0xFFFFFFFF)) & 0xFFFFFFFF |
||||
|
||||
class TCKeyfilePool: |
||||
STATE_SIZE = 64 |
||||
KEYFILE_SIZE = 1 * 1024 * 1024 |
||||
|
||||
def __init__(self): |
||||
self.state = bytearray(self.STATE_SIZE) |
||||
|
||||
def add(self, file) -> None: |
||||
crc = 0xFFFFFFFF |
||||
|
||||
pos = 0 |
||||
for _ in range(self.KEYFILE_SIZE): |
||||
b = file.read(1) |
||||
if not b: |
||||
break |
||||
crc = crc32p(b, crc) |
||||
for b in crc.to_bytes(4, byteorder='big'): |
||||
self.state[pos] = (self.state[pos] + b) & 0xff |
||||
pos += 1 |
||||
pos %= len(self.state) |
||||
|
||||
def apply(self, password: bytes) -> bytes: |
||||
if len(password) < len(self.state): |
||||
password = password.ljust(len(self.state), b'\x00') |
||||
return bytes((p + s) & 0xFF for (p, s) in zip(password, self.state)) + password[len(self.state):] |
||||
|
||||
|
||||
class TCVolumeFlags(enum.Flag): |
||||
System = (1 << 0) |
||||
InPlace = (1 << 1) |
||||
|
||||
class TCVolumeHeader(Struct): |
||||
magic: Fixed(b'TRUE') |
||||
# 1.0: TrueCrypt 1.0 |
||||
# 2.0: TrueCrypt 4.1 |
||||
# 3.0: TrueCrypt 5.0 |
||||
# 4.0: TrueCrypt 6.0 |
||||
# 5.0: TrueCrypt 7.0 |
||||
fmt_version: uint16be |
||||
prog_version: uint16be |
||||
key_checksum: uint32be |
||||
vol_timestamp: uint64be |
||||
hdr_timestamp: uint64be |
||||
hidvol_size: If(self.fmt_version >= 2, uint64be, Ignored(Data(8))) |
||||
vol_size: If(self.fmt_version >= 3, uint64be, Ignored(Data(8))) |
||||
master_key_off: If(self.fmt_version >= 3, uint64be, Ignored(Data(8))) |
||||
master_key_size: If(self.fmt_version >= 3, uint64be, Ignored(Data(8))) |
||||
flags: If(self.fmt_version >= 4, Enum(TCVolumeFlags, uint32be), Ignored(Data(4))) |
||||
sector_size: If(self.fmt_version >= 5, uint32be, Ignored(Data(4))) |
||||
_reserved84: Ignored(Data(120)) |
||||
hdr_checksum: If(self.fmt_version >= 4, uint32be, Ignored(Data(4))) |
||||
master_iv: Data(32) |
||||
master_key: Data(224) |
||||
|
||||
|
||||
SECTOR_SIZE = 512 |
||||
|
||||
|
||||
def main(): |
||||
import os |
||||
import math |
||||
import argparse |
||||
import atwork |
||||
|
||||
parser = argparse.ArgumentParser() |
||||
parser.add_argument('-k', '--keyfile', type=argparse.FileType('rb'), action='append') |
||||
parser.add_argument('-p', '--password') |
||||
parser.add_argument('-x', '--hidden', action='store_true') |
||||
parser.add_argument('infile', type=argparse.FileType('rb')) |
||||
parser.add_argument('outfile', type=argparse.FileType('wb'), nargs='?') |
||||
args = parser.parse_args() |
||||
|
||||
with atwork.Task('generating key') as t: |
||||
key = b'' |
||||
if args.password: |
||||
key += args.password.encode('utf-8') |
||||
if args.keyfile: |
||||
pool = TCKeyfilePool() |
||||
for f in args.keyfile: |
||||
pool.add(f) |
||||
key = pool.apply(key) |
||||
|
||||
with atwork.Task('decrypting header') as t: |
||||
found = False |
||||
for offset in (0, -1536): |
||||
if offset < 0: |
||||
args.infile.seek(offset, os.SEEK_END) |
||||
else: |
||||
args.infile.seek(offset, os.SEEK_SET) |
||||
salt = args.infile.read(64) |
||||
hdr = args.infile.read(448) |
||||
|
||||
for (prf_method, crypt_key, crypt_iv) in prf_all(key, salt, 32): |
||||
for (cipher_algo, cipher_mode, dec_hdr) in decrypt_all(crypt_key, crypt_iv, hdr, 16): |
||||
if dec_hdr.startswith(b'TRUE'): |
||||
found = True |
||||
break |
||||
if found: |
||||
break |
||||
if found: |
||||
break |
||||
else: |
||||
raise ValueError('nope') |
||||
|
||||
tchdr = parse(TCVolumeHeader, dec_hdr) |
||||
t.success('hidden' if offset < 0 else 'normal') |
||||
t.message(tchdr) |
||||
|
||||
if args.outfile: |
||||
CHUNK_SIZE = 0x8000 |
||||
|
||||
with atwork.Task('decrypting volume', total=math.ceil(tchdr.hidvol_size / CHUNK_SIZE), unit='chunk') as t: |
||||
if tchdr.hidvol_size: |
||||
t.message('hidden') |
||||
args.infile.seek(-1536 - tchdr.hidvol_size, os.SEEK_END) |
||||
else: |
||||
t.message('normal') |
||||
args.infile.seek(SECTOR_SIZE, os.SEEK_SET) |
||||
|
||||
c = cipher_mode(cipher_algo(tchdr.master_key), tchdr.master_iv) |
||||
for i in range(0, tchdr.hidvol_size, CHUNK_SIZE): |
||||
dec = c.decrypt(args.infile.read(CHUNK_SIZE), 16 + i) |
||||
args.outfile.write(dec) |
||||
t.step() |
||||
|
||||
if __name__ == '__main__': |
||||
main() |
Loading…
Reference in new issue