Remove AlfCrypto libraries and perform everything in Python

The old AlfCrypto DLL, SO and DYLIB files are ancient,
I don't have the systems to recompile them all, they
cause issues on ARM Macs, and I doubt with all the Python
improvements over the last years that they have a significant
performance advantage. And even if that's the case, nobody is
importing hundreds of DRM books at the same time so it shouldn't
hurt if some decryptions might take a bit longer.
This commit is contained in:
NoDRM 2022-08-06 20:13:19 +02:00
parent 9276d77f63
commit 410e086d08
10 changed files with 81 additions and 443 deletions

View File

@ -187,15 +187,12 @@ class DeDRM(FileTypePlugin):
os.mkdir(self.alfdir) os.mkdir(self.alfdir)
# only continue if we've never run this version of the plugin before # only continue if we've never run this version of the plugin before
self.verdir = os.path.join(self.maindir,PLUGIN_VERSION) self.verdir = os.path.join(self.maindir,PLUGIN_VERSION)
if not os.path.exists(self.verdir): if not os.path.exists(self.verdir) and not iswindows and not isosx:
if iswindows:
names = ["alfcrypto.dll","alfcrypto64.dll"] names = ["kindlekey.py","adobekey.py","ignoblekeyNookStudy.py"]
elif isosx:
names = ["libalfcrypto.dylib"]
else:
names = ["libalfcrypto32.so","libalfcrypto64.so","kindlekey.py","adobekey.py","subasyncio.py"]
lib_dict = self.load_resources(names) lib_dict = self.load_resources(names)
print("{0} v{1}: Copying needed library files from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION)) print("{0} v{1}: Copying needed Python scripts from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION))
for entry, data in lib_dict.items(): for entry, data in lib_dict.items():
file_path = os.path.join(self.alfdir, entry) file_path = os.path.join(self.alfdir, entry)

Binary file not shown.

View File

