mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-06-24 07:52:46 +00:00
tools v2.2
This commit is contained in:
@@ -1,62 +0,0 @@
|
||||
Inept Epub DeDRM - ineptepub_vXX_plugin.zip
|
||||
Requires Calibre version 0.6.44 or higher.
|
||||
|
||||
All credit given to I <3 Cabbages for the original standalone scripts.
|
||||
I had the much easier job of converting them to a Calibre plugin.
|
||||
|
||||
This plugin is meant to decrypt Adobe Digital Edition Epubs that are protected
|
||||
with Adobe's Adept encryption. It is meant to function without having to install
|
||||
any dependencies... other than having Calibre installed, of course. It will still
|
||||
work if you have Python and PyCrypto already installed, but they aren't necessary.
|
||||
|
||||
Installation:
|
||||
|
||||
Go to Calibre's Preferences page... click on the Plugins button. Use the file
|
||||
dialog button to select the plugin's zip file (ineptepub_vXX_plugin.zip) and
|
||||
click the 'Add' button. you're done.
|
||||
|
||||
Configuration:
|
||||
|
||||
When first run, the plugin will attempt to find your Adobe Digital Editions installation
|
||||
(on Windows and Mac OS's). If successful, it will create an 'adeptkey.der' file and
|
||||
save it in Calibre's configuration directory. It will use that file on subsequent runs.
|
||||
If there are already '*.der' files in the directory, the plugin won't attempt to
|
||||
find the Adobe Digital Editions installation installation.
|
||||
|
||||
So if you have Adobe Digital Editions installation installed on the same machine as Calibre...
|
||||
you are ready to go. If not... keep reading.
|
||||
|
||||
If you already have keyfiles generated with I <3 Cabbages' ineptkey.pyw script,
|
||||
you can put those keyfiles in Calibre's configuration directory. The easiest
|
||||
way to find the correct directory is to go to Calibre's Preferences page... click
|
||||
on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre
|
||||
configuration directory' button. Paste your keyfiles in there. Just make sure that
|
||||
they have different names and are saved with the '.der' extension (like the ineptkey
|
||||
script produces). This directory isn't touched when upgrading Calibre, so it's quite
|
||||
safe to leave them there.
|
||||
|
||||
Since there is no Linux version of Adobe Digital Editions, Linux users will have to
|
||||
obtain a keyfile through other methods and put the file in Calibre's configuration directory.
|
||||
|
||||
All keyfiles with a '.der' extension found in Calibre's configuration directory will
|
||||
be used to attempt to decrypt a book.
|
||||
|
||||
** NOTE ** There is no plugin customization data for the Inept Epub DeDRM plugin.
|
||||
|
||||
Troubleshooting:
|
||||
|
||||
If you find that it's not working for you (imported epubs still have DRM), you can
|
||||
save a lot of time and trouble by trying to add the epub to Calibre with the command
|
||||
line tools. This will print out a lot of helpful debugging info that can be copied into
|
||||
any online help requests. I'm going to ask you to do it first, anyway, so you might
|
||||
as well get used to it. ;)
|
||||
|
||||
Open a command prompt (terminal) and change to the directory where the ebook you're
|
||||
trying to import resides. Then type the command "calibredb add your_ebook.epub".
|
||||
Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the
|
||||
filename of your book is. Copy the resulting output and paste it into any online
|
||||
help request you make.
|
||||
|
||||
** Note: the Mac version of Calibre doesn't install the command line tools by default.
|
||||
If you go to the 'Preferences' page and click on the miscellaneous button, you'll
|
||||
see the option to install the command line tools.
|
||||
@@ -19,24 +19,86 @@ class ADEPTError(Exception):
|
||||
if iswindows:
|
||||
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
|
||||
string_at, Structure, c_void_p, cast, c_size_t, memmove, CDLL, c_int, \
|
||||
c_long, c_ulong
|
||||
|
||||
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
||||
import _winreg as winreg
|
||||
|
||||
def _load_crypto_libcrypto():
|
||||
from ctypes.util import find_library
|
||||
libcrypto = find_library('libeay32')
|
||||
if libcrypto is None:
|
||||
raise ADEPTError('libcrypto not found')
|
||||
libcrypto = CDLL(libcrypto)
|
||||
AES_MAXNR = 14
|
||||
c_char_pp = POINTER(c_char_p)
|
||||
c_int_p = POINTER(c_int)
|
||||
class AES_KEY(Structure):
|
||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
||||
('rounds', c_int)]
|
||||
AES_KEY_p = POINTER(AES_KEY)
|
||||
|
||||
try:
|
||||
from Crypto.Cipher import AES as _aes
|
||||
except ImportError:
|
||||
_aes = None
|
||||
def F(restype, name, argtypes):
|
||||
func = getattr(libcrypto, name)
|
||||
func.restype = restype
|
||||
func.argtypes = argtypes
|
||||
return func
|
||||
|
||||
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 AES(object):
|
||||
def __init__(self, userkey):
|
||||
self._blocksize = len(userkey)
|
||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||
raise ADEPTError('AES improper key used')
|
||||
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
|
||||
|
||||
def _load_crypto_pycrypto():
|
||||
from Crypto.Cipher import AES as _AES
|
||||
class AES(object):
|
||||
def __init__(self, key):
|
||||
self._aes = _AES.new(key, _AES.MODE_CBC)
|
||||
def decrypt(self, data):
|
||||
return self._aes.decrypt(data)
|
||||
return AES
|
||||
|
||||
def _load_crypto():
|
||||
AES = None
|
||||
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
|
||||
try:
|
||||
AES = loader()
|
||||
break
|
||||
except (ImportError, ADEPTError):
|
||||
pass
|
||||
return AES
|
||||
|
||||
AES = _load_crypto()
|
||||
|
||||
|
||||
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]
|
||||
@@ -47,7 +109,7 @@ if iswindows:
|
||||
return buffer.value
|
||||
return GetSystemDirectory
|
||||
GetSystemDirectory = GetSystemDirectory()
|
||||
|
||||
|
||||
def GetVolumeSerialNumber():
|
||||
GetVolumeInformationW = kernel32.GetVolumeInformationW
|
||||
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
|
||||
@@ -61,7 +123,7 @@ if iswindows:
|
||||
return vsn.value
|
||||
return GetVolumeSerialNumber
|
||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||
|
||||
|
||||
def GetUserName():
|
||||
GetUserNameW = advapi32.GetUserNameW
|
||||
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
|
||||
@@ -75,11 +137,11 @@ if iswindows:
|
||||
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]
|
||||
@@ -89,9 +151,9 @@ if iswindows:
|
||||
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]
|
||||
@@ -100,22 +162,22 @@ if iswindows:
|
||||
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
|
||||
@@ -157,7 +219,7 @@ if iswindows:
|
||||
"\x5b" # pop %rbx
|
||||
"\xc3" # retq
|
||||
)
|
||||
|
||||
|
||||
def cpuid0():
|
||||
_cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS)
|
||||
buf = create_string_buffer(12)
|
||||
@@ -166,14 +228,14 @@ if iswindows:
|
||||
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,
|
||||
@@ -191,10 +253,14 @@ if iswindows:
|
||||
return string_at(outdata.pbData, outdata.cbData)
|
||||
return CryptUnprotectData
|
||||
CryptUnprotectData = CryptUnprotectData()
|
||||
|
||||
|
||||
def retrieve_key():
|
||||
if _aes is None:
|
||||
raise ADEPTError("Couldn\'t load PyCrypto")
|
||||
if AES is None:
|
||||
tkMessageBox.showerror(
|
||||
"ADEPT Key",
|
||||
"This script requires PyCrypto or OpenSSL which must be installed "
|
||||
"separately. Read the top-of-script comment for details.")
|
||||
return False
|
||||
root = GetSystemDirectory().split('\\')[0] + '\\'
|
||||
serial = GetVolumeSerialNumber(root)
|
||||
vendor = cpuid0()
|
||||
@@ -236,7 +302,8 @@ if iswindows:
|
||||
if userkey is None:
|
||||
raise ADEPTError('Could not locate privateLicenseKey')
|
||||
userkey = userkey.decode('base64')
|
||||
userkey = _aes.new(keykey, _aes.MODE_CBC).decrypt(userkey)
|
||||
aes = AES(keykey)
|
||||
userkey = aes.decrypt(userkey)
|
||||
userkey = userkey[26:-ord(userkey[-1])]
|
||||
return userkey
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#! /usr/bin/python
|
||||
|
||||
# ineptepub_v01_plugin.py
|
||||
# ineptepub_plugin.py
|
||||
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||
# later. <http://www.gnu.org/licenses/>
|
||||
#
|
||||
@@ -41,6 +41,8 @@
|
||||
#
|
||||
# Revision history:
|
||||
# 0.1 - Initial release
|
||||
# 0.1.1 - Allow Windows users to make use of openssl if they have it installed.
|
||||
# - Incorporated SomeUpdates zipfix routine.
|
||||
|
||||
|
||||
"""
|
||||
@@ -76,7 +78,10 @@ def _load_crypto_libcrypto():
|
||||
Structure, c_ulong, create_string_buffer, cast
|
||||
from ctypes.util import find_library
|
||||
|
||||
libcrypto = find_library('crypto')
|
||||
if sys.platform.startswith('win'):
|
||||
libcrypto = find_library('libeay32')
|
||||
else:
|
||||
libcrypto = find_library('crypto')
|
||||
if libcrypto is None:
|
||||
raise ADEPTError('libcrypto not found')
|
||||
libcrypto = CDLL(libcrypto)
|
||||
@@ -358,7 +363,7 @@ class IneptDeDRM(FileTypePlugin):
|
||||
Credit given to I <3 Cabbages for the original stand-alone scripts.'
|
||||
supported_platforms = ['linux', 'osx', 'windows']
|
||||
author = 'DiapDealer'
|
||||
version = (0, 1, 0)
|
||||
version = (0, 1, 1)
|
||||
minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions.
|
||||
file_types = set(['epub'])
|
||||
on_import = True
|
||||
@@ -376,7 +381,6 @@ class IneptDeDRM(FileTypePlugin):
|
||||
# Add the included Carbon import directory for Mac users.
|
||||
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||
ppath = os.path.join(self.sys_insertion_path, pdir)
|
||||
#sys.path.insert(0, ppath)
|
||||
sys.path.append(ppath)
|
||||
|
||||
AES, RSA = _load_crypto()
|
||||
@@ -433,10 +437,19 @@ class IneptDeDRM(FileTypePlugin):
|
||||
# Attempt to decrypt epub with each encryption key found.
|
||||
for userkey in userkeys:
|
||||
# Create a TemporaryPersistent file to work with.
|
||||
# Check original epub archive for zip errors.
|
||||
import zipfix
|
||||
inf = self.temporary_file('.epub')
|
||||
try:
|
||||
fr = zipfix.fixZip(path_to_ebook, inf.name)
|
||||
fr.fix()
|
||||
except Exception, e:
|
||||
raise Exception(e)
|
||||
return
|
||||
of = self.temporary_file('.epub')
|
||||
|
||||
# Give the user key, ebook and TemporaryPersistent file to the plugin_main function.
|
||||
result = plugin_main(userkey, path_to_ebook, of.name)
|
||||
result = plugin_main(userkey, inf.name, of.name)
|
||||
|
||||
# Ebook is not an Adobe Adept epub... do nothing and pass it on.
|
||||
# This allows a non-encrypted epub to be imported without error messages.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import zlib
|
||||
import zipfile
|
||||
import os
|
||||
import os.path
|
||||
import getopt
|
||||
from struct import unpack
|
||||
|
||||
|
||||
_FILENAME_LEN_OFFSET = 26
|
||||
_EXTRA_LEN_OFFSET = 28
|
||||
_FILENAME_OFFSET = 30
|
||||
_MAX_SIZE = 64 * 1024
|
||||
|
||||
class fixZip:
|
||||
def __init__(self, zinput, zoutput):
|
||||
self.inzip = zipfile.ZipFile(zinput,'r')
|
||||
self.outzip = zipfile.ZipFile(zoutput,'w')
|
||||
# open the input zip for reading only as a raw file
|
||||
self.bzf = file(zinput,'rb')
|
||||
|
||||
def getlocalname(self, zi):
|
||||
local_header_offset = zi.header_offset
|
||||
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
||||
leninfo = self.bzf.read(2)
|
||||
local_name_length, = unpack('<H', leninfo)
|
||||
self.bzf.seek(local_header_offset + _FILENAME_OFFSET)
|
||||
local_name = self.bzf.read(local_name_length)
|
||||
return local_name
|
||||
|
||||
def uncompress(self, cmpdata):
|
||||
dc = zlib.decompressobj(-15)
|
||||
data = ''
|
||||
while len(cmpdata) > 0:
|
||||
if len(cmpdata) > _MAX_SIZE :
|
||||
newdata = cmpdata[0:_MAX_SIZE]
|
||||
cmpdata = cmpdata[_MAX_SIZE:]
|
||||
else:
|
||||
newdata = cmpdata
|
||||
cmpdata = ''
|
||||
newdata = dc.decompress(newdata)
|
||||
unprocessed = dc.unconsumed_tail
|
||||
if len(unprocessed) == 0:
|
||||
newdata += dc.flush()
|
||||
data += newdata
|
||||
cmpdata += unprocessed
|
||||
unprocessed = ''
|
||||
return data
|
||||
|
||||
def getfiledata(self, zi):
|
||||
# get file name length and exta data length to find start of file data
|
||||
local_header_offset = zi.header_offset
|
||||
|
||||
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
||||
leninfo = self.bzf.read(2)
|
||||
local_name_length, = unpack('<H', leninfo)
|
||||
|
||||
self.bzf.seek(local_header_offset + _EXTRA_LEN_OFFSET)
|
||||
exinfo = self.bzf.read(2)
|
||||
extra_field_length, = unpack('<H', exinfo)
|
||||
|
||||
self.bzf.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
|
||||
data = None
|
||||
|
||||
# if not compressed we are good to go
|
||||
if zi.compress_type == zipfile.ZIP_STORED:
|
||||
data = self.bzf.read(zi.file_size)
|
||||
|
||||
# if compressed we must decompress it using zlib
|
||||
if zi.compress_type == zipfile.ZIP_DEFLATED:
|
||||
cmpdata = self.bzf.read(zi.compress_size)
|
||||
data = self.uncompress(cmpdata)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
||||
def fix(self):
|
||||
# get the zipinfo for each member of the input archive
|
||||
# and copy member over to output archive
|
||||
# if problems exist with local vs central filename, fix them
|
||||
|
||||
for i, zinfo in enumerate(self.inzip.infolist()):
|
||||
data = None
|
||||
nzinfo = zinfo
|
||||
|
||||
try:
|
||||
data = self.inzip.read(zinfo)
|
||||
except zipfile.BadZipfile or zipfile.error:
|
||||
local_name = self.getlocalname(zinfo)
|
||||
data = self.getfiledata(zinfo)
|
||||
nzinfo.filename = local_name
|
||||
|
||||
nzinfo.date_time = zinfo.date_time
|
||||
nzinfo.compress_type = zinfo.compress_type
|
||||
nzinfo.flag_bits = 0
|
||||
nzinfo.internal_attr = 0
|
||||
self.outzip.writestr(nzinfo,data)
|
||||
|
||||
self.bzf.close()
|
||||
self.inzip.close()
|
||||
self.outzip.close()
|
||||
|
||||
|
||||
def usage():
|
||||
print """usage: zipfix.py inputzip outputzip
|
||||
inputzip is the source zipfile to fix
|
||||
outputzip is the fixed zip archive
|
||||
"""
|
||||
|
||||
|
||||
def main(argv=sys.argv):
|
||||
if len(argv)!=3:
|
||||
usage()
|
||||
return 1
|
||||
infile = None
|
||||
outfile = None
|
||||
infile = argv[1]
|
||||
outfile = argv[2]
|
||||
if not os.path.exists(infile):
|
||||
print "Error: Input Zip File does not exist"
|
||||
return 1
|
||||
try:
|
||||
fr = fixZip(infile, outfile)
|
||||
fr.fix()
|
||||
return 0
|
||||
except Exception, e:
|
||||
print "Error Occurred ", e
|
||||
return 2
|
||||
|
||||
if __name__ == '__main__' :
|
||||
sys.exit(main())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user