mirror of
https://github.com/Leseratte10/acsm-calibre-plugin.git
synced 2024-12-22 17:29:56 +06:00
Bunch of code to extract keys from WINE
This commit is contained in:
parent
ed9e6d534a
commit
4c3ee827f0
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -10,6 +10,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- 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
|
- name: Compile
|
||||||
run: |
|
run: |
|
||||||
./bundle_calibre_plugin.sh
|
./bundle_calibre_plugin.sh
|
||||||
|
@ -7,6 +7,12 @@
|
|||||||
[ ! -f calibre-plugin/pyasn1.zip ] && ./package_modules.sh
|
[ ! -f calibre-plugin/pyasn1.zip ] && ./package_modules.sh
|
||||||
|
|
||||||
pushd calibre-plugin
|
pushd calibre-plugin
|
||||||
|
pushd key-wine
|
||||||
|
|
||||||
|
# Compile:
|
||||||
|
make
|
||||||
|
|
||||||
|
popd
|
||||||
|
|
||||||
zip -r ../calibre-plugin.zip *
|
zip -r ../calibre-plugin.zip *
|
||||||
|
|
||||||
|
14
calibre-plugin/key-wine/Makefile
Normal file
14
calibre-plugin/key-wine/Makefile
Normal file
@ -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
|
||||||
|
|
157
calibre-plugin/key-wine/cpuid.py
Normal file
157
calibre-plugin/key-wine/cpuid.py
Normal file
@ -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))
|
||||||
|
|
326
calibre-plugin/key-wine/getEncryptionKeyLinux.py
Normal file
326
calibre-plugin/key-wine/getEncryptionKeyLinux.py
Normal file
@ -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()
|
190
calibre-plugin/key-wine/getEncryptionKeyWindows.py
Normal file
190
calibre-plugin/key-wine/getEncryptionKeyWindows.py
Normal file
@ -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
|
||||||
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
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()
|
173
calibre-plugin/key-wine/main.c
Normal file
173
calibre-plugin/key-wine/main.c
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wincrypt.h>
|
||||||
|
#include <dpapi.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user