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:
melvyn2
2022-06-20 23:15:09 -07:00
committed by Florian Bach
parent 61a03fe988
commit 0cb13b9d38
3 changed files with 120 additions and 521 deletions
+18 -335
View File
@@ -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__":