Last active
July 17, 2023 05:03
-
-
Save gileri/bb44a834736ae3c2aa83c273424e531d to your computer and use it in GitHub Desktop.
ADEPT DRM-protected decryption with fixes for more recent version of PyCrypto
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Tested under on 2017-04 under wine, python-2.7.13.msi, pycrypto-2.6.win32-py2.7.exe | |
From https://thescienceofdestruction.wordpress.com/2011/08/10/downloading-and-decrypting-adobe-adept-drm-protected-ebooks-on-linux/ : | |
Fixes by rknop : https://thescienceofdestruction.wordpress.com/2011/08/10/downloading-and-decrypting-adobe-adept-drm-protected-ebooks-on-linux/#comment-696 | |
This howto shows how to download eBooks using Adobe’s ADEPT DRM (Digital Rights Management) scheme on Linux and how to decrypt those books so that you can use your eBook viewer of choice to read them and copy them to any device you wish. | |
To download content protected with ADEPT, we need the Adobe Digital Editions eBook viewer. Sadly there is no Linux version, so we need to run the Windows version with Wine. Get the Windows installer from here. | |
Go to the folder where you downloaded the installer and run it with the command | |
wine setup.exe | |
After the installation, run Adobe Digital Editions straight from the installer or with the command | |
wine ~/.wine/drive_c/"Program Files/Adobe/Adobe Digital Editions/digitaleditions.exe" | |
In order to access DRM-protected content, you need to “authorize” your computer using the Adobe ID. You can get the ID by creating an account at the Adobe website here. Then in Adobe Digital Editions select “Authorize computer…” from the Library-menu and enter the ID and password. | |
Now we can actually download the book. For this we need a file with the .acsm-extension. This file contains the necessary information for Adobe Digital Editions to locate and download the book. You’ll probably get this file by clicking on the download link your eBook vendor provided. Save the file to disk. Now load the file by dragging its icon on the Adobe Digital Editions window. Adobe Digital Editions should now start downloading the book. After the download has finished, quit Adobe Digital Editions. | |
The DRM-protected pdf- or ePub-file should now be located in the folder ~/My Digital Editions. Next, we decrypt the DRM scheme. For this, we’ll use two Python-scripts: the key retrieval script ineptkey.pyw and the decryption script ineptpdf.pyw or ineptepub.pyw (depending on the file format). Copy the scripts into the same folder with the book. Unfortunately, as these scripts are for Windows and OSX only, we’ll again have to use Wine. To run the scripts, we need the Windows version of Python (get it from here) and the cryptography package PyCrypto (from here). The Python version should be at least 2.6 (I use 2.7 here, newer versions might work as well). Make sure that the PyCrypto version matches the Python one. Run the installers with the commands | |
wine msiexec /i python-2.7.2.msi | |
and | |
wine msiexec /i pycrypto-2.3.win32-py2.7.msi | |
Now, go to the folder with the book and the scripts and run the first script with the command | |
wine ~/.wine/drive_c/Python27/python.exe ineptkey.pyw | |
This will create the file adeptkey.der in the same folder. Now, run the second script with | |
wine ~/.wine/drive_c/Python27/python.exe ineptpdf.pyw | |
or | |
wine ~/.wine/drive_c/Python27/python.exe ineptepub.pyw | |
In the dialogue select the book as input file and name the output file what you like. Click on “Decrypt.” | |
Voila! You should now have the decrypted pdf- or ePub-file in the ~/My Digital Editions folder. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/python | |
# -*- coding: utf-8 -*- | |
# ineptepub.pyw, version 5.2 | |
# Copyright © 2009-2010 i♥cabbages | |
# Released under the terms of the GNU General Public Licence, version 3 or | |
# later. <http://www.gnu.org/licenses/> | |
# Windows users: Before running this program, you must first install Python 2.6 | |
# from <http://www.python.org/download/> and PyCrypto from | |
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to | |
# install the version for Python 2.6). Save this script file as | |
# ineptepub.pyw and double-click on it to run it. | |
# | |
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this | |
# program from the command line (pythonw ineptepub.pyw) or by double-clicking | |
# it when it has been associated with PythonLauncher. | |
# Revision history: | |
# 1 - Initial release | |
# 2 - Rename to INEPT, fix exit code | |
# 5 - Version bump to avoid (?) confusion; | |
# Improve OS X support by using OpenSSL when available | |
# 5.1 - Improve OpenSSL error checking | |
# 5.2 - Fix ctypes error causing segfaults on some systems | |
""" | |
Decrypt Adobe ADEPT-encrypted EPUB books. | |
""" | |
from __future__ import with_statement | |
__license__ = 'GPL v3' | |
import sys | |
import os | |
import zlib | |
import zipfile | |
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED | |
from contextlib import closing | |
import xml.etree.ElementTree as etree | |
import Tkinter | |
import Tkconstants | |
import tkFileDialog | |
import tkMessageBox | |
class ADEPTError(Exception): | |
pass | |
def _load_crypto_libcrypto(): | |
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ | |
Structure, c_ulong, create_string_buffer, cast | |
from ctypes.util import find_library | |
libcrypto = find_library('crypto') | |
if libcrypto is None: | |
raise ADEPTError('libcrypto not found') | |
libcrypto = CDLL(libcrypto) | |
RSA_NO_PADDING = 3 | |
AES_MAXNR = 14 | |
c_char_pp = POINTER(c_char_p) | |
c_int_p = POINTER(c_int) | |
class RSA(Structure): | |
pass | |
RSA_p = POINTER(RSA) | |
class AES_KEY(Structure): | |
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), | |
('rounds', c_int)] | |
AES_KEY_p = POINTER(AES_KEY) | |
def F(restype, name, argtypes): | |
func = getattr(libcrypto, name) | |
func.restype = restype | |
func.argtypes = argtypes | |
return func | |
d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey', | |
[RSA_p, c_char_pp, c_long]) | |
RSA_size = F(c_int, 'RSA_size', [RSA_p]) | |
RSA_private_decrypt = F(c_int, 'RSA_private_decrypt', | |
[c_int, c_char_p, c_char_p, RSA_p, c_int]) | |
RSA_free = F(None, 'RSA_free', [RSA_p]) | |
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key', | |
[c_char_p, c_int, AES_KEY_p]) | |
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt', | |
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, | |
c_int]) | |
class RSA(object): | |
def __init__(self, der): | |
buf = create_string_buffer(der) | |
pp = c_char_pp(cast(buf, c_char_p)) | |
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der)) | |
if rsa is None: | |
raise ADEPTError('Error parsing ADEPT user key DER') | |
def decrypt(self, from_): | |
rsa = self._rsa | |
to = create_string_buffer(RSA_size(rsa)) | |
dlen = RSA_private_decrypt(len(from_), from_, to, rsa, | |
RSA_NO_PADDING) | |
if dlen < 0: | |
raise ADEPTError('RSA decryption failed') | |
return to[:dlen] | |
def __del__(self): | |
if self._rsa is not None: | |
RSA_free(self._rsa) | |
self._rsa = None | |
class AES(object): | |
def __init__(self, userkey): | |
self._blocksize = len(userkey) | |
key = self._key = AES_KEY() | |
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key) | |
if rv < 0: | |
raise ADEPTError('Failed to initialize AES key') | |
def decrypt(self, data): | |
out = create_string_buffer(len(data)) | |
iv = ("\x00" * self._blocksize) | |
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0) | |
if rv == 0: | |
raise ADEPTError('AES decryption failed') | |
return out.raw | |
return (AES, RSA) | |
def _load_crypto_pycrypto(): | |
from Crypto.Cipher import AES as _AES | |
from Crypto.PublicKey import RSA as _RSA | |
from Crypto import Random | |
# ASN.1 parsing code from tlslite | |
class ASN1Error(Exception): | |
pass | |
class ASN1Parser(object): | |
class Parser(object): | |
def __init__(self, bytes): | |
self.bytes = bytes | |
self.index = 0 | |
def get(self, length): | |
if self.index + length > len(self.bytes): | |
raise ASN1Error("Error decoding ASN.1") | |
x = 0 | |
for count in range(length): | |
x <<= 8 | |
x |= self.bytes[self.index] | |
self.index += 1 | |
return x | |
def getFixBytes(self, lengthBytes): | |
bytes = self.bytes[self.index : self.index+lengthBytes] | |
self.index += lengthBytes | |
return bytes | |
def getVarBytes(self, lengthLength): | |
lengthBytes = self.get(lengthLength) | |
return self.getFixBytes(lengthBytes) | |
def getFixList(self, length, lengthList): | |
l = [0] * lengthList | |
for x in range(lengthList): | |
l[x] = self.get(length) | |
return l | |
def getVarList(self, length, lengthLength): | |
lengthList = self.get(lengthLength) | |
if lengthList % length != 0: | |
raise ASN1Error("Error decoding ASN.1") | |
lengthList = int(lengthList/length) | |
l = [0] * lengthList | |
for x in range(lengthList): | |
l[x] = self.get(length) | |
return l | |
def startLengthCheck(self, lengthLength): | |
self.lengthCheck = self.get(lengthLength) | |
self.indexCheck = self.index | |
def setLengthCheck(self, length): | |
self.lengthCheck = length | |
self.indexCheck = self.index | |
def stopLengthCheck(self): | |
if (self.index - self.indexCheck) != self.lengthCheck: | |
raise ASN1Error("Error decoding ASN.1") | |
def atLengthCheck(self): | |
if (self.index - self.indexCheck) < self.lengthCheck: | |
return False | |
elif (self.index - self.indexCheck) == self.lengthCheck: | |
return True | |
else: | |
raise ASN1Error("Error decoding ASN.1") | |
def __init__(self, bytes): | |
p = self.Parser(bytes) | |
p.get(1) | |
self.length = self._getASN1Length(p) | |
self.value = p.getFixBytes(self.length) | |
def getChild(self, which): | |
p = self.Parser(self.value) | |
for x in range(which+1): | |
markIndex = p.index | |
p.get(1) | |
length = self._getASN1Length(p) | |
p.getFixBytes(length) | |
return ASN1Parser(p.bytes[markIndex:p.index]) | |
def _getASN1Length(self, p): | |
firstLength = p.get(1) | |
if firstLength<=127: | |
return firstLength | |
else: | |
lengthLength = firstLength & 0x7F | |
return p.get(lengthLength) | |
class AES(object): | |
def __init__(self, key): | |
iv = Random.new().read(_AES.block_size) | |
self._aes = _AES.new(key, _AES.MODE_CBC, iv) | |
def decrypt(self, data): | |
return self._aes.decrypt(data) | |
class RSA(object): | |
def __init__(self, der): | |
key = ASN1Parser([ord(x) for x in der]) | |
key = [key.getChild(x).value for x in xrange(1, 4)] | |
key = [self.bytesToNumber(v) for v in key] | |
self._rsa = _RSA.construct(key) | |
def bytesToNumber(self, bytes): | |
total = 0L | |
for byte in bytes: | |
total = (total << 8) + byte | |
return total | |
def decrypt(self, data): | |
return self._rsa.decrypt(data) | |
return (AES, RSA) | |
def _load_crypto(): | |
AES = RSA = None | |
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): | |
try: | |
AES, RSA = loader() | |
break | |
except (ImportError, ADEPTError): | |
pass | |
return (AES, RSA) | |
AES, RSA = _load_crypto() | |
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml') | |
NSMAP = {'adept': 'http://ns.adobe.com/adept', | |
'enc': 'http://www.w3.org/2001/04/xmlenc#'} | |
class ZipInfo(zipfile.ZipInfo): | |
def __init__(self, *args, **kwargs): | |
if 'compress_type' in kwargs: | |
compress_type = kwargs.pop('compress_type') | |
super(ZipInfo, self).__init__(*args, **kwargs) | |
self.compress_type = compress_type | |
class Decryptor(object): | |
def __init__(self, bookkey, encryption): | |
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag) | |
self._aes = AES(bookkey) | |
encryption = etree.fromstring(encryption) | |
self._encrypted = encrypted = set() | |
expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'), | |
enc('CipherReference')) | |
for elem in encryption.findall(expr): | |
path = elem.get('URI', None) | |
if path is not None: | |
encrypted.add(path) | |
def decompress(self, bytes): | |
dc = zlib.decompressobj(-15) | |
bytes = dc.decompress(bytes) | |
ex = dc.decompress('Z') + dc.flush() | |
if ex: | |
bytes = bytes + ex | |
return bytes | |
def decrypt(self, path, data): | |
if path in self._encrypted: | |
data = self._aes.decrypt(data)[16:] | |
data = data[:-ord(data[-1])] | |
data = self.decompress(data) | |
return data | |
def cli_main(argv=sys.argv): | |
progname = os.path.basename(argv[0]) | |
if AES is None: | |
print "%s: This script requires OpenSSL or PyCrypto, which must be" \ | |
" installed separately. Read the top-of-script comment for" \ | |
" details." % (progname,) | |
return 1 | |
if len(argv) != 4: | |
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,) | |
return 1 | |
keypath, inpath, outpath = argv[1:] | |
with open(keypath, 'rb') as f: | |
keyder = f.read() | |
rsa = RSA(keyder) | |
with closing(ZipFile(open(inpath, 'rb'))) as inf: | |
namelist = set(inf.namelist()) | |
if 'META-INF/rights.xml' not in namelist or \ | |
'META-INF/encryption.xml' not in namelist: | |
raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,)) | |
for name in META_NAMES: | |
namelist.remove(name) | |
rights = etree.fromstring(inf.read('META-INF/rights.xml')) | |
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) | |
expr = './/%s' % (adept('encryptedKey'),) | |
bookkey = ''.join(rights.findtext(expr)) | |
bookkey = rsa.decrypt(bookkey.decode('base64')) | |
# Padded as per RSAES-PKCS1-v1_5 | |
if bookkey[-17] != '\x00': | |
raise ADEPTError('problem decrypting session key') | |
encryption = inf.read('META-INF/encryption.xml') | |
decryptor = Decryptor(bookkey[-16:], encryption) | |
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False) | |
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf: | |
zi = ZipInfo('mimetype', compress_type=ZIP_STORED) | |
outf.writestr(zi, inf.read('mimetype')) | |
for path in namelist: | |
data = inf.read(path) | |
outf.writestr(path, decryptor.decrypt(path, data)) | |
return 0 | |
class DecryptionDialog(Tkinter.Frame): | |
def __init__(self, root): | |
Tkinter.Frame.__init__(self, root, border=5) | |
self.status = Tkinter.Label(self, text='Select files for decryption') | |
self.status.pack(fill=Tkconstants.X, expand=1) | |
body = Tkinter.Frame(self) | |
body.pack(fill=Tkconstants.X, expand=1) | |
sticky = Tkconstants.E + Tkconstants.W | |
body.grid_columnconfigure(1, weight=2) | |
Tkinter.Label(body, text='Key file').grid(row=0) | |
self.keypath = Tkinter.Entry(body, width=30) | |
self.keypath.grid(row=0, column=1, sticky=sticky) | |
if os.path.exists('adeptkey.der'): | |
self.keypath.insert(0, 'adeptkey.der') | |
button = Tkinter.Button(body, text="...", command=self.get_keypath) | |
button.grid(row=0, column=2) | |
Tkinter.Label(body, text='Input file').grid(row=1) | |
self.inpath = Tkinter.Entry(body, width=30) | |
self.inpath.grid(row=1, column=1, sticky=sticky) | |
button = Tkinter.Button(body, text="...", command=self.get_inpath) | |
button.grid(row=1, column=2) | |
Tkinter.Label(body, text='Output file').grid(row=2) | |
self.outpath = Tkinter.Entry(body, width=30) | |
self.outpath.grid(row=2, column=1, sticky=sticky) | |
button = Tkinter.Button(body, text="...", command=self.get_outpath) | |
button.grid(row=2, column=2) | |
buttons = Tkinter.Frame(self) | |
buttons.pack() | |
botton = Tkinter.Button( | |
buttons, text="Decrypt", width=10, command=self.decrypt) | |
botton.pack(side=Tkconstants.LEFT) | |
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) | |
button = Tkinter.Button( | |
buttons, text="Quit", width=10, command=self.quit) | |
button.pack(side=Tkconstants.RIGHT) | |
def get_keypath(self): | |
keypath = tkFileDialog.askopenfilename( | |
parent=None, title='Select ADEPT key file', | |
defaultextension='.der', filetypes=[('DER-encoded files', '.der'), | |
('All Files', '.*')]) | |
if keypath: | |
keypath = os.path.normpath(keypath) | |
self.keypath.delete(0, Tkconstants.END) | |
self.keypath.insert(0, keypath) | |
return | |
def get_inpath(self): | |
inpath = tkFileDialog.askopenfilename( | |
parent=None, title='Select ADEPT-encrypted EPUB file to decrypt', | |
defaultextension='.epub', filetypes=[('EPUB files', '.epub'), | |
('All files', '.*')]) | |
if inpath: | |
inpath = os.path.normpath(inpath) | |
self.inpath.delete(0, Tkconstants.END) | |
self.inpath.insert(0, inpath) | |
return | |
def get_outpath(self): | |
outpath = tkFileDialog.asksaveasfilename( | |
parent=None, title='Select unencrypted EPUB file to produce', | |
defaultextension='.epub', filetypes=[('EPUB files', '.epub'), | |
('All files', '.*')]) | |
if outpath: | |
outpath = os.path.normpath(outpath) | |
self.outpath.delete(0, Tkconstants.END) | |
self.outpath.insert(0, outpath) | |
return | |
def decrypt(self): | |
keypath = self.keypath.get() | |
inpath = self.inpath.get() | |
outpath = self.outpath.get() | |
if not keypath or not os.path.exists(keypath): | |
self.status['text'] = 'Specified key file does not exist' | |
return | |
if not inpath or not os.path.exists(inpath): | |
self.status['text'] = 'Specified input file does not exist' | |
return | |
if not outpath: | |
self.status['text'] = 'Output file not specified' | |
return | |
if inpath == outpath: | |
self.status['text'] = 'Must have different input and output files' | |
return | |
argv = [sys.argv[0], keypath, inpath, outpath] | |
self.status['text'] = 'Decrypting...' | |
try: | |
cli_main(argv) | |
except Exception, e: | |
self.status['text'] = 'Error: ' + str(e) | |
return | |
self.status['text'] = 'File successfully decrypted' | |
def gui_main(): | |
root = Tkinter.Tk() | |
if AES is None: | |
root.withdraw() | |
tkMessageBox.showerror( | |
"INEPT EPUB Decrypter", | |
"This script requires OpenSSL or PyCrypto, which must be" | |
" installed separately. Read the top-of-script comment for" | |
" details.") | |
return 1 | |
root.title('INEPT EPUB Decrypter') | |
root.resizable(True, False) | |
root.minsize(300, 0) | |
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) | |
root.mainloop() | |
return 0 | |
if __name__ == '__main__': | |
if len(sys.argv) > 1: | |
sys.exit(cli_main()) | |
sys.exit(gui_main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/python | |
# -*- coding: utf-8 -*- | |
# ineptkey.pyw, version 5 | |
# Copyright © 2009-2010 i♥cabbages | |
# Released under the terms of the GNU General Public Licence, version 3 or | |
# later. <http://www.gnu.org/licenses/> | |
# Windows users: Before running this program, you must first install Python 2.6 | |
# from <http://www.python.org/download/> and PyCrypto from | |
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make certain | |
# to install the version for Python 2.6). Then save this script file as | |
# ineptkey.pyw and double-click on it to run it. It will create a file named | |
# adeptkey.der in the same directory. This is your ADEPT user key. | |
# | |
# Mac OS X users: Save this script file as ineptkey.pyw. You can run this | |
# program from the command line (pythonw ineptkey.pyw) or by double-clicking | |
# it when it has been associated with PythonLauncher. It will create a file | |
# named adeptkey.der in the same directory. This is your ADEPT user key. | |
# Revision history: | |
# 1 - Initial release, for Adobe Digital Editions 1.7 | |
# 2 - Better algorithm for finding pLK; improved error handling | |
# 3 - Rename to INEPT | |
# 4 - Series of changes by joblack (and others?) -- | |
# 4.1 - quick beta fix for ADE 1.7.2 (anon) | |
# 4.2 - added old 1.7.1 processing | |
# 4.3 - better key search | |
# 4.4 - Make it working on 64-bit Python | |
# 5 - Clean up and improve 4.x changes; | |
# Clean up and merge OS X support by unknown | |
""" | |
Retrieve Adobe ADEPT user key. | |
""" | |
from __future__ import with_statement | |
__license__ = 'GPL v3' | |
import sys | |
import os | |
import struct | |
import Tkinter | |
import Tkconstants | |
import tkMessageBox | |
import traceback | |
from Crypto import Random | |
class ADEPTError(Exception): | |
pass | |
if sys.platform.startswith('win'): | |
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \ | |
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \ | |
string_at, Structure, c_void_p, cast, c_size_t, memmove | |
from ctypes.wintypes import LPVOID, DWORD, BOOL | |
import _winreg as winreg | |
try: | |
from Crypto.Cipher import AES | |
except ImportError: | |
AES = None | |
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device' | |
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation' | |
MAX_PATH = 255 | |
kernel32 = windll.kernel32 | |
advapi32 = windll.advapi32 | |
crypt32 = windll.crypt32 | |
def GetSystemDirectory(): | |
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(): | |
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 GetUserName(): | |
GetUserNameW = advapi32.GetUserNameW | |
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)] | |
GetUserNameW.restype = c_uint | |
def GetUserName(): | |
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) | |
return buffer.value.encode('utf-16-le')[::2] | |
return GetUserName | |
GetUserName = GetUserName() | |
PAGE_EXECUTE_READWRITE = 0x40 | |
MEM_COMMIT = 0x1000 | |
MEM_RESERVE = 0x2000 | |
def VirtualAlloc(): | |
_VirtualAlloc = kernel32.VirtualAlloc | |
_VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD] | |
_VirtualAlloc.restype = LPVOID | |
def VirtualAlloc(addr, size, alloctype=(MEM_COMMIT | MEM_RESERVE), | |
protect=PAGE_EXECUTE_READWRITE): | |
return _VirtualAlloc(addr, size, alloctype, protect) | |
return VirtualAlloc | |
VirtualAlloc = VirtualAlloc() | |
MEM_RELEASE = 0x8000 | |
def VirtualFree(): | |
_VirtualFree = kernel32.VirtualFree | |
_VirtualFree.argtypes = [LPVOID, c_size_t, DWORD] | |
_VirtualFree.restype = BOOL | |
def VirtualFree(addr, size=0, freetype=MEM_RELEASE): | |
return _VirtualFree(addr, size, freetype) | |
return VirtualFree | |
VirtualFree = VirtualFree() | |
class NativeFunction(object): | |
def __init__(self, restype, argtypes, insns): | |
self._buf = buf = VirtualAlloc(None, len(insns)) | |
memmove(buf, insns, len(insns)) | |
ftype = CFUNCTYPE(restype, *argtypes) | |
self._native = ftype(buf) | |
def __call__(self, *args): | |
return self._native(*args) | |
def __del__(self): | |
if self._buf is not None: | |
VirtualFree(self._buf) | |
self._buf = None | |
if struct.calcsize("P") == 4: | |
CPUID0_INSNS = ( | |
"\x53" # push %ebx | |
"\x31\xc0" # xor %eax,%eax | |
"\x0f\xa2" # cpuid | |
"\x8b\x44\x24\x08" # mov 0x8(%esp),%eax | |
"\x89\x18" # mov %ebx,0x0(%eax) | |
"\x89\x50\x04" # mov %edx,0x4(%eax) | |
"\x89\x48\x08" # mov %ecx,0x8(%eax) | |
"\x5b" # pop %ebx | |
"\xc3" # ret | |
) | |
CPUID1_INSNS = ( | |
"\x53" # push %ebx | |
"\x31\xc0" # xor %eax,%eax | |
"\x40" # inc %eax | |
"\x0f\xa2" # cpuid | |
"\x5b" # pop %ebx | |
"\xc3" # ret | |
) | |
else: | |
CPUID0_INSNS = ( | |
"\x49\x89\xd8" # mov %rbx,%r8 | |
"\x49\x89\xc9" # mov %rcx,%r9 | |
"\x48\x31\xc0" # xor %rax,%rax | |
"\x0f\xa2" # cpuid | |
"\x4c\x89\xc8" # mov %r9,%rax | |
"\x89\x18" # mov %ebx,0x0(%rax) | |
"\x89\x50\x04" # mov %edx,0x4(%rax) | |
"\x89\x48\x08" # mov %ecx,0x8(%rax) | |
"\x4c\x89\xc3" # mov %r8,%rbx | |
"\xc3" # retq | |
) | |
CPUID1_INSNS = ( | |
"\x53" # push %rbx | |
"\x48\x31\xc0" # xor %rax,%rax | |
"\x48\xff\xc0" # inc %rax | |
"\x0f\xa2" # cpuid | |
"\x5b" # pop %rbx | |
"\xc3" # retq | |
) | |
def cpuid0(): | |
_cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS) | |
buf = create_string_buffer(12) | |
def cpuid0(): | |
_cpuid0(buf) | |
return buf.raw | |
return cpuid0 | |
cpuid0 = cpuid0() | |
cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS) | |
class DataBlob(Structure): | |
_fields_ = [('cbData', c_uint), | |
('pbData', c_void_p)] | |
DataBlob_p = POINTER(DataBlob) | |
def CryptUnprotectData(): | |
_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 | |
def CryptUnprotectData(indata, entropy): | |
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)): | |
raise ADEPTError("Failed to decrypt user key key (sic)") | |
return string_at(outdata.pbData, outdata.cbData) | |
return CryptUnprotectData | |
CryptUnprotectData = CryptUnprotectData() | |
def retrieve_key(keypath): | |
if AES is None: | |
tkMessageBox.showerror( | |
"ADEPT Key", | |
"This script requires PyCrypto, which must be installed " | |
"separately. Read the top-of-script comment for details.") | |
return False | |
root = GetSystemDirectory().split('\\')[0] + '\\' | |
serial = GetVolumeSerialNumber(root) | |
vendor = cpuid0() | |
signature = struct.pack('>I', cpuid1())[1:] | |
user = GetUserName() | |
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user) | |
cuser = winreg.HKEY_CURRENT_USER | |
try: | |
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH) | |
except WindowsError: | |
raise ADEPTError("Adobe Digital Editions not activated") | |
device = winreg.QueryValueEx(regkey, 'key')[0] | |
keykey = CryptUnprotectData(device, entropy) | |
userkey = None | |
try: | |
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH) | |
except WindowsError: | |
raise ADEPTError("Could not locate ADE activation") | |
for i in xrange(0, 16): | |
try: | |
plkparent = winreg.OpenKey(plkroot, "%04d" % (i,)) | |
except WindowsError: | |
break | |
ktype = winreg.QueryValueEx(plkparent, None)[0] | |
if ktype != 'credentials': | |
continue | |
for j in xrange(0, 16): | |
try: | |
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,)) | |
except WindowsError: | |
break | |
ktype = winreg.QueryValueEx(plkkey, None)[0] | |
if ktype != 'privateLicenseKey': | |
continue | |
userkey = winreg.QueryValueEx(plkkey, 'value')[0] | |
break | |
if userkey is not None: | |
break | |
if userkey is None: | |
raise ADEPTError('Could not locate privateLicenseKey') | |
userkey = userkey.decode('base64') | |
iv = Random.new().read(AES.block_size) | |
userkey = AES.new(keykey, AES.MODE_CBC, iv).decrypt(userkey) | |
userkey = userkey[26:-ord(userkey[-1])] | |
with open(keypath, 'wb') as f: | |
f.write(userkey) | |
return True | |
elif sys.platform.startswith('darwin'): | |
import xml.etree.ElementTree as etree | |
import Carbon.File | |
import Carbon.Folder | |
import Carbon.Folders | |
import MacOS | |
ACTIVATION_PATH = 'Adobe/Digital Editions/activation.dat' | |
NSMAP = {'adept': 'http://ns.adobe.com/adept', | |
'enc': 'http://www.w3.org/2001/04/xmlenc#'} | |
def find_folder(domain, dtype): | |
try: | |
fsref = Carbon.Folder.FSFindFolder(domain, dtype, False) | |
return Carbon.File.pathname(fsref) | |
except MacOS.Error: | |
return None | |
def find_app_support_file(subpath): | |
dtype = Carbon.Folders.kApplicationSupportFolderType | |
for domain in Carbon.Folders.kUserDomain, Carbon.Folders.kLocalDomain: | |
path = find_folder(domain, dtype) | |
if path is None: | |
continue | |
path = os.path.join(path, subpath) | |
if os.path.isfile(path): | |
return path | |
return None | |
def retrieve_key(keypath): | |
actpath = find_app_support_file(ACTIVATION_PATH) | |
if actpath is None: | |
raise ADEPTError("Could not locate ADE activation") | |
tree = etree.parse(actpath) | |
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag) | |
expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey')) | |
userkey = tree.findtext(expr) | |
userkey = userkey.decode('base64') | |
userkey = userkey[26:] | |
with open(keypath, 'wb') as f: | |
f.write(userkey) | |
return True | |
elif sys.platform.startswith('cygwin'): | |
def retrieve_key(keypath): | |
tkMessageBox.showerror( | |
"ADEPT Key", | |
"This script requires a Windows-native Python, and cannot be run " | |
"under Cygwin. Please install a Windows-native Python and/or " | |
"check your file associations.") | |
return False | |
else: | |
def retrieve_key(keypath): | |
tkMessageBox.showerror( | |
"ADEPT Key", | |
"This script only supports Windows and Mac OS X. For Linux " | |
"you should be able to run ADE and this script under Wine (with " | |
"an appropriate version of Windows Python installed).") | |
return False | |
class ExceptionDialog(Tkinter.Frame): | |
def __init__(self, root, text): | |
Tkinter.Frame.__init__(self, root, border=5) | |
label = Tkinter.Label(self, text="Unexpected error:", | |
anchor=Tkconstants.W, justify=Tkconstants.LEFT) | |
label.pack(fill=Tkconstants.X, expand=0) | |
self.text = Tkinter.Text(self) | |
self.text.pack(fill=Tkconstants.BOTH, expand=1) | |
self.text.insert(Tkconstants.END, text) | |
def main(argv=sys.argv): | |
root = Tkinter.Tk() | |
root.withdraw() | |
progname = os.path.basename(argv[0]) | |
keypath = 'adeptkey.der' | |
success = False | |
try: | |
success = retrieve_key(keypath) | |
except ADEPTError, e: | |
tkMessageBox.showerror("ADEPT Key", "Error: " + str(e)) | |
except Exception: | |
root.wm_state('normal') | |
root.title('ADEPT Key') | |
text = traceback.format_exc() | |
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1) | |
root.mainloop() | |
if not success: | |
return 1 | |
tkMessageBox.showinfo( | |
"ADEPT Key", "Key successfully retrieved to %s" % (keypath)) | |
return 0 | |
if __name__ == '__main__': | |
sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment