diff --git a/DeDRM_plugin/adobekey.py b/DeDRM_plugin/adobekey.py index f957ed1..9871eda 100644 --- a/DeDRM_plugin/adobekey.py +++ b/DeDRM_plugin/adobekey.py @@ -30,13 +30,14 @@ # 6.0 - Work if TkInter is missing # 7.0 - Python 3 for calibre 5 # 7.1 - Fix "failed to decrypt user key key" error (read username from registry) +# 7.2 - Fix decryption error on Python2 if there's unicode in the username """ Retrieve Adobe ADEPT user key. """ __license__ = 'GPL v3' -__version__ = '7.1' +__version__ = '7.2' import sys, os, struct, getopt from base64 import b64decode @@ -240,14 +241,21 @@ if iswindows: def GetUserName2(): try: - import winreg + from winreg import OpenKey, QueryValueEx, HKEY_CURRENT_USER except ImportError: - import _winreg as winreg + # We're on Python 2 + try: + # The default _winreg on Python2 isn't unicode-safe. + # Check if we have winreg_unicode, a unicode-safe alternative. + # Without winreg_unicode, this will fail with Unicode chars in the username. + from adobekey_winreg_unicode import OpenKey, QueryValueEx, HKEY_CURRENT_USER + except: + from _winreg import OpenKey, QueryValueEx, HKEY_CURRENT_USER try: DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device' - regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, DEVICE_KEY_PATH) - userREG = winreg.QueryValueEx(regkey, 'username')[0].encode('utf-16-le')[::2] + regkey = OpenKey(HKEY_CURRENT_USER, DEVICE_KEY_PATH) + userREG = QueryValueEx(regkey, 'username')[0].encode('utf-16-le')[::2] return userREG except: return None @@ -398,11 +406,16 @@ if iswindows: plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH) except WindowsError: raise ADEPTError("Could not locate ADE activation") - for i in range(0, 16): + + i = -1 + while True: + i = i + 1 # start with 0 try: plkparent = winreg.OpenKey(plkroot, "%04d" % (i,)) - except WindowsError: + except: + # No more keys break + ktype = winreg.QueryValueEx(plkparent, None)[0] if ktype != 'credentials': continue @@ -476,6 +489,8 @@ elif isosx: return None def adeptkeys(): + # TODO: All the code to support extracting multiple activation keys + # TODO: seems to be Windows-only currently, still needs to be added for Mac. actpath = findActivationDat() if actpath is None: raise ADEPTError("Could not find ADE activation.dat file.") diff --git a/DeDRM_plugin/adobekey_winreg_unicode.py b/DeDRM_plugin/adobekey_winreg_unicode.py new file mode 100644 index 0000000..6c719c4 --- /dev/null +++ b/DeDRM_plugin/adobekey_winreg_unicode.py @@ -0,0 +1,271 @@ +# This is based on https://github.com/DanielStutzbach/winreg_unicode +# The original _winreg in Python2 doesn't support unicode. +# This causes issues if there's unicode chars in the username needed to decrypt the key. + +''' +Copyright 2010 Stutzbach Enterprises, LLC (daniel@stutzbachenterprises.com) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + 3. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +''' + +import ctypes, ctypes.wintypes + +ERROR_SUCCESS = 0 +ERROR_MORE_DATA = 234 + +KEY_READ = 0x20019 + +REG_NONE = 0 +REG_SZ = 1 +REG_EXPAND_SZ = 2 +REG_BINARY = 3 +REG_DWORD = 4 +REG_DWORD_BIG_ENDIAN = 5 +REG_DWORD_LITTLE_ENDIAN = 4 +REG_LINK = 6 +REG_MULTI_SZ = 7 +REG_RESOURCE_LIST = 8 +REG_FULL_RESOURCE_DESCRIPTOR = 9 +REG_RESOURCE_REQUIREMENTS_LIST = 10 + +c_HKEY = ctypes.c_void_p +DWORD = ctypes.wintypes.DWORD +BYTE = ctypes.wintypes.BYTE +LPDWORD = ctypes.POINTER(DWORD) +LPBYTE = ctypes.POINTER(BYTE) + +advapi32 = ctypes.windll.advapi32 + +class FILETIME(ctypes.Structure): + _fields_ = [("dwLowDateTime", DWORD), + ("dwHighDateTime", DWORD)] + +RegCloseKey = advapi32.RegCloseKey +RegCloseKey.restype = ctypes.c_long +RegCloseKey.argtypes = [c_HKEY] + +RegOpenKeyEx = advapi32.RegOpenKeyExW +RegOpenKeyEx.restype = ctypes.c_long +RegOpenKeyEx.argtypes = [c_HKEY, ctypes.c_wchar_p, ctypes.c_ulong, + ctypes.c_ulong, ctypes.POINTER(c_HKEY)] + +RegQueryInfoKey = advapi32.RegQueryInfoKeyW +RegQueryInfoKey.restype = ctypes.c_long +RegQueryInfoKey.argtypes = [c_HKEY, ctypes.c_wchar_p, LPDWORD, LPDWORD, + LPDWORD, LPDWORD, LPDWORD, LPDWORD, + LPDWORD, LPDWORD, LPDWORD, + ctypes.POINTER(FILETIME)] + +RegEnumValue = advapi32.RegEnumValueW +RegEnumValue.restype = ctypes.c_long +RegEnumValue.argtypes = [c_HKEY, DWORD, ctypes.c_wchar_p, LPDWORD, + LPDWORD, LPDWORD, LPBYTE, LPDWORD] + +RegEnumKeyEx = advapi32.RegEnumKeyExW +RegEnumKeyEx.restype = ctypes.c_long +RegEnumKeyEx.argtypes = [c_HKEY, DWORD, ctypes.c_wchar_p, LPDWORD, + LPDWORD, ctypes.c_wchar_p, LPDWORD, + ctypes.POINTER(FILETIME)] + +RegQueryValueEx = advapi32.RegQueryValueExW +RegQueryValueEx.restype = ctypes.c_long +RegQueryValueEx.argtypes = [c_HKEY, ctypes.c_wchar_p, LPDWORD, LPDWORD, + LPBYTE, LPDWORD] + +def check_code(code): + if code == ERROR_SUCCESS: + return + raise ctypes.WinError(2) + +class HKEY(object): + def __init__(self): + self.hkey = c_HKEY() + + def __enter__(self): + return self + + def __exit__(self, exc_type=None, exc_val=None, exc_tb=None): + self.Close() + return False + + def Detach(self): + rv = self.cast(self.hkey, self.c_ulong).value + self.hkey = c_HKEY() + return rv + + def __nonzero__(self): + return bool(self.hkey) + + def Close(self): + if not self.hkey: + return + if RegCloseKey is None or check_code is None or c_HKEY is None: + return # globals become None during exit + rc = RegCloseKey(self.hkey) + self.hkey = c_HKEY() + check_code(rc) + + def __del__(self): + self.Close() + +class RootHKEY(ctypes.Structure): + def __init__(self, value): + self.hkey = c_HKEY(value) + + def Close(self): + pass + +HKEY_CLASSES_ROOT = RootHKEY(0x80000000) +HKEY_CURRENT_USER = RootHKEY(0x80000001) +HKEY_LOCAL_MACHINE = RootHKEY(0x80000002) +HKEY_USERS = RootHKEY(0x80000003) +HKEY_PERFORMANCE_DATA = RootHKEY(0x80000004) +HKEY_CURRENT_CONFIG = RootHKEY(0x80000005) +HKEY_DYN_DATA = RootHKEY(0x80000006) + +def OpenKey(key, sub_key): + new_key = HKEY() + rc = RegOpenKeyEx(key.hkey, sub_key, 0, KEY_READ, + ctypes.cast(ctypes.byref(new_key.hkey), + ctypes.POINTER(c_HKEY))) + check_code(rc) + return new_key + +def QueryInfoKey(key): + null = LPDWORD() + num_sub_keys = DWORD() + num_values = DWORD() + ft = FILETIME() + rc = RegQueryInfoKey(key.hkey, ctypes.c_wchar_p(), null, null, + ctypes.byref(num_sub_keys), null, null, + ctypes.byref(num_values), null, null, null, + ctypes.byref(ft)) + check_code(rc) + return (num_sub_keys.value, num_values.value, + ft.dwLowDateTime | (ft.dwHighDateTime << 32)) + +def EnumValue(key, index): + null = LPDWORD() + value_size = DWORD() + data_size = DWORD() + rc = RegQueryInfoKey(key.hkey, ctypes.c_wchar_p(), null, null, null, + null, null, null, + ctypes.byref(value_size), ctypes.byref(data_size), + null, ctypes.POINTER(FILETIME)()) + check_code(rc) + value_size.value += 1 + data_size.value += 1 + + value = ctypes.create_unicode_buffer(value_size.value) + + while True: + data = ctypes.create_string_buffer(data_size.value) + + tmp_value_size = DWORD(value_size.value) + tmp_data_size = DWORD(data_size.value) + typ = DWORD() + rc = RegEnumValue(key.hkey, index, + ctypes.cast(value, ctypes.c_wchar_p), + ctypes.byref(tmp_value_size), null, + ctypes.byref(typ), + ctypes.cast(data, LPBYTE), + ctypes.byref(tmp_data_size)) + + if rc != ERROR_MORE_DATA: + break + + data_size.value *= 2 + + check_code(rc) + return (value.value, Reg2Py(data, tmp_data_size.value, typ.value), + typ.value) + +def split_multi_sz(data, size): + if size == 0: + return [] + Q = size + P = 0 + rv = [] + while P < Q and data[P].value != u'\0': + rv.append[P] + while P < Q and data[P].value != u'\0': + P += 1 + P += 1 + rv.append(size) + return [ctypes.wstring_at(ctypes.pointer(data[rv[i]]), + rv[i+1] - rv[i]).rstrip(u'\x00') + for i in range(len(rv)-1)] + +def Reg2Py(data, size, typ): + if typ == REG_DWORD: + if size == 0: + return 0 + return ctypes.cast(data, ctypes.POINTER(ctypes.c_int)).contents.value + elif typ == REG_SZ or typ == REG_EXPAND_SZ: + return ctypes.wstring_at(data, size // 2).rstrip(u'\x00') + elif typ == REG_MULTI_SZ: + return split_multi_sz(ctypes.cast(data, ctypes.c_wchar_p), size // 2) + else: + if size == 0: + return None + return ctypes.string_at(data, size) + +def EnumKey(key, index): + tmpbuf = ctypes.create_unicode_buffer(257) + length = DWORD(257) + rc = RegEnumKeyEx(key.hkey, index, + ctypes.cast(tmpbuf, ctypes.c_wchar_p), + ctypes.byref(length), + LPDWORD(), ctypes.c_wchar_p(), LPDWORD(), + ctypes.POINTER(FILETIME)()) + check_code(rc) + return ctypes.wstring_at(tmpbuf, length.value).rstrip(u'\x00') + +def QueryValueEx(key, value_name): + size = 256 + typ = DWORD() + while True: + tmp_size = DWORD(size) + buf = ctypes.create_string_buffer(size) + rc = RegQueryValueEx(key.hkey, value_name, LPDWORD(), + ctypes.byref(typ), + ctypes.cast(buf, LPBYTE), ctypes.byref(tmp_size)) + if rc != ERROR_MORE_DATA: + break + + size *= 2 + check_code(rc) + return (Reg2Py(buf, tmp_size.value, typ.value), typ.value) + +__all__ = ['OpenKey', 'QueryInfoKey', 'EnumValue', 'EnumKey', 'QueryValueEx', + 'HKEY_CLASSES_ROOT', 'HKEY_CURRENT_USER', 'HKEY_LOCAL_MACHINE', + 'HKEY_USERS', 'HKEY_PERFORMANCE_DATA', 'HKEY_CURRENT_CONFIG', + 'HKEY_DYN_DATA', 'REG_NONE', 'REG_SZ', 'REG_EXPAND_SZ', + 'REG_BINARY', 'REG_DWORD', 'REG_DWORD_BIG_ENDIAN', + 'REG_DWORD_LITTLE_ENDIAN', 'REG_LINK', 'REG_MULTI_SZ', + 'REG_RESOURCE_LIST', 'REG_FULL_RESOURCE_DESCRIPTOR', + 'REG_RESOURCE_REQUIREMENTS_LIST'] \ No newline at end of file