diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 6037533..b136b4b 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -10,6 +10,10 @@ jobs:
steps:
- uses: actions/checkout@v2
+ - name: Install compilers
+ run: |
+ sudo apt update ; sudo apt install -y gcc-mingw-w64-i686 gcc-mingw-w64-x86-64
+
- name: Compile
run: |
./bundle_calibre_plugin.sh
diff --git a/bundle_calibre_plugin.sh b/bundle_calibre_plugin.sh
index 5431fb3..9a22bfd 100755
--- a/bundle_calibre_plugin.sh
+++ b/bundle_calibre_plugin.sh
@@ -7,6 +7,12 @@
[ ! -f calibre-plugin/pyasn1.zip ] && ./package_modules.sh
pushd calibre-plugin
+pushd key-wine
+
+# Compile:
+make
+
+popd
zip -r ../calibre-plugin.zip *
diff --git a/calibre-plugin/key-wine/Makefile b/calibre-plugin/key-wine/Makefile
new file mode 100644
index 0000000..f25d6b8
--- /dev/null
+++ b/calibre-plugin/key-wine/Makefile
@@ -0,0 +1,14 @@
+all: decrypt_win32.exe decrypt_win64.exe
+
+.PHONY: clean
+clean:
+ rm decrypt_win32.exe decrypt_win64.exe 2>/dev/null || /bin/true
+
+decrypt_win32.exe: main.c Makefile
+ i686-w64-mingw32-gcc main.c -Os -o decrypt_win32.exe -lcrypt32
+ i686-w64-mingw32-strip decrypt_win32.exe
+
+decrypt_win64.exe: main.c Makefile
+ x86_64-w64-mingw32-gcc main.c -Os -o decrypt_win64.exe -lcrypt32
+ x86_64-w64-mingw32-strip decrypt_win64.exe
+
diff --git a/calibre-plugin/key-wine/cpuid.py b/calibre-plugin/key-wine/cpuid.py
new file mode 100644
index 0000000..b0d51a4
--- /dev/null
+++ b/calibre-plugin/key-wine/cpuid.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2018 Anders Høst
+#
+
+# Licensed under MIT
+# See https://github.com/flababah/cpuid.py/blob/master/cpuid.py
+
+from __future__ import print_function
+
+import platform
+import os
+import ctypes
+from ctypes import c_uint32, c_int, c_long, c_ulong, c_size_t, c_void_p, POINTER, CFUNCTYPE
+
+# Posix x86_64:
+# Three first call registers : RDI, RSI, RDX
+# Volatile registers : RAX, RCX, RDX, RSI, RDI, R8-11
+
+# Windows x86_64:
+# Three first call registers : RCX, RDX, R8
+# Volatile registers : RAX, RCX, RDX, R8-11
+
+# cdecl 32 bit:
+# Three first call registers : Stack (%esp)
+# Volatile registers : EAX, ECX, EDX
+
+_POSIX_64_OPC = [
+ 0x53, # push %rbx
+ 0x89, 0xf0, # mov %esi,%eax
+ 0x89, 0xd1, # mov %edx,%ecx
+ 0x0f, 0xa2, # cpuid
+ 0x89, 0x07, # mov %eax,(%rdi)
+ 0x89, 0x5f, 0x04, # mov %ebx,0x4(%rdi)
+ 0x89, 0x4f, 0x08, # mov %ecx,0x8(%rdi)
+ 0x89, 0x57, 0x0c, # mov %edx,0xc(%rdi)
+ 0x5b, # pop %rbx
+ 0xc3 # retq
+]
+
+_WINDOWS_64_OPC = [
+ 0x53, # push %rbx
+ 0x89, 0xd0, # mov %edx,%eax
+ 0x49, 0x89, 0xc9, # mov %rcx,%r9
+ 0x44, 0x89, 0xc1, # mov %r8d,%ecx
+ 0x0f, 0xa2, # cpuid
+ 0x41, 0x89, 0x01, # mov %eax,(%r9)
+ 0x41, 0x89, 0x59, 0x04, # mov %ebx,0x4(%r9)
+ 0x41, 0x89, 0x49, 0x08, # mov %ecx,0x8(%r9)
+ 0x41, 0x89, 0x51, 0x0c, # mov %edx,0xc(%r9)
+ 0x5b, # pop %rbx
+ 0xc3 # retq
+]
+
+_CDECL_32_OPC = [
+ 0x53, # push %ebx
+ 0x57, # push %edi
+ 0x8b, 0x7c, 0x24, 0x0c, # mov 0xc(%esp),%edi
+ 0x8b, 0x44, 0x24, 0x10, # mov 0x10(%esp),%eax
+ 0x8b, 0x4c, 0x24, 0x14, # mov 0x14(%esp),%ecx
+ 0x0f, 0xa2, # cpuid
+ 0x89, 0x07, # mov %eax,(%edi)
+ 0x89, 0x5f, 0x04, # mov %ebx,0x4(%edi)
+ 0x89, 0x4f, 0x08, # mov %ecx,0x8(%edi)
+ 0x89, 0x57, 0x0c, # mov %edx,0xc(%edi)
+ 0x5f, # pop %edi
+ 0x5b, # pop %ebx
+ 0xc3 # ret
+]
+
+is_windows = os.name == "nt"
+is_64bit = ctypes.sizeof(ctypes.c_voidp) == 8
+
+class CPUID_struct(ctypes.Structure):
+ _fields_ = [(r, c_uint32) for r in ("eax", "ebx", "ecx", "edx")]
+
+class CPUID(object):
+ def __init__(self):
+ if platform.machine() not in ("AMD64", "x86_64", "x86", "i686"):
+ raise SystemError("Only available for x86")
+
+ if is_windows:
+ if is_64bit:
+ # VirtualAlloc seems to fail under some weird
+ # circumstances when ctypes.windll.kernel32 is
+ # used under 64 bit Python. CDLL fixes this.
+ self.win = ctypes.CDLL("kernel32.dll")
+ opc = _WINDOWS_64_OPC
+ else:
+ # Here ctypes.windll.kernel32 is needed to get the
+ # right DLL. Otherwise it will fail when running
+ # 32 bit Python on 64 bit Windows.
+ self.win = ctypes.windll.kernel32
+ opc = _CDECL_32_OPC
+ else:
+ opc = _POSIX_64_OPC if is_64bit else _CDECL_32_OPC
+
+ size = len(opc)
+ code = (ctypes.c_ubyte * size)(*opc)
+
+ if is_windows:
+ self.win.VirtualAlloc.restype = c_void_p
+ self.win.VirtualAlloc.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_ulong, ctypes.c_ulong]
+ self.addr = self.win.VirtualAlloc(None, size, 0x1000, 0x40)
+ if not self.addr:
+ raise MemoryError("Could not allocate RWX memory")
+ else:
+ self.libc = ctypes.cdll.LoadLibrary(None)
+ self.libc.valloc.restype = ctypes.c_void_p
+ self.libc.valloc.argtypes = [ctypes.c_size_t]
+ self.addr = self.libc.valloc(size)
+ if not self.addr:
+ raise MemoryError("Could not allocate memory")
+
+ self.libc.mprotect.restype = c_int
+ self.libc.mprotect.argtypes = [c_void_p, c_size_t, c_int]
+ ret = self.libc.mprotect(self.addr, size, 1 | 2 | 4)
+ if ret != 0:
+ raise OSError("Failed to set RWX")
+
+
+ ctypes.memmove(self.addr, code, size)
+
+ func_type = CFUNCTYPE(None, POINTER(CPUID_struct), c_uint32, c_uint32)
+ self.func_ptr = func_type(self.addr)
+
+ def __call__(self, eax, ecx=0):
+ struct = CPUID_struct()
+ self.func_ptr(struct, eax, ecx)
+ return struct.eax, struct.ebx, struct.ecx, struct.edx
+
+ def __del__(self):
+ if is_windows:
+ self.win.VirtualFree.restype = c_long
+ self.win.VirtualFree.argtypes = [c_void_p, c_size_t, c_ulong]
+ self.win.VirtualFree(self.addr, 0, 0x8000)
+ elif self.libc:
+ # Seems to throw exception when the program ends and
+ # libc is cleaned up before the object?
+ self.libc.free.restype = None
+ self.libc.free.argtypes = [c_void_p]
+ self.libc.free(self.addr)
+
+if __name__ == "__main__":
+ def valid_inputs():
+ cpuid = CPUID()
+ for eax in (0x0, 0x80000000):
+ highest, _, _, _ = cpuid(eax)
+ while eax <= highest:
+ regs = cpuid(eax)
+ yield (eax, regs)
+ eax += 1
+
+ print(" ".join(x.ljust(8) for x in ("CPUID", "A", "B", "C", "D")).strip())
+ for eax, regs in valid_inputs():
+ print("%08x" % eax, " ".join("%08x" % reg for reg in regs))
+
diff --git a/calibre-plugin/key-wine/getEncryptionKeyLinux.py b/calibre-plugin/key-wine/getEncryptionKeyLinux.py
new file mode 100644
index 0000000..c5e2967
--- /dev/null
+++ b/calibre-plugin/key-wine/getEncryptionKeyLinux.py
@@ -0,0 +1,326 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+
+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 char == ord('\\'):
+ # Get next char:
+ i += 1
+ char = user[i][0].encode("latin-1")[0]
+ 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 char not in hex_char_list:
+ user_new.append(ord('x'))
+ # This seems to be fallback code.
+ # Probably a bug in wine: We should also add "char" - but Wine doesn't either ...
+ else:
+ ival = "" + chr(char)
+
+ # Read up to 3 more chars
+ next = user[i + 1][0].encode("latin-1")[0]
+ if next in hex_char_list:
+ ival += chr(next)
+ i += 1
+
+ next = user[i + 1][0].encode("latin-1")[0]
+ if next in hex_char_list:
+ ival += chr(next)
+ i += 1
+
+ next = user[i + 1][0].encode("latin-1")[0]
+ 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 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 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
+
+ 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 use a default serial number of "0".
+ serial = 0
+
+ print("Serial: " + str(serial))
+
+ cpu = cpuid.CPUID()
+ _, b, c, d = cpu(0)
+ vendor = struct.pack("III", b, d, c)
+
+ print("Vendor: " + vendor.decode("utf-8"))
+
+ signature, _, _, _ = cpu(1)
+ signature = struct.pack('>I', signature)[1:]
+
+ print("Signature: " + str(signature.hex()))
+
+ # 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()
+
+ 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(' ', '').replace(',', '')
+ key_line = bytes.fromhex(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
+
+
+ print("Encrypted key: " + str(key_line))
+
+ import hexdump
+
+ hexdump.hexdump(key_line)
+
+ # These should all be "bytes" or "bytearray"
+ #print(type(vendor))
+ #print(type(signature))
+ #print(type(user))
+
+ entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
+
+ print("Entropy: " + str(entropy))
+
+ # We would now call CryptUnprotectData to decrypt the stuff,
+ # but unfortunately there's no working Linux implementation
+ # for that. 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
+ print(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 Wine username change?")
+ return None
+
+
+def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
+ import subprocess, os, re
+
+ 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"
+
+ import base64
+
+ env_dict["X_DECRYPT_DATA"] = data.hex()
+ env_dict["X_DECRYPT_ENTROPY"] = entropy.hex()
+
+ 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.dirname(os.path.abspath(__file__))
+
+ 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]
+ print("Successfully got encryption key from WINE: " + key_string)
+ master_key = bytes.fromhex(key_string)
+ return True, master_key
+
+
+ else:
+ print("Huh. That didn't work. ")
+ err = int(prog_output.decode("utf-8").split(':')[1])
+ if err == -4:
+ err = int(prog_output.decode("utf-8").split(':')[2])
+
+ print("Program reported: " + prog_output.decode("utf-8"))
+ print("Debug log: ")
+ print(prog_stderr.decode("utf-8"))
+ return False, err
+
+
+
+GetMasterKey()
diff --git a/calibre-plugin/key-wine/getEncryptionKeyWindows.py b/calibre-plugin/key-wine/getEncryptionKeyWindows.py
new file mode 100644
index 0000000..e1805f8
--- /dev/null
+++ b/calibre-plugin/key-wine/getEncryptionKeyWindows.py
@@ -0,0 +1,190 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Most of the code in this file has been taken from adobekey.pyw written by i♥cabbages
+# adobekey.pyw, version 7.0
+# Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al.
+# Released under the terms of the GNU General Public Licence, version 3
+#
+
+try:
+ from ctypes import windll
+except ImportError:
+ import os
+ if os.name != 'nt':
+ print("This script is for Windows!")
+ exit()
+ else:
+ raise
+
+def GetSystemDirectory():
+ from ctypes import windll, c_wchar_p, c_uint, create_unicode_buffer
+ MAX_PATH = 255
+
+ kernel32 = windll.kernel32
+ GetSystemDirectoryW = kernel32.GetSystemDirectoryW
+ GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
+ GetSystemDirectoryW.restype = c_uint
+ def GetSystemDirectory():
+ buffer = create_unicode_buffer(MAX_PATH + 1)
+ GetSystemDirectoryW(buffer, len(buffer))
+ return buffer.value
+ return GetSystemDirectory
+GetSystemDirectory = GetSystemDirectory()
+
+def GetVolumeSerialNumber():
+ from ctypes import windll, c_wchar_p, c_uint, POINTER, byref
+
+ kernel32 = windll.kernel32
+ GetVolumeInformationW = kernel32.GetVolumeInformationW
+ GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
+ POINTER(c_uint), POINTER(c_uint),
+ POINTER(c_uint), c_wchar_p, c_uint]
+ GetVolumeInformationW.restype = c_uint
+ def GetVolumeSerialNumber(path):
+ vsn = c_uint(0)
+ GetVolumeInformationW(
+ path, None, 0, byref(vsn), None, None, None, 0)
+ return vsn.value
+ return GetVolumeSerialNumber
+GetVolumeSerialNumber = GetVolumeSerialNumber()
+
+
+def GetUserNameWINAPI():
+ from ctypes import windll, c_wchar_p, c_uint, POINTER, byref, create_unicode_buffer
+ advapi32 = windll.advapi32
+ GetUserNameW = advapi32.GetUserNameW
+ GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
+ GetUserNameW.restype = c_uint
+
+ buffer = create_unicode_buffer(32)
+ size = c_uint(len(buffer))
+ while not GetUserNameW(buffer, byref(size)):
+ buffer = create_unicode_buffer(len(buffer) * 2)
+ size.value = len(buffer)
+
+ # Yes, it's actually implemented like that. Encode in UTF16 but only take the lowest byte of each character.
+ return buffer.value.encode('utf-16-le')[::2]
+
+def GetUserNameREG():
+ try:
+ import winreg
+ except ImportError:
+ import _winreg as winreg
+
+ try:
+ DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
+ regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, DEVICE_KEY_PATH)
+ # Yes, it's actually implemented like that. Encode in UTF16 but only take the lowest byte of each character.
+ userREG = winreg.QueryValueEx(regkey, 'username')[0].encode('utf-16-le')[::2]
+ return userREG
+ except:
+ return None
+
+
+from ctypes import Structure, c_uint, c_void_p, POINTER
+class DataBlob(Structure):
+ _fields_ = [('cbData', c_uint),
+ ('pbData', c_void_p)]
+DataBlob_p = POINTER(DataBlob)
+
+def CryptUnprotectData(indata, entropy):
+ from ctypes import windll, c_wchar_p, c_uint, byref, cast, create_string_buffer, string_at
+
+ crypt32 = windll.crypt32
+ _CryptUnprotectData = crypt32.CryptUnprotectData
+ _CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
+ c_void_p, c_void_p, c_uint, DataBlob_p]
+ _CryptUnprotectData.restype = c_uint
+ indatab = create_string_buffer(indata)
+ indata = DataBlob(len(indata), cast(indatab, c_void_p))
+ entropyb = create_string_buffer(entropy)
+ entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
+ outdata = DataBlob()
+ if not _CryptUnprotectData(byref(indata), None, byref(entropy),
+ None, None, 0, byref(outdata)):
+ return None
+ return string_at(outdata.pbData, outdata.cbData)
+
+def GetMasterKey():
+
+ import os
+
+ if os.name != 'nt':
+ print("This script is for Windows!")
+
+ # Get serial number of root drive
+ root = GetSystemDirectory().split('\\')[0] + '\\'
+ serial = GetVolumeSerialNumber(root)
+ print("Serial: " + str(serial))
+
+
+ # Get CPU vendor:
+ import cpuid, struct
+ cpu = cpuid.CPUID()
+ _, b, c, d = cpu(0)
+ vendor = struct.pack("III", b, d, c)
+ print("Vendor: " + vendor.decode("utf-8"))
+
+ signature, _, _, _ = cpu(1)
+ signature = struct.pack('>I', signature)[1:]
+
+ print("Signature: " + str(signature.hex()))
+
+ # Search for the username in the registry:
+ user = None
+
+ user_from_registry = GetUserNameREG()
+ current_user_name = GetUserNameWINAPI()
+
+ if (user_from_registry is not None):
+ # Found entry
+ user = user_from_registry
+ else:
+ user = current_user_name
+
+ if (user_from_registry is not None and user_from_registry != current_user_name):
+ print("Username: {0}/{1} mismatch, using {0}".format(str(user_from_registry), str(current_user_name)))
+ elif (user_from_registry is not None):
+ print("Username: {0} (Registry)".format(str(user_from_registry)))
+ else:
+ print("Username: {0} (WinAPI)".format(str(current_user_name)))
+
+
+
+ # Find the value we want to decrypt from the registry:
+ try:
+ import winreg
+ except ImportError:
+ import _winreg as winreg
+
+ try:
+ DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
+ regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, DEVICE_KEY_PATH)
+ device = winreg.QueryValueEx(regkey, 'key')[0]
+ except:
+ print("Can't find encrypted device key.")
+
+
+ print("Encrypted key: " + str(device))
+
+ # These three must all be bytes.
+ #print(type(vendor))
+ #print(type(signature))
+ #print(type(user))
+
+ entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
+
+
+ print("Entropy: " + str(entropy))
+
+ keykey = CryptUnprotectData(device, entropy)
+ if (keykey is None):
+ print("Couldn't decrypt key!")
+ return None
+
+ print("Decrypted key: " + str(keykey))
+
+ return keykey
+
+GetMasterKey()
diff --git a/calibre-plugin/key-wine/main.c b/calibre-plugin/key-wine/main.c
new file mode 100644
index 0000000..1cbe24d
--- /dev/null
+++ b/calibre-plugin/key-wine/main.c
@@ -0,0 +1,173 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef DEBUG
+#undef DEBUG
+#endif
+
+int char2int(char input) {
+ if (input >= '0' && input <= '9')
+ return input - '0';
+ if (input >= 'A' && input <= 'F')
+ return input - 'A' + 10;
+ if (input >= 'a' && input <= 'f')
+ return input - 'a' + 10;
+
+ printf("PROGOUTPUT:-3");
+ exit(-3);
+}
+
+void hex2bin(const char * src, char * dst) {
+ while (*src && src[1]) {
+ *(dst++) = char2int(*src) * 16 + char2int(src[1]);
+ src += 2;
+ }
+}
+
+#ifdef DEBUG
+void hexDump (
+ const char * desc,
+ const void * addr,
+ const int len,
+ int perLine
+) {
+ // Silently ignore silly per-line values.
+
+ if (perLine < 4 || perLine > 64) perLine = 16;
+
+ int i;
+ unsigned char buff[perLine+1];
+ const unsigned char * pc = (const unsigned char *)addr;
+
+ // Output description if given.
+
+ if (desc != NULL) printf ("%s:\n", desc);
+
+ // Length checks.
+
+ if (len == 0) {
+ fprintf(stderr, " ZERO LENGTH\n");
+ return;
+ }
+ if (len < 0) {
+ fprintf(stderr, " NEGATIVE LENGTH: %d\n", len);
+ return;
+ }
+
+ // Process every byte in the data.
+
+ for (i = 0; i < len; i++) {
+ // Multiple of perLine means new or first line (with line offset).
+
+ if ((i % perLine) == 0) {
+ // Only print previous-line ASCII buffer for lines beyond first.
+
+ if (i != 0) printf (" %s\n", buff);
+
+ // Output the offset of current line.
+
+ fprintf (stderr, " %04x ", i);
+ }
+
+ // Now the hex code for the specific character.
+
+ fprintf (stderr, " %02x", pc[i]);
+
+ // And buffer a printable ASCII character for later.
+
+ if ((pc[i] < 0x20) || (pc[i] > 0x7e)) // isprint() may be better.
+ buff[i % perLine] = '.';
+ else
+ buff[i % perLine] = pc[i];
+ buff[(i % perLine) + 1] = '\0';
+ }
+
+ // Pad out last line if not exactly perLine characters.
+
+ while ((i % perLine) != 0) {
+ fprintf (stderr, " ");
+ i++;
+ }
+
+ // And print the final ASCII buffer.
+
+ fprintf (stderr, " %s\n", buff);
+}
+#endif
+
+
+int main() {
+ char * var_data = "X_DECRYPT_DATA";
+ char * var_entropy = "X_DECRYPT_ENTROPY";
+
+ char * data_hex = getenv(var_data);
+ char * entropy_hex = getenv(var_entropy);
+
+ if (data_hex == NULL || entropy_hex == NULL) {
+ printf("PROGOUTPUT:-1");
+ exit(-1);
+ }
+
+ char * data_bytes = malloc((strlen(data_hex) / 2));
+ char * entropy_bytes = malloc((strlen(entropy_hex) / 2));
+
+ if (data_bytes == NULL || entropy_bytes == NULL) {
+ printf("PROGOUTPUT:-2");
+ exit(-2);
+ }
+
+ hex2bin(data_hex, data_bytes);
+ hex2bin(entropy_hex, entropy_bytes);
+
+#ifdef DEBUG
+ hexDump("data", data_bytes, strlen(data_hex)/2, 16);
+ hexDump("entropy", entropy_bytes, strlen(entropy_hex)/2, 16);
+#endif
+
+
+
+DATA_BLOB input_data;
+DATA_BLOB entropy_data;
+
+
+DATA_BLOB output_data;
+
+input_data.pbData = data_bytes;
+input_data.cbData = strlen(data_hex)/2;
+
+entropy_data.pbData = entropy_bytes;
+entropy_data.cbData = strlen(entropy_hex)/2;
+
+int ret = CryptUnprotectData(
+ &input_data,
+ NULL,
+ &entropy_data,
+ NULL,
+ NULL,
+ 0,
+ &output_data);
+
+if (ret) {
+ if (output_data.cbData != 16) {
+ printf("PROGOUTPUT:-5:%d", output_data.cbData);
+ exit(-5);
+ }
+ // Success! Return decrypted data
+ printf("PROGOUTPUT:0:");
+ for (int i = 0; i < 16; i++) {
+ printf("%02x", output_data.pbData[i]);
+ }
+ exit(0);
+}
+else {
+ printf("PROGOUTPUT:-4:%d", GetLastError());
+ exit(-4);
+}
+
+
+}
+