@ -8,260 +8,90 @@
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm> # pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
# pbkdf2.py This code may be freely used and modified for any purpose. # pbkdf2.py This code may be freely used and modified for any purpose.
import sys, os
import hmac import hmac
from struct import pack from struct import pack
import hashlib import hashlib
import aescbc
# interface to needed routines libalfcrypto class Pukall_Cipher(object):
def _load_libalfcrypto(): def __init__(self):
import ctypes self.key = None
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, addressof, string_at, cast, sizeof
pointer_size = ctypes.sizeof(ctypes.c_voidp) def PC1(self, key, src, decryption=True):
name_of_lib = None sum1 = 0;
if sys.platform.startswith('darwin'): sum2 = 0;
name_of_lib = 'libalfcrypto.dylib' keyXorVal = 0;
elif sys.platform.startswith('win'): if len(key)!=16:
if pointer_size == 4: raise Exception("PC1: Bad key length")
name_of_lib = 'alfcrypto.dll' wkey = []
else: for i in range(8):
name_of_lib = 'alfcrypto64.dll' wkey.append(key[i*2]<<8 | key[i*2+1])
else: dst = bytearray(len(src))
if pointer_size == 4: for i in range(len(src)):
name_of_lib = 'libalfcrypto32.so' temp1 = 0;
else: byteXorVal = 0;
name_of_lib = 'libalfcrypto64.so' for j in range(8):
temp1 ^= wkey[j]
# hard code to local location for libalfcrypto sum2 = (sum2+j)*20021 + sum1
libalfcrypto = os.path.join(sys.path[0],name_of_lib) sum1 = (temp1*346)&0xFFFF
if not os.path.isfile(libalfcrypto): sum2 = (sum2+sum1)&0xFFFF
libalfcrypto = os.path.join(sys.path[0], 'lib', name_of_lib) temp1 = (temp1*20021+1)&0xFFFF
if not os.path.isfile(libalfcrypto): byteXorVal ^= temp1 ^ sum2
libalfcrypto = os.path.join('.',name_of_lib) curByte = src[i]
if not os.path.isfile(libalfcrypto): if not decryption:
raise Exception('libalfcrypto not found at %s' % libalfcrypto) keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
libalfcrypto = CDLL(libalfcrypto)
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
def F(restype, name, argtypes):
func = getattr(libalfcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
# aes cbc decryption
#
# struct aes_key_st {
# unsigned long rd_key[4 *(AES_MAXNR + 1)];
# int rounds;
# };
#
# typedef struct aes_key_st AES_KEY;
#
# int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
#
#
# void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
# const unsigned long length, const AES_KEY *key,
# unsigned char *ivec, const int enc);
AES_MAXNR = 14
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, c_int])
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
# Pukall 1 Cipher
# unsigned char *PC1(const unsigned char *key, unsigned int klen, const unsigned char *src,
# unsigned char *dest, unsigned int len, int decryption);
PC1 = F(c_char_p, 'PC1', [c_char_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong])
# Topaz Encryption
# typedef struct _TpzCtx {
# unsigned int v[2];
# } TpzCtx;
#
# void topazCryptoInit(TpzCtx *ctx, const unsigned char *key, int klen);
# void topazCryptoDecrypt(const TpzCtx *ctx, const unsigned char *in, unsigned char *out, int len);
class TPZ_CTX(Structure):
_fields_ = [('v', c_long * 2)]
TPZ_CTX_p = POINTER(TPZ_CTX)
topazCryptoInit = F(None, 'topazCryptoInit', [TPZ_CTX_p, c_char_p, c_ulong])
topazCryptoDecrypt = F(None, 'topazCryptoDecrypt', [TPZ_CTX_p, c_char_p, c_char_p, c_ulong])
class AES_CBC(object):
def __init__(self):
self._blocksize = 0
self._keyctx = None
self._iv = 0
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise Exception('AES CBC improper key used')
return
keyctx = self._keyctx = AES_KEY()
self._iv = iv
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
raise Exception('Failed to initialize AES CBC key')
def decrypt(self, data):
out = create_string_buffer(len(data))
mutable_iv = create_string_buffer(self._iv, len(self._iv))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, mutable_iv, 0)
if rv == 0:
raise Exception('AES CBC decryption failed')
return out.raw
class Pukall_Cipher(object):
def __init__(self):
self.key = None
def PC1(self, key, src, decryption=True):
self.key = key
out = create_string_buffer(len(src))
de = 0
if decryption: if decryption:
de = 1 keyXorVal = curByte * 257;
rv = PC1(key, len(key), src, out, len(src), de) for j in range(8):
return out.raw wkey[j] ^= keyXorVal;
dst[i] = curByte
return bytes(dst)
class Topaz_Cipher(object): class Topaz_Cipher(object):
def __init__(self): def __init__(self):
self._ctx = None self._ctx = None
def ctx_init(self, key): def ctx_init(self, key):
tpz_ctx = self._ctx = TPZ_CTX() ctx1 = 0x0CAFFE19E
topazCryptoInit(tpz_ctx, key, len(key)) if isinstance(key, str):
return tpz_ctx key = key.encode('latin-1')
for keyByte in key:
ctx2 = ctx1
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
self._ctx = [ctx1, ctx2]
return [ctx1,ctx2]
def decrypt(self, data, ctx=None): def decrypt(self, data, ctx=None):
if ctx == None: if ctx == None:
ctx = self._ctx ctx = self._ctx
out = create_string_buffer(len(data)) ctx1 = ctx[0]
topazCryptoDecrypt(ctx, data, out, len(data)) ctx2 = ctx[1]
return out.raw plainText = ""
if isinstance(data, str):
data = data.encode('latin-1')
for dataByte in data:
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
ctx2 = ctx1
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
plainText += chr(m)
return plainText
print("Using Library AlfCrypto DLL/DYLIB/SO") class AES_CBC(object):
return (AES_CBC, Pukall_Cipher, Topaz_Cipher) def __init__(self):
self._key = None
self._iv = None
self.aes = None
def set_decrypt_key(self, userkey, iv):
self._key = userkey
self._iv = iv
self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey))
def _load_python_alfcrypto(): def decrypt(self, data):
iv = self._iv
import aescbc cleartext = self.aes.decrypt(iv + data)
return cleartext
class Pukall_Cipher(object):
def __init__(self):
self.key = None
def PC1(self, key, src, decryption=True):
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
raise Exception('Pukall_Cipher: Bad key length.')
wkey = []
for i in range(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = ""
for i in range(len(src)):
temp1 = 0;
byteXorVal = 0;
for j in range(8):
temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF
sum2 = (sum2+sum1)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF
byteXorVal ^= temp1 ^ sum2
curByte = ord(src[i])
if not decryption:
keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption:
keyXorVal = curByte * 257;
for j in range(8):
wkey[j] ^= keyXorVal;
dst+=chr(curByte)
return dst
class Topaz_Cipher(object):
def __init__(self):
self._ctx = None
def ctx_init(self, key):
ctx1 = 0x0CAFFE19E
if isinstance(key, str):
key = key.encode('latin-1')
for keyByte in key:
ctx2 = ctx1
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
self._ctx = [ctx1, ctx2]
return [ctx1,ctx2]
def decrypt(self, data, ctx=None):
if ctx == None:
ctx = self._ctx
ctx1 = ctx[0]
ctx2 = ctx[1]
plainText = ""
if isinstance(data, str):
data = data.encode('latin-1')
for dataByte in data:
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
ctx2 = ctx1
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
plainText += chr(m)
return plainText
class AES_CBC(object):
def __init__(self):
self._key = None
self._iv = None
self.aes = None
def set_decrypt_key(self, userkey, iv):
self._key = userkey
self._iv = iv
self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey))
def decrypt(self, data):
iv = self._iv
cleartext = self.aes.decrypt(iv + data)
return cleartext
print("Using Library AlfCrypto Python")
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
def _load_crypto():
AES_CBC = Pukall_Cipher = Topaz_Cipher = None
cryptolist = (_load_libalfcrypto, _load_python_alfcrypto)
for loader in cryptolist:
try:
AES_CBC, Pukall_Cipher, Topaz_Cipher = loader()
break
except (ImportError, Exception):
pass
return AES_CBC, Pukall_Cipher, Topaz_Cipher
AES_CBC, Pukall_Cipher, Topaz_Cipher = _load_crypto()
class KeyIVGen(object): class KeyIVGen(object):

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -80,10 +80,7 @@ import sys
import os import os
import struct import struct
import binascii import binascii
try: from alfcrypto import Pukall_Cipher
from alfcrypto import Pukall_Cipher
except:
print("AlfCrypto not found. Using python PC1 implementation.")
from utilities import SafeUnbuffered from utilities import SafeUnbuffered
@ -140,41 +137,8 @@ def PC1(key, src, decryption=True):
# if we can get it from alfcrypto, use that # if we can get it from alfcrypto, use that
try: try:
return Pukall_Cipher().PC1(key,src,decryption) return Pukall_Cipher().PC1(key,src,decryption)
except NameError: except:
pass raise
except TypeError:
pass
# use slow python version, since Pukall_Cipher didn't load
sum1 = 0;
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
DrmException ("PC1: Bad key length")
wkey = []
for i in range(8):
wkey.append(key[i*2]<<8 | key[i*2+1])
dst = bytearray(len(src))
for i in range(len(src)):
temp1 = 0;
byteXorVal = 0;
for j in range(8):
temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF
sum2 = (sum2+sum1)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF
byteXorVal ^= temp1 ^ sum2
curByte = src[i]
if not decryption:
keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption:
keyXorVal = curByte * 257;
for j in range(8):
wkey[j] ^= keyXorVal;
dst[i] = curByte
return bytes(dst)
# accepts unicode returns unicode # accepts unicode returns unicode
def checksumPid(s): def checksumPid(s):
@ -232,12 +196,7 @@ class MobiBook:
pass pass
def __init__(self, infile): def __init__(self, infile):
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2020 The Dark Reverser, Apprentice Harper et al.".format(__version__)) print("MobiDeDrm v{0:s}.\nCopyright © 2008-2022 The Dark Reverser, Apprentice Harper et al.".format(__version__))
try:
from alfcrypto import Pukall_Cipher
except:
print("AlfCrypto not found. Using python PC1 implementation.")
# initial sanity check on file # initial sanity check on file
self.data_file = open(infile, 'rb').read() self.data_file = open(infile, 'rb').read()

