From 4c3ee827f07bf607c8198a03a6549c9a001e6869 Mon Sep 17 00:00:00 2001 From: Florian Bach Date: Sat, 20 Nov 2021 14:14:59 +0100 Subject: [PATCH] Bunch of code to extract keys from WINE --- .github/workflows/main.yml | 4 + bundle_calibre_plugin.sh | 6 + calibre-plugin/key-wine/Makefile | 14 + calibre-plugin/key-wine/cpuid.py | 157 +++++++++ .../key-wine/getEncryptionKeyLinux.py | 326 ++++++++++++++++++ .../key-wine/getEncryptionKeyWindows.py | 190 ++++++++++ calibre-plugin/key-wine/main.c | 173 ++++++++++ 7 files changed, 870 insertions(+) create mode 100644 calibre-plugin/key-wine/Makefile create mode 100644 calibre-plugin/key-wine/cpuid.py create mode 100644 calibre-plugin/key-wine/getEncryptionKeyLinux.py create mode 100644 calibre-plugin/key-wine/getEncryptionKeyWindows.py create mode 100644 calibre-plugin/key-wine/main.c 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); +} + + +} +