mirror of
https://github.com/Leseratte10/acsm-calibre-plugin
synced 2026-06-01 17:27:23 -04:00
Move all win32 logic to wine executable
Actions like reading the registry and serial number were being done in python even though the final decryption was done in wine. This commit moves all windows logic except architecture detection into the exe ran under wine to simplify the architecture.
This commit is contained in:
@@ -5,307 +5,7 @@
|
||||
|
||||
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: {}".format(serial))
|
||||
|
||||
cpu = cpuid.CPUID()
|
||||
_, b, c, d = cpu(0)
|
||||
vendor = struct.pack("III", b, d, c)
|
||||
|
||||
if (verbose_logging):
|
||||
print("Vendor: {}".format(vendor))
|
||||
|
||||
signature, _, _, _ = cpu(1)
|
||||
signature = struct.pack('>I', signature)[1:]
|
||||
|
||||
if (verbose_logging):
|
||||
print("Signature: {}".format(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()
|
||||
|
||||
# Comes as bytearray
|
||||
if sys.version_info[0] == 3:
|
||||
user = bytes(user)
|
||||
else:
|
||||
user = str(user)
|
||||
|
||||
if verbose_logging:
|
||||
print("Username: {}".format(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: {}".format(binascii.hexlify(key_line)))
|
||||
|
||||
# These should all be "bytes" (Py3) or "str" (Py2)
|
||||
# print(type(vendor))
|
||||
# print(type(signature))
|
||||
# print(type(user))
|
||||
|
||||
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
|
||||
|
||||
if verbose_logging:
|
||||
print("Entropy: {}".format(binascii.hexlify(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: {}".format(binascii.hexlify(keykey)))
|
||||
return keykey
|
||||
|
||||
else:
|
||||
print("Error number: {}".format(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):
|
||||
def GetMasterKey(wineprefix):
|
||||
import subprocess, os, re
|
||||
|
||||
verbose_logging = False
|
||||
@@ -319,7 +19,7 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
|
||||
print("Asking WINE to decrypt encrypted key for us ...")
|
||||
|
||||
if wineprefix == "" or not os.path.exists(wineprefix):
|
||||
print("Wineprefix not found!!")
|
||||
print("Wineprefix not found!")
|
||||
return None
|
||||
|
||||
|
||||
@@ -334,27 +34,21 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
|
||||
if not line:
|
||||
break
|
||||
|
||||
stuff = re.match(r'#arch=(win32|win64)', line)
|
||||
if (stuff):
|
||||
winearch = stuff.groups()[0]
|
||||
archkey = re.match(r'#arch=(win32|win64)', line)
|
||||
if (archkey):
|
||||
winearch = archkey.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).decode("utf-8")
|
||||
env_dict["X_DECRYPT_ENTROPY"] = binascii.hexlify(entropy).decode("utf-8")
|
||||
|
||||
try:
|
||||
from calibre.utils.config import config_dir
|
||||
pluginsdir = os.path.join(config_dir,"plugins")
|
||||
@@ -366,37 +60,26 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
|
||||
|
||||
# calls decrypt_win32.exe or decrypt_win64.exe
|
||||
proc = subprocess.Popen(["wine", "decrypt_" + winearch + ".exe"], shell=False, cwd=moddir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
prog_output, prog_stderr = proc.communicate()
|
||||
stdout = prog_output.decode("utf-8")
|
||||
stderr = prog_stderr.decode("utf-8")
|
||||
prog_stdout, prog_stderr = proc.communicate()
|
||||
|
||||
if stdout.startswith("PROGOUTPUT:0:"):
|
||||
key_string = stdout.split(':')[2]
|
||||
if verbose_logging:
|
||||
print("Stderr log:\n{}".format(prog_stderr.decode("utf-8")))
|
||||
print("Stdout log: {}".format(prog_stdout.decode("utf-8")))
|
||||
print("Exit code: {}".format(proc.returncode))
|
||||
|
||||
if proc.returncode == 0:
|
||||
if verbose_logging:
|
||||
print("Successfully got encryption key from WINE: {}".format(key_string))
|
||||
print("Successfully got encryption key from WINE: {}".format(prog_stdout.decode("utf-8")))
|
||||
else:
|
||||
print("Successfully got encryption key from WINE.")
|
||||
master_key = binascii.unhexlify(key_string)
|
||||
return True, master_key
|
||||
|
||||
|
||||
master_key = binascii.unhexlify(prog_stdout)
|
||||
return master_key
|
||||
else:
|
||||
print("Huh. That didn't work. ")
|
||||
try:
|
||||
err = int(stdout.split(':')[1])
|
||||
if err == -4:
|
||||
err = int(stdout.split(':')[2])
|
||||
new_serial = int(stdout.split(':')[3])
|
||||
if verbose_logging:
|
||||
print("New serial: {}".format(new_serial))
|
||||
except:
|
||||
err = None
|
||||
print("Failed to extract encryption key from WINE.")
|
||||
print("Exit code: {}".format(proc.returncode))
|
||||
|
||||
if verbose_logging:
|
||||
# print("Stderr log:\n{}".format(stderr))
|
||||
print("Program output: {}".format(stdout))
|
||||
|
||||
return False, err
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user