View File

@ -1,148 +0,0 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
import os, sys
import signal
import threading
import subprocess
from subprocess import Popen, PIPE, STDOUT
# **heavily** chopped up and modfied version of asyncproc.py
# to make it actually work on Windows as well as Mac/Linux
# For the original see:
# "http://www.lysator.liu.se/~bellman/download/"
# author is "Thomas Bellman <bellman@lysator.liu.se>"
# available under GPL version 3 or Later
# create an asynchronous subprocess whose output can be collected in
# a non-blocking manner
# What a mess! Have to use threads just to get non-blocking io
# in a cross-platform manner
# luckily all thread use is hidden within this class
class Process(object):
def __init__(self, *params, **kwparams):
if len(params) <= 3:
kwparams.setdefault('stdin', subprocess.PIPE)
if len(params) <= 4:
kwparams.setdefault('stdout', subprocess.PIPE)
if len(params) <= 5:
kwparams.setdefault('stderr', subprocess.PIPE)
self.__pending_input = []
self.__collected_outdata = []
self.__collected_errdata = []
self.__exitstatus = None
self.__lock = threading.Lock()
self.__inputsem = threading.Semaphore(0)
self.__quit = False
self.__process = subprocess.Popen(*params, **kwparams)
if self.__process.stdin:
self.__stdin_thread = threading.Thread(
name="stdin-thread",
target=self.__feeder, args=(self.__pending_input,
self.__process.stdin))
self.__stdin_thread.setDaemon(True)
self.__stdin_thread.start()
if self.__process.stdout:
self.__stdout_thread = threading.Thread(
name="stdout-thread",
target=self.__reader, args=(self.__collected_outdata,
self.__process.stdout))
self.__stdout_thread.setDaemon(True)
self.__stdout_thread.start()
if self.__process.stderr:
self.__stderr_thread = threading.Thread(
name="stderr-thread",
target=self.__reader, args=(self.__collected_errdata,
self.__process.stderr))
self.__stderr_thread.setDaemon(True)
self.__stderr_thread.start()
def pid(self):
return self.__process.pid
def kill(self, signal):
self.__process.send_signal(signal)
# check on subprocess (pass in 'nowait') to act like poll
def wait(self, flag):
if flag.lower() == 'nowait':
rc = self.__process.poll()
else:
rc = self.__process.wait()
if rc != None:
if self.__process.stdin:
self.closeinput()
if self.__process.stdout:
self.__stdout_thread.join()
if self.__process.stderr:
self.__stderr_thread.join()
return self.__process.returncode
def terminate(self):
if self.__process.stdin:
self.closeinput()
self.__process.terminate()
# thread gets data from subprocess stdout
def __reader(self, collector, source):
while True:
data = os.read(source.fileno(), 65536)
self.__lock.acquire()
collector.append(data)
self.__lock.release()
if data == "":
source.close()
break
return
# thread feeds data to subprocess stdin
def __feeder(self, pending, drain):
while True:
self.__inputsem.acquire()
self.__lock.acquire()
if not pending and self.__quit:
drain.close()
self.__lock.release()
break
data = pending.pop(0)
self.__lock.release()
drain.write(data)
# non-blocking read of data from subprocess stdout
def read(self):
self.__lock.acquire()
outdata = "".join(self.__collected_outdata)
del self.__collected_outdata[:]
self.__lock.release()
return outdata
# non-blocking read of data from subprocess stderr
def readerr(self):
self.__lock.acquire()
errdata = "".join(self.__collected_errdata)
del self.__collected_errdata[:]
self.__lock.release()
return errdata
# non-blocking write to stdin of subprocess
def write(self, data):
if self.__process.stdin is None:
raise ValueError("Writing to process with stdin not a pipe")
self.__lock.acquire()
self.__pending_input.append(data)
self.__inputsem.release()
self.__lock.release()
# close stdinput of subprocess
def closeinput(self):
self.__lock.acquire()
self.__quit = True
self.__inputsem.release()
self.__lock.release()