mirror of
https://github.com/Leseratte10/acsm-calibre-plugin
synced 2024-12-27 09:58:19 +01:00
404 lines
13 KiB
Python
404 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#@@CALIBRE_COMPAT_CODE@@
|
|
|
|
import sys, binascii
|
|
|
|
def unfuck(user):
|
|
# Wine uses a pretty nonstandard encoding in their registry file.
|
|
# I haven't found any existing Python implementation for that,
|
|
# so I looked at the C code and wrote my own.
|
|
# This implementation doesn't support multi-byte UTF-8 chars,
|
|
# but a standard-conforming Wine registry won't contain
|
|
# these anyways, so who cares.
|
|
|
|
hex_char_list = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 97, 98, 99, 100, 101, 102]
|
|
|
|
# Remove the quotation marks at beginning and end:
|
|
user = user.strip()[1:-1]
|
|
user_new = bytearray()
|
|
i = 0
|
|
while i < len(user):
|
|
# Convert string of len 1 to a byte
|
|
char = user[i][0].encode("latin-1")[0]
|
|
if sys.version_info[0] == 2:
|
|
char = ord(char)
|
|
|
|
if char == ord('\\'):
|
|
# Get next char:
|
|
i += 1
|
|
char = user[i][0].encode("latin-1")[0]
|
|
if sys.version_info[0] == 2:
|
|
char = ord(char)
|
|
|
|
if (char == ord('a')):
|
|
user_new.append(0x07)
|
|
elif (char == ord('b')):
|
|
user_new.append(0x08)
|
|
elif (char == ord('e')):
|
|
user_new.append(0x1b)
|
|
elif (char == ord('f')):
|
|
user_new.append(0x0c)
|
|
elif (char == ord('n')):
|
|
user_new.append(0x0a)
|
|
elif (char == ord('r')):
|
|
user_new.append(0x0d)
|
|
elif (char == ord('t')):
|
|
user_new.append(0x09)
|
|
elif (char == ord('v')):
|
|
user_new.append(0x0b)
|
|
elif (char == ord('x')):
|
|
# Get next char
|
|
i += 1
|
|
char = user[i][0].encode("latin-1")[0]
|
|
if sys.version_info[0] == 2:
|
|
char = ord(char)
|
|
if char not in hex_char_list:
|
|
user_new.append(ord('x'))
|
|
# This seems to be fallback code.
|
|
# Subtract 1 so the next char (the one that's not a hex char)
|
|
# is handled normally.
|
|
i -= 1
|
|
else:
|
|
ival = "" + chr(char)
|
|
|
|
# Read up to 3 more chars
|
|
next = user[i + 1][0].encode("latin-1")[0]
|
|
if sys.version_info[0] == 2:
|
|
next = ord(next)
|
|
|
|
if next in hex_char_list:
|
|
ival += chr(next)
|
|
i += 1
|
|
|
|
next = user[i + 1][0].encode("latin-1")[0]
|
|
if sys.version_info[0] == 2:
|
|
next = ord(next)
|
|
|
|
if next in hex_char_list:
|
|
ival += chr(next)
|
|
i += 1
|
|
|
|
next = user[i + 1][0].encode("latin-1")[0]
|
|
if sys.version_info[0] == 2:
|
|
next = ord(next)
|
|
|
|
if next in hex_char_list:
|
|
ival += chr(next)
|
|
i += 1
|
|
|
|
# ival now contains "00e9". Convert to int ...
|
|
ival = int(ival, 16)
|
|
# then drop everything except the lowest byte
|
|
ival = ival & 0xFF
|
|
# then add it to the array
|
|
user_new.append(ival)
|
|
elif (char >= ord('0') and char <= ord('9') ):
|
|
|
|
octal = char - ord('0')
|
|
|
|
# Read up to 2 more chars
|
|
next = user[i + 1][0].encode("latin-1")[0]
|
|
if sys.version_info[0] == 2:
|
|
next = ord(next)
|
|
|
|
if next >= ord('0') and next <= ord('9'):
|
|
octal = (octal * 8) + (next - ord('0'))
|
|
i += 1
|
|
|
|
next = user[i + 1][0].encode("latin-1")[0]
|
|
if sys.version_info[0] == 2:
|
|
next = ord(next)
|
|
|
|
if next >= ord('0') and next <= ord('9'):
|
|
octal = (octal * 8) + (next - ord('0'))
|
|
i += 1
|
|
|
|
else:
|
|
|
|
if (char < 0x80):
|
|
user_new.append(char)
|
|
else:
|
|
print("Multi-Byte UTF-8, not supported")
|
|
print("This should never happen in a standard-conform Wine registry ...")
|
|
return False
|
|
|
|
# Parse next char
|
|
i += 1
|
|
|
|
return user_new
|
|
|
|
def GetMasterKey(path_to_wine_prefix):
|
|
|
|
import os
|
|
|
|
if os.name == 'nt':
|
|
print("Hey! This is for Linux!")
|
|
return
|
|
|
|
verbose_logging = False
|
|
try:
|
|
import calibre_plugins.deacsm.prefs as prefs
|
|
deacsmprefs = prefs.DeACSM_Prefs()
|
|
verbose_logging = deacsmprefs["detailed_logging"]
|
|
except:
|
|
pass
|
|
|
|
|
|
import cpuid
|
|
import struct
|
|
|
|
try:
|
|
# Linux / Wine code just assumes that the system drive is C:\
|
|
serial_file = open(os.path.join(path_to_wine_prefix, "drive_c", ".windows-serial"), "r")
|
|
serial = serial_file.read()
|
|
serial_file.close()
|
|
serial = int(serial, 16)
|
|
except:
|
|
# If this file is not present, Wine will usually use a default serial number of "0".
|
|
# There are some edge cases where Wine uses a different serial number even when that
|
|
# .windows-serial file is not present.
|
|
serial = 0
|
|
|
|
if (verbose_logging):
|
|
print("Serial: " + str(serial))
|
|
|
|
cpu = cpuid.CPUID()
|
|
_, b, c, d = cpu(0)
|
|
vendor = struct.pack("III", b, d, c)
|
|
|
|
if (verbose_logging):
|
|
print("Vendor: " + vendor.decode("utf-8"))
|
|
|
|
signature, _, _, _ = cpu(1)
|
|
signature = struct.pack('>I', signature)[1:]
|
|
|
|
if (verbose_logging):
|
|
print("Signature: " + str(binascii.hexlify(signature)))
|
|
|
|
# Search for the username in the registry:
|
|
user = None
|
|
|
|
|
|
# Linux - loop through the Wine registry file to find the "username" attribute
|
|
try:
|
|
registry_file = open(os.path.join(path_to_wine_prefix, "user.reg"))
|
|
waiting_for_username = False
|
|
while True:
|
|
line = registry_file.readline()
|
|
if not line:
|
|
break
|
|
|
|
if waiting_for_username:
|
|
if (not line.lower().startswith("\"username\"=")):
|
|
continue
|
|
|
|
# If we end up here, we have the username.
|
|
user = line.split('=', 1)[1].strip()
|
|
user = unfuck(user)
|
|
break
|
|
|
|
else:
|
|
if (line.startswith("[Software\\\\Adobe\\\\Adept\\\\Device]")):
|
|
waiting_for_username = True
|
|
|
|
if (line.startswith("[Volatile Environment]")):
|
|
waiting_for_username = True
|
|
|
|
registry_file.close()
|
|
except:
|
|
# There was an error hunting through the registry.
|
|
raise
|
|
pass
|
|
|
|
if (user is None):
|
|
print("Error while determining username ...")
|
|
exit()
|
|
|
|
if verbose_logging:
|
|
print("Username: " + str(user))
|
|
|
|
# Find the value we want to decrypt from the registry. loop through the Wine registry file to find the "key" attribute
|
|
try:
|
|
registry_file = open(os.path.join(path_to_wine_prefix, "user.reg"))
|
|
waiting_for_key = False
|
|
key_line = None
|
|
while True:
|
|
line = registry_file.readline()
|
|
if not line:
|
|
break
|
|
|
|
if waiting_for_key:
|
|
if (not line.lower().startswith("\"key\"=")):
|
|
continue
|
|
|
|
# If we end up here, we have the key.
|
|
key_line = line
|
|
while (key_line.strip().endswith('\\')):
|
|
key_line = key_line.strip()[:-1] + registry_file.readline()
|
|
|
|
# Now parse ...
|
|
key_line = key_line.split(':', 1)[1]
|
|
key_line = key_line.replace('\t', '').replace('\r', '').replace('\n', '').replace(' ', '').replace(',', '')
|
|
key_line = binascii.unhexlify(key_line)
|
|
|
|
else:
|
|
if (line.startswith("[Software\\\\Adobe\\\\Adept\\\\Device]")):
|
|
waiting_for_key = True
|
|
|
|
|
|
|
|
|
|
registry_file.close()
|
|
except:
|
|
# There was an error hunting through the registry.
|
|
raise
|
|
pass
|
|
|
|
if key_line is None:
|
|
print("No ADE activation found ...")
|
|
return None
|
|
|
|
if verbose_logging:
|
|
print("Encrypted key: " + binascii.hexlify(key_line))
|
|
|
|
# These should all be "bytes" (Py3) or "str" (Py2)
|
|
#print(type(vendor))
|
|
#print(type(signature))
|
|
#print(type(user))
|
|
|
|
if sys.version_info[0] == 2:
|
|
user = bytes(user)
|
|
|
|
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
|
|
|
|
if verbose_logging:
|
|
print("Entropy: " + str(entropy))
|
|
|
|
# We would now call CryptUnprotectData to decrypt the stuff,
|
|
# but unfortunately there's no working Linux implementation
|
|
# for that.
|
|
#
|
|
# The plan was to handle everything in Python so we don't have
|
|
# to interact with Wine - that's why we're doing all the registry
|
|
# handling ourselves.
|
|
# Unfortunately, that doesn't work for the actual decryption.
|
|
#
|
|
# This means we have to call a Windows binary through
|
|
# Wine just for this one single decryption call ...
|
|
|
|
success, data = CryptUnprotectDataExecuteWine(path_to_wine_prefix, key_line, entropy)
|
|
if (success):
|
|
keykey = data
|
|
if verbose_logging:
|
|
print("Key key: ")
|
|
print(binascii.hexlify(keykey))
|
|
return keykey
|
|
|
|
else:
|
|
print("Error number: " + str(data))
|
|
if data == 13: # WINError ERROR_INVALID_DATA
|
|
print("Could not decrypt data with the given key. Did the entropy change?")
|
|
return None
|
|
|
|
|
|
def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
|
|
import subprocess, os, re
|
|
|
|
verbose_logging = False
|
|
try:
|
|
import calibre_plugins.deacsm.prefs as prefs
|
|
deacsmprefs = prefs.DeACSM_Prefs()
|
|
verbose_logging = deacsmprefs["detailed_logging"]
|
|
except:
|
|
pass
|
|
|
|
print("Asking WINE to decrypt encrypted key for us ...")
|
|
|
|
if wineprefix == "" or not os.path.exists(wineprefix):
|
|
print("Wineprefix not found!!")
|
|
return None
|
|
|
|
|
|
# Default to win32 binary, unless we find arch in registry
|
|
winearch = "win32"
|
|
|
|
try:
|
|
system_registry_path = os.path.join(wineprefix, "system.reg")
|
|
regfile = open(system_registry_path, "r")
|
|
while True:
|
|
line = regfile.readline()
|
|
if not line:
|
|
break
|
|
|
|
stuff = re.match(r'#arch=(win32|win64)', line)
|
|
if (stuff):
|
|
winearch = stuff.groups()[0]
|
|
break
|
|
regfile.close()
|
|
except:
|
|
pass
|
|
|
|
|
|
# Execute!
|
|
|
|
env_dict = os.environ
|
|
env_dict["PYTHONPATH"] = ""
|
|
env_dict["WINEPREFIX"] = wineprefix
|
|
#env_dict["WINEDEBUG"] = "-all,+crypt"
|
|
env_dict["WINEDEBUG"] = "+err,+fixme"
|
|
|
|
# Use environment variables to get the input data to the application.
|
|
env_dict["X_DECRYPT_DATA"] = binascii.hexlify(data)
|
|
env_dict["X_DECRYPT_ENTROPY"] = binascii.hexlify(entropy)
|
|
|
|
try:
|
|
from calibre.utils.config import config_dir
|
|
pluginsdir = os.path.join(config_dir,"plugins")
|
|
maindir = os.path.join(pluginsdir,"DeACSM")
|
|
moddir = os.path.join(maindir,"modules")
|
|
except:
|
|
import os
|
|
moddir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "keyextract")
|
|
|
|
proc = subprocess.Popen(["wine", "decrypt_" + winearch + ".exe" ], shell=False, cwd=moddir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
prog_output, prog_stderr = proc.communicate()
|
|
# calls decrypt_win32.exe or decrypt_win64.exe
|
|
|
|
|
|
if prog_output.decode("utf-8").startswith("PROGOUTPUT:0:"):
|
|
key_string = prog_output.decode("utf-8").split(':')[2]
|
|
if verbose_logging:
|
|
print("Successfully got encryption key from WINE: " + key_string)
|
|
#print("Debug log:")
|
|
#print(prog_stderr.decode("utf-8"))
|
|
else:
|
|
print("Successfully got encryption key from WINE.")
|
|
master_key = binascii.unhexlify(key_string)
|
|
return True, master_key
|
|
|
|
|
|
else:
|
|
print("Huh. That didn't work. ")
|
|
try:
|
|
err = int(prog_output.decode("utf-8").split(':')[1])
|
|
if err == -4:
|
|
err = int(prog_output.decode("utf-8").split(':')[2])
|
|
new_serial = int(prog_output.decode("utf-8").split(':')[3])
|
|
if verbose_logging:
|
|
print("New serial: " + str(new_serial))
|
|
except:
|
|
pass
|
|
|
|
if verbose_logging:
|
|
print("Program reported: " + prog_output.decode("utf-8"))
|
|
print("Debug log: ")
|
|
print(prog_stderr.decode("utf-8"))
|
|
|
|
return False, err
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("Do not execute this directly!")
|
|
exit()
|