Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38eabe7612 | ||
|
|
9162698f89 | ||
|
|
506d97d5f0 | ||
|
|
a76ba56cd8 | ||
|
|
8e73edc012 | ||
|
|
c386ac6e6d | ||
|
|
5f0671db7f |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.pyc
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|||||||
@@ -2,17 +2,18 @@ From Apprentice Alf's Blog
|
|||||||
|
|
||||||
Adobe Adept ePub and PDF, .epub, .pdf
|
Adobe Adept ePub and PDF, .epub, .pdf
|
||||||
|
|
||||||
The wonderful I♥CABBAGES has produced scripts that will remove the DRM from ePubs and PDFs encryped with Adobe’s DRM. Installing these scripts is a little more complex that the Mobipocket and eReader decryption tools, as they require installation of the PyCrypto package for Windows Boxes. For Mac OS X and Linux boxes, these scripts use the already installed OpenSSL libcrypto so there is no additional requirements for these platforms.
|
This directory includes modified versions of the I♥CABBAGES Adobe Adept inept scripts for epubs. These scripts have been modified to work with OpenSSL on Windows as well as Linux and Mac OS X. His original scripts can be found in the clearly labelled folder. If a Windows User has OpenSSL installed, these scripts will make use of it in place of PyCrypto.
|
||||||
|
|
||||||
|
The wonderful I♥CABBAGES has produced scripts that will remove the DRM from ePubs and PDFs encryped with Adobe’s DRM. These scripts require installation of the PyCrypto python package *or* the OpenSSL library on Windows. For Mac OS X and Linux boxes, these scripts use the already installed OpenSSL libcrypto so there is no additional requirements for these platforms.
|
||||||
|
|
||||||
For more info, see the author's blog:
|
For more info, see the author's blog:
|
||||||
http://i-u2665-cabbages.blogspot.com/2009_02_01_archive.html
|
http://i-u2665-cabbages.blogspot.com/2009_02_01_archive.html
|
||||||
|
|
||||||
There are two scripts:
|
There are two scripts:
|
||||||
|
|
||||||
The first is called ineptkey_v5.pyw. Simply double-click to launch it and it will create a key file that is needed later to actually remove the DRM. This script need only be run once unless you change your ADE account information.
|
The first is called ineptkey_v5.1.pyw. Simply double-click to launch it and it will create a key file that is needed later to actually remove the DRM. This script need only be run once unless you change your ADE account information.
|
||||||
|
|
||||||
The second is called in ineptepub_v5.pyw. Simply double-click to launch it. It will ask for your previously generated key file and the path to the book you want to remove the DRM from.
|
|
||||||
|
|
||||||
|
The second is called in ineptepub_v5.3.pyw. Simply double-click to launch it. It will ask for your previously generated key file and the path to the book you want to remove the DRM from.
|
||||||
|
|
||||||
Both of these scripts are gui python programs. Python 2.X (32 bit) is already installed in Mac OSX. We recommend ActiveState's Active Python Version 2.X (32 bit) for Windows users.
|
Both of these scripts are gui python programs. Python 2.X (32 bit) is already installed in Mac OSX. We recommend ActiveState's Active Python Version 2.X (32 bit) for Windows users.
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
# Improve OS X support by using OpenSSL when available
|
# Improve OS X support by using OpenSSL when available
|
||||||
# 5.1 - Improve OpenSSL error checking
|
# 5.1 - Improve OpenSSL error checking
|
||||||
# 5.2 - Fix ctypes error causing segfaults on some systems
|
# 5.2 - Fix ctypes error causing segfaults on some systems
|
||||||
|
# 5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o
|
||||||
"""
|
"""
|
||||||
Decrypt Adobe ADEPT-encrypted EPUB books.
|
Decrypt Adobe ADEPT-encrypted EPUB books.
|
||||||
"""
|
"""
|
||||||
@@ -53,7 +53,11 @@ def _load_crypto_libcrypto():
|
|||||||
Structure, c_ulong, create_string_buffer, cast
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
from ctypes.util import find_library
|
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:
|
if libcrypto is None:
|
||||||
raise ADEPTError('libcrypto not found')
|
raise ADEPTError('libcrypto not found')
|
||||||
libcrypto = CDLL(libcrypto)
|
libcrypto = CDLL(libcrypto)
|
||||||
@@ -116,6 +120,9 @@ def _load_crypto_libcrypto():
|
|||||||
class AES(object):
|
class AES(object):
|
||||||
def __init__(self, userkey):
|
def __init__(self, userkey):
|
||||||
self._blocksize = len(userkey)
|
self._blocksize = len(userkey)
|
||||||
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
|
raise ADEPTError('AES improper key used')
|
||||||
|
return
|
||||||
key = self._key = AES_KEY()
|
key = self._key = AES_KEY()
|
||||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
||||||
if rv < 0:
|
if rv < 0:
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
# 4.4 - Make it working on 64-bit Python
|
# 4.4 - Make it working on 64-bit Python
|
||||||
# 5 - Clean up and improve 4.x changes;
|
# 5 - Clean up and improve 4.x changes;
|
||||||
# Clean up and merge OS X support by unknown
|
# Clean up and merge OS X support by unknown
|
||||||
|
# 5.1 - add support for using OpenSSL on Windows in place of PyCrypto
|
||||||
|
# 5.2 - added support for output of key to a particular file
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Retrieve Adobe ADEPT user key.
|
Retrieve Adobe ADEPT user key.
|
||||||
@@ -53,14 +55,76 @@ class ADEPTError(Exception):
|
|||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
||||||
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
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
|
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
||||||
import _winreg as winreg
|
import _winreg as winreg
|
||||||
|
|
||||||
try:
|
def _load_crypto_libcrypto():
|
||||||
from Crypto.Cipher import AES
|
from ctypes.util import find_library
|
||||||
except ImportError:
|
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)
|
||||||
|
|
||||||
|
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
|
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'
|
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
||||||
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
||||||
@@ -230,7 +294,7 @@ if sys.platform.startswith('win'):
|
|||||||
if AES is None:
|
if AES is None:
|
||||||
tkMessageBox.showerror(
|
tkMessageBox.showerror(
|
||||||
"ADEPT Key",
|
"ADEPT Key",
|
||||||
"This script requires PyCrypto, which must be installed "
|
"This script requires PyCrypto or OpenSSL which must be installed "
|
||||||
"separately. Read the top-of-script comment for details.")
|
"separately. Read the top-of-script comment for details.")
|
||||||
return False
|
return False
|
||||||
root = GetSystemDirectory().split('\\')[0] + '\\'
|
root = GetSystemDirectory().split('\\')[0] + '\\'
|
||||||
@@ -274,7 +338,8 @@ if sys.platform.startswith('win'):
|
|||||||
if userkey is None:
|
if userkey is None:
|
||||||
raise ADEPTError('Could not locate privateLicenseKey')
|
raise ADEPTError('Could not locate privateLicenseKey')
|
||||||
userkey = userkey.decode('base64')
|
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])]
|
userkey = userkey[26:-ord(userkey[-1])]
|
||||||
with open(keypath, 'wb') as f:
|
with open(keypath, 'wb') as f:
|
||||||
f.write(userkey)
|
f.write(userkey)
|
||||||
@@ -351,6 +416,20 @@ class ExceptionDialog(Tkinter.Frame):
|
|||||||
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||||
self.text.insert(Tkconstants.END, text)
|
self.text.insert(Tkconstants.END, text)
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
keypath = argv[1]
|
||||||
|
try:
|
||||||
|
success = retrieve_key(keypath)
|
||||||
|
except ADEPTError, e:
|
||||||
|
print "Key generation Error: " + str(e)
|
||||||
|
return 1
|
||||||
|
except Exception, e:
|
||||||
|
print "General Error: " + str(e)
|
||||||
|
return 1
|
||||||
|
if not success:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
def main(argv=sys.argv):
|
||||||
root = Tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
@@ -374,4 +453,6 @@ def main(argv=sys.argv):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
sys.exit(cli_main())
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
# 1 - Initial release
|
# 1 - Initial release
|
||||||
# 2 - Added OS X support by using OpenSSL when available
|
# 2 - Added OS X support by using OpenSSL when available
|
||||||
# 3 - screen out improper key lengths to prevent segfaults on Linux
|
# 3 - screen out improper key lengths to prevent segfaults on Linux
|
||||||
|
# 3.1 - Allow Windows versions of libcrypto to be found
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
@@ -36,7 +37,10 @@ def _load_crypto_libcrypto():
|
|||||||
Structure, c_ulong, create_string_buffer, cast
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
from ctypes.util import find_library
|
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:
|
if libcrypto is None:
|
||||||
raise IGNOBLEError('libcrypto not found')
|
raise IGNOBLEError('libcrypto not found')
|
||||||
libcrypto = CDLL(libcrypto)
|
libcrypto = CDLL(libcrypto)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
# Revision history:
|
# Revision history:
|
||||||
# 1 - Initial release
|
# 1 - Initial release
|
||||||
# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
|
# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
|
||||||
|
# 2.1 - Allow Windows versions of libcrypto to be found
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||||
@@ -40,7 +41,10 @@ def _load_crypto_libcrypto():
|
|||||||
Structure, c_ulong, create_string_buffer, cast
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
from ctypes.util import find_library
|
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:
|
if libcrypto is None:
|
||||||
print 'libcrypto not found'
|
print 'libcrypto not found'
|
||||||
raise IGNOBLEError('libcrypto not found')
|
raise IGNOBLEError('libcrypto not found')
|
||||||
|
|||||||
Binary file not shown.
@@ -1,682 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# This is a WINDOWS python script. You need a Python interpreter to run it.
|
|
||||||
# For example, ActiveState Python, which exists for windows.
|
|
||||||
#
|
|
||||||
# It can run standalone to convert K4PC files, or it can be installed as a
|
|
||||||
# plugin for Calibre (http://calibre-ebook.com/about) so that importing
|
|
||||||
# K4PC files with DRM is no londer a multi-step process.
|
|
||||||
#
|
|
||||||
# ***NOTE*** Calibre and K4PC must be installed on the same windows machine
|
|
||||||
# for the plugin version to function properly.
|
|
||||||
#
|
|
||||||
# To create a Calibre plugin, rename this file so that the filename
|
|
||||||
# ends in '_plugin.py', put it into a ZIP file and import that ZIP into Calibre
|
|
||||||
# using its plugin configuration GUI.
|
|
||||||
#
|
|
||||||
# Thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump from
|
|
||||||
# which this script steals most unashamedly.
|
|
||||||
#
|
|
||||||
# Changelog
|
|
||||||
# 0.01 - Initial version - Utilizes skindle and CMBDTC method of obtaining
|
|
||||||
# book specific pids from K4PC books. If Calibre and K4PC are installed
|
|
||||||
# on the same windows machine, Calibre plugin functionality is once
|
|
||||||
# again restored.
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
Comprehensive Mazama Book DRM with Topaz Cryptography V2.0
|
|
||||||
|
|
||||||
-----BEGIN PUBLIC KEY-----
|
|
||||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
|
|
||||||
M8hYfnNEI0yQmn5Ti+W8biT7EatpauE/5jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
|
|
||||||
B62UCxeu/fpfnvNHDN/wPWWH4jynZ2M6cdcnE5LQ+FfeKqZn7gnG2No1U9h7oOHx
|
|
||||||
y2/pHuYme7U1TsgSjwIDAQAB
|
|
||||||
-----END PUBLIC KEY-----
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
import csv
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import getopt
|
|
||||||
import zlib
|
|
||||||
import binascii
|
|
||||||
from struct import pack
|
|
||||||
from struct import unpack
|
|
||||||
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
|
|
||||||
import _winreg as winreg
|
|
||||||
import traceback
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
__version__ = '0.01'
|
|
||||||
|
|
||||||
global kindleDatabase
|
|
||||||
MAX_PATH = 255
|
|
||||||
kernel32 = windll.kernel32
|
|
||||||
advapi32 = windll.advapi32
|
|
||||||
crypt32 = windll.crypt32
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
|
||||||
#
|
|
||||||
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
|
||||||
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
|
||||||
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
||||||
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Exceptions for all the problems that might happen during the script
|
|
||||||
#
|
|
||||||
class DrmException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DataBlob(Structure):
|
|
||||||
_fields_ = [('cbData', c_uint),
|
|
||||||
('pbData', c_void_p)]
|
|
||||||
DataBlob_p = POINTER(DataBlob)
|
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
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 DrmException("Failed to Unprotect Data")
|
|
||||||
return string_at(outdata.pbData, outdata.cbData)
|
|
||||||
return CryptUnprotectData
|
|
||||||
CryptUnprotectData = CryptUnprotectData()
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the MD5 digest of "message"
|
|
||||||
#
|
|
||||||
def MD5(message):
|
|
||||||
ctx = hashlib.md5()
|
|
||||||
ctx.update(message)
|
|
||||||
return ctx.digest()
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the MD5 digest of "message"
|
|
||||||
#
|
|
||||||
def SHA1(message):
|
|
||||||
ctx = hashlib.sha1()
|
|
||||||
ctx.update(message)
|
|
||||||
return ctx.digest()
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Locate and open the Kindle.info file.
|
|
||||||
#
|
|
||||||
def openKindleInfo():
|
|
||||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
|
||||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
|
||||||
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Parse the Kindle.info file and return the records as a list of key-values
|
|
||||||
#
|
|
||||||
def parseKindleInfo():
|
|
||||||
DB = {}
|
|
||||||
infoReader = openKindleInfo()
|
|
||||||
infoReader.read(1)
|
|
||||||
data = infoReader.read()
|
|
||||||
items = data.split('{')
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
splito = item.split(':')
|
|
||||||
DB[splito[0]] =splito[1]
|
|
||||||
return DB
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
|
|
||||||
#
|
|
||||||
def findNameForHash(hash):
|
|
||||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
|
||||||
result = ""
|
|
||||||
for name in names:
|
|
||||||
if hash == encodeHash(name, charMap2):
|
|
||||||
result = name
|
|
||||||
break
|
|
||||||
return name
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Print all the records from the kindle.info file.
|
|
||||||
#
|
|
||||||
def printKindleInfo():
|
|
||||||
for record in kindleDatabase:
|
|
||||||
name = findNameForHash(record)
|
|
||||||
if name != "" :
|
|
||||||
print (name)
|
|
||||||
print ("--------------------------\n")
|
|
||||||
else :
|
|
||||||
print ("Unknown Record")
|
|
||||||
print getKindleInfoValueForHash(record)
|
|
||||||
print "\n"
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
|
|
||||||
#
|
|
||||||
def getKindleInfoValueForHash(hashedKey):
|
|
||||||
global kindleDatabase
|
|
||||||
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
|
|
||||||
return CryptUnprotectData(encryptedValue,"")
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
|
|
||||||
#
|
|
||||||
def getKindleInfoValueForKey(key):
|
|
||||||
return getKindleInfoValueForHash(encodeHash(key,charMap2))
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# 8 bits to six bits encoding from hash to generate PID string
|
|
||||||
#
|
|
||||||
def encodePID(hash):
|
|
||||||
global charMap3
|
|
||||||
PID = ""
|
|
||||||
for position in range (0,8):
|
|
||||||
PID += charMap3[getSixBitsFromBitField(hash,position)]
|
|
||||||
return PID
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Hash the bytes in data and then encode the digest with the characters in map
|
|
||||||
#
|
|
||||||
def encodeHash(data,map):
|
|
||||||
return encode(MD5(data),map)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Encode the bytes in data with the characters in map
|
|
||||||
#
|
|
||||||
def encode(data, map):
|
|
||||||
result = ""
|
|
||||||
for char in data:
|
|
||||||
value = ord(char)
|
|
||||||
Q = (value ^ 0x80) // len(map)
|
|
||||||
R = value % len(map)
|
|
||||||
result += map[Q]
|
|
||||||
result += map[R]
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Decode the string in data with the characters in map. Returns the decoded bytes
|
|
||||||
#
|
|
||||||
def decode(data,map):
|
|
||||||
result = ""
|
|
||||||
for i in range (0,len(data),2):
|
|
||||||
high = map.find(data[i])
|
|
||||||
low = map.find(data[i+1])
|
|
||||||
value = (((high * 0x40) ^ 0x80) & 0xFF) + low
|
|
||||||
result += pack("B",value)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Encryption table used to generate the device PID
|
|
||||||
#
|
|
||||||
def generatePidEncryptionTable() :
|
|
||||||
table = []
|
|
||||||
for counter1 in range (0,0x100):
|
|
||||||
value = counter1
|
|
||||||
for counter2 in range (0,8):
|
|
||||||
if (value & 1 == 0) :
|
|
||||||
value = value >> 1
|
|
||||||
else :
|
|
||||||
value = value >> 1
|
|
||||||
value = value ^ 0xEDB88320
|
|
||||||
table.append(value)
|
|
||||||
return table
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Seed value used to generate the device PID
|
|
||||||
#
|
|
||||||
def generatePidSeed(table,dsn) :
|
|
||||||
value = 0
|
|
||||||
for counter in range (0,4) :
|
|
||||||
index = (ord(dsn[counter]) ^ value) &0xFF
|
|
||||||
value = (value >> 8) ^ table[index]
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Generate the device PID
|
|
||||||
#
|
|
||||||
def generateDevicePID(table,dsn,nbRoll):
|
|
||||||
seed = generatePidSeed(table,dsn)
|
|
||||||
pidAscii = ""
|
|
||||||
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
|
|
||||||
index = 0
|
|
||||||
|
|
||||||
for counter in range (0,nbRoll):
|
|
||||||
pid[index] = pid[index] ^ ord(dsn[counter])
|
|
||||||
index = (index+1) %8
|
|
||||||
|
|
||||||
for counter in range (0,8):
|
|
||||||
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
|
||||||
pidAscii += charMap4[index]
|
|
||||||
return pidAscii
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Returns two bit at offset from a bit field
|
|
||||||
#
|
|
||||||
def getTwoBitsFromBitField(bitField,offset):
|
|
||||||
byteNumber = offset // 4
|
|
||||||
bitPosition = 6 - 2*(offset % 4)
|
|
||||||
|
|
||||||
return ord(bitField[byteNumber]) >> bitPosition & 3
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the six bits at offset from a bit field
|
|
||||||
#
|
|
||||||
def getSixBitsFromBitField(bitField,offset):
|
|
||||||
offset *= 3
|
|
||||||
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# MobiDeDrm-0.16 Stuff
|
|
||||||
#
|
|
||||||
class Unbuffered:
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
def write(self, data):
|
|
||||||
self.stream.write(data)
|
|
||||||
self.stream.flush()
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.stream, attr)
|
|
||||||
|
|
||||||
|
|
||||||
# Implementation of Pukall Cipher 1
|
|
||||||
def PC1(key, src, decryption=True):
|
|
||||||
sum1 = 0;
|
|
||||||
sum2 = 0;
|
|
||||||
keyXorVal = 0;
|
|
||||||
if len(key)!=16:
|
|
||||||
print "Bad key length!"
|
|
||||||
return None
|
|
||||||
wkey = []
|
|
||||||
for i in xrange(8):
|
|
||||||
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
|
||||||
|
|
||||||
dst = ""
|
|
||||||
for i in xrange(len(src)):
|
|
||||||
temp1 = 0;
|
|
||||||
byteXorVal = 0;
|
|
||||||
for j in xrange(8):
|
|
||||||
temp1 ^= wkey[j]
|
|
||||||
sum2 = (sum2+j)*20021 + sum1
|
|
||||||
sum1 = (temp1*346)&0xFFFF
|
|
||||||
sum2 = (sum2+sum1)&0xFFFF
|
|
||||||
temp1 = (temp1*20021+1)&0xFFFF
|
|
||||||
byteXorVal ^= temp1 ^ sum2
|
|
||||||
curByte = ord(src[i])
|
|
||||||
if not decryption:
|
|
||||||
keyXorVal = curByte * 257;
|
|
||||||
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
|
||||||
if decryption:
|
|
||||||
keyXorVal = curByte * 257;
|
|
||||||
for j in xrange(8):
|
|
||||||
wkey[j] ^= keyXorVal;
|
|
||||||
dst+=chr(curByte)
|
|
||||||
return dst
|
|
||||||
|
|
||||||
|
|
||||||
def getSizeOfTrailingDataEntries(ptr, size, flags):
|
|
||||||
def getSizeOfTrailingDataEntry(ptr, size):
|
|
||||||
bitpos, result = 0, 0
|
|
||||||
if size <= 0:
|
|
||||||
return result
|
|
||||||
while True:
|
|
||||||
v = ord(ptr[size-1])
|
|
||||||
result |= (v & 0x7F) << bitpos
|
|
||||||
bitpos += 7
|
|
||||||
size -= 1
|
|
||||||
if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
|
|
||||||
return result
|
|
||||||
num = 0
|
|
||||||
testflags = flags >> 1
|
|
||||||
while testflags:
|
|
||||||
if testflags & 1:
|
|
||||||
num += getSizeOfTrailingDataEntry(ptr, size - num)
|
|
||||||
testflags >>= 1
|
|
||||||
# Multibyte data, if present, is included in the encryption, so
|
|
||||||
# we do not need to check the low bit.
|
|
||||||
# if flags & 1:
|
|
||||||
# num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
|
||||||
return num
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# This class does all the heavy lifting.
|
|
||||||
#
|
|
||||||
class DrmStripper:
|
|
||||||
def loadSection(self, section):
|
|
||||||
if (section + 1 == self.num_sections):
|
|
||||||
endoff = len(self.data_file)
|
|
||||||
else:
|
|
||||||
endoff = self.sections[section + 1][0]
|
|
||||||
off = self.sections[section][0]
|
|
||||||
return self.data_file[off:endoff]
|
|
||||||
|
|
||||||
def patch(self, off, new):
|
|
||||||
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
|
|
||||||
|
|
||||||
def patchSection(self, section, new, in_off = 0):
|
|
||||||
if (section + 1 == self.num_sections):
|
|
||||||
endoff = len(self.data_file)
|
|
||||||
else:
|
|
||||||
endoff = self.sections[section + 1][0]
|
|
||||||
off = self.sections[section][0]
|
|
||||||
assert off + in_off + len(new) <= endoff
|
|
||||||
self.patch(off + in_off, new)
|
|
||||||
|
|
||||||
def parseDRM(self, data, count, pid):
|
|
||||||
pid = pid.ljust(16,'\0')
|
|
||||||
keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
|
|
||||||
temp_key = PC1(keyvec1, pid, False)
|
|
||||||
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
|
||||||
found_key = None
|
|
||||||
for i in xrange(count):
|
|
||||||
verification, size, type, cksum, cookie = unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
|
||||||
cookie = PC1(temp_key, cookie)
|
|
||||||
ver,flags,finalkey,expiry,expiry2 = unpack('>LL16sLL', cookie)
|
|
||||||
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
|
|
||||||
found_key = finalkey
|
|
||||||
break
|
|
||||||
if not found_key:
|
|
||||||
# Then try the default encoding that doesn't require a PID
|
|
||||||
temp_key = keyvec1
|
|
||||||
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
|
||||||
for i in xrange(count):
|
|
||||||
verification, size, type, cksum, cookie = unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
|
||||||
cookie = PC1(temp_key, cookie)
|
|
||||||
ver,flags,finalkey,expiry,expiry2 = unpack('>LL16sLL', cookie)
|
|
||||||
if verification == ver and cksum == temp_key_sum:
|
|
||||||
found_key = finalkey
|
|
||||||
break
|
|
||||||
return found_key
|
|
||||||
|
|
||||||
def __init__(self, data_file):
|
|
||||||
self.data_file = data_file
|
|
||||||
header = data_file[0:72]
|
|
||||||
if header[0x3C:0x3C+8] != 'BOOKMOBI':
|
|
||||||
raise DrmException("invalid file format")
|
|
||||||
self.num_sections, = unpack('>H', data_file[76:78])
|
|
||||||
|
|
||||||
self.sections = []
|
|
||||||
for i in xrange(self.num_sections):
|
|
||||||
offset, a1,a2,a3,a4 = unpack('>LBBBB', data_file[78+i*8:78+i*8+8])
|
|
||||||
flags, val = a1, a2<<16|a3<<8|a4
|
|
||||||
self.sections.append( (offset, flags, val) )
|
|
||||||
|
|
||||||
sect = self.loadSection(0)
|
|
||||||
records, = unpack('>H', sect[0x8:0x8+2])
|
|
||||||
mobi_length, = unpack('>L',sect[0x14:0x18])
|
|
||||||
mobi_version, = unpack('>L',sect[0x68:0x6C])
|
|
||||||
extra_data_flags = 0
|
|
||||||
print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length)
|
|
||||||
if (mobi_length >= 0xE4) and (mobi_version >= 5):
|
|
||||||
extra_data_flags, = unpack('>H', sect[0xF2:0xF4])
|
|
||||||
print "Extra Data Flags = %d" %extra_data_flags
|
|
||||||
|
|
||||||
crypto_type, = unpack('>H', sect[0xC:0xC+2])
|
|
||||||
if crypto_type == 0:
|
|
||||||
print "This book is not encrypted."
|
|
||||||
else:
|
|
||||||
if crypto_type == 1:
|
|
||||||
raise DrmException("cannot decode Mobipocket encryption type 1")
|
|
||||||
if crypto_type != 2:
|
|
||||||
raise DrmException("unknown encryption type: %d" % crypto_type)
|
|
||||||
|
|
||||||
# determine the EXTH Offset.
|
|
||||||
exth_off = unpack('>I', sect[20:24])[0] + 16 + self.sections[0][0]
|
|
||||||
# Grab the entire EXTH block and feed it to the getK4PCPids function.
|
|
||||||
exth = data_file[exth_off:self.sections[0+1][0]]
|
|
||||||
pid = getK4PCPids(exth)
|
|
||||||
|
|
||||||
# calculate the keys
|
|
||||||
drm_ptr, drm_count, drm_size, drm_flags = unpack('>LLLL', sect[0xA8:0xA8+16])
|
|
||||||
if drm_count == 0:
|
|
||||||
raise DrmException("no PIDs found in this file")
|
|
||||||
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
|
|
||||||
if not found_key:
|
|
||||||
raise DrmException("no key found. maybe the PID is incorrect")
|
|
||||||
|
|
||||||
# kill the drm keys
|
|
||||||
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
|
||||||
# kill the drm pointers
|
|
||||||
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
|
|
||||||
# clear the crypto type
|
|
||||||
self.patchSection(0, "\0" * 2, 0xC)
|
|
||||||
|
|
||||||
# decrypt sections
|
|
||||||
print "\nDecrypting. Please wait . . .",
|
|
||||||
new_data = self.data_file[:self.sections[1][0]]
|
|
||||||
for i in xrange(1, records+1):
|
|
||||||
data = self.loadSection(i)
|
|
||||||
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
|
|
||||||
if i%100 == 0:
|
|
||||||
print ".",
|
|
||||||
# print "record %d, extra_size %d" %(i,extra_size)
|
|
||||||
new_data += PC1(found_key, data[0:len(data) - extra_size])
|
|
||||||
if extra_size > 0:
|
|
||||||
new_data += data[-extra_size:]
|
|
||||||
#self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
|
|
||||||
if self.num_sections > records+1:
|
|
||||||
new_data += self.data_file[self.sections[records+1][0]:]
|
|
||||||
self.data_file = new_data
|
|
||||||
print "done!"
|
|
||||||
print "\nPlease only use your new-found powers for good."
|
|
||||||
|
|
||||||
def getResult(self):
|
|
||||||
return self.data_file
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
|
|
||||||
# file to calculate the book pid.
|
|
||||||
#
|
|
||||||
def getK4PCPids(exth):
|
|
||||||
global kindleDatabase
|
|
||||||
try:
|
|
||||||
kindleDatabase = parseKindleInfo()
|
|
||||||
except Exception as message:
|
|
||||||
print(message)
|
|
||||||
|
|
||||||
if kindleDatabase != None :
|
|
||||||
|
|
||||||
# Get the Mazama Random number
|
|
||||||
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
|
|
||||||
|
|
||||||
# Get the HDD serial
|
|
||||||
encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1)
|
|
||||||
|
|
||||||
# Get the current user name
|
|
||||||
encodedUsername = encodeHash(GetUserName(),charMap1)
|
|
||||||
|
|
||||||
# concat, hash and encode to calculate the DSN
|
|
||||||
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
|
|
||||||
|
|
||||||
print("\nDSN: " + DSN)
|
|
||||||
|
|
||||||
|
|
||||||
# Compute the device PID (for which I can tell, is used for nothing).
|
|
||||||
# But hey, stuff being printed out is apparently cool.
|
|
||||||
table = generatePidEncryptionTable()
|
|
||||||
devicePID = generateDevicePID(table,DSN,4)
|
|
||||||
|
|
||||||
print("Device PID: " + devicePID)
|
|
||||||
|
|
||||||
# Compute book PID
|
|
||||||
exth_records = {}
|
|
||||||
nitems, = unpack('>I', exth[8:12])
|
|
||||||
pos = 12
|
|
||||||
# Parse the EXTH records, storing data indexed by type
|
|
||||||
for i in xrange(nitems):
|
|
||||||
type, size = unpack('>II', exth[pos: pos + 8])
|
|
||||||
content = exth[pos + 8: pos + size]
|
|
||||||
|
|
||||||
exth_records[type] = content
|
|
||||||
pos += size
|
|
||||||
|
|
||||||
# Grab the contents of the type 209 exth record
|
|
||||||
if exth_records[209] != None:
|
|
||||||
data = exth_records[209]
|
|
||||||
else:
|
|
||||||
raise DrmException("\nNo EXTH record type 209 - Perhaps not a K4PC file?")
|
|
||||||
|
|
||||||
# Parse the 209 data to find the the exth record with the token data.
|
|
||||||
# The last character of the 209 data points to the record with the token.
|
|
||||||
# Always 208 from my experience, but I'll leave the logic in case that changes.
|
|
||||||
for i in xrange(len(data)):
|
|
||||||
if ord(data[i]) != 0:
|
|
||||||
if exth_records[ord(data[i])] != None:
|
|
||||||
token = exth_records[ord(data[i])]
|
|
||||||
|
|
||||||
# Get the kindle account token
|
|
||||||
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
|
|
||||||
|
|
||||||
print("Account Token: " + kindleAccountToken)
|
|
||||||
|
|
||||||
pidHash = SHA1(DSN+kindleAccountToken+exth_records[209]+token)
|
|
||||||
|
|
||||||
bookPID = encodePID(pidHash)
|
|
||||||
|
|
||||||
if exth_records[503] != None:
|
|
||||||
print "Pid for " + exth_records[503] + ": " + bookPID
|
|
||||||
else:
|
|
||||||
print ("Book PID: " + bookPID )
|
|
||||||
|
|
||||||
return bookPID
|
|
||||||
|
|
||||||
raise DrmException("\nCould not access K4PC data - Perhaps K4PC is not installed/configured?")
|
|
||||||
return null
|
|
||||||
|
|
||||||
if not __name__ == "__main__":
|
|
||||||
from calibre.customize import FileTypePlugin
|
|
||||||
|
|
||||||
class K4PCDeDRM(FileTypePlugin):
|
|
||||||
name = 'K4PCDeDRM' # Name of the plugin
|
|
||||||
description = 'Removes DRM from K4PC files'
|
|
||||||
supported_platforms = ['windows'] # Platforms this plugin will run on
|
|
||||||
author = 'DiapDealer' # The author of this plugin
|
|
||||||
version = (0, 0, 1) # The version number of this plugin
|
|
||||||
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
|
|
||||||
on_import = True # Run this plugin during the import
|
|
||||||
|
|
||||||
def run(self, path_to_ebook):
|
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
|
||||||
from PyQt4.Qt import QMessageBox
|
|
||||||
data_file = file(path_to_ebook, 'rb').read()
|
|
||||||
|
|
||||||
try:
|
|
||||||
unlocked_file = DrmStripper(data_file).getResult()
|
|
||||||
except DrmException:
|
|
||||||
# ignore the error
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
of = self.temporary_file('.mobi')
|
|
||||||
of.write(unlocked_file)
|
|
||||||
of.close()
|
|
||||||
return of.name
|
|
||||||
|
|
||||||
if is_ok_to_use_qt():
|
|
||||||
d = QMessageBox(QMessageBox.Warning, "K4PCDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
|
|
||||||
d.show()
|
|
||||||
d.raise_()
|
|
||||||
d.exec_()
|
|
||||||
return path_to_ebook
|
|
||||||
|
|
||||||
#def customization_help(self, gui=False):
|
|
||||||
# return 'Enter PID (separate multiple PIDs with comma)'
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.stdout=Unbuffered(sys.stdout)
|
|
||||||
print ('K4PCDeDrm v%(__version__)s '
|
|
||||||
'provided DiapDealer.' % globals())
|
|
||||||
if len(sys.argv)<3:
|
|
||||||
print "Removes DRM protection from K4PC books"
|
|
||||||
print "Usage:"
|
|
||||||
print " %s <infile> <outfile>" % sys.argv[0]
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
infile = sys.argv[1]
|
|
||||||
outfile = sys.argv[2]
|
|
||||||
data_file = file(infile, 'rb').read()
|
|
||||||
try:
|
|
||||||
strippedFile = DrmStripper(data_file)
|
|
||||||
file(outfile, 'wb').write(strippedFile.getResult())
|
|
||||||
except DrmException, e:
|
|
||||||
print "Error: %s" % e
|
|
||||||
sys.exit(1)
|
|
||||||
sys.exit(0)
|
|
||||||
Binary file not shown.
23
Calibre_Plugins/README-K4MobiDeDRM-plugin.txt
Normal file
23
Calibre_Plugins/README-K4MobiDeDRM-plugin.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Plugin for K4PC, K4Mac and Mobi Books
|
||||||
|
|
||||||
|
Will work on Linux (standard DRM Mobi books only), Mac OS X (standard DRM Mobi books and "Kindle for Mac" books, and Windows (standard DRM Mobi books and "Kindle for PC" books.
|
||||||
|
|
||||||
|
This plugin supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM plugins. If you install this plugin, those plugins can be safely removed.
|
||||||
|
|
||||||
|
This plugin is meant to convert "Kindle for PC", "Kindle for Mac" and "Mobi" ebooks with DRM to unlocked Mobi files. Calibre can then convert them to whatever format you desire. It is meant to function without having to install any dependencies except for Calibre being on your same machine and in the same account as your "Kindle for PC" or "Kindle for Mac" application if you are going to remove the DRM from those types of books.
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (k4mobidedrm_vXX_plugin.zip) and click the 'Add' button. You're done.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
Highlight the plugin (K4MobiDeDRM under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter a comma separated list of your 10 digit PIDs. This is not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books.
|
||||||
|
|
||||||
|
|
||||||
|
Troubleshooting:
|
||||||
|
If you find that it's not working for you (imported azw's are not converted to mobi format), you can save a lot of time and trouble by trying to add the azw file 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.azw". Don't type the quotes and obviously change the 'your_ebook.azw' 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.
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
K4PCDeDRM - K4PCDeDRM_X.XX_plugin.zip
|
|
||||||
Requires Calibre version 0.6.44 or higher.
|
|
||||||
|
|
||||||
This work is based on the work of cmbtc, skindle, mobidedrm. and skindleAll I had the much easier job of converting them to a Calibre plugin.
|
|
||||||
|
|
||||||
This plugin is meant to Kindle for PC azw ebooks that are protected
|
|
||||||
with Amazon's Mobi based encryption. It is meant to function without having to install any dependencies... other than having both Calibre installed and Kindle for PC on the same machine, of course.
|
|
||||||
|
|
||||||
Installation:
|
|
||||||
|
|
||||||
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (K4PCDeDRM_X.XX_plugin.zip) and click the 'Add' button. you're done.
|
|
||||||
|
|
||||||
21
Calibre_Plugins/README-eReaderPDB2PML-plugin.txt
Normal file
21
Calibre_Plugins/README-eReaderPDB2PML-plugin.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
eReader PDB2PML - eReaderPDB2PML_vXX_plugin.zip
|
||||||
|
|
||||||
|
All credit given to The Dark Reverser for the original standalone script. I had the much easier job of converting it to a Calibre plugin.
|
||||||
|
|
||||||
|
This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files. Calibre can then convert it to whatever format you desire. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. I've included the psyco libraries (compiled for each platform) for speed. If your system can use them, great! Otherwise, they won't be used and things will just work slower.
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (eReaderPDB2PML_vXX_plugin.zip) and click the 'Add' button. You're done.
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
Highlight the plugin (eReader PDB 2 PML under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter your name and last 8 digits of the credit card number separated by a comma: Your Name,12341234
|
||||||
|
|
||||||
|
If you've purchased books with more than one credit card, separate the info with a colon: Your Name,12341234:Other Name,23452345 (NOTE: Do NOT put quotes around your name like you do with the original script!!)
|
||||||
|
|
||||||
|
Troubleshooting:
|
||||||
|
If you find that it's not working for you (imported pdb's are not converted to pmlz format), you can save a lot of time and trouble by trying to add the pdb 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.pdb". Don't type the quotes and obviously change the 'your_ebook.pdb' 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.
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
MobiDeDRM - MobiDeDRM_X.XX_plugin.zip
|
|
||||||
Requires Calibre version 0.6.44 or higher.
|
|
||||||
|
|
||||||
This work is based on the current mobidedrm.py code.
|
|
||||||
|
|
||||||
This plugin is meant to Mobipocket and Kindle ebooks that are protected
|
|
||||||
with Amazon's Mobi based encryption. It is meant to function without having to install any dependencies... other than having both Calibre installed. You must know the PID orf the device you are using or the book specific PID to use this plugin.
|
|
||||||
|
|
||||||
Installation:
|
|
||||||
|
|
||||||
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (MobiDeDRM_X.XX_plugin.zip) and click the 'Add' button.
|
|
||||||
|
|
||||||
Then enter your PIDS in the plugin customization window separated by commas (with no spaces).
|
|
||||||
26
Calibre_Plugins/Win_OpenSSL_0.9.8o.txt
Normal file
26
Calibre_Plugins/Win_OpenSSL_0.9.8o.txt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
Installing openssl on Windows 64-bit (Windows 2000 and higher)
|
||||||
|
|
||||||
|
Win64 OpenSSL v0.9.8o (8Mb)
|
||||||
|
http://www.slproweb.com/download/Win64OpenSSL-0_9_8o.exe
|
||||||
|
(if you get an error message about missing Visual C++ redistributables... cancel the install and install the below support program from Microsoft, THEN install OpenSSL)
|
||||||
|
|
||||||
|
Visual C++ 2008 Redistributables (x64) (1.7Mb)
|
||||||
|
http://www.microsoft.com/downloads/details.aspx?familyid=bd2a6171-e2d6-4230-b809-9a8d7548c1b6
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Installing openssl on Windows 32-bit (Windows 2000 and higher)
|
||||||
|
|
||||||
|
Win32 OpenSSL v0.9.8o (8Mb)
|
||||||
|
http://www.slproweb.com/download/Win32OpenSSL-0_9_8o.exe
|
||||||
|
(if you get an error message about missing Visual C++ redistributables... cancel the install and install the below support program from Microsoft, THEN install OpenSSL)
|
||||||
|
|
||||||
|
Visual C++ 2008 Redistributables (1.7Mb)
|
||||||
|
http://www.microsoft.com/downloads/details.aspx?familyid=9B2DA534-3E03-4391-8A4D-074B9F2BC1BF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Other versions of OpenSSL (and versions for Windows older than Windows 2000) can be found on the following website.
|
||||||
|
|
||||||
|
Shining Light Productions
|
||||||
|
http://www.slproweb.com/products/Win32OpenSSL.html
|
||||||
BIN
Calibre_Plugins/eReaderPDB2PML_plugin.zip
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin.zip
Normal file
Binary file not shown.
148
Calibre_Plugins/eReaderPDB2PML_plugin/eReaderPDB2PML_plugin.py
Normal file
148
Calibre_Plugins/eReaderPDB2PML_plugin/eReaderPDB2PML_plugin.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# eReaderPDB2PML_plugin.py
|
||||||
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
|
# later. <http://www.gnu.org/licenses/>
|
||||||
|
#
|
||||||
|
# All credit given to The Dark Reverser for the original standalone script.
|
||||||
|
# I had the much easier job of converting it to Calibre a plugin.
|
||||||
|
#
|
||||||
|
# This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files.
|
||||||
|
# Calibre can then convert it to whatever format you desire.
|
||||||
|
# It is meant to function without having to install any dependencies...
|
||||||
|
# other than having Calibre installed, of course. I've included the psyco libraries
|
||||||
|
# (compiled for each platform) for speed. If your system can use them, great!
|
||||||
|
# Otherwise, they won't be used and things will just work slower.
|
||||||
|
#
|
||||||
|
# Installation:
|
||||||
|
# Go to Calibre's Preferences page... click on the Plugins button. Use the file
|
||||||
|
# dialog button to select the plugin's zip file (eReaderPDB2PML_vXX_plugin.zip) and
|
||||||
|
# click the 'Add' button. You're done.
|
||||||
|
#
|
||||||
|
# Configuration:
|
||||||
|
# Highlight the plugin (eReader PDB 2 PML) and click the
|
||||||
|
# "Customize Plugin" button on Calibre's Preferences->Plugins page.
|
||||||
|
# Enter your name and the last 8 digits of the credit card number separated by
|
||||||
|
# a comma: Your Name,12341234
|
||||||
|
#
|
||||||
|
# If you've purchased books with more than one credit card, separate the info with
|
||||||
|
# a colon: Your Name,12341234:Other Name,23452345
|
||||||
|
# NOTE: Do NOT put quotes around your name like you do with the original script!!
|
||||||
|
#
|
||||||
|
# Revision history:
|
||||||
|
# 0.0.1 - Initial release
|
||||||
|
# 0.0.2 - updated to distinguish it from earlier non-openssl version
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
from calibre.customize import FileTypePlugin
|
||||||
|
|
||||||
|
class eRdrDeDRM(FileTypePlugin):
|
||||||
|
name = 'eReader PDB 2 PML' # Name of the plugin
|
||||||
|
description = 'Removes DRM from secure pdb files. \
|
||||||
|
Credit given to The Dark Reverser for the original standalone script.'
|
||||||
|
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
|
||||||
|
author = 'DiapDealer' # The author of this plugin
|
||||||
|
version = (0, 0, 2) # The version number of this plugin
|
||||||
|
file_types = set(['pdb']) # The file types that this plugin will be applied to
|
||||||
|
on_import = True # Run this plugin during the import
|
||||||
|
|
||||||
|
def run(self, path_to_ebook):
|
||||||
|
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||||
|
from calibre.constants import iswindows, isosx
|
||||||
|
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||||
|
ppath = os.path.join(self.sys_insertion_path, pdir)
|
||||||
|
sys.path.insert(0, ppath)
|
||||||
|
|
||||||
|
global bookname, erdr2pml
|
||||||
|
import erdr2pml
|
||||||
|
|
||||||
|
if 'psyco' in sys.modules:
|
||||||
|
print 'Using psyco acceleration for %s.' % pdir
|
||||||
|
else:
|
||||||
|
print 'NOT using psyco acceleration for %s. Conversion may be slow.' % pdir
|
||||||
|
|
||||||
|
infile = path_to_ebook
|
||||||
|
bookname = os.path.splitext(os.path.basename(infile))[0]
|
||||||
|
outdir = PersistentTemporaryDirectory()
|
||||||
|
pmlzfile = self.temporary_file(bookname + '.pmlz')
|
||||||
|
|
||||||
|
if self.site_customization:
|
||||||
|
keydata = self.site_customization
|
||||||
|
ar = keydata.split(':')
|
||||||
|
for i in ar:
|
||||||
|
try:
|
||||||
|
name, cc = i.split(',')
|
||||||
|
except ValueError:
|
||||||
|
sys.path.remove(ppath)
|
||||||
|
print ' Error parsing user supplied data.'
|
||||||
|
return path_to_ebook
|
||||||
|
|
||||||
|
try:
|
||||||
|
print "Processing..."
|
||||||
|
import time
|
||||||
|
start_time = time.time()
|
||||||
|
pmlfilepath = self.convertEreaderToPml(infile, name, cc, outdir)
|
||||||
|
|
||||||
|
if pmlfilepath and pmlfilepath != 1:
|
||||||
|
import zipfile
|
||||||
|
import shutil
|
||||||
|
print " Creating PMLZ file"
|
||||||
|
myZipFile = zipfile.ZipFile(pmlzfile.name,'w',zipfile.ZIP_STORED, False)
|
||||||
|
list = os.listdir(outdir)
|
||||||
|
for file in list:
|
||||||
|
localname = file
|
||||||
|
filePath = os.path.join(outdir,file)
|
||||||
|
if os.path.isfile(filePath):
|
||||||
|
myZipFile.write(filePath, localname)
|
||||||
|
elif os.path.isdir(filePath):
|
||||||
|
imageList = os.listdir(filePath)
|
||||||
|
localimgdir = os.path.basename(filePath)
|
||||||
|
for image in imageList:
|
||||||
|
localname = os.path.join(localimgdir,image)
|
||||||
|
imagePath = os.path.join(filePath,image)
|
||||||
|
if os.path.isfile(imagePath):
|
||||||
|
myZipFile.write(imagePath, localname)
|
||||||
|
myZipFile.close()
|
||||||
|
end_time = time.time()
|
||||||
|
search_time = end_time - start_time
|
||||||
|
print 'elapsed time: %.2f seconds' % (search_time, )
|
||||||
|
print "done"
|
||||||
|
return pmlzfile.name
|
||||||
|
else:
|
||||||
|
raise ValueError('Error Creating PML file.')
|
||||||
|
except ValueError, e:
|
||||||
|
print "Error: %s" % e
|
||||||
|
pass
|
||||||
|
raise Exception('Couldn\'t decrypt pdb file.')
|
||||||
|
else:
|
||||||
|
raise Exception('No name and CC# provided.')
|
||||||
|
|
||||||
|
def convertEreaderToPml(self, infile, name, cc, outdir):
|
||||||
|
|
||||||
|
print " Decoding File"
|
||||||
|
sect = erdr2pml.Sectionizer(infile, 'PNRdPPrs')
|
||||||
|
er = erdr2pml.EreaderProcessor(sect.loadSection, name, cc)
|
||||||
|
|
||||||
|
if er.getNumImages() > 0:
|
||||||
|
print " Extracting images"
|
||||||
|
#imagedir = bookname + '_img/'
|
||||||
|
imagedir = 'images/'
|
||||||
|
imagedirpath = os.path.join(outdir,imagedir)
|
||||||
|
if not os.path.exists(imagedirpath):
|
||||||
|
os.makedirs(imagedirpath)
|
||||||
|
for i in xrange(er.getNumImages()):
|
||||||
|
name, contents = er.getImage(i)
|
||||||
|
file(os.path.join(imagedirpath, name), 'wb').write(contents)
|
||||||
|
|
||||||
|
print " Extracting pml"
|
||||||
|
pml_string = er.getText()
|
||||||
|
pmlfilename = bookname + ".pml"
|
||||||
|
try:
|
||||||
|
file(os.path.join(outdir, pmlfilename),'wb').write(erdr2pml.cleanPML(pml_string))
|
||||||
|
return os.path.join(outdir, pmlfilename)
|
||||||
|
except:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def customization_help(self, gui=False):
|
||||||
|
return 'Enter Account Name & Last 8 digits of Credit Card number (separate with a comma)'
|
||||||
@@ -54,26 +54,30 @@
|
|||||||
# 0.13 - change to unbuffered stdout for use with gui front ends
|
# 0.13 - change to unbuffered stdout for use with gui front ends
|
||||||
# 0.14 - contributed enhancement to support --make-pmlz switch
|
# 0.14 - contributed enhancement to support --make-pmlz switch
|
||||||
# 0.15 - enabled high-ascii to pml character encoding. DropBook now works on Mac.
|
# 0.15 - enabled high-ascii to pml character encoding. DropBook now works on Mac.
|
||||||
|
# 0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
|
||||||
|
|
||||||
__version__='0.15'
|
Des = None
|
||||||
|
|
||||||
# Import Psyco if available
|
import openssl_des
|
||||||
try:
|
Des = openssl_des.load_libcrypto()
|
||||||
# Dumb speed hack 1
|
|
||||||
# http://psyco.sourceforge.net
|
# if that did not work then use pure python implementation
|
||||||
import psyco
|
# of DES and try to speed it up with Psycho
|
||||||
psyco.full()
|
if Des == None:
|
||||||
pass
|
import python_des
|
||||||
except ImportError:
|
Des = python_des.Des
|
||||||
pass
|
# Import Psyco if available
|
||||||
try:
|
try:
|
||||||
# Dumb speed hack 2
|
# Dumb speed hack 1
|
||||||
# All map() calls converted to list comprehension (some use zip)
|
# http://psyco.sourceforge.net
|
||||||
# override zip with izip - saves memory and in rough testing
|
import psyco
|
||||||
# appears to be faster zip() is only used in the converted map() calls
|
psyco.full()
|
||||||
from itertools import izip as zip
|
pass
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
__version__='0.16'
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
@@ -101,223 +105,6 @@ import logging
|
|||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
#logging.basicConfig(level=logging.DEBUG)
|
#logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
ECB = 0
|
|
||||||
CBC = 1
|
|
||||||
class Des(object):
|
|
||||||
__pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
|
|
||||||
9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
|
|
||||||
62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
|
|
||||||
13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3]
|
|
||||||
__left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
|
|
||||||
__pc2 = [13, 16, 10, 23, 0, 4,2, 27, 14, 5, 20, 9,
|
|
||||||
22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
|
|
||||||
40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
|
|
||||||
43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31]
|
|
||||||
__ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
|
|
||||||
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
|
|
||||||
56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2,
|
|
||||||
60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
|
|
||||||
__expansion_table = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8,
|
|
||||||
7, 8, 9, 10, 11, 12,11, 12, 13, 14, 15, 16,
|
|
||||||
15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
|
|
||||||
23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31, 0]
|
|
||||||
__sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
|
|
||||||
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
|
||||||
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
|
|
||||||
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
|
|
||||||
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
|
|
||||||
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
|
|
||||||
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
|
|
||||||
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
|
|
||||||
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
|
|
||||||
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
|
||||||
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
|
|
||||||
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
|
|
||||||
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
|
|
||||||
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
|
||||||
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
|
|
||||||
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
|
|
||||||
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
|
|
||||||
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
|
||||||
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
|
|
||||||
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
|
|
||||||
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
|
|
||||||
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
|
||||||
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
|
|
||||||
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
|
|
||||||
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
|
|
||||||
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
|
||||||
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
|
|
||||||
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
|
|
||||||
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
|
|
||||||
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
|
||||||
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
|
|
||||||
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
|
|
||||||
__p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
|
|
||||||
4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
|
|
||||||
__fp = [39, 7, 47, 15, 55, 23, 63, 31,38, 6, 46, 14, 54, 22, 62, 30,
|
|
||||||
37, 5, 45, 13, 53, 21, 61, 29,36, 4, 44, 12, 52, 20, 60, 28,
|
|
||||||
35, 3, 43, 11, 51, 19, 59, 27,34, 2, 42, 10, 50, 18, 58, 26,
|
|
||||||
33, 1, 41, 9, 49, 17, 57, 25,32, 0, 40, 8, 48, 16, 56, 24]
|
|
||||||
# Type of crypting being done
|
|
||||||
ENCRYPT = 0x00
|
|
||||||
DECRYPT = 0x01
|
|
||||||
def __init__(self, key, mode=ECB, IV=None):
|
|
||||||
if len(key) != 8:
|
|
||||||
raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
|
|
||||||
self.block_size = 8
|
|
||||||
self.key_size = 8
|
|
||||||
self.__padding = ''
|
|
||||||
self.setMode(mode)
|
|
||||||
if IV:
|
|
||||||
self.setIV(IV)
|
|
||||||
self.L = []
|
|
||||||
self.R = []
|
|
||||||
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
|
|
||||||
self.final = []
|
|
||||||
self.setKey(key)
|
|
||||||
def getKey(self):
|
|
||||||
return self.__key
|
|
||||||
def setKey(self, key):
|
|
||||||
self.__key = key
|
|
||||||
self.__create_sub_keys()
|
|
||||||
def getMode(self):
|
|
||||||
return self.__mode
|
|
||||||
def setMode(self, mode):
|
|
||||||
self.__mode = mode
|
|
||||||
def getIV(self):
|
|
||||||
return self.__iv
|
|
||||||
def setIV(self, IV):
|
|
||||||
if not IV or len(IV) != self.block_size:
|
|
||||||
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
|
||||||
self.__iv = IV
|
|
||||||
def getPadding(self):
|
|
||||||
return self.__padding
|
|
||||||
def __String_to_BitList(self, data):
|
|
||||||
l = len(data) * 8
|
|
||||||
result = [0] * l
|
|
||||||
pos = 0
|
|
||||||
for c in data:
|
|
||||||
i = 7
|
|
||||||
ch = ord(c)
|
|
||||||
while i >= 0:
|
|
||||||
if ch & (1 << i) != 0:
|
|
||||||
result[pos] = 1
|
|
||||||
else:
|
|
||||||
result[pos] = 0
|
|
||||||
pos += 1
|
|
||||||
i -= 1
|
|
||||||
return result
|
|
||||||
def __BitList_to_String(self, data):
|
|
||||||
result = ''
|
|
||||||
pos = 0
|
|
||||||
c = 0
|
|
||||||
while pos < len(data):
|
|
||||||
c += data[pos] << (7 - (pos % 8))
|
|
||||||
if (pos % 8) == 7:
|
|
||||||
result += chr(c)
|
|
||||||
c = 0
|
|
||||||
pos += 1
|
|
||||||
return result
|
|
||||||
def __permutate(self, table, block):
|
|
||||||
return [block[x] for x in table]
|
|
||||||
def __create_sub_keys(self):
|
|
||||||
key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
|
|
||||||
i = 0
|
|
||||||
self.L = key[:28]
|
|
||||||
self.R = key[28:]
|
|
||||||
while i < 16:
|
|
||||||
j = 0
|
|
||||||
while j < Des.__left_rotations[i]:
|
|
||||||
self.L.append(self.L[0])
|
|
||||||
del self.L[0]
|
|
||||||
self.R.append(self.R[0])
|
|
||||||
del self.R[0]
|
|
||||||
j += 1
|
|
||||||
self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
|
|
||||||
i += 1
|
|
||||||
def __des_crypt(self, block, crypt_type):
|
|
||||||
block = self.__permutate(Des.__ip, block)
|
|
||||||
self.L = block[:32]
|
|
||||||
self.R = block[32:]
|
|
||||||
if crypt_type == Des.ENCRYPT:
|
|
||||||
iteration = 0
|
|
||||||
iteration_adjustment = 1
|
|
||||||
else:
|
|
||||||
iteration = 15
|
|
||||||
iteration_adjustment = -1
|
|
||||||
i = 0
|
|
||||||
while i < 16:
|
|
||||||
tempR = self.R[:]
|
|
||||||
self.R = self.__permutate(Des.__expansion_table, self.R)
|
|
||||||
self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
|
|
||||||
B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
|
|
||||||
j = 0
|
|
||||||
Bn = [0] * 32
|
|
||||||
pos = 0
|
|
||||||
while j < 8:
|
|
||||||
m = (B[j][0] << 1) + B[j][5]
|
|
||||||
n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
|
|
||||||
v = Des.__sbox[j][(m << 4) + n]
|
|
||||||
Bn[pos] = (v & 8) >> 3
|
|
||||||
Bn[pos + 1] = (v & 4) >> 2
|
|
||||||
Bn[pos + 2] = (v & 2) >> 1
|
|
||||||
Bn[pos + 3] = v & 1
|
|
||||||
pos += 4
|
|
||||||
j += 1
|
|
||||||
self.R = self.__permutate(Des.__p, Bn)
|
|
||||||
self.R = [x ^ y for x, y in zip(self.R, self.L)]
|
|
||||||
self.L = tempR
|
|
||||||
i += 1
|
|
||||||
iteration += iteration_adjustment
|
|
||||||
self.final = self.__permutate(Des.__fp, self.R + self.L)
|
|
||||||
return self.final
|
|
||||||
def crypt(self, data, crypt_type):
|
|
||||||
if not data:
|
|
||||||
return ''
|
|
||||||
if len(data) % self.block_size != 0:
|
|
||||||
if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
|
|
||||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
|
|
||||||
if not self.getPadding():
|
|
||||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
|
|
||||||
else:
|
|
||||||
data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
|
|
||||||
if self.getMode() == CBC:
|
|
||||||
if self.getIV():
|
|
||||||
iv = self.__String_to_BitList(self.getIV())
|
|
||||||
else:
|
|
||||||
raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
|
|
||||||
i = 0
|
|
||||||
dict = {}
|
|
||||||
result = []
|
|
||||||
while i < len(data):
|
|
||||||
block = self.__String_to_BitList(data[i:i+8])
|
|
||||||
if self.getMode() == CBC:
|
|
||||||
if crypt_type == Des.ENCRYPT:
|
|
||||||
block = [x ^ y for x, y in zip(block, iv)]
|
|
||||||
processed_block = self.__des_crypt(block, crypt_type)
|
|
||||||
if crypt_type == Des.DECRYPT:
|
|
||||||
processed_block = [x ^ y for x, y in zip(processed_block, iv)]
|
|
||||||
iv = block
|
|
||||||
else:
|
|
||||||
iv = processed_block
|
|
||||||
else:
|
|
||||||
processed_block = self.__des_crypt(block, crypt_type)
|
|
||||||
result.append(self.__BitList_to_String(processed_block))
|
|
||||||
i += 8
|
|
||||||
if crypt_type == Des.DECRYPT and self.getPadding():
|
|
||||||
s = result[-1]
|
|
||||||
while s[-1] == self.getPadding():
|
|
||||||
s = s[:-1]
|
|
||||||
result[-1] = s
|
|
||||||
return ''.join(result)
|
|
||||||
def encrypt(self, data, pad=''):
|
|
||||||
self.__padding = pad
|
|
||||||
return self.crypt(data, Des.ENCRYPT)
|
|
||||||
def decrypt(self, data, pad=''):
|
|
||||||
self.__padding = pad
|
|
||||||
return self.crypt(data, Des.DECRYPT)
|
|
||||||
|
|
||||||
class Sectionizer(object):
|
class Sectionizer(object):
|
||||||
def __init__(self, filename, ident):
|
def __init__(self, filename, ident):
|
||||||
self.contents = file(filename, 'rb').read()
|
self.contents = file(filename, 'rb').read()
|
||||||
@@ -685,8 +472,5 @@ def main(argv=None):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
#import cProfile
|
|
||||||
#command = """sys.exit(main())"""
|
|
||||||
#cProfile.runctx( command, globals(), locals(), filename="cprofile.profile" )
|
|
||||||
|
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
||||||
90
Calibre_Plugins/eReaderPDB2PML_plugin/openssl_des.py
Normal file
90
Calibre_Plugins/eReaderPDB2PML_plugin/openssl_des.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
|
# implement just enough of des from openssl to make erdr2pml.py happy
|
||||||
|
|
||||||
|
def load_libcrypto():
|
||||||
|
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_char, c_int, c_long, \
|
||||||
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
|
from ctypes.util import find_library
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
|
||||||
|
if libcrypto is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
|
||||||
|
# typedef struct DES_ks
|
||||||
|
# {
|
||||||
|
# union
|
||||||
|
# {
|
||||||
|
# DES_cblock cblock;
|
||||||
|
# /* make sure things are correct size on machines with
|
||||||
|
# * 8 byte longs */
|
||||||
|
# DES_LONG deslong[2];
|
||||||
|
# } ks[16];
|
||||||
|
# } DES_key_schedule;
|
||||||
|
|
||||||
|
# just create a big enough place to hold everything
|
||||||
|
# it will have alignment of structure so we should be okay (16 byte aligned?)
|
||||||
|
class DES_KEY_SCHEDULE(Structure):
|
||||||
|
_fields_ = [('DES_cblock1', c_char * 16),
|
||||||
|
('DES_cblock2', c_char * 16),
|
||||||
|
('DES_cblock3', c_char * 16),
|
||||||
|
('DES_cblock4', c_char * 16),
|
||||||
|
('DES_cblock5', c_char * 16),
|
||||||
|
('DES_cblock6', c_char * 16),
|
||||||
|
('DES_cblock7', c_char * 16),
|
||||||
|
('DES_cblock8', c_char * 16),
|
||||||
|
('DES_cblock9', c_char * 16),
|
||||||
|
('DES_cblock10', c_char * 16),
|
||||||
|
('DES_cblock11', c_char * 16),
|
||||||
|
('DES_cblock12', c_char * 16),
|
||||||
|
('DES_cblock13', c_char * 16),
|
||||||
|
('DES_cblock14', c_char * 16),
|
||||||
|
('DES_cblock15', c_char * 16),
|
||||||
|
('DES_cblock16', c_char * 16)]
|
||||||
|
|
||||||
|
DES_KEY_SCHEDULE_p = POINTER(DES_KEY_SCHEDULE)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
DES_set_key = F(None, 'DES_set_key',[c_char_p, DES_KEY_SCHEDULE_p])
|
||||||
|
DES_ecb_encrypt = F(None, 'DES_ecb_encrypt',[c_char_p, c_char_p, DES_KEY_SCHEDULE_p, c_int])
|
||||||
|
|
||||||
|
|
||||||
|
class DES(object):
|
||||||
|
def __init__(self, key):
|
||||||
|
if len(key) != 8 :
|
||||||
|
raise Error('DES improper key used')
|
||||||
|
return
|
||||||
|
self.key = key
|
||||||
|
self.keyschedule = DES_KEY_SCHEDULE()
|
||||||
|
DES_set_key(self.key, self.keyschedule)
|
||||||
|
def desdecrypt(self, data):
|
||||||
|
ob = create_string_buffer(len(data))
|
||||||
|
DES_ecb_encrypt(data, ob, self.keyschedule, 0)
|
||||||
|
return ob.raw
|
||||||
|
def decrypt(self, data):
|
||||||
|
if not data:
|
||||||
|
return ''
|
||||||
|
i = 0
|
||||||
|
result = []
|
||||||
|
while i < len(data):
|
||||||
|
block = data[i:i+8]
|
||||||
|
processed_block = self.desdecrypt(block)
|
||||||
|
result.append(processed_block)
|
||||||
|
i += 8
|
||||||
|
return ''.join(result)
|
||||||
|
|
||||||
|
return DES
|
||||||
|
|
||||||
218
Calibre_Plugins/eReaderPDB2PML_plugin/python_des.py
Normal file
218
Calibre_Plugins/eReaderPDB2PML_plugin/python_des.py
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
ECB = 0
|
||||||
|
CBC = 1
|
||||||
|
class Des(object):
|
||||||
|
__pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
|
||||||
|
9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
|
||||||
|
62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
|
||||||
|
13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3]
|
||||||
|
__left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
|
||||||
|
__pc2 = [13, 16, 10, 23, 0, 4,2, 27, 14, 5, 20, 9,
|
||||||
|
22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
|
||||||
|
40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
|
||||||
|
43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31]
|
||||||
|
__ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
|
||||||
|
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
|
||||||
|
56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2,
|
||||||
|
60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
|
||||||
|
__expansion_table = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8,
|
||||||
|
7, 8, 9, 10, 11, 12,11, 12, 13, 14, 15, 16,
|
||||||
|
15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
|
||||||
|
23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31, 0]
|
||||||
|
__sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
|
||||||
|
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
||||||
|
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
|
||||||
|
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
|
||||||
|
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
|
||||||
|
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
|
||||||
|
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
|
||||||
|
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
|
||||||
|
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
|
||||||
|
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
||||||
|
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
|
||||||
|
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
|
||||||
|
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
|
||||||
|
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
||||||
|
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
|
||||||
|
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
|
||||||
|
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
|
||||||
|
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
||||||
|
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
|
||||||
|
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
|
||||||
|
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
|
||||||
|
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
||||||
|
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
|
||||||
|
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
|
||||||
|
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
|
||||||
|
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
||||||
|
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
|
||||||
|
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
|
||||||
|
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
|
||||||
|
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
||||||
|
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
|
||||||
|
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
|
||||||
|
__p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
|
||||||
|
4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
|
||||||
|
__fp = [39, 7, 47, 15, 55, 23, 63, 31,38, 6, 46, 14, 54, 22, 62, 30,
|
||||||
|
37, 5, 45, 13, 53, 21, 61, 29,36, 4, 44, 12, 52, 20, 60, 28,
|
||||||
|
35, 3, 43, 11, 51, 19, 59, 27,34, 2, 42, 10, 50, 18, 58, 26,
|
||||||
|
33, 1, 41, 9, 49, 17, 57, 25,32, 0, 40, 8, 48, 16, 56, 24]
|
||||||
|
# Type of crypting being done
|
||||||
|
ENCRYPT = 0x00
|
||||||
|
DECRYPT = 0x01
|
||||||
|
def __init__(self, key, mode=ECB, IV=None):
|
||||||
|
if len(key) != 8:
|
||||||
|
raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
|
||||||
|
self.block_size = 8
|
||||||
|
self.key_size = 8
|
||||||
|
self.__padding = ''
|
||||||
|
self.setMode(mode)
|
||||||
|
if IV:
|
||||||
|
self.setIV(IV)
|
||||||
|
self.L = []
|
||||||
|
self.R = []
|
||||||
|
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
|
||||||
|
self.final = []
|
||||||
|
self.setKey(key)
|
||||||
|
def getKey(self):
|
||||||
|
return self.__key
|
||||||
|
def setKey(self, key):
|
||||||
|
self.__key = key
|
||||||
|
self.__create_sub_keys()
|
||||||
|
def getMode(self):
|
||||||
|
return self.__mode
|
||||||
|
def setMode(self, mode):
|
||||||
|
self.__mode = mode
|
||||||
|
def getIV(self):
|
||||||
|
return self.__iv
|
||||||
|
def setIV(self, IV):
|
||||||
|
if not IV or len(IV) != self.block_size:
|
||||||
|
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
||||||
|
self.__iv = IV
|
||||||
|
def getPadding(self):
|
||||||
|
return self.__padding
|
||||||
|
def __String_to_BitList(self, data):
|
||||||
|
l = len(data) * 8
|
||||||
|
result = [0] * l
|
||||||
|
pos = 0
|
||||||
|
for c in data:
|
||||||
|
i = 7
|
||||||
|
ch = ord(c)
|
||||||
|
while i >= 0:
|
||||||
|
if ch & (1 << i) != 0:
|
||||||
|
result[pos] = 1
|
||||||
|
else:
|
||||||
|
result[pos] = 0
|
||||||
|
pos += 1
|
||||||
|
i -= 1
|
||||||
|
return result
|
||||||
|
def __BitList_to_String(self, data):
|
||||||
|
result = ''
|
||||||
|
pos = 0
|
||||||
|
c = 0
|
||||||
|
while pos < len(data):
|
||||||
|
c += data[pos] << (7 - (pos % 8))
|
||||||
|
if (pos % 8) == 7:
|
||||||
|
result += chr(c)
|
||||||
|
c = 0
|
||||||
|
pos += 1
|
||||||
|
return result
|
||||||
|
def __permutate(self, table, block):
|
||||||
|
return [block[x] for x in table]
|
||||||
|
def __create_sub_keys(self):
|
||||||
|
key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
|
||||||
|
i = 0
|
||||||
|
self.L = key[:28]
|
||||||
|
self.R = key[28:]
|
||||||
|
while i < 16:
|
||||||
|
j = 0
|
||||||
|
while j < Des.__left_rotations[i]:
|
||||||
|
self.L.append(self.L[0])
|
||||||
|
del self.L[0]
|
||||||
|
self.R.append(self.R[0])
|
||||||
|
del self.R[0]
|
||||||
|
j += 1
|
||||||
|
self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
|
||||||
|
i += 1
|
||||||
|
def __des_crypt(self, block, crypt_type):
|
||||||
|
block = self.__permutate(Des.__ip, block)
|
||||||
|
self.L = block[:32]
|
||||||
|
self.R = block[32:]
|
||||||
|
if crypt_type == Des.ENCRYPT:
|
||||||
|
iteration = 0
|
||||||
|
iteration_adjustment = 1
|
||||||
|
else:
|
||||||
|
iteration = 15
|
||||||
|
iteration_adjustment = -1
|
||||||
|
i = 0
|
||||||
|
while i < 16:
|
||||||
|
tempR = self.R[:]
|
||||||
|
self.R = self.__permutate(Des.__expansion_table, self.R)
|
||||||
|
self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
|
||||||
|
B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
|
||||||
|
j = 0
|
||||||
|
Bn = [0] * 32
|
||||||
|
pos = 0
|
||||||
|
while j < 8:
|
||||||
|
m = (B[j][0] << 1) + B[j][5]
|
||||||
|
n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
|
||||||
|
v = Des.__sbox[j][(m << 4) + n]
|
||||||
|
Bn[pos] = (v & 8) >> 3
|
||||||
|
Bn[pos + 1] = (v & 4) >> 2
|
||||||
|
Bn[pos + 2] = (v & 2) >> 1
|
||||||
|
Bn[pos + 3] = v & 1
|
||||||
|
pos += 4
|
||||||
|
j += 1
|
||||||
|
self.R = self.__permutate(Des.__p, Bn)
|
||||||
|
self.R = [x ^ y for x, y in zip(self.R, self.L)]
|
||||||
|
self.L = tempR
|
||||||
|
i += 1
|
||||||
|
iteration += iteration_adjustment
|
||||||
|
self.final = self.__permutate(Des.__fp, self.R + self.L)
|
||||||
|
return self.final
|
||||||
|
def crypt(self, data, crypt_type):
|
||||||
|
if not data:
|
||||||
|
return ''
|
||||||
|
if len(data) % self.block_size != 0:
|
||||||
|
if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
|
||||||
|
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
|
||||||
|
if not self.getPadding():
|
||||||
|
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
|
||||||
|
else:
|
||||||
|
data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
|
||||||
|
if self.getMode() == CBC:
|
||||||
|
if self.getIV():
|
||||||
|
iv = self.__String_to_BitList(self.getIV())
|
||||||
|
else:
|
||||||
|
raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
|
||||||
|
i = 0
|
||||||
|
dict = {}
|
||||||
|
result = []
|
||||||
|
while i < len(data):
|
||||||
|
block = self.__String_to_BitList(data[i:i+8])
|
||||||
|
if self.getMode() == CBC:
|
||||||
|
if crypt_type == Des.ENCRYPT:
|
||||||
|
block = [x ^ y for x, y in zip(block, iv)]
|
||||||
|
processed_block = self.__des_crypt(block, crypt_type)
|
||||||
|
if crypt_type == Des.DECRYPT:
|
||||||
|
processed_block = [x ^ y for x, y in zip(processed_block, iv)]
|
||||||
|
iv = block
|
||||||
|
else:
|
||||||
|
iv = processed_block
|
||||||
|
else:
|
||||||
|
processed_block = self.__des_crypt(block, crypt_type)
|
||||||
|
result.append(self.__BitList_to_String(processed_block))
|
||||||
|
i += 8
|
||||||
|
if crypt_type == Des.DECRYPT and self.getPadding():
|
||||||
|
s = result[-1]
|
||||||
|
while s[-1] == self.getPadding():
|
||||||
|
s = s[:-1]
|
||||||
|
result[-1] = s
|
||||||
|
return ''.join(result)
|
||||||
|
def encrypt(self, data, pad=''):
|
||||||
|
self.__padding = pad
|
||||||
|
return self.crypt(data, Des.ENCRYPT)
|
||||||
|
def decrypt(self, data, pad=''):
|
||||||
|
self.__padding = pad
|
||||||
|
return self.crypt(data, Des.DECRYPT)
|
||||||
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/__init__.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/__init__.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/_psyco.pyd
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/_psyco.pyd
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/builtin.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/builtin.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/classes.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/classes.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/clibrary.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/clibrary.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/core.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/core.pyo
Normal file
Binary file not shown.
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/logger.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/logger.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/profiler.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/profiler.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/support.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/support.pyo
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,65 +0,0 @@
|
|||||||
Ignoble Epub DeDRM - ignobleepub_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 Barnes & Noble 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 (ignobleepub_vXX_plugin.zip) and
|
|
||||||
click the 'Add' button. you're done.
|
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
1) The easiest way to configure the plugin is to enter your name (Barnes & Noble account
|
|
||||||
name) and credit card number (the one used to purchase the books) into the plugin's
|
|
||||||
customization window. It's the same info you would enter into the ignoblekeygen script.
|
|
||||||
Highlight the plugin (Ignoble Epub DeDRM) and click the "Customize Plugin" button on
|
|
||||||
Calibre's Preferences->Plugins page. Enter the name and credit card number separated
|
|
||||||
by a comma: Your Name,1234123412341234
|
|
||||||
|
|
||||||
If you've purchased books with more than one credit card, separate that other info with
|
|
||||||
a colon: Your Name,1234123412341234:Other Name,2345234523452345
|
|
||||||
|
|
||||||
** NOTE ** The above method is your only option if you don't have/can't run the original
|
|
||||||
I <3 Cabbages scripts on your particular machine.
|
|
||||||
|
|
||||||
** NOTE ** Your credit card number will be on display in Calibre's Plugin configuration
|
|
||||||
page when using the above method. If other people have access to your computer,
|
|
||||||
you may want to use the second configuration method below.
|
|
||||||
|
|
||||||
2) If you already have keyfiles generated with I <3 Cabbages' ignoblekeygen.pyw
|
|
||||||
script, you can put those keyfiles into 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 '.b64' extension (like the ignoblekeygen
|
|
||||||
script produces). This directory isn't touched when upgrading Calibre, so it's quite safe
|
|
||||||
to leave then there.
|
|
||||||
|
|
||||||
All keyfiles from method 2 and all data entered from method 1 will be used to attempt
|
|
||||||
to decrypt a book. You can use method 1 or method 2, or a combination of both.
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# ignobleepub_v01_plugin.py
|
# ignobleepub_plugin.py
|
||||||
# Released under the terms of the GNU General Public Licence, version 3 or
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
# later. <http://www.gnu.org/licenses/>
|
# later. <http://www.gnu.org/licenses/>
|
||||||
#
|
#
|
||||||
@@ -41,7 +41,9 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 0.1 - Initial release
|
# 0.1.0 - Initial release
|
||||||
|
# 0.1.1 - Allow Windows users to make use of openssl if they have it installed.
|
||||||
|
# - Incorporated SomeUpdates zipfix routine.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -77,7 +79,10 @@ def _load_crypto_libcrypto():
|
|||||||
Structure, c_ulong, create_string_buffer, cast
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
from ctypes.util import find_library
|
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:
|
if libcrypto is None:
|
||||||
raise IGNOBLEError('libcrypto not found')
|
raise IGNOBLEError('libcrypto not found')
|
||||||
libcrypto = CDLL(libcrypto)
|
libcrypto = CDLL(libcrypto)
|
||||||
@@ -261,7 +266,7 @@ class IgnobleDeDRM(FileTypePlugin):
|
|||||||
Credit given to I <3 Cabbages for the original stand-alone scripts.'
|
Credit given to I <3 Cabbages for the original stand-alone scripts.'
|
||||||
supported_platforms = ['linux', 'osx', 'windows']
|
supported_platforms = ['linux', 'osx', 'windows']
|
||||||
author = 'DiapDealer'
|
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.
|
minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions.
|
||||||
file_types = set(['epub'])
|
file_types = set(['epub'])
|
||||||
on_import = True
|
on_import = True
|
||||||
@@ -277,13 +282,12 @@ class IgnobleDeDRM(FileTypePlugin):
|
|||||||
# Add the included pycrypto import directory for Windows users.
|
# Add the included pycrypto import directory for Windows users.
|
||||||
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||||
ppath = os.path.join(self.sys_insertion_path, pdir)
|
ppath = os.path.join(self.sys_insertion_path, pdir)
|
||||||
#sys.path.insert(0, ppath)
|
|
||||||
sys.path.append(ppath)
|
sys.path.append(ppath)
|
||||||
|
|
||||||
AES, AES2 = _load_crypto()
|
AES, AES2 = _load_crypto()
|
||||||
|
|
||||||
if AES == None or AES2 == None:
|
if AES == None or AES2 == None:
|
||||||
# Failed to load libcrypto or PyCrypto... Adobe Epubs can\'t be decrypted.'
|
# Failed to load libcrypto or PyCrypto... Adobe Epubs can't be decrypted.'
|
||||||
sys.path.remove(ppath)
|
sys.path.remove(ppath)
|
||||||
raise IGNOBLEError('IgnobleEpub - Failed to load crypto libs.')
|
raise IGNOBLEError('IgnobleEpub - Failed to load crypto libs.')
|
||||||
return
|
return
|
||||||
@@ -337,10 +341,19 @@ class IgnobleDeDRM(FileTypePlugin):
|
|||||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||||
for userkey in userkeys:
|
for userkey in userkeys:
|
||||||
# Create a TemporaryPersistent file to work with.
|
# 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')
|
of = self.temporary_file('.epub')
|
||||||
|
|
||||||
# Give the user key, ebook and TemporaryPersistent file to the Stripper function.
|
# Give the user key, ebook and TemporaryPersistent file to the Stripper function.
|
||||||
result = plugin_main(userkey, path_to_ebook, of.name)
|
result = plugin_main(userkey, inf.name, of.name)
|
||||||
|
|
||||||
# Ebook is not a B&N Adept epub... do nothing and pass it on.
|
# Ebook is not a B&N Adept epub... do nothing and pass it on.
|
||||||
# This allows a non-encrypted epub to be imported without error messages.
|
# This allows a non-encrypted epub to be imported without error messages.
|
||||||
|
|||||||
BIN
Calibre_Plugins/ignobleepub_plugin/windows/Crypto/Cipher/AES.pyd
Normal file
BIN
Calibre_Plugins/ignobleepub_plugin/windows/Crypto/Cipher/AES.pyd
Normal file
Binary file not shown.
136
Calibre_Plugins/ignobleepub_plugin/zipfix.py
Normal file
136
Calibre_Plugins/ignobleepub_plugin/zipfix.py
Normal file
@@ -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())
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
@@ -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,14 +19,76 @@ class ADEPTError(Exception):
|
|||||||
if iswindows:
|
if iswindows:
|
||||||
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
||||||
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
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
|
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
||||||
import _winreg as winreg
|
import _winreg as winreg
|
||||||
|
|
||||||
try:
|
def _load_crypto_libcrypto():
|
||||||
from Crypto.Cipher import AES as _aes
|
from ctypes.util import find_library
|
||||||
except ImportError:
|
libcrypto = find_library('libeay32')
|
||||||
_aes = None
|
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)
|
||||||
|
|
||||||
|
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'
|
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
||||||
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
||||||
@@ -193,8 +255,12 @@ if iswindows:
|
|||||||
CryptUnprotectData = CryptUnprotectData()
|
CryptUnprotectData = CryptUnprotectData()
|
||||||
|
|
||||||
def retrieve_key():
|
def retrieve_key():
|
||||||
if _aes is None:
|
if AES is None:
|
||||||
raise ADEPTError("Couldn\'t load PyCrypto")
|
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] + '\\'
|
root = GetSystemDirectory().split('\\')[0] + '\\'
|
||||||
serial = GetVolumeSerialNumber(root)
|
serial = GetVolumeSerialNumber(root)
|
||||||
vendor = cpuid0()
|
vendor = cpuid0()
|
||||||
@@ -236,42 +302,39 @@ if iswindows:
|
|||||||
if userkey is None:
|
if userkey is None:
|
||||||
raise ADEPTError('Could not locate privateLicenseKey')
|
raise ADEPTError('Could not locate privateLicenseKey')
|
||||||
userkey = userkey.decode('base64')
|
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])]
|
userkey = userkey[26:-ord(userkey[-1])]
|
||||||
return userkey
|
return userkey
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
import Carbon.File
|
import subprocess
|
||||||
import Carbon.Folder
|
|
||||||
import Carbon.Folders
|
|
||||||
import MacOS
|
|
||||||
|
|
||||||
ACTIVATION_PATH = 'Adobe/Digital Editions/activation.dat'
|
|
||||||
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
||||||
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
||||||
|
|
||||||
def find_folder(domain, dtype):
|
def findActivationDat():
|
||||||
try:
|
home = os.getenv('HOME')
|
||||||
fsref = Carbon.Folder.FSFindFolder(domain, dtype, False)
|
cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"'
|
||||||
return Carbon.File.pathname(fsref)
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
except MacOS.Error:
|
p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
return None
|
out1, out2 = p2.communicate()
|
||||||
|
reslst = out1.split('\n')
|
||||||
def find_app_support_file(subpath):
|
cnt = len(reslst)
|
||||||
dtype = Carbon.Folders.kApplicationSupportFolderType
|
for j in xrange(cnt):
|
||||||
for domain in Carbon.Folders.kUserDomain, Carbon.Folders.kLocalDomain:
|
resline = reslst[j]
|
||||||
path = find_folder(domain, dtype)
|
pp = resline.find('activation.dat')
|
||||||
if path is None:
|
if pp >= 0:
|
||||||
continue
|
ActDatPath = resline
|
||||||
path = os.path.join(path, subpath)
|
break
|
||||||
if os.path.isfile(path):
|
if os.path.exists(ActDatPath):
|
||||||
return path
|
return ActDatPath
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def retrieve_key():
|
def retrieve_key():
|
||||||
actpath = find_app_support_file(ACTIVATION_PATH)
|
actpath = findActivationDat()
|
||||||
if actpath is None:
|
if actpath is None:
|
||||||
raise ADEPTError("Could not locate ADE activation")
|
raise ADEPTError("Could not locate ADE activation")
|
||||||
tree = etree.parse(actpath)
|
tree = etree.parse(actpath)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#! /usr/bin/python
|
#! /usr/bin/python
|
||||||
|
|
||||||
# ineptepub_v01_plugin.py
|
# ineptepub_plugin.py
|
||||||
# Released under the terms of the GNU General Public Licence, version 3 or
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
# later. <http://www.gnu.org/licenses/>
|
# later. <http://www.gnu.org/licenses/>
|
||||||
#
|
#
|
||||||
@@ -41,6 +41,10 @@
|
|||||||
#
|
#
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 0.1 - Initial release
|
# 0.1 - Initial release
|
||||||
|
# 0.1.1 - Allow Windows users to make use of openssl if they have it installed.
|
||||||
|
# - Incorporated SomeUpdates zipfix routine.
|
||||||
|
# 0.1.2 - Removed Carbon dependency for Mac users. Fixes an issue that was a
|
||||||
|
# result of Calibre changing to python 2.7.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -76,7 +80,10 @@ def _load_crypto_libcrypto():
|
|||||||
Structure, c_ulong, create_string_buffer, cast
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
from ctypes.util import find_library
|
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:
|
if libcrypto is None:
|
||||||
raise ADEPTError('libcrypto not found')
|
raise ADEPTError('libcrypto not found')
|
||||||
libcrypto = CDLL(libcrypto)
|
libcrypto = CDLL(libcrypto)
|
||||||
@@ -358,7 +365,7 @@ class IneptDeDRM(FileTypePlugin):
|
|||||||
Credit given to I <3 Cabbages for the original stand-alone scripts.'
|
Credit given to I <3 Cabbages for the original stand-alone scripts.'
|
||||||
supported_platforms = ['linux', 'osx', 'windows']
|
supported_platforms = ['linux', 'osx', 'windows']
|
||||||
author = 'DiapDealer'
|
author = 'DiapDealer'
|
||||||
version = (0, 1, 0)
|
version = (0, 1, 2)
|
||||||
minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions.
|
minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions.
|
||||||
file_types = set(['epub'])
|
file_types = set(['epub'])
|
||||||
on_import = True
|
on_import = True
|
||||||
@@ -376,7 +383,6 @@ class IneptDeDRM(FileTypePlugin):
|
|||||||
# Add the included Carbon import directory for Mac users.
|
# Add the included Carbon import directory for Mac users.
|
||||||
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||||
ppath = os.path.join(self.sys_insertion_path, pdir)
|
ppath = os.path.join(self.sys_insertion_path, pdir)
|
||||||
#sys.path.insert(0, ppath)
|
|
||||||
sys.path.append(ppath)
|
sys.path.append(ppath)
|
||||||
|
|
||||||
AES, RSA = _load_crypto()
|
AES, RSA = _load_crypto()
|
||||||
@@ -416,7 +422,7 @@ class IneptDeDRM(FileTypePlugin):
|
|||||||
try:
|
try:
|
||||||
keydata = retrieve_key()
|
keydata = retrieve_key()
|
||||||
userkeys.append(keydata)
|
userkeys.append(keydata)
|
||||||
keypath = os.path.join(confpath, 'adeptkey.der')
|
keypath = os.path.join(confpath, 'calibre-adeptkey.der')
|
||||||
with open(keypath, 'wb') as f:
|
with open(keypath, 'wb') as f:
|
||||||
f.write(keydata)
|
f.write(keydata)
|
||||||
print 'IneptEpub: Created keyfile from ADE install.'
|
print 'IneptEpub: Created keyfile from ADE install.'
|
||||||
@@ -433,10 +439,19 @@ class IneptDeDRM(FileTypePlugin):
|
|||||||
# Attempt to decrypt epub with each encryption key found.
|
# Attempt to decrypt epub with each encryption key found.
|
||||||
for userkey in userkeys:
|
for userkey in userkeys:
|
||||||
# Create a TemporaryPersistent file to work with.
|
# 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')
|
of = self.temporary_file('.epub')
|
||||||
|
|
||||||
# Give the user key, ebook and TemporaryPersistent file to the plugin_main function.
|
# 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.
|
# 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.
|
# This allows a non-encrypted epub to be imported without error messages.
|
||||||
|
|||||||
BIN
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Cipher/AES.pyd
Normal file
BIN
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Cipher/AES.pyd
Normal file
Binary file not shown.
BIN
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Hash/SHA256.pyd
Normal file
BIN
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Hash/SHA256.pyd
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
136
Calibre_Plugins/ineptepub_plugin/zipfix.py
Normal file
136
Calibre_Plugins/ineptepub_plugin/zipfix.py
Normal file
@@ -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())
|
||||||
|
|
||||||
|
|
||||||
BIN
Calibre_Plugins/k4mobidedrm_plugin.zip
Normal file
BIN
Calibre_Plugins/k4mobidedrm_plugin.zip
Normal file
Binary file not shown.
600
Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py
Normal file
600
Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py
Normal file
@@ -0,0 +1,600 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# engine to remove drm from Kindle for Mac and Kindle for PC books
|
||||||
|
# for personal use for archiving and converting your ebooks
|
||||||
|
|
||||||
|
# PLEASE DO NOT PIRATE EBOOKS!
|
||||||
|
|
||||||
|
# We want all authors and publishers, and eBook stores to live
|
||||||
|
# long and prosperous lives but at the same time we just want to
|
||||||
|
# be able to read OUR books on whatever device we want and to keep
|
||||||
|
# readable for a long, long time
|
||||||
|
|
||||||
|
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
|
||||||
|
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
|
||||||
|
# and many many others
|
||||||
|
|
||||||
|
# It can run standalone to convert K4M/K4PC/Mobi files, or it can be installed as a
|
||||||
|
# plugin for Calibre (http://calibre-ebook.com/about) so that importing
|
||||||
|
# K4 or Mobi with DRM is no londer a multi-step process.
|
||||||
|
#
|
||||||
|
# ***NOTE*** If you are using this script as a calibre plugin for a K4M or K4PC ebook
|
||||||
|
# then calibre must be installed on the same machine and in the same account as K4PC or K4M
|
||||||
|
# for the plugin version to function properly.
|
||||||
|
#
|
||||||
|
# To create a Calibre plugin, rename this file so that the filename
|
||||||
|
# ends in '_plugin.py', put it into a ZIP file with all its supporting python routines
|
||||||
|
# and import that ZIP into Calibre using its plugin configuration GUI.
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__version__ = '1.2'
|
||||||
|
|
||||||
|
class Unbuffered:
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
def write(self, data):
|
||||||
|
self.stream.write(data)
|
||||||
|
self.stream.flush()
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os, csv, getopt
|
||||||
|
import binascii
|
||||||
|
import zlib
|
||||||
|
import re
|
||||||
|
from struct import pack, unpack, unpack_from
|
||||||
|
|
||||||
|
|
||||||
|
#Exception Handling
|
||||||
|
class DrmException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
#
|
||||||
|
# crypto digestroutines
|
||||||
|
#
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
def MD5(message):
|
||||||
|
ctx = hashlib.md5()
|
||||||
|
ctx.update(message)
|
||||||
|
return ctx.digest()
|
||||||
|
|
||||||
|
def SHA1(message):
|
||||||
|
ctx = hashlib.sha1()
|
||||||
|
ctx.update(message)
|
||||||
|
return ctx.digest()
|
||||||
|
|
||||||
|
# determine if we are running as a calibre plugin
|
||||||
|
if 'calibre' in sys.modules:
|
||||||
|
inCalibre = True
|
||||||
|
global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
else:
|
||||||
|
inCalibre = False
|
||||||
|
|
||||||
|
#
|
||||||
|
# start of Kindle specific routines
|
||||||
|
#
|
||||||
|
|
||||||
|
if not inCalibre:
|
||||||
|
import mobidedrm
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
|
||||||
|
global kindleDatabase
|
||||||
|
|
||||||
|
# Encode the bytes in data with the characters in map
|
||||||
|
def encode(data, map):
|
||||||
|
result = ""
|
||||||
|
for char in data:
|
||||||
|
value = ord(char)
|
||||||
|
Q = (value ^ 0x80) // len(map)
|
||||||
|
R = value % len(map)
|
||||||
|
result += map[Q]
|
||||||
|
result += map[R]
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Hash the bytes in data and then encode the digest with the characters in map
|
||||||
|
def encodeHash(data,map):
|
||||||
|
return encode(MD5(data),map)
|
||||||
|
|
||||||
|
# Decode the string in data with the characters in map. Returns the decoded bytes
|
||||||
|
def decode(data,map):
|
||||||
|
result = ""
|
||||||
|
for i in range (0,len(data)-1,2):
|
||||||
|
high = map.find(data[i])
|
||||||
|
low = map.find(data[i+1])
|
||||||
|
if (high == -1) or (low == -1) :
|
||||||
|
break
|
||||||
|
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
|
||||||
|
result += pack("B",value)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Parse the Kindle.info file and return the records as a list of key-values
|
||||||
|
def parseKindleInfo(kInfoFile):
|
||||||
|
DB = {}
|
||||||
|
infoReader = openKindleInfo(kInfoFile)
|
||||||
|
infoReader.read(1)
|
||||||
|
data = infoReader.read()
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
items = data.split('{')
|
||||||
|
else :
|
||||||
|
items = data.split('[')
|
||||||
|
for item in items:
|
||||||
|
splito = item.split(':')
|
||||||
|
DB[splito[0]] =splito[1]
|
||||||
|
return DB
|
||||||
|
|
||||||
|
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
|
||||||
|
def getKindleInfoValueForHash(hashedKey):
|
||||||
|
global kindleDatabase
|
||||||
|
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
return CryptUnprotectData(encryptedValue,"")
|
||||||
|
else:
|
||||||
|
cleartext = CryptUnprotectData(encryptedValue)
|
||||||
|
return decode(cleartext, charMap1)
|
||||||
|
|
||||||
|
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
|
||||||
|
def getKindleInfoValueForKey(key):
|
||||||
|
return getKindleInfoValueForHash(encodeHash(key,charMap2))
|
||||||
|
|
||||||
|
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string.
|
||||||
|
def findNameForHash(hash):
|
||||||
|
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||||
|
result = ""
|
||||||
|
for name in names:
|
||||||
|
if hash == encodeHash(name, charMap2):
|
||||||
|
result = name
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Print all the records from the kindle.info file (option -i)
|
||||||
|
def printKindleInfo():
|
||||||
|
for record in kindleDatabase:
|
||||||
|
name = findNameForHash(record)
|
||||||
|
if name != "" :
|
||||||
|
print (name)
|
||||||
|
print ("--------------------------")
|
||||||
|
else :
|
||||||
|
print ("Unknown Record")
|
||||||
|
print getKindleInfoValueForHash(record)
|
||||||
|
print "\n"
|
||||||
|
|
||||||
|
#
|
||||||
|
# PID generation routines
|
||||||
|
#
|
||||||
|
|
||||||
|
# Returns two bit at offset from a bit field
|
||||||
|
def getTwoBitsFromBitField(bitField,offset):
|
||||||
|
byteNumber = offset // 4
|
||||||
|
bitPosition = 6 - 2*(offset % 4)
|
||||||
|
return ord(bitField[byteNumber]) >> bitPosition & 3
|
||||||
|
|
||||||
|
# Returns the six bits at offset from a bit field
|
||||||
|
def getSixBitsFromBitField(bitField,offset):
|
||||||
|
offset *= 3
|
||||||
|
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
|
||||||
|
return value
|
||||||
|
|
||||||
|
# 8 bits to six bits encoding from hash to generate PID string
|
||||||
|
def encodePID(hash):
|
||||||
|
global charMap3
|
||||||
|
PID = ""
|
||||||
|
for position in range (0,8):
|
||||||
|
PID += charMap3[getSixBitsFromBitField(hash,position)]
|
||||||
|
return PID
|
||||||
|
|
||||||
|
# Encryption table used to generate the device PID
|
||||||
|
def generatePidEncryptionTable() :
|
||||||
|
table = []
|
||||||
|
for counter1 in range (0,0x100):
|
||||||
|
value = counter1
|
||||||
|
for counter2 in range (0,8):
|
||||||
|
if (value & 1 == 0) :
|
||||||
|
value = value >> 1
|
||||||
|
else :
|
||||||
|
value = value >> 1
|
||||||
|
value = value ^ 0xEDB88320
|
||||||
|
table.append(value)
|
||||||
|
return table
|
||||||
|
|
||||||
|
# Seed value used to generate the device PID
|
||||||
|
def generatePidSeed(table,dsn) :
|
||||||
|
value = 0
|
||||||
|
for counter in range (0,4) :
|
||||||
|
index = (ord(dsn[counter]) ^ value) &0xFF
|
||||||
|
value = (value >> 8) ^ table[index]
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Generate the device PID
|
||||||
|
def generateDevicePID(table,dsn,nbRoll):
|
||||||
|
seed = generatePidSeed(table,dsn)
|
||||||
|
pidAscii = ""
|
||||||
|
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
|
||||||
|
index = 0
|
||||||
|
for counter in range (0,nbRoll):
|
||||||
|
pid[index] = pid[index] ^ ord(dsn[counter])
|
||||||
|
index = (index+1) %8
|
||||||
|
for counter in range (0,8):
|
||||||
|
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
||||||
|
pidAscii += charMap4[index]
|
||||||
|
return pidAscii
|
||||||
|
|
||||||
|
# convert from 8 digit PID to 10 digit PID with checksum
|
||||||
|
def checksumPid(s):
|
||||||
|
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||||
|
crc = crc ^ (crc >> 16)
|
||||||
|
res = s
|
||||||
|
l = len(letters)
|
||||||
|
for i in (0,1):
|
||||||
|
b = crc & 0xff
|
||||||
|
pos = (b // l) ^ (b % l)
|
||||||
|
res += letters[pos%l]
|
||||||
|
crc >>= 8
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class MobiPeek:
|
||||||
|
def loadSection(self, section):
|
||||||
|
before, after = self.sections[section:section+2]
|
||||||
|
self.f.seek(before)
|
||||||
|
return self.f.read(after - before)
|
||||||
|
def __init__(self, filename):
|
||||||
|
self.f = file(filename, 'rb')
|
||||||
|
self.header = self.f.read(78)
|
||||||
|
self.ident = self.header[0x3C:0x3C+8]
|
||||||
|
if self.ident != 'BOOKMOBI' and self.ident != 'TEXtREAd':
|
||||||
|
raise DrmException('invalid file format')
|
||||||
|
self.num_sections, = unpack_from('>H', self.header, 76)
|
||||||
|
sections = self.f.read(self.num_sections*8)
|
||||||
|
self.sections = unpack_from('>%dL' % (self.num_sections*2), sections, 0)[::2] + (0xfffffff, )
|
||||||
|
self.sect0 = self.loadSection(0)
|
||||||
|
self.f.close()
|
||||||
|
def getBookTitle(self):
|
||||||
|
# get book title
|
||||||
|
toff, tlen = unpack('>II', self.sect0[0x54:0x5c])
|
||||||
|
tend = toff + tlen
|
||||||
|
title = self.sect0[toff:tend]
|
||||||
|
return title
|
||||||
|
def getexthData(self):
|
||||||
|
# if exth region exists then grab it
|
||||||
|
# get length of this header
|
||||||
|
length, type, codepage, unique_id, version = unpack('>LLLLL', self.sect0[20:40])
|
||||||
|
exth_flag, = unpack('>L', self.sect0[0x80:0x84])
|
||||||
|
exth = ''
|
||||||
|
if exth_flag & 0x40:
|
||||||
|
exth = self.sect0[16 + length:]
|
||||||
|
return exth
|
||||||
|
def isNotEncrypted(self):
|
||||||
|
lock_type, = unpack('>H', self.sect0[0xC:0xC+2])
|
||||||
|
if lock_type == 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
|
||||||
|
# file to calculate the book pid.
|
||||||
|
def getK4Pids(exth, title, kInfoFile=None):
|
||||||
|
global kindleDatabase
|
||||||
|
try:
|
||||||
|
kindleDatabase = parseKindleInfo(kInfoFile)
|
||||||
|
except Exception, message:
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
if kindleDatabase != None :
|
||||||
|
# Get the Mazama Random number
|
||||||
|
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
|
||||||
|
|
||||||
|
# Get the HDD serial
|
||||||
|
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
|
||||||
|
|
||||||
|
# Get the current user name
|
||||||
|
encodedUsername = encodeHash(GetUserName(),charMap1)
|
||||||
|
|
||||||
|
# concat, hash and encode to calculate the DSN
|
||||||
|
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
|
||||||
|
|
||||||
|
print("\nDSN: " + DSN)
|
||||||
|
|
||||||
|
# Compute the device PID (for which I can tell, is used for nothing).
|
||||||
|
# But hey, stuff being printed out is apparently cool.
|
||||||
|
table = generatePidEncryptionTable()
|
||||||
|
devicePID = generateDevicePID(table,DSN,4)
|
||||||
|
|
||||||
|
print("Device PID: " + checksumPid(devicePID))
|
||||||
|
|
||||||
|
# Compute book PID
|
||||||
|
exth_records = {}
|
||||||
|
nitems, = unpack('>I', exth[8:12])
|
||||||
|
pos = 12
|
||||||
|
|
||||||
|
exth_records[209] = None
|
||||||
|
# Parse the exth records, storing data indexed by type
|
||||||
|
for i in xrange(nitems):
|
||||||
|
type, size = unpack('>II', exth[pos: pos + 8])
|
||||||
|
content = exth[pos + 8: pos + size]
|
||||||
|
|
||||||
|
exth_records[type] = content
|
||||||
|
pos += size
|
||||||
|
|
||||||
|
# Grab the contents of the type 209 exth record
|
||||||
|
if exth_records[209] != None:
|
||||||
|
data = exth_records[209]
|
||||||
|
else:
|
||||||
|
raise DrmException("\nNo EXTH record type 209 - Perhaps not a K4 file?")
|
||||||
|
|
||||||
|
# Parse the 209 data to find the the exth record with the token data.
|
||||||
|
# The last character of the 209 data points to the record with the token.
|
||||||
|
# Always 208 from my experience, but I'll leave the logic in case that changes.
|
||||||
|
for i in xrange(len(data)):
|
||||||
|
if ord(data[i]) != 0:
|
||||||
|
if exth_records[ord(data[i])] != None:
|
||||||
|
token = exth_records[ord(data[i])]
|
||||||
|
|
||||||
|
# Get the kindle account token
|
||||||
|
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
|
||||||
|
|
||||||
|
print("Account Token: " + kindleAccountToken)
|
||||||
|
|
||||||
|
pidHash = SHA1(DSN+kindleAccountToken+exth_records[209]+token)
|
||||||
|
|
||||||
|
bookPID = encodePID(pidHash)
|
||||||
|
bookPID = checksumPid(bookPID)
|
||||||
|
|
||||||
|
if exth_records[503] != None:
|
||||||
|
print "Pid for " + exth_records[503] + ": " + bookPID
|
||||||
|
else:
|
||||||
|
print "Pid for " + title + ":" + bookPID
|
||||||
|
return bookPID
|
||||||
|
|
||||||
|
raise DrmException("\nCould not access K4 data - Perhaps K4 is not installed/configured?")
|
||||||
|
return null
|
||||||
|
|
||||||
|
def usage(progname):
|
||||||
|
print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
|
||||||
|
print "Usage:"
|
||||||
|
print " %s [-k <kindle.info>] [-p <pidnums>] <infile> <outfile> " % progname
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main
|
||||||
|
#
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
global kindleDatabase
|
||||||
|
import mobidedrm
|
||||||
|
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
kInfoFiles = []
|
||||||
|
pidnums = ""
|
||||||
|
|
||||||
|
print ('K4MobiDeDrm v%(__version__)s '
|
||||||
|
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
|
||||||
|
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "k:p:")
|
||||||
|
except getopt.GetoptError, err:
|
||||||
|
print str(err)
|
||||||
|
usage(progname)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
if len(args)<2:
|
||||||
|
usage(progname)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
for o, a in opts:
|
||||||
|
if o == "-k":
|
||||||
|
if a == None :
|
||||||
|
raise DrmException("Invalid parameter for -k")
|
||||||
|
kInfoFiles.append(a)
|
||||||
|
if o == "-p":
|
||||||
|
if a == None :
|
||||||
|
raise DrmException("Invalid parameter for -p")
|
||||||
|
pidnums = a
|
||||||
|
|
||||||
|
kindleDatabase = None
|
||||||
|
infile = args[0]
|
||||||
|
outfile = args[1]
|
||||||
|
DecodeErrorString = ""
|
||||||
|
try:
|
||||||
|
# first try with K4PC/K4M
|
||||||
|
ex = MobiPeek(infile)
|
||||||
|
if ex.isNotEncrypted():
|
||||||
|
print "File was Not Encrypted"
|
||||||
|
return 2
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||||
|
except DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file(outfile, 'wb').write(unlocked_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# now try alternate kindle.info files
|
||||||
|
if kInfoFiles:
|
||||||
|
for infoFile in kInfoFiles:
|
||||||
|
kindleDatabase = None
|
||||||
|
try:
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title, infoFile)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||||
|
except DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file(outfile, 'wb').write(unlocked_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Lastly, try from the pid list
|
||||||
|
pids = pidnums.split(',')
|
||||||
|
for pid in pids:
|
||||||
|
try:
|
||||||
|
print 'Trying: "'+ pid + '"'
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file(outfile, 'wb').write(unlocked_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# we could not unencrypt book
|
||||||
|
print DecodeErrorString
|
||||||
|
print "Error: Could Not Unencrypt Book"
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
|
sys.exit(main())
|
||||||
|
|
||||||
|
|
||||||
|
if not __name__ == "__main__" and inCalibre:
|
||||||
|
from calibre.customize import FileTypePlugin
|
||||||
|
|
||||||
|
class K4DeDRM(FileTypePlugin):
|
||||||
|
name = 'K4PC, K4Mac, Mobi DeDRM' # Name of the plugin
|
||||||
|
description = 'Removes DRM from K4PC, K4Mac, and Mobi files. \
|
||||||
|
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
|
||||||
|
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
|
||||||
|
author = 'DiapDealer, SomeUpdates' # The author of this plugin
|
||||||
|
version = (0, 1, 4) # The version number of this plugin
|
||||||
|
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
|
||||||
|
on_import = True # Run this plugin during the import
|
||||||
|
priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
|
||||||
|
|
||||||
|
def run(self, path_to_ebook):
|
||||||
|
from calibre.gui2 import is_ok_to_use_qt
|
||||||
|
from PyQt4.Qt import QMessageBox
|
||||||
|
|
||||||
|
# Head Topaz files off at the pass and warn the user that they will NOT
|
||||||
|
# be decrypted. Changes the file extension from .azw or .prc to .tpz so
|
||||||
|
# Calibre can at least read the metadata properly and the user can find
|
||||||
|
# them by sorting on 'format'.
|
||||||
|
with open(path_to_ebook, 'rb') as f:
|
||||||
|
raw = f.read()
|
||||||
|
if raw.startswith('TPZ'):
|
||||||
|
tf = self.temporary_file('.tpz')
|
||||||
|
if is_ok_to_use_qt():
|
||||||
|
d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "%s is a Topaz book. It will NOT be decrypted!" % path_to_ebook)
|
||||||
|
d.show()
|
||||||
|
d.raise_()
|
||||||
|
d.exec_()
|
||||||
|
tf.write(raw)
|
||||||
|
tf.close
|
||||||
|
return tf.name
|
||||||
|
|
||||||
|
global kindleDatabase
|
||||||
|
global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
import mobidedrm
|
||||||
|
|
||||||
|
# Get supplied list of PIDs to try from plugin customization.
|
||||||
|
pidnums = self.site_customization
|
||||||
|
|
||||||
|
# Load any kindle info files (*.info) included Calibre's config directory.
|
||||||
|
kInfoFiles = []
|
||||||
|
try:
|
||||||
|
# Find Calibre's configuration directory.
|
||||||
|
confpath = os.path.split(os.path.split(self.plugin_path)[0])[0]
|
||||||
|
print 'K4MobiDeDRM: Calibre configuration directory = %s' % confpath
|
||||||
|
files = os.listdir(confpath)
|
||||||
|
filefilter = re.compile("\.info$", re.IGNORECASE)
|
||||||
|
files = filter(filefilter.search, files)
|
||||||
|
|
||||||
|
if files:
|
||||||
|
for filename in files:
|
||||||
|
fpath = os.path.join(confpath, filename)
|
||||||
|
kInfoFiles.append(fpath)
|
||||||
|
print 'K4MobiDeDRM: Kindle info file %s found in config folder.' % filename
|
||||||
|
except IOError:
|
||||||
|
print 'K4MobiDeDRM: Error reading kindle info files from config directory.'
|
||||||
|
pass
|
||||||
|
|
||||||
|
# first try with book specifc pid from K4PC or K4M
|
||||||
|
try:
|
||||||
|
kindleDatabase = None
|
||||||
|
ex = MobiPeek(path_to_ebook)
|
||||||
|
if ex.isNotEncrypted():
|
||||||
|
return path_to_ebook
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
|
||||||
|
except DrmException:
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
of = self.temporary_file('.mobi')
|
||||||
|
of.write(unlocked_file)
|
||||||
|
of.close()
|
||||||
|
return of.name
|
||||||
|
|
||||||
|
# Now try alternate kindle info files
|
||||||
|
if kInfoFiles:
|
||||||
|
for infoFile in kInfoFiles:
|
||||||
|
kindleDatabase = None
|
||||||
|
try:
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title, infoFile)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
|
||||||
|
except DrmException:
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
of = self.temporary_file('.mobi')
|
||||||
|
of.write(unlocked_file)
|
||||||
|
of.close()
|
||||||
|
return of.name
|
||||||
|
|
||||||
|
# now try from the pid list
|
||||||
|
pids = pidnums.split(',')
|
||||||
|
for pid in pids:
|
||||||
|
try:
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook, pid)
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
of = self.temporary_file('.mobi')
|
||||||
|
of.write(unlocked_file)
|
||||||
|
of.close()
|
||||||
|
return of.name
|
||||||
|
|
||||||
|
#if you reached here then no luck raise and exception
|
||||||
|
if is_ok_to_use_qt():
|
||||||
|
d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
|
||||||
|
d.show()
|
||||||
|
d.raise_()
|
||||||
|
d.exec_()
|
||||||
|
raise Exception("K4MobiDeDRM plugin could not decode the file")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def customization_help(self, gui=False):
|
||||||
|
return 'Enter each 10 character PID separated by a comma (no spaces).'
|
||||||
334
Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py
Normal file
334
Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
# standlone set of Mac OSX specific routines needed for K4DeDRM
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
#Exception Handling
|
||||||
|
class K4MDrmException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
import signal
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
from subprocess import Popen, PIPE, STDOUT
|
||||||
|
|
||||||
|
# **heavily** chopped up and modfied version of asyncproc.py
|
||||||
|
# to make it actually work on Windows as well as Mac/Linux
|
||||||
|
# For the original see:
|
||||||
|
# "http://www.lysator.liu.se/~bellman/download/"
|
||||||
|
# author is "Thomas Bellman <bellman@lysator.liu.se>"
|
||||||
|
# available under GPL version 3 or Later
|
||||||
|
|
||||||
|
# create an asynchronous subprocess whose output can be collected in
|
||||||
|
# a non-blocking manner
|
||||||
|
|
||||||
|
# What a mess! Have to use threads just to get non-blocking io
|
||||||
|
# in a cross-platform manner
|
||||||
|
|
||||||
|
# luckily all thread use is hidden within this class
|
||||||
|
|
||||||
|
class Process(object):
|
||||||
|
def __init__(self, *params, **kwparams):
|
||||||
|
if len(params) <= 3:
|
||||||
|
kwparams.setdefault('stdin', subprocess.PIPE)
|
||||||
|
if len(params) <= 4:
|
||||||
|
kwparams.setdefault('stdout', subprocess.PIPE)
|
||||||
|
if len(params) <= 5:
|
||||||
|
kwparams.setdefault('stderr', subprocess.PIPE)
|
||||||
|
self.__pending_input = []
|
||||||
|
self.__collected_outdata = []
|
||||||
|
self.__collected_errdata = []
|
||||||
|
self.__exitstatus = None
|
||||||
|
self.__lock = threading.Lock()
|
||||||
|
self.__inputsem = threading.Semaphore(0)
|
||||||
|
self.__quit = False
|
||||||
|
|
||||||
|
self.__process = subprocess.Popen(*params, **kwparams)
|
||||||
|
|
||||||
|
if self.__process.stdin:
|
||||||
|
self.__stdin_thread = threading.Thread(
|
||||||
|
name="stdin-thread",
|
||||||
|
target=self.__feeder, args=(self.__pending_input,
|
||||||
|
self.__process.stdin))
|
||||||
|
self.__stdin_thread.setDaemon(True)
|
||||||
|
self.__stdin_thread.start()
|
||||||
|
|
||||||
|
if self.__process.stdout:
|
||||||
|
self.__stdout_thread = threading.Thread(
|
||||||
|
name="stdout-thread",
|
||||||
|
target=self.__reader, args=(self.__collected_outdata,
|
||||||
|
self.__process.stdout))
|
||||||
|
self.__stdout_thread.setDaemon(True)
|
||||||
|
self.__stdout_thread.start()
|
||||||
|
|
||||||
|
if self.__process.stderr:
|
||||||
|
self.__stderr_thread = threading.Thread(
|
||||||
|
name="stderr-thread",
|
||||||
|
target=self.__reader, args=(self.__collected_errdata,
|
||||||
|
self.__process.stderr))
|
||||||
|
self.__stderr_thread.setDaemon(True)
|
||||||
|
self.__stderr_thread.start()
|
||||||
|
|
||||||
|
def pid(self):
|
||||||
|
return self.__process.pid
|
||||||
|
|
||||||
|
def kill(self, signal):
|
||||||
|
self.__process.send_signal(signal)
|
||||||
|
|
||||||
|
# check on subprocess (pass in 'nowait') to act like poll
|
||||||
|
def wait(self, flag):
|
||||||
|
if flag.lower() == 'nowait':
|
||||||
|
rc = self.__process.poll()
|
||||||
|
else:
|
||||||
|
rc = self.__process.wait()
|
||||||
|
if rc != None:
|
||||||
|
if self.__process.stdin:
|
||||||
|
self.closeinput()
|
||||||
|
if self.__process.stdout:
|
||||||
|
self.__stdout_thread.join()
|
||||||
|
if self.__process.stderr:
|
||||||
|
self.__stderr_thread.join()
|
||||||
|
return self.__process.returncode
|
||||||
|
|
||||||
|
def terminate(self):
|
||||||
|
if self.__process.stdin:
|
||||||
|
self.closeinput()
|
||||||
|
self.__process.terminate()
|
||||||
|
|
||||||
|
# thread gets data from subprocess stdout
|
||||||
|
def __reader(self, collector, source):
|
||||||
|
while True:
|
||||||
|
data = os.read(source.fileno(), 65536)
|
||||||
|
self.__lock.acquire()
|
||||||
|
collector.append(data)
|
||||||
|
self.__lock.release()
|
||||||
|
if data == "":
|
||||||
|
source.close()
|
||||||
|
break
|
||||||
|
return
|
||||||
|
|
||||||
|
# thread feeds data to subprocess stdin
|
||||||
|
def __feeder(self, pending, drain):
|
||||||
|
while True:
|
||||||
|
self.__inputsem.acquire()
|
||||||
|
self.__lock.acquire()
|
||||||
|
if not pending and self.__quit:
|
||||||
|
drain.close()
|
||||||
|
self.__lock.release()
|
||||||
|
break
|
||||||
|
data = pending.pop(0)
|
||||||
|
self.__lock.release()
|
||||||
|
drain.write(data)
|
||||||
|
|
||||||
|
# non-blocking read of data from subprocess stdout
|
||||||
|
def read(self):
|
||||||
|
self.__lock.acquire()
|
||||||
|
outdata = "".join(self.__collected_outdata)
|
||||||
|
del self.__collected_outdata[:]
|
||||||
|
self.__lock.release()
|
||||||
|
return outdata
|
||||||
|
|
||||||
|
# non-blocking read of data from subprocess stderr
|
||||||
|
def readerr(self):
|
||||||
|
self.__lock.acquire()
|
||||||
|
errdata = "".join(self.__collected_errdata)
|
||||||
|
del self.__collected_errdata[:]
|
||||||
|
self.__lock.release()
|
||||||
|
return errdata
|
||||||
|
|
||||||
|
# non-blocking write to stdin of subprocess
|
||||||
|
def write(self, data):
|
||||||
|
if self.__process.stdin is None:
|
||||||
|
raise ValueError("Writing to process with stdin not a pipe")
|
||||||
|
self.__lock.acquire()
|
||||||
|
self.__pending_input.append(data)
|
||||||
|
self.__inputsem.release()
|
||||||
|
self.__lock.release()
|
||||||
|
|
||||||
|
# close stdinput of subprocess
|
||||||
|
def closeinput(self):
|
||||||
|
self.__lock.acquire()
|
||||||
|
self.__quit = True
|
||||||
|
self.__inputsem.release()
|
||||||
|
self.__lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
# interface to needed routines in openssl's libcrypto
|
||||||
|
def _load_crypto_libcrypto():
|
||||||
|
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
||||||
|
Structure, c_ulong, create_string_buffer, addressof, string_at, cast
|
||||||
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
if libcrypto is None:
|
||||||
|
raise K4MDrmException('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)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
|
||||||
|
|
||||||
|
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
|
||||||
|
|
||||||
|
PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
|
||||||
|
[c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
||||||
|
|
||||||
|
class LibCrypto(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._blocksize = 0
|
||||||
|
self._keyctx = None
|
||||||
|
self.iv = 0
|
||||||
|
|
||||||
|
def set_decrypt_key(self, userkey, iv):
|
||||||
|
self._blocksize = len(userkey)
|
||||||
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
|
raise K4MDrmException('AES improper key used')
|
||||||
|
return
|
||||||
|
keyctx = self._keyctx = AES_KEY()
|
||||||
|
self.iv = iv
|
||||||
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
|
||||||
|
if rv < 0:
|
||||||
|
raise K4MDrmException('Failed to initialize AES key')
|
||||||
|
|
||||||
|
def decrypt(self, data):
|
||||||
|
out = create_string_buffer(len(data))
|
||||||
|
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
|
||||||
|
if rv == 0:
|
||||||
|
raise K4MDrmException('AES decryption failed')
|
||||||
|
return out.raw
|
||||||
|
|
||||||
|
def keyivgen(self, passwd):
|
||||||
|
salt = '16743'
|
||||||
|
saltlen = 5
|
||||||
|
passlen = len(passwd)
|
||||||
|
iter = 0x3e8
|
||||||
|
keylen = 80
|
||||||
|
out = create_string_buffer(keylen)
|
||||||
|
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
|
||||||
|
return out.raw
|
||||||
|
return LibCrypto
|
||||||
|
|
||||||
|
def _load_crypto():
|
||||||
|
LibCrypto = None
|
||||||
|
try:
|
||||||
|
LibCrypto = _load_crypto_libcrypto()
|
||||||
|
except (ImportError, K4MDrmException):
|
||||||
|
pass
|
||||||
|
return LibCrypto
|
||||||
|
|
||||||
|
LibCrypto = _load_crypto()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Utility Routines
|
||||||
|
#
|
||||||
|
|
||||||
|
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||||
|
# returns with the serial number of drive whose BSD Name is "disk0"
|
||||||
|
def GetVolumeSerialNumber():
|
||||||
|
sernum = os.getenv('MYSERIALNUMBER')
|
||||||
|
if sernum != None:
|
||||||
|
return sernum
|
||||||
|
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
||||||
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
|
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||||
|
poll = p.wait('wait')
|
||||||
|
results = p.read()
|
||||||
|
reslst = results.split('\n')
|
||||||
|
cnt = len(reslst)
|
||||||
|
bsdname = None
|
||||||
|
sernum = None
|
||||||
|
foundIt = False
|
||||||
|
for j in xrange(cnt):
|
||||||
|
resline = reslst[j]
|
||||||
|
pp = resline.find('"Serial Number" = "')
|
||||||
|
if pp >= 0:
|
||||||
|
sernum = resline[pp+19:-1]
|
||||||
|
sernum = sernum.strip()
|
||||||
|
bb = resline.find('"BSD Name" = "')
|
||||||
|
if bb >= 0:
|
||||||
|
bsdname = resline[bb+14:-1]
|
||||||
|
bsdname = bsdname.strip()
|
||||||
|
if (bsdname == 'disk0') and (sernum != None):
|
||||||
|
foundIt = True
|
||||||
|
break
|
||||||
|
if not foundIt:
|
||||||
|
sernum = '9999999999'
|
||||||
|
return sernum
|
||||||
|
|
||||||
|
# uses unix env to get username instead of using sysctlbyname
|
||||||
|
def GetUserName():
|
||||||
|
username = os.getenv('USER')
|
||||||
|
return username
|
||||||
|
|
||||||
|
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||||||
|
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||||
|
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
|
||||||
|
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||||
|
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
|
||||||
|
def encode(data, map):
|
||||||
|
result = ""
|
||||||
|
for char in data:
|
||||||
|
value = ord(char)
|
||||||
|
Q = (value ^ 0x80) // len(map)
|
||||||
|
R = value % len(map)
|
||||||
|
result += map[Q]
|
||||||
|
result += map[R]
|
||||||
|
return result
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
def SHA256(message):
|
||||||
|
ctx = hashlib.sha256()
|
||||||
|
ctx.update(message)
|
||||||
|
return ctx.digest()
|
||||||
|
|
||||||
|
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||||
|
def CryptUnprotectData(encryptedData):
|
||||||
|
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
|
||||||
|
passwdData = encode(SHA256(sp),charMap1)
|
||||||
|
crp = LibCrypto()
|
||||||
|
key_iv = crp.keyivgen(passwdData)
|
||||||
|
key = key_iv[0:32]
|
||||||
|
iv = key_iv[32:48]
|
||||||
|
crp.set_decrypt_key(key,iv)
|
||||||
|
cleartext = crp.decrypt(encryptedData)
|
||||||
|
return cleartext
|
||||||
|
|
||||||
|
# Locate and open the .kindle-info file
|
||||||
|
def openKindleInfo(kInfoFile=None):
|
||||||
|
if kInfoFile == None:
|
||||||
|
home = os.getenv('HOME')
|
||||||
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
|
||||||
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
|
p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||||
|
poll = p1.wait('wait')
|
||||||
|
results = p1.read()
|
||||||
|
reslst = results.split('\n')
|
||||||
|
kinfopath = 'NONE'
|
||||||
|
cnt = len(reslst)
|
||||||
|
for j in xrange(cnt):
|
||||||
|
resline = reslst[j]
|
||||||
|
pp = resline.find('.kindle-info')
|
||||||
|
if pp >= 0:
|
||||||
|
kinfopath = resline
|
||||||
|
break
|
||||||
|
if not os.path.exists(kinfopath):
|
||||||
|
raise K4MDrmException('Error: .kindle-info file can not be found')
|
||||||
|
return open(kinfopath,'r')
|
||||||
|
else:
|
||||||
|
return open(kInfoFile, 'r')
|
||||||
110
Calibre_Plugins/k4mobidedrm_plugin/k4pcutils.py
Normal file
110
Calibre_Plugins/k4mobidedrm_plugin/k4pcutils.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# K4PC Windows specific routines
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
import _winreg as winreg
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
MAX_PATH = 255
|
||||||
|
|
||||||
|
kernel32 = windll.kernel32
|
||||||
|
advapi32 = windll.advapi32
|
||||||
|
crypt32 = windll.crypt32
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||||||
|
#
|
||||||
|
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||||
|
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
||||||
|
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||||
|
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Exceptions for all the problems that might happen during the script
|
||||||
|
#
|
||||||
|
class DrmException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DataBlob(Structure):
|
||||||
|
_fields_ = [('cbData', c_uint),
|
||||||
|
('pbData', c_void_p)]
|
||||||
|
DataBlob_p = POINTER(DataBlob)
|
||||||
|
|
||||||
|
|
||||||
|
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 = GetSystemDirectory().split('\\')[0] + '\\'):
|
||||||
|
vsn = c_uint(0)
|
||||||
|
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
|
||||||
|
return str(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()
|
||||||
|
|
||||||
|
|
||||||
|
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 DrmException("Failed to Unprotect Data")
|
||||||
|
return string_at(outdata.pbData, outdata.cbData)
|
||||||
|
return CryptUnprotectData
|
||||||
|
CryptUnprotectData = CryptUnprotectData()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Locate and open the Kindle.info file.
|
||||||
|
#
|
||||||
|
def openKindleInfo(kInfoFile=None):
|
||||||
|
if kInfoFile == None:
|
||||||
|
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
||||||
|
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||||
|
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
|
||||||
|
else:
|
||||||
|
return open(kInfoFile, 'r')
|
||||||
@@ -3,14 +3,6 @@
|
|||||||
# This is a python script. You need a Python interpreter to run it.
|
# This is a python script. You need a Python interpreter to run it.
|
||||||
# For example, ActiveState Python, which exists for windows.
|
# For example, ActiveState Python, which exists for windows.
|
||||||
#
|
#
|
||||||
# It can run standalone to convert files, or it can be installed as a
|
|
||||||
# plugin for Calibre (http://calibre-ebook.com/about) so that
|
|
||||||
# importing files with DRM 'Just Works'.
|
|
||||||
#
|
|
||||||
# To create a Calibre plugin, rename this file so that the filename
|
|
||||||
# ends in '_plugin.py', put it into a ZIP file and import that Calibre
|
|
||||||
# using its plugin configuration GUI.
|
|
||||||
#
|
|
||||||
# Changelog
|
# Changelog
|
||||||
# 0.01 - Initial version
|
# 0.01 - Initial version
|
||||||
# 0.02 - Huffdic compressed books were not properly decrypted
|
# 0.02 - Huffdic compressed books were not properly decrypted
|
||||||
@@ -37,10 +29,19 @@
|
|||||||
# in utf8 file are encrypted. (Although neither kind gets compressed.)
|
# in utf8 file are encrypted. (Although neither kind gets compressed.)
|
||||||
# This knowledge leads to a simplification of the test for the
|
# This knowledge leads to a simplification of the test for the
|
||||||
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
|
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
|
||||||
# 0.15 - Now outputs 'hearbeat', and is also quicker for long files.
|
# 0.15 - Now outputs 'heartbeat', and is also quicker for long files.
|
||||||
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
|
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
|
||||||
|
# 0.17 - added modifications to support its use as an imported python module
|
||||||
|
# both inside calibre and also in other places (ie K4DeDRM tools)
|
||||||
|
# 0.17a- disabled the standalone plugin feature since a plugin can not import
|
||||||
|
# a plugin
|
||||||
|
# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
|
||||||
|
# Removed the disabled Calibre plug-in code
|
||||||
|
# Permit use of 8-digit PIDs
|
||||||
|
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
|
||||||
|
# 0.20 - Corretion: It seems that multibyte entries are encrypted in a v6 file.
|
||||||
|
|
||||||
__version__ = '0.16'
|
__version__ = '0.20'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
@@ -123,10 +124,11 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
|
|||||||
if testflags & 1:
|
if testflags & 1:
|
||||||
num += getSizeOfTrailingDataEntry(ptr, size - num)
|
num += getSizeOfTrailingDataEntry(ptr, size - num)
|
||||||
testflags >>= 1
|
testflags >>= 1
|
||||||
# Multibyte data, if present, is included in the encryption, so
|
# Check the low bit to see if there's multibyte data present.
|
||||||
# we do not need to check the low bit.
|
# if multibyte data is included in the encryped data, we'll
|
||||||
# if flags & 1:
|
# have already cleared this flag.
|
||||||
# num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
if flags & 1:
|
||||||
|
num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
||||||
return num
|
return num
|
||||||
|
|
||||||
class DrmStripper:
|
class DrmStripper:
|
||||||
@@ -177,9 +179,14 @@ class DrmStripper:
|
|||||||
return found_key
|
return found_key
|
||||||
|
|
||||||
def __init__(self, data_file, pid):
|
def __init__(self, data_file, pid):
|
||||||
if checksumPid(pid[0:-2]) != pid:
|
if len(pid)==10:
|
||||||
raise DrmException("invalid PID checksum")
|
if checksumPid(pid[0:-2]) != pid:
|
||||||
pid = pid[0:-2]
|
raise DrmException("invalid PID checksum")
|
||||||
|
pid = pid[0:-2]
|
||||||
|
elif len(pid)==8:
|
||||||
|
print "PID without checksum given. With checksum PID is "+checksumPid(pid)
|
||||||
|
else:
|
||||||
|
raise DrmException("Invalid PID length")
|
||||||
|
|
||||||
self.data_file = data_file
|
self.data_file = data_file
|
||||||
header = data_file[0:72]
|
header = data_file[0:72]
|
||||||
@@ -202,6 +209,10 @@ class DrmStripper:
|
|||||||
if (mobi_length >= 0xE4) and (mobi_version >= 5):
|
if (mobi_length >= 0xE4) and (mobi_version >= 5):
|
||||||
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
|
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
|
||||||
print "Extra Data Flags = %d" %extra_data_flags
|
print "Extra Data Flags = %d" %extra_data_flags
|
||||||
|
if mobi_version < 7:
|
||||||
|
# multibyte utf8 data is included in the encryption for mobi_version 6 and below
|
||||||
|
# so clear that byte so that we leave it to be decrypted.
|
||||||
|
extra_data_flags &= 0xFFFE
|
||||||
|
|
||||||
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
|
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
|
||||||
if crypto_type == 0:
|
if crypto_type == 0:
|
||||||
@@ -248,63 +259,33 @@ class DrmStripper:
|
|||||||
def getResult(self):
|
def getResult(self):
|
||||||
return self.data_file
|
return self.data_file
|
||||||
|
|
||||||
if not __name__ == "__main__":
|
def getUnencryptedBook(infile,pid):
|
||||||
from calibre.customize import FileTypePlugin
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
|
data_file = file(infile, 'rb').read()
|
||||||
|
strippedFile = DrmStripper(data_file, pid)
|
||||||
|
return strippedFile.getResult()
|
||||||
|
|
||||||
class MobiDeDRM(FileTypePlugin):
|
def main(argv=sys.argv):
|
||||||
name = 'MobiDeDRM' # Name of the plugin
|
|
||||||
description = 'Removes DRM from secure Mobi files'
|
|
||||||
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
|
|
||||||
author = 'The Dark Reverser' # The author of this plugin
|
|
||||||
version = (0, 1, 6) # The version number of this plugin
|
|
||||||
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
|
|
||||||
on_import = True # Run this plugin during the import
|
|
||||||
|
|
||||||
def run(self, path_to_ebook):
|
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
|
||||||
from PyQt4.Qt import QMessageBox
|
|
||||||
PID = self.site_customization
|
|
||||||
data_file = file(path_to_ebook, 'rb').read()
|
|
||||||
ar = PID.split(',')
|
|
||||||
for i in ar:
|
|
||||||
try:
|
|
||||||
unlocked_file = DrmStripper(data_file, i).getResult()
|
|
||||||
except DrmException:
|
|
||||||
# ignore the error
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
of = self.temporary_file('.mobi')
|
|
||||||
of.write(unlocked_file)
|
|
||||||
of.close()
|
|
||||||
return of.name
|
|
||||||
if is_ok_to_use_qt():
|
|
||||||
d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
|
|
||||||
d.show()
|
|
||||||
d.raise_()
|
|
||||||
d.exec_()
|
|
||||||
return path_to_ebook
|
|
||||||
|
|
||||||
def customization_help(self, gui=False):
|
|
||||||
return 'Enter PID (separate multiple PIDs with comma)'
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.stdout=Unbuffered(sys.stdout)
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
print ('MobiDeDrm v%(__version__)s. '
|
||||||
'Copyright 2008-2010 The Dark Reverser.' % globals())
|
'Copyright 2008-2010 The Dark Reverser.' % globals())
|
||||||
if len(sys.argv)<4:
|
if len(argv)<4:
|
||||||
print "Removes protection from Mobipocket books"
|
print "Removes protection from Mobipocket books"
|
||||||
print "Usage:"
|
print "Usage:"
|
||||||
print " %s <infile> <outfile> <PID>" % sys.argv[0]
|
print " %s <infile> <outfile> <PID>" % sys.argv[0]
|
||||||
sys.exit(1)
|
return 1
|
||||||
else:
|
else:
|
||||||
infile = sys.argv[1]
|
infile = argv[1]
|
||||||
outfile = sys.argv[2]
|
outfile = argv[2]
|
||||||
pid = sys.argv[3]
|
pid = argv[3]
|
||||||
data_file = file(infile, 'rb').read()
|
|
||||||
try:
|
try:
|
||||||
strippedFile = DrmStripper(data_file, pid)
|
stripped_file = getUnencryptedBook(infile, pid)
|
||||||
file(outfile, 'wb').write(strippedFile.getResult())
|
file(outfile, 'wb').write(stripped_file)
|
||||||
except DrmException, e:
|
except DrmException, e:
|
||||||
print "Error: %s" % e
|
print "Error: %s" % e
|
||||||
sys.exit(1)
|
return 1
|
||||||
sys.exit(0)
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
1172
DeDRM_Macintosh_Application/DeDRM.app.txt
Normal file
1172
DeDRM_Macintosh_Application/DeDRM.app.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,16 +23,22 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>droplet</string>
|
<string>droplet</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>DeDRM 1.3, Copyright © 2010 by Apprentice Alf.</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>droplet</string>
|
<string>droplet</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>Mobipocket Unlocker 9</string>
|
<string>DeDRM</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.3</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>dplt</string>
|
<string>dplt</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.5.0</string>
|
||||||
<key>LSRequiresCarbon</key>
|
<key>LSRequiresCarbon</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>WindowState</key>
|
<key>WindowState</key>
|
||||||
@@ -40,9 +46,9 @@
|
|||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>ScriptWindowState</string>
|
<string>ScriptWindowState</string>
|
||||||
<key>positionOfDivider</key>
|
<key>positionOfDivider</key>
|
||||||
<real>422</real>
|
<real>739</real>
|
||||||
<key>savedFrame</key>
|
<key>savedFrame</key>
|
||||||
<string>91 171 1059 678 0 0 1440 878 </string>
|
<string>1533 -24 1262 818 1440 -150 1680 1050 </string>
|
||||||
<key>selectedTabView</key>
|
<key>selectedTabView</key>
|
||||||
<string>result</string>
|
<string>result</string>
|
||||||
</dict>
|
</dict>
|
||||||
Binary file not shown.
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>DeDRM Progress</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.apprenticealf.DeDRMProgress</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>DeDRM Progress</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>NSAppleScriptEnabled</key>
|
||||||
|
<string>YES</string>
|
||||||
|
<key>NSMainNibFile</key>
|
||||||
|
<string>MainMenu</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
APPL????
|
||||||
Binary file not shown.
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IBClasses</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CLASS</key>
|
||||||
|
<string>FirstResponder</string>
|
||||||
|
<key>LANGUAGE</key>
|
||||||
|
<string>ObjC</string>
|
||||||
|
<key>SUPERCLASS</key>
|
||||||
|
<string>NSObject</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>IBVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IBFramework Version</key>
|
||||||
|
<string>680</string>
|
||||||
|
<key>IBLastKnownRelativeProjectPath</key>
|
||||||
|
<string>../Display Panel.xcodeproj</string>
|
||||||
|
<key>IBOldestOS</key>
|
||||||
|
<integer>5</integer>
|
||||||
|
<key>IBOpenObjects</key>
|
||||||
|
<array/>
|
||||||
|
<key>IBSystem Version</key>
|
||||||
|
<string>9L31a</string>
|
||||||
|
<key>targetFramework</key>
|
||||||
|
<string>IBCocoaFramework</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 362 B After Width: | Height: | Size: 362 B |
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# base64.py, version 1.0
|
||||||
|
# Copyright © 2010 Apprentice Alf
|
||||||
|
|
||||||
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
|
# later. <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
# Revision history:
|
||||||
|
# 1 - Initial release. To allow Applescript to do base64 encoding
|
||||||
|
|
||||||
|
"""
|
||||||
|
Provide base64 encoding.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
|
||||||
|
def usage(progname):
|
||||||
|
print "Applies base64 encoding to the supplied file, sending to standard output"
|
||||||
|
print "Usage:"
|
||||||
|
print " %s <infile>" % progname
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
|
||||||
|
if len(argv)<2:
|
||||||
|
usage(progname)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
keypath = argv[1]
|
||||||
|
with open(keypath, 'rb') as f:
|
||||||
|
keyder = f.read()
|
||||||
|
print keyder.encode('base64')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(cli_main())
|
||||||
|
|
||||||
@@ -0,0 +1,476 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
#
|
||||||
|
# erdr2pml.py
|
||||||
|
#
|
||||||
|
# This is a python script. You need a Python interpreter to run it.
|
||||||
|
# For example, ActiveState Python, which exists for windows.
|
||||||
|
# Changelog
|
||||||
|
#
|
||||||
|
# Based on ereader2html version 0.08 plus some later small fixes
|
||||||
|
#
|
||||||
|
# 0.01 - Initial version
|
||||||
|
# 0.02 - Support more eReader files. Support bold text and links. Fix PML decoder parsing bug.
|
||||||
|
# 0.03 - Fix incorrect variable usage at one place.
|
||||||
|
# 0.03b - enhancement by DeBockle (version 259 support)
|
||||||
|
# Custom version 0.03 - no change to eReader support, only usability changes
|
||||||
|
# - start of pep-8 indentation (spaces not tab), fix trailing blanks
|
||||||
|
# - version variable, only one place to change
|
||||||
|
# - added main routine, now callable as a library/module,
|
||||||
|
# means tools can add optional support for ereader2html
|
||||||
|
# - outdir is no longer a mandatory parameter (defaults based on input name if missing)
|
||||||
|
# - time taken output to stdout
|
||||||
|
# - Psyco support - reduces runtime by a factor of (over) 3!
|
||||||
|
# E.g. (~600Kb file) 90 secs down to 24 secs
|
||||||
|
# - newstyle classes
|
||||||
|
# - changed map call to list comprehension
|
||||||
|
# may not work with python 2.3
|
||||||
|
# without Psyco this reduces runtime to 90%
|
||||||
|
# E.g. 90 secs down to 77 secs
|
||||||
|
# Psyco with map calls takes longer, do not run with map in Psyco JIT!
|
||||||
|
# - izip calls used instead of zip (if available), further reduction
|
||||||
|
# in run time (factor of 4.5).
|
||||||
|
# E.g. (~600Kb file) 90 secs down to 20 secs
|
||||||
|
# - Python 2.6+ support, avoid DeprecationWarning with sha/sha1
|
||||||
|
# 0.04 - Footnote support, PML output, correct charset in html, support more PML tags
|
||||||
|
# - Feature change, dump out PML file
|
||||||
|
# - Added supprt for footnote tags. NOTE footnote ids appear to be bad (not usable)
|
||||||
|
# in some pdb files :-( due to the same id being used multiple times
|
||||||
|
# - Added correct charset encoding (pml is based on cp1252)
|
||||||
|
# - Added logging support.
|
||||||
|
# 0.05 - Improved type 272 support for sidebars, links, chapters, metainfo, etc
|
||||||
|
# 0.06 - Merge of 0.04 and 0.05. Improved HTML output
|
||||||
|
# Placed images in subfolder, so that it's possible to just
|
||||||
|
# drop the book.pml file onto DropBook to make an unencrypted
|
||||||
|
# copy of the eReader file.
|
||||||
|
# Using that with Calibre works a lot better than the HTML
|
||||||
|
# conversion in this code.
|
||||||
|
# 0.07 - Further Improved type 272 support for sidebars with all earlier fixes
|
||||||
|
# 0.08 - fixed typos, removed extraneous things
|
||||||
|
# 0.09 - fixed typos in first_pages to first_page to again support older formats
|
||||||
|
# 0.10 - minor cleanups
|
||||||
|
# 0.11 - fixups for using correct xml for footnotes and sidebars for use with Dropbook
|
||||||
|
# 0.12 - Fix added to prevent lowercasing of image names when the pml code itself uses a different case in the link name.
|
||||||
|
# 0.13 - change to unbuffered stdout for use with gui front ends
|
||||||
|
# 0.14 - contributed enhancement to support --make-pmlz switch
|
||||||
|
# 0.15 - enabled high-ascii to pml character encoding. DropBook now works on Mac.
|
||||||
|
# 0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
|
||||||
|
|
||||||
|
Des = None
|
||||||
|
|
||||||
|
import openssl_des
|
||||||
|
Des = openssl_des.load_libcrypto()
|
||||||
|
|
||||||
|
# if that did not work then use pure python implementation
|
||||||
|
# of DES and try to speed it up with Psycho
|
||||||
|
if Des == None:
|
||||||
|
import python_des
|
||||||
|
Des = python_des.Des
|
||||||
|
# Import Psyco if available
|
||||||
|
try:
|
||||||
|
# Dumb speed hack 1
|
||||||
|
# http://psyco.sourceforge.net
|
||||||
|
import psyco
|
||||||
|
psyco.full()
|
||||||
|
pass
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
__version__='0.16'
|
||||||
|
|
||||||
|
class Unbuffered:
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
def write(self, data):
|
||||||
|
self.stream.write(data)
|
||||||
|
self.stream.flush()
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
|
|
||||||
|
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hashlib import sha1
|
||||||
|
except ImportError:
|
||||||
|
# older Python release
|
||||||
|
import sha
|
||||||
|
sha1 = lambda s: sha.new(s)
|
||||||
|
import cgi
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig()
|
||||||
|
#logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
class Sectionizer(object):
|
||||||
|
def __init__(self, filename, ident):
|
||||||
|
self.contents = file(filename, 'rb').read()
|
||||||
|
self.header = self.contents[0:72]
|
||||||
|
self.num_sections, = struct.unpack('>H', self.contents[76:78])
|
||||||
|
if self.header[0x3C:0x3C+8] != ident:
|
||||||
|
raise ValueError('Invalid file format')
|
||||||
|
self.sections = []
|
||||||
|
for i in xrange(self.num_sections):
|
||||||
|
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8])
|
||||||
|
flags, val = a1, a2<<16|a3<<8|a4
|
||||||
|
self.sections.append( (offset, flags, val) )
|
||||||
|
def loadSection(self, section):
|
||||||
|
if section + 1 == self.num_sections:
|
||||||
|
end_off = len(self.contents)
|
||||||
|
else:
|
||||||
|
end_off = self.sections[section + 1][0]
|
||||||
|
off = self.sections[section][0]
|
||||||
|
return self.contents[off:end_off]
|
||||||
|
|
||||||
|
def sanitizeFileName(s):
|
||||||
|
r = ''
|
||||||
|
for c in s:
|
||||||
|
if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-":
|
||||||
|
r += c
|
||||||
|
return r
|
||||||
|
|
||||||
|
def fixKey(key):
|
||||||
|
def fixByte(b):
|
||||||
|
return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
|
||||||
|
return "".join([chr(fixByte(ord(a))) for a in key])
|
||||||
|
|
||||||
|
def deXOR(text, sp, table):
|
||||||
|
r=''
|
||||||
|
j = sp
|
||||||
|
for i in xrange(len(text)):
|
||||||
|
r += chr(ord(table[j]) ^ ord(text[i]))
|
||||||
|
j = j + 1
|
||||||
|
if j == len(table):
|
||||||
|
j = 0
|
||||||
|
return r
|
||||||
|
|
||||||
|
class EreaderProcessor(object):
|
||||||
|
def __init__(self, section_reader, username, creditcard):
|
||||||
|
self.section_reader = section_reader
|
||||||
|
data = section_reader(0)
|
||||||
|
version, = struct.unpack('>H', data[0:2])
|
||||||
|
self.version = version
|
||||||
|
logging.info('eReader file format version %s', version)
|
||||||
|
if version != 272 and version != 260 and version != 259:
|
||||||
|
raise ValueError('incorrect eReader version %d (error 1)' % version)
|
||||||
|
data = section_reader(1)
|
||||||
|
self.data = data
|
||||||
|
des = Des(fixKey(data[0:8]))
|
||||||
|
cookie_shuf, cookie_size = struct.unpack('>LL', des.decrypt(data[-8:]))
|
||||||
|
if cookie_shuf < 3 or cookie_shuf > 0x14 or cookie_size < 0xf0 or cookie_size > 0x200:
|
||||||
|
raise ValueError('incorrect eReader version (error 2)')
|
||||||
|
input = des.decrypt(data[-cookie_size:])
|
||||||
|
def unshuff(data, shuf):
|
||||||
|
r = [''] * len(data)
|
||||||
|
j = 0
|
||||||
|
for i in xrange(len(data)):
|
||||||
|
j = (j + shuf) % len(data)
|
||||||
|
r[j] = data[i]
|
||||||
|
assert len("".join(r)) == len(data)
|
||||||
|
return "".join(r)
|
||||||
|
r = unshuff(input[0:-8], cookie_shuf)
|
||||||
|
|
||||||
|
def fixUsername(s):
|
||||||
|
r = ''
|
||||||
|
for c in s.lower():
|
||||||
|
if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'):
|
||||||
|
r += c
|
||||||
|
return r
|
||||||
|
|
||||||
|
user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff)
|
||||||
|
drm_sub_version = struct.unpack('>H', r[0:2])[0]
|
||||||
|
self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1
|
||||||
|
self.num_image_pages = struct.unpack('>H', r[26:26+2])[0]
|
||||||
|
self.first_image_page = struct.unpack('>H', r[24:24+2])[0]
|
||||||
|
if self.version == 272:
|
||||||
|
self.num_footnote_pages = struct.unpack('>H', r[46:46+2])[0]
|
||||||
|
self.first_footnote_page = struct.unpack('>H', r[44:44+2])[0]
|
||||||
|
self.num_sidebar_pages = struct.unpack('>H', r[38:38+2])[0]
|
||||||
|
self.first_sidebar_page = struct.unpack('>H', r[36:36+2])[0]
|
||||||
|
# self.num_bookinfo_pages = struct.unpack('>H', r[34:34+2])[0]
|
||||||
|
# self.first_bookinfo_page = struct.unpack('>H', r[32:32+2])[0]
|
||||||
|
# self.num_chapter_pages = struct.unpack('>H', r[22:22+2])[0]
|
||||||
|
# self.first_chapter_page = struct.unpack('>H', r[20:20+2])[0]
|
||||||
|
# self.num_link_pages = struct.unpack('>H', r[30:30+2])[0]
|
||||||
|
# self.first_link_page = struct.unpack('>H', r[28:28+2])[0]
|
||||||
|
# self.num_xtextsize_pages = struct.unpack('>H', r[54:54+2])[0]
|
||||||
|
# self.first_xtextsize_page = struct.unpack('>H', r[52:52+2])[0]
|
||||||
|
|
||||||
|
# **before** data record 1 was decrypted and unshuffled, it contained data
|
||||||
|
# to create an XOR table and which is used to fix footnote record 0, link records, chapter records, etc
|
||||||
|
self.xortable_offset = struct.unpack('>H', r[40:40+2])[0]
|
||||||
|
self.xortable_size = struct.unpack('>H', r[42:42+2])[0]
|
||||||
|
self.xortable = self.data[self.xortable_offset:self.xortable_offset + self.xortable_size]
|
||||||
|
else:
|
||||||
|
self.num_footnote_pages = 0
|
||||||
|
self.num_sidebar_pages = 0
|
||||||
|
self.first_footnote_page = -1
|
||||||
|
self.first_sidebar_page = -1
|
||||||
|
# self.num_bookinfo_pages = 0
|
||||||
|
# self.num_chapter_pages = 0
|
||||||
|
# self.num_link_pages = 0
|
||||||
|
# self.num_xtextsize_pages = 0
|
||||||
|
# self.first_bookinfo_page = -1
|
||||||
|
# self.first_chapter_page = -1
|
||||||
|
# self.first_link_page = -1
|
||||||
|
# self.first_xtextsize_page = -1
|
||||||
|
|
||||||
|
logging.debug('self.num_text_pages %d', self.num_text_pages)
|
||||||
|
logging.debug('self.num_footnote_pages %d, self.first_footnote_page %d', self.num_footnote_pages , self.first_footnote_page)
|
||||||
|
logging.debug('self.num_sidebar_pages %d, self.first_sidebar_page %d', self.num_sidebar_pages , self.first_sidebar_page)
|
||||||
|
self.flags = struct.unpack('>L', r[4:8])[0]
|
||||||
|
reqd_flags = (1<<9) | (1<<7) | (1<<10)
|
||||||
|
if (self.flags & reqd_flags) != reqd_flags:
|
||||||
|
print "Flags: 0x%X" % self.flags
|
||||||
|
raise ValueError('incompatible eReader file')
|
||||||
|
des = Des(fixKey(user_key))
|
||||||
|
if version == 259:
|
||||||
|
if drm_sub_version != 7:
|
||||||
|
raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version)
|
||||||
|
encrypted_key_sha = r[44:44+20]
|
||||||
|
encrypted_key = r[64:64+8]
|
||||||
|
elif version == 260:
|
||||||
|
if drm_sub_version != 13:
|
||||||
|
raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version)
|
||||||
|
encrypted_key = r[44:44+8]
|
||||||
|
encrypted_key_sha = r[52:52+20]
|
||||||
|
elif version == 272:
|
||||||
|
encrypted_key = r[172:172+8]
|
||||||
|
encrypted_key_sha = r[56:56+20]
|
||||||
|
self.content_key = des.decrypt(encrypted_key)
|
||||||
|
if sha1(self.content_key).digest() != encrypted_key_sha:
|
||||||
|
raise ValueError('Incorrect Name and/or Credit Card')
|
||||||
|
|
||||||
|
def getNumImages(self):
|
||||||
|
return self.num_image_pages
|
||||||
|
|
||||||
|
def getImage(self, i):
|
||||||
|
sect = self.section_reader(self.first_image_page + i)
|
||||||
|
name = sect[4:4+32].strip('\0')
|
||||||
|
data = sect[62:]
|
||||||
|
return sanitizeFileName(name), data
|
||||||
|
|
||||||
|
|
||||||
|
# def getChapterNamePMLOffsetData(self):
|
||||||
|
# cv = ''
|
||||||
|
# if self.num_chapter_pages > 0:
|
||||||
|
# for i in xrange(self.num_chapter_pages):
|
||||||
|
# chaps = self.section_reader(self.first_chapter_page + i)
|
||||||
|
# j = i % self.xortable_size
|
||||||
|
# offname = deXOR(chaps, j, self.xortable)
|
||||||
|
# offset = struct.unpack('>L', offname[0:4])[0]
|
||||||
|
# name = offname[4:].strip('\0')
|
||||||
|
# cv += '%d|%s\n' % (offset, name)
|
||||||
|
# return cv
|
||||||
|
|
||||||
|
# def getLinkNamePMLOffsetData(self):
|
||||||
|
# lv = ''
|
||||||
|
# if self.num_link_pages > 0:
|
||||||
|
# for i in xrange(self.num_link_pages):
|
||||||
|
# links = self.section_reader(self.first_link_page + i)
|
||||||
|
# j = i % self.xortable_size
|
||||||
|
# offname = deXOR(links, j, self.xortable)
|
||||||
|
# offset = struct.unpack('>L', offname[0:4])[0]
|
||||||
|
# name = offname[4:].strip('\0')
|
||||||
|
# lv += '%d|%s\n' % (offset, name)
|
||||||
|
# return lv
|
||||||
|
|
||||||
|
# def getExpandedTextSizesData(self):
|
||||||
|
# ts = ''
|
||||||
|
# if self.num_xtextsize_pages > 0:
|
||||||
|
# tsize = deXOR(self.section_reader(self.first_xtextsize_page), 0, self.xortable)
|
||||||
|
# for i in xrange(self.num_text_pages):
|
||||||
|
# xsize = struct.unpack('>H', tsize[0:2])[0]
|
||||||
|
# ts += "%d\n" % xsize
|
||||||
|
# tsize = tsize[2:]
|
||||||
|
# return ts
|
||||||
|
|
||||||
|
# def getBookInfo(self):
|
||||||
|
# bkinfo = ''
|
||||||
|
# if self.num_bookinfo_pages > 0:
|
||||||
|
# info = self.section_reader(self.first_bookinfo_page)
|
||||||
|
# bkinfo = deXOR(info, 0, self.xortable)
|
||||||
|
# bkinfo = bkinfo.replace('\0','|')
|
||||||
|
# bkinfo += '\n'
|
||||||
|
# return bkinfo
|
||||||
|
|
||||||
|
def getText(self):
|
||||||
|
des = Des(fixKey(self.content_key))
|
||||||
|
r = ''
|
||||||
|
for i in xrange(self.num_text_pages):
|
||||||
|
logging.debug('get page %d', i)
|
||||||
|
r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
|
||||||
|
|
||||||
|
# now handle footnotes pages
|
||||||
|
if self.num_footnote_pages > 0:
|
||||||
|
r += '\n'
|
||||||
|
# the record 0 of the footnote section must pass through the Xor Table to make it useful
|
||||||
|
sect = self.section_reader(self.first_footnote_page)
|
||||||
|
fnote_ids = deXOR(sect, 0, self.xortable)
|
||||||
|
# the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated
|
||||||
|
des = Des(fixKey(self.content_key))
|
||||||
|
for i in xrange(1,self.num_footnote_pages):
|
||||||
|
logging.debug('get footnotepage %d', i)
|
||||||
|
id_len = ord(fnote_ids[2])
|
||||||
|
id = fnote_ids[3:3+id_len]
|
||||||
|
fmarker = '<footnote id="%s">\n' % id
|
||||||
|
fmarker += zlib.decompress(des.decrypt(self.section_reader(self.first_footnote_page + i)))
|
||||||
|
fmarker += '\n</footnote>\n'
|
||||||
|
r += fmarker
|
||||||
|
fnote_ids = fnote_ids[id_len+4:]
|
||||||
|
|
||||||
|
# now handle sidebar pages
|
||||||
|
if self.num_sidebar_pages > 0:
|
||||||
|
r += '\n'
|
||||||
|
# the record 0 of the sidebar section must pass through the Xor Table to make it useful
|
||||||
|
sect = self.section_reader(self.first_sidebar_page)
|
||||||
|
sbar_ids = deXOR(sect, 0, self.xortable)
|
||||||
|
# the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated
|
||||||
|
des = Des(fixKey(self.content_key))
|
||||||
|
for i in xrange(1,self.num_sidebar_pages):
|
||||||
|
id_len = ord(sbar_ids[2])
|
||||||
|
id = sbar_ids[3:3+id_len]
|
||||||
|
smarker = '<sidebar id="%s">\n' % id
|
||||||
|
smarker += zlib.decompress(des.decrypt(self.section_reader(self.first_footnote_page + i)))
|
||||||
|
smarker += '\n</sidebar>\n'
|
||||||
|
r += smarker
|
||||||
|
sbar_ids = sbar_ids[id_len+4:]
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
def cleanPML(pml):
|
||||||
|
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
|
||||||
|
pml2 = pml
|
||||||
|
for k in xrange(128,256):
|
||||||
|
badChar = chr(k)
|
||||||
|
pml2 = pml2.replace(badChar, '\\a%03d' % k)
|
||||||
|
return pml2
|
||||||
|
|
||||||
|
def convertEreaderToPml(infile, name, cc, outdir):
|
||||||
|
if not os.path.exists(outdir):
|
||||||
|
os.makedirs(outdir)
|
||||||
|
|
||||||
|
print " Decoding File"
|
||||||
|
sect = Sectionizer(infile, 'PNRdPPrs')
|
||||||
|
er = EreaderProcessor(sect.loadSection, name, cc)
|
||||||
|
|
||||||
|
if er.getNumImages() > 0:
|
||||||
|
print " Extracting images"
|
||||||
|
imagedir = bookname + '_img/'
|
||||||
|
imagedirpath = os.path.join(outdir,imagedir)
|
||||||
|
if not os.path.exists(imagedirpath):
|
||||||
|
os.makedirs(imagedirpath)
|
||||||
|
for i in xrange(er.getNumImages()):
|
||||||
|
name, contents = er.getImage(i)
|
||||||
|
file(os.path.join(imagedirpath, name), 'wb').write(contents)
|
||||||
|
|
||||||
|
print " Extracting pml"
|
||||||
|
pml_string = er.getText()
|
||||||
|
pmlfilename = bookname + ".pml"
|
||||||
|
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
|
||||||
|
|
||||||
|
# bkinfo = er.getBookInfo()
|
||||||
|
# if bkinfo != '':
|
||||||
|
# print " Extracting book meta information"
|
||||||
|
# file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print "Converts DRMed eReader books to PML Source"
|
||||||
|
print "Usage:"
|
||||||
|
print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number "
|
||||||
|
print " "
|
||||||
|
print "Options: "
|
||||||
|
print " -h prints this message"
|
||||||
|
print " --make-pmlz create PMLZ instead of using output directory"
|
||||||
|
print " "
|
||||||
|
print "Note:"
|
||||||
|
print " if ommitted, outdir defaults based on 'infile.pdb'"
|
||||||
|
print " It's enough to enter the last 8 digits of the credit card number"
|
||||||
|
return
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
global bookname
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"])
|
||||||
|
except getopt.GetoptError, err:
|
||||||
|
print str(err)
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
make_pmlz = False
|
||||||
|
zipname = None
|
||||||
|
for o, a in opts:
|
||||||
|
if o == "-h":
|
||||||
|
usage()
|
||||||
|
return 0
|
||||||
|
elif o == "--make-pmlz":
|
||||||
|
make_pmlz = True
|
||||||
|
zipname = ''
|
||||||
|
|
||||||
|
print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
|
||||||
|
|
||||||
|
if len(args)!=3 and len(args)!=4:
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
if len(args)==3:
|
||||||
|
infile, name, cc = args[0], args[1], args[2]
|
||||||
|
outdir = infile[:-4] + '_Source'
|
||||||
|
elif len(args)==4:
|
||||||
|
infile, outdir, name, cc = args[0], args[1], args[2], args[3]
|
||||||
|
|
||||||
|
if make_pmlz :
|
||||||
|
# ignore specified outdir, use tempdir instead
|
||||||
|
outdir = tempfile.mkdtemp()
|
||||||
|
|
||||||
|
bookname = os.path.splitext(os.path.basename(infile))[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
print "Processing..."
|
||||||
|
import time
|
||||||
|
start_time = time.time()
|
||||||
|
convertEreaderToPml(infile, name, cc, outdir)
|
||||||
|
|
||||||
|
if make_pmlz :
|
||||||
|
import zipfile
|
||||||
|
import shutil
|
||||||
|
print " Creating PMLZ file"
|
||||||
|
zipname = infile[:-4] + '.pmlz'
|
||||||
|
myZipFile = zipfile.ZipFile(zipname,'w',zipfile.ZIP_STORED, False)
|
||||||
|
list = os.listdir(outdir)
|
||||||
|
for file in list:
|
||||||
|
localname = file
|
||||||
|
filePath = os.path.join(outdir,file)
|
||||||
|
if os.path.isfile(filePath):
|
||||||
|
myZipFile.write(filePath, localname)
|
||||||
|
elif os.path.isdir(filePath):
|
||||||
|
imageList = os.listdir(filePath)
|
||||||
|
localimgdir = os.path.basename(filePath)
|
||||||
|
for image in imageList:
|
||||||
|
localname = os.path.join(localimgdir,image)
|
||||||
|
imagePath = os.path.join(filePath,image)
|
||||||
|
if os.path.isfile(imagePath):
|
||||||
|
myZipFile.write(imagePath, localname)
|
||||||
|
myZipFile.close()
|
||||||
|
# remove temporary directory
|
||||||
|
shutil.rmtree(outdir)
|
||||||
|
|
||||||
|
end_time = time.time()
|
||||||
|
search_time = end_time - start_time
|
||||||
|
print 'elapsed time: %.2f seconds' % (search_time, )
|
||||||
|
if make_pmlz :
|
||||||
|
print 'output is %s' % zipname
|
||||||
|
else :
|
||||||
|
print 'output in %s' % outdir
|
||||||
|
print "done"
|
||||||
|
except ValueError, e:
|
||||||
|
print "Error: %s" % e
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
#! /usr/bin/python
|
||||||
|
|
||||||
|
# ignobleepub.pyw, version 3
|
||||||
|
|
||||||
|
# To run this program install Python 2.6 from <http://www.python.org/download/>
|
||||||
|
# and OpenSSL or 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
|
||||||
|
# ignobleepub.pyw and double-click on it to run it.
|
||||||
|
|
||||||
|
# Revision history:
|
||||||
|
# 1 - Initial release
|
||||||
|
# 2 - Added OS X support by using OpenSSL when available
|
||||||
|
# 3 - screen out improper key lengths to prevent segfaults on Linux
|
||||||
|
# 3.1 - Allow Windows versions of libcrypto to be found
|
||||||
|
|
||||||
|
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 IGNOBLEError(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
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
if libcrypto is None:
|
||||||
|
raise IGNOBLEError('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)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
||||||
|
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
||||||
|
c_int])
|
||||||
|
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 IGNOBLEError('AES improper key used')
|
||||||
|
return
|
||||||
|
key = self._key = AES_KEY()
|
||||||
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
||||||
|
if rv < 0:
|
||||||
|
raise IGNOBLEError('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 IGNOBLEError('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, IGNOBLEError):
|
||||||
|
pass
|
||||||
|
return AES
|
||||||
|
AES = _load_crypto()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Decrypt Barnes & Noble ADEPT encrypted EPUB books.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
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.new(bookkey, AES.MODE_CBC)
|
||||||
|
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:
|
||||||
|
keyb64 = f.read()
|
||||||
|
key = keyb64.decode('base64')[:16]
|
||||||
|
# aes = AES.new(key, AES.MODE_CBC)
|
||||||
|
aes = AES(key)
|
||||||
|
|
||||||
|
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 IGNOBLEError('%s: not an B&N 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 = aes.decrypt(bookkey.decode('base64'))
|
||||||
|
bookkey = bookkey[:-ord(bookkey[-1])]
|
||||||
|
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('bnepubkey.b64'):
|
||||||
|
self.keypath.insert(0, 'bnepubkey.b64')
|
||||||
|
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 B&N EPUB key file',
|
||||||
|
defaultextension='.b64',
|
||||||
|
filetypes=[('base64-encoded files', '.b64'),
|
||||||
|
('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 B&N-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(
|
||||||
|
"Ignoble 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('Ignoble 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())
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
#! /usr/bin/python
|
||||||
|
|
||||||
|
# ignoblekeygen.pyw, version 2
|
||||||
|
|
||||||
|
# To run this program install Python 2.6 from <http://www.python.org/download/>
|
||||||
|
# and OpenSSL or 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
|
||||||
|
# ignoblekeygen.pyw and double-click on it to run it.
|
||||||
|
|
||||||
|
# Revision history:
|
||||||
|
# 1 - Initial release
|
||||||
|
# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
|
||||||
|
# 2.1 - Allow Windows versions of libcrypto to be found
|
||||||
|
|
||||||
|
"""
|
||||||
|
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
import Tkinter
|
||||||
|
import Tkconstants
|
||||||
|
import tkFileDialog
|
||||||
|
import tkMessageBox
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# use openssl's libcrypt if it exists in place of pycrypto
|
||||||
|
# code extracted from the Adobe Adept DRM removal code also by I HeartCabbages
|
||||||
|
class IGNOBLEError(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
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
if libcrypto is None:
|
||||||
|
print 'libcrypto not found'
|
||||||
|
raise IGNOBLEError('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)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_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, iv):
|
||||||
|
self._blocksize = len(userkey)
|
||||||
|
self._iv = iv
|
||||||
|
key = self._key = AES_KEY()
|
||||||
|
rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
|
||||||
|
if rv < 0:
|
||||||
|
raise IGNOBLEError('Failed to initialize AES Encrypt key')
|
||||||
|
|
||||||
|
def encrypt(self, data):
|
||||||
|
out = create_string_buffer(len(data))
|
||||||
|
rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
|
||||||
|
if rv == 0:
|
||||||
|
raise IGNOBLEError('AES encryption failed')
|
||||||
|
return out.raw
|
||||||
|
|
||||||
|
return AES
|
||||||
|
|
||||||
|
|
||||||
|
def _load_crypto_pycrypto():
|
||||||
|
from Crypto.Cipher import AES as _AES
|
||||||
|
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, key, iv):
|
||||||
|
self._aes = _AES.new(key, _AES.MODE_CBC, iv)
|
||||||
|
|
||||||
|
def encrypt(self, data):
|
||||||
|
return self._aes.encrypt(data)
|
||||||
|
|
||||||
|
return AES
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _load_crypto():
|
||||||
|
AES = None
|
||||||
|
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
|
||||||
|
try:
|
||||||
|
AES = loader()
|
||||||
|
break
|
||||||
|
except (ImportError, IGNOBLEError):
|
||||||
|
pass
|
||||||
|
return AES
|
||||||
|
|
||||||
|
AES = _load_crypto()
|
||||||
|
|
||||||
|
def normalize_name(name):
|
||||||
|
return ''.join(x for x in name.lower() if x != ' ')
|
||||||
|
|
||||||
|
def generate_keyfile(name, ccn, outpath):
|
||||||
|
name = normalize_name(name) + '\x00'
|
||||||
|
ccn = ccn + '\x00'
|
||||||
|
name_sha = hashlib.sha1(name).digest()[:16]
|
||||||
|
ccn_sha = hashlib.sha1(ccn).digest()[:16]
|
||||||
|
both_sha = hashlib.sha1(name + ccn).digest()
|
||||||
|
aes = AES(ccn_sha, name_sha)
|
||||||
|
crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
|
||||||
|
userkey = hashlib.sha1(crypt).digest()
|
||||||
|
with open(outpath, 'wb') as f:
|
||||||
|
f.write(userkey.encode('base64'))
|
||||||
|
return userkey
|
||||||
|
|
||||||
|
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 NAME CC# OUTFILE" % (progname,)
|
||||||
|
return 1
|
||||||
|
name, ccn, outpath = argv[1:]
|
||||||
|
generate_keyfile(name, ccn, outpath)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
|
def __init__(self, root):
|
||||||
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
|
self.status = Tkinter.Label(self, text='Enter parameters')
|
||||||
|
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='Name').grid(row=1)
|
||||||
|
self.name = Tkinter.Entry(body, width=30)
|
||||||
|
self.name.grid(row=1, column=1, sticky=sticky)
|
||||||
|
Tkinter.Label(body, text='CC#').grid(row=2)
|
||||||
|
self.ccn = Tkinter.Entry(body, width=30)
|
||||||
|
self.ccn.grid(row=2, column=1, sticky=sticky)
|
||||||
|
Tkinter.Label(body, text='Output file').grid(row=0)
|
||||||
|
self.keypath = Tkinter.Entry(body, width=30)
|
||||||
|
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||||
|
self.keypath.insert(0, 'bnepubkey.b64')
|
||||||
|
button = Tkinter.Button(body, text="...", command=self.get_keypath)
|
||||||
|
button.grid(row=0, column=2)
|
||||||
|
buttons = Tkinter.Frame(self)
|
||||||
|
buttons.pack()
|
||||||
|
botton = Tkinter.Button(
|
||||||
|
buttons, text="Generate", width=10, command=self.generate)
|
||||||
|
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.asksaveasfilename(
|
||||||
|
parent=None, title='Select B&N EPUB key file to produce',
|
||||||
|
defaultextension='.b64',
|
||||||
|
filetypes=[('base64-encoded files', '.b64'),
|
||||||
|
('All Files', '.*')])
|
||||||
|
if keypath:
|
||||||
|
keypath = os.path.normpath(keypath)
|
||||||
|
self.keypath.delete(0, Tkconstants.END)
|
||||||
|
self.keypath.insert(0, keypath)
|
||||||
|
return
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
name = self.name.get()
|
||||||
|
ccn = self.ccn.get()
|
||||||
|
keypath = self.keypath.get()
|
||||||
|
if not name:
|
||||||
|
self.status['text'] = 'Name not specified'
|
||||||
|
return
|
||||||
|
if not ccn:
|
||||||
|
self.status['text'] = 'Credit card number not specified'
|
||||||
|
return
|
||||||
|
if not keypath:
|
||||||
|
self.status['text'] = 'Output keyfile path not specified'
|
||||||
|
return
|
||||||
|
self.status['text'] = 'Generating...'
|
||||||
|
try:
|
||||||
|
generate_keyfile(name, ccn, keypath)
|
||||||
|
except Exception, e:
|
||||||
|
self.status['text'] = 'Error: ' + str(e)
|
||||||
|
return
|
||||||
|
self.status['text'] = 'Keyfile successfully generated'
|
||||||
|
|
||||||
|
def gui_main():
|
||||||
|
root = Tkinter.Tk()
|
||||||
|
if AES is None:
|
||||||
|
root.withdraw()
|
||||||
|
tkMessageBox.showerror(
|
||||||
|
"Ignoble EPUB Keyfile Generator",
|
||||||
|
"This script requires OpenSSL or PyCrypto, which must be installed "
|
||||||
|
"separately. Read the top-of-script comment for details.")
|
||||||
|
return 1
|
||||||
|
root.title('Ignoble EPUB Keyfile Generator')
|
||||||
|
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())
|
||||||
@@ -0,0 +1,462 @@
|
|||||||
|
#! /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
|
||||||
|
# 5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
|
raise ADEPTError('AES improper key used')
|
||||||
|
return
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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):
|
||||||
|
self._aes = _AES.new(key, _AES.MODE_CBC)
|
||||||
|
|
||||||
|
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())
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
#! /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
|
||||||
|
# 5.1 - add support for using OpenSSL on Windows in place of PyCrypto
|
||||||
|
# 5.2 - added support for output of key to a particular file
|
||||||
|
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
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, 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)
|
||||||
|
|
||||||
|
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]
|
||||||
|
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 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()
|
||||||
|
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')
|
||||||
|
aes = AES(keykey)
|
||||||
|
userkey = aes.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 cli_main(argv=sys.argv):
|
||||||
|
keypath = argv[1]
|
||||||
|
try:
|
||||||
|
success = retrieve_key(keypath)
|
||||||
|
except ADEPTError, e:
|
||||||
|
print "Key generation Error: " + str(e)
|
||||||
|
return 1
|
||||||
|
except Exception, e:
|
||||||
|
print "General Error: " + str(e)
|
||||||
|
return 1
|
||||||
|
if not success:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
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__':
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
sys.exit(cli_main())
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,582 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# engine to remove drm from Kindle for Mac and Kindle for PC books
|
||||||
|
# for personal use for archiving and converting your ebooks
|
||||||
|
|
||||||
|
# PLEASE DO NOT PIRATE EBOOKS!
|
||||||
|
|
||||||
|
# We want all authors and publishers, and eBook stores to live
|
||||||
|
# long and prosperous lives but at the same time we just want to
|
||||||
|
# be able to read OUR books on whatever device we want and to keep
|
||||||
|
# readable for a long, long time
|
||||||
|
|
||||||
|
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
|
||||||
|
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
|
||||||
|
# and many many others
|
||||||
|
|
||||||
|
# It can run standalone to convert K4M/K4PC/Mobi files, or it can be installed as a
|
||||||
|
# plugin for Calibre (http://calibre-ebook.com/about) so that importing
|
||||||
|
# K4 or Mobi with DRM is no londer a multi-step process.
|
||||||
|
#
|
||||||
|
# ***NOTE*** If you are using this script as a calibre plugin for a K4M or K4PC ebook
|
||||||
|
# then calibre must be installed on the same machine and in the same account as K4PC or K4M
|
||||||
|
# for the plugin version to function properly.
|
||||||
|
#
|
||||||
|
# To create a Calibre plugin, rename this file so that the filename
|
||||||
|
# ends in '_plugin.py', put it into a ZIP file with all its supporting python routines
|
||||||
|
# and import that ZIP into Calibre using its plugin configuration GUI.
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__version__ = '1.2'
|
||||||
|
|
||||||
|
class Unbuffered:
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
def write(self, data):
|
||||||
|
self.stream.write(data)
|
||||||
|
self.stream.flush()
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os, csv, getopt
|
||||||
|
import binascii
|
||||||
|
import zlib
|
||||||
|
import re
|
||||||
|
from struct import pack, unpack, unpack_from
|
||||||
|
|
||||||
|
|
||||||
|
#Exception Handling
|
||||||
|
class DrmException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
#
|
||||||
|
# crypto digestroutines
|
||||||
|
#
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
def MD5(message):
|
||||||
|
ctx = hashlib.md5()
|
||||||
|
ctx.update(message)
|
||||||
|
return ctx.digest()
|
||||||
|
|
||||||
|
def SHA1(message):
|
||||||
|
ctx = hashlib.sha1()
|
||||||
|
ctx.update(message)
|
||||||
|
return ctx.digest()
|
||||||
|
|
||||||
|
# determine if we are running as a calibre plugin
|
||||||
|
if 'calibre' in sys.modules:
|
||||||
|
inCalibre = True
|
||||||
|
global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
else:
|
||||||
|
inCalibre = False
|
||||||
|
|
||||||
|
#
|
||||||
|
# start of Kindle specific routines
|
||||||
|
#
|
||||||
|
|
||||||
|
if not inCalibre:
|
||||||
|
import mobidedrm
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
|
||||||
|
global kindleDatabase
|
||||||
|
|
||||||
|
# Encode the bytes in data with the characters in map
|
||||||
|
def encode(data, map):
|
||||||
|
result = ""
|
||||||
|
for char in data:
|
||||||
|
value = ord(char)
|
||||||
|
Q = (value ^ 0x80) // len(map)
|
||||||
|
R = value % len(map)
|
||||||
|
result += map[Q]
|
||||||
|
result += map[R]
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Hash the bytes in data and then encode the digest with the characters in map
|
||||||
|
def encodeHash(data,map):
|
||||||
|
return encode(MD5(data),map)
|
||||||
|
|
||||||
|
# Decode the string in data with the characters in map. Returns the decoded bytes
|
||||||
|
def decode(data,map):
|
||||||
|
result = ""
|
||||||
|
for i in range (0,len(data)-1,2):
|
||||||
|
high = map.find(data[i])
|
||||||
|
low = map.find(data[i+1])
|
||||||
|
if (high == -1) or (low == -1) :
|
||||||
|
break
|
||||||
|
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
|
||||||
|
result += pack("B",value)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Parse the Kindle.info file and return the records as a list of key-values
|
||||||
|
def parseKindleInfo(kInfoFile):
|
||||||
|
DB = {}
|
||||||
|
infoReader = openKindleInfo(kInfoFile)
|
||||||
|
infoReader.read(1)
|
||||||
|
data = infoReader.read()
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
items = data.split('{')
|
||||||
|
else :
|
||||||
|
items = data.split('[')
|
||||||
|
for item in items:
|
||||||
|
splito = item.split(':')
|
||||||
|
DB[splito[0]] =splito[1]
|
||||||
|
return DB
|
||||||
|
|
||||||
|
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
|
||||||
|
def getKindleInfoValueForHash(hashedKey):
|
||||||
|
global kindleDatabase
|
||||||
|
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
return CryptUnprotectData(encryptedValue,"")
|
||||||
|
else:
|
||||||
|
cleartext = CryptUnprotectData(encryptedValue)
|
||||||
|
return decode(cleartext, charMap1)
|
||||||
|
|
||||||
|
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
|
||||||
|
def getKindleInfoValueForKey(key):
|
||||||
|
return getKindleInfoValueForHash(encodeHash(key,charMap2))
|
||||||
|
|
||||||
|
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string.
|
||||||
|
def findNameForHash(hash):
|
||||||
|
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||||
|
result = ""
|
||||||
|
for name in names:
|
||||||
|
if hash == encodeHash(name, charMap2):
|
||||||
|
result = name
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Print all the records from the kindle.info file (option -i)
|
||||||
|
def printKindleInfo():
|
||||||
|
for record in kindleDatabase:
|
||||||
|
name = findNameForHash(record)
|
||||||
|
if name != "" :
|
||||||
|
print (name)
|
||||||
|
print ("--------------------------")
|
||||||
|
else :
|
||||||
|
print ("Unknown Record")
|
||||||
|
print getKindleInfoValueForHash(record)
|
||||||
|
print "\n"
|
||||||
|
|
||||||
|
#
|
||||||
|
# PID generation routines
|
||||||
|
#
|
||||||
|
|
||||||
|
# Returns two bit at offset from a bit field
|
||||||
|
def getTwoBitsFromBitField(bitField,offset):
|
||||||
|
byteNumber = offset // 4
|
||||||
|
bitPosition = 6 - 2*(offset % 4)
|
||||||
|
return ord(bitField[byteNumber]) >> bitPosition & 3
|
||||||
|
|
||||||
|
# Returns the six bits at offset from a bit field
|
||||||
|
def getSixBitsFromBitField(bitField,offset):
|
||||||
|
offset *= 3
|
||||||
|
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
|
||||||
|
return value
|
||||||
|
|
||||||
|
# 8 bits to six bits encoding from hash to generate PID string
|
||||||
|
def encodePID(hash):
|
||||||
|
global charMap3
|
||||||
|
PID = ""
|
||||||
|
for position in range (0,8):
|
||||||
|
PID += charMap3[getSixBitsFromBitField(hash,position)]
|
||||||
|
return PID
|
||||||
|
|
||||||
|
# Encryption table used to generate the device PID
|
||||||
|
def generatePidEncryptionTable() :
|
||||||
|
table = []
|
||||||
|
for counter1 in range (0,0x100):
|
||||||
|
value = counter1
|
||||||
|
for counter2 in range (0,8):
|
||||||
|
if (value & 1 == 0) :
|
||||||
|
value = value >> 1
|
||||||
|
else :
|
||||||
|
value = value >> 1
|
||||||
|
value = value ^ 0xEDB88320
|
||||||
|
table.append(value)
|
||||||
|
return table
|
||||||
|
|
||||||
|
# Seed value used to generate the device PID
|
||||||
|
def generatePidSeed(table,dsn) :
|
||||||
|
value = 0
|
||||||
|
for counter in range (0,4) :
|
||||||
|
index = (ord(dsn[counter]) ^ value) &0xFF
|
||||||
|
value = (value >> 8) ^ table[index]
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Generate the device PID
|
||||||
|
def generateDevicePID(table,dsn,nbRoll):
|
||||||
|
seed = generatePidSeed(table,dsn)
|
||||||
|
pidAscii = ""
|
||||||
|
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
|
||||||
|
index = 0
|
||||||
|
for counter in range (0,nbRoll):
|
||||||
|
pid[index] = pid[index] ^ ord(dsn[counter])
|
||||||
|
index = (index+1) %8
|
||||||
|
for counter in range (0,8):
|
||||||
|
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
||||||
|
pidAscii += charMap4[index]
|
||||||
|
return pidAscii
|
||||||
|
|
||||||
|
# convert from 8 digit PID to 10 digit PID with checksum
|
||||||
|
def checksumPid(s):
|
||||||
|
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||||
|
crc = crc ^ (crc >> 16)
|
||||||
|
res = s
|
||||||
|
l = len(letters)
|
||||||
|
for i in (0,1):
|
||||||
|
b = crc & 0xff
|
||||||
|
pos = (b // l) ^ (b % l)
|
||||||
|
res += letters[pos%l]
|
||||||
|
crc >>= 8
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class MobiPeek:
|
||||||
|
def loadSection(self, section):
|
||||||
|
before, after = self.sections[section:section+2]
|
||||||
|
self.f.seek(before)
|
||||||
|
return self.f.read(after - before)
|
||||||
|
def __init__(self, filename):
|
||||||
|
self.f = file(filename, 'rb')
|
||||||
|
self.header = self.f.read(78)
|
||||||
|
self.ident = self.header[0x3C:0x3C+8]
|
||||||
|
if self.ident != 'BOOKMOBI' and self.ident != 'TEXtREAd':
|
||||||
|
raise DrmException('invalid file format')
|
||||||
|
self.num_sections, = unpack_from('>H', self.header, 76)
|
||||||
|
sections = self.f.read(self.num_sections*8)
|
||||||
|
self.sections = unpack_from('>%dL' % (self.num_sections*2), sections, 0)[::2] + (0xfffffff, )
|
||||||
|
self.sect0 = self.loadSection(0)
|
||||||
|
self.f.close()
|
||||||
|
def getBookTitle(self):
|
||||||
|
# get book title
|
||||||
|
toff, tlen = unpack('>II', self.sect0[0x54:0x5c])
|
||||||
|
tend = toff + tlen
|
||||||
|
title = self.sect0[toff:tend]
|
||||||
|
return title
|
||||||
|
def getexthData(self):
|
||||||
|
# if exth region exists then grab it
|
||||||
|
# get length of this header
|
||||||
|
length, type, codepage, unique_id, version = unpack('>LLLLL', self.sect0[20:40])
|
||||||
|
exth_flag, = unpack('>L', self.sect0[0x80:0x84])
|
||||||
|
exth = ''
|
||||||
|
if exth_flag & 0x40:
|
||||||
|
exth = self.sect0[16 + length:]
|
||||||
|
return exth
|
||||||
|
def isNotEncrypted(self):
|
||||||
|
lock_type, = unpack('>H', self.sect0[0xC:0xC+2])
|
||||||
|
if lock_type == 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
|
||||||
|
# file to calculate the book pid.
|
||||||
|
def getK4Pids(exth, title, kInfoFile=None):
|
||||||
|
global kindleDatabase
|
||||||
|
try:
|
||||||
|
kindleDatabase = parseKindleInfo(kInfoFile)
|
||||||
|
except Exception, message:
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
if kindleDatabase != None :
|
||||||
|
# Get the Mazama Random number
|
||||||
|
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
|
||||||
|
|
||||||
|
# Get the HDD serial
|
||||||
|
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
|
||||||
|
|
||||||
|
# Get the current user name
|
||||||
|
encodedUsername = encodeHash(GetUserName(),charMap1)
|
||||||
|
|
||||||
|
# concat, hash and encode to calculate the DSN
|
||||||
|
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
|
||||||
|
|
||||||
|
print("\nDSN: " + DSN)
|
||||||
|
|
||||||
|
# Compute the device PID (for which I can tell, is used for nothing).
|
||||||
|
# But hey, stuff being printed out is apparently cool.
|
||||||
|
table = generatePidEncryptionTable()
|
||||||
|
devicePID = generateDevicePID(table,DSN,4)
|
||||||
|
|
||||||
|
print("Device PID: " + checksumPid(devicePID))
|
||||||
|
|
||||||
|
# Compute book PID
|
||||||
|
exth_records = {}
|
||||||
|
nitems, = unpack('>I', exth[8:12])
|
||||||
|
pos = 12
|
||||||
|
|
||||||
|
exth_records[209] = None
|
||||||
|
# Parse the exth records, storing data indexed by type
|
||||||
|
for i in xrange(nitems):
|
||||||
|
type, size = unpack('>II', exth[pos: pos + 8])
|
||||||
|
content = exth[pos + 8: pos + size]
|
||||||
|
|
||||||
|
exth_records[type] = content
|
||||||
|
pos += size
|
||||||
|
|
||||||
|
# Grab the contents of the type 209 exth record
|
||||||
|
if exth_records[209] != None:
|
||||||
|
data = exth_records[209]
|
||||||
|
else:
|
||||||
|
raise DrmException("\nNo EXTH record type 209 - Perhaps not a K4 file?")
|
||||||
|
|
||||||
|
# Parse the 209 data to find the the exth record with the token data.
|
||||||
|
# The last character of the 209 data points to the record with the token.
|
||||||
|
# Always 208 from my experience, but I'll leave the logic in case that changes.
|
||||||
|
for i in xrange(len(data)):
|
||||||
|
if ord(data[i]) != 0:
|
||||||
|
if exth_records[ord(data[i])] != None:
|
||||||
|
token = exth_records[ord(data[i])]
|
||||||
|
|
||||||
|
# Get the kindle account token
|
||||||
|
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
|
||||||
|
|
||||||
|
print("Account Token: " + kindleAccountToken)
|
||||||
|
|
||||||
|
pidHash = SHA1(DSN+kindleAccountToken+exth_records[209]+token)
|
||||||
|
|
||||||
|
bookPID = encodePID(pidHash)
|
||||||
|
bookPID = checksumPid(bookPID)
|
||||||
|
|
||||||
|
if exth_records[503] != None:
|
||||||
|
print "Pid for " + exth_records[503] + ": " + bookPID
|
||||||
|
else:
|
||||||
|
print "Pid for " + title + ":" + bookPID
|
||||||
|
return bookPID
|
||||||
|
|
||||||
|
raise DrmException("\nCould not access K4 data - Perhaps K4 is not installed/configured?")
|
||||||
|
return null
|
||||||
|
|
||||||
|
def usage(progname):
|
||||||
|
print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
|
||||||
|
print "Usage:"
|
||||||
|
print " %s [-k <kindle.info>] [-p <pidnums>] <infile> <outfile> " % progname
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main
|
||||||
|
#
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
global kindleDatabase
|
||||||
|
import mobidedrm
|
||||||
|
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
kInfoFiles = []
|
||||||
|
pidnums = ""
|
||||||
|
|
||||||
|
print ('K4MobiDeDrm v%(__version__)s '
|
||||||
|
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
|
||||||
|
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "k:p:")
|
||||||
|
except getopt.GetoptError, err:
|
||||||
|
print str(err)
|
||||||
|
usage(progname)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
if len(args)<2:
|
||||||
|
usage(progname)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
for o, a in opts:
|
||||||
|
if o == "-k":
|
||||||
|
if a == None :
|
||||||
|
raise DrmException("Invalid parameter for -k")
|
||||||
|
kInfoFiles.append(a)
|
||||||
|
if o == "-p":
|
||||||
|
if a == None :
|
||||||
|
raise DrmException("Invalid parameter for -p")
|
||||||
|
pidnums = a
|
||||||
|
|
||||||
|
kindleDatabase = None
|
||||||
|
infile = args[0]
|
||||||
|
outfile = args[1]
|
||||||
|
DecodeErrorString = ""
|
||||||
|
try:
|
||||||
|
# first try with K4PC/K4M
|
||||||
|
ex = MobiPeek(infile)
|
||||||
|
if ex.isNotEncrypted():
|
||||||
|
print "File was Not Encrypted"
|
||||||
|
return 2
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||||
|
except DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file(outfile, 'wb').write(unlocked_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# now try alternate kindle.info files
|
||||||
|
if kInfoFiles:
|
||||||
|
for infoFile in kInfoFiles:
|
||||||
|
kindleDatabase = None
|
||||||
|
try:
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title, infoFile)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||||
|
except DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file(outfile, 'wb').write(unlocked_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Lastly, try from the pid list
|
||||||
|
pids = pidnums.split(',')
|
||||||
|
for pid in pids:
|
||||||
|
try:
|
||||||
|
print 'Trying: "'+ pid + '"'
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file(outfile, 'wb').write(unlocked_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# we could not unencrypt book
|
||||||
|
print DecodeErrorString
|
||||||
|
print "Error: Could Not Unencrypt Book"
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
|
sys.exit(main())
|
||||||
|
|
||||||
|
|
||||||
|
if not __name__ == "__main__" and inCalibre:
|
||||||
|
from calibre.customize import FileTypePlugin
|
||||||
|
|
||||||
|
class K4DeDRM(FileTypePlugin):
|
||||||
|
name = 'K4PC, K4Mac, Mobi DeDRM' # Name of the plugin
|
||||||
|
description = 'Removes DRM from K4PC, K4Mac, and Mobi files. \
|
||||||
|
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
|
||||||
|
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
|
||||||
|
author = 'DiapDealer, SomeUpdates' # The author of this plugin
|
||||||
|
version = (0, 1, 2) # The version number of this plugin
|
||||||
|
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
|
||||||
|
on_import = True # Run this plugin during the import
|
||||||
|
priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
|
||||||
|
|
||||||
|
def run(self, path_to_ebook):
|
||||||
|
from calibre.gui2 import is_ok_to_use_qt
|
||||||
|
from PyQt4.Qt import QMessageBox
|
||||||
|
global kindleDatabase
|
||||||
|
global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
import mobidedrm
|
||||||
|
|
||||||
|
# Get supplied list of PIDs to try from plugin customization.
|
||||||
|
pidnums = self.site_customization
|
||||||
|
|
||||||
|
# Load any kindle info files (*.info) included Calibre's config directory.
|
||||||
|
kInfoFiles = []
|
||||||
|
try:
|
||||||
|
# Find Calibre's configuration directory.
|
||||||
|
confpath = os.path.split(os.path.split(self.plugin_path)[0])[0]
|
||||||
|
print 'K4MobiDeDRM: Calibre configuration directory = %s' % confpath
|
||||||
|
files = os.listdir(confpath)
|
||||||
|
filefilter = re.compile("\.info$", re.IGNORECASE)
|
||||||
|
files = filter(filefilter.search, files)
|
||||||
|
|
||||||
|
if files:
|
||||||
|
for filename in files:
|
||||||
|
fpath = os.path.join(confpath, filename)
|
||||||
|
kInfoFiles.append(fpath)
|
||||||
|
print 'K4MobiDeDRM: Kindle info file %s found in config folder.' % filename
|
||||||
|
except IOError:
|
||||||
|
print 'K4MobiDeDRM: Error reading kindle info files from config directory.'
|
||||||
|
pass
|
||||||
|
|
||||||
|
# first try with book specifc pid from K4PC or K4M
|
||||||
|
try:
|
||||||
|
kindleDatabase = None
|
||||||
|
ex = MobiPeek(path_to_ebook)
|
||||||
|
if ex.isNotEncrypted():
|
||||||
|
return path_to_ebook
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
|
||||||
|
except DrmException:
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
of = self.temporary_file('.mobi')
|
||||||
|
of.write(unlocked_file)
|
||||||
|
of.close()
|
||||||
|
return of.name
|
||||||
|
|
||||||
|
# Now try alternate kindle info files
|
||||||
|
if kInfoFiles:
|
||||||
|
for infoFile in kInfoFiles:
|
||||||
|
kindleDatabase = None
|
||||||
|
try:
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title, infoFile)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
|
||||||
|
except DrmException:
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
of = self.temporary_file('.mobi')
|
||||||
|
of.write(unlocked_file)
|
||||||
|
of.close()
|
||||||
|
return of.name
|
||||||
|
|
||||||
|
# now try from the pid list
|
||||||
|
pids = pidnums.split(',')
|
||||||
|
for pid in pids:
|
||||||
|
try:
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook, pid)
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
of = self.temporary_file('.mobi')
|
||||||
|
of.write(unlocked_file)
|
||||||
|
of.close()
|
||||||
|
return of.name
|
||||||
|
|
||||||
|
#if you reached here then no luck raise and exception
|
||||||
|
if is_ok_to_use_qt():
|
||||||
|
d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
|
||||||
|
d.show()
|
||||||
|
d.raise_()
|
||||||
|
d.exec_()
|
||||||
|
raise Exception("K4MobiDeDRM plugin could not decode the file")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def customization_help(self, gui=False):
|
||||||
|
return 'Enter each 10 character PID separated by a comma (no spaces).'
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
# standlone set of Mac OSX specific routines needed for K4DeDRM
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
#Exception Handling
|
||||||
|
class K4MDrmException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
import signal
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
from subprocess import Popen, PIPE, STDOUT
|
||||||
|
|
||||||
|
# **heavily** chopped up and modfied version of asyncproc.py
|
||||||
|
# to make it actually work on Windows as well as Mac/Linux
|
||||||
|
# For the original see:
|
||||||
|
# "http://www.lysator.liu.se/~bellman/download/"
|
||||||
|
# author is "Thomas Bellman <bellman@lysator.liu.se>"
|
||||||
|
# available under GPL version 3 or Later
|
||||||
|
|
||||||
|
# create an asynchronous subprocess whose output can be collected in
|
||||||
|
# a non-blocking manner
|
||||||
|
|
||||||
|
# What a mess! Have to use threads just to get non-blocking io
|
||||||
|
# in a cross-platform manner
|
||||||
|
|
||||||
|
# luckily all thread use is hidden within this class
|
||||||
|
|
||||||
|
class Process(object):
|
||||||
|
def __init__(self, *params, **kwparams):
|
||||||
|
if len(params) <= 3:
|
||||||
|
kwparams.setdefault('stdin', subprocess.PIPE)
|
||||||
|
if len(params) <= 4:
|
||||||
|
kwparams.setdefault('stdout', subprocess.PIPE)
|
||||||
|
if len(params) <= 5:
|
||||||
|
kwparams.setdefault('stderr', subprocess.PIPE)
|
||||||
|
self.__pending_input = []
|
||||||
|
self.__collected_outdata = []
|
||||||
|
self.__collected_errdata = []
|
||||||
|
self.__exitstatus = None
|
||||||
|
self.__lock = threading.Lock()
|
||||||
|
self.__inputsem = threading.Semaphore(0)
|
||||||
|
self.__quit = False
|
||||||
|
|
||||||
|
self.__process = subprocess.Popen(*params, **kwparams)
|
||||||
|
|
||||||
|
if self.__process.stdin:
|
||||||
|
self.__stdin_thread = threading.Thread(
|
||||||
|
name="stdin-thread",
|
||||||
|
target=self.__feeder, args=(self.__pending_input,
|
||||||
|
self.__process.stdin))
|
||||||
|
self.__stdin_thread.setDaemon(True)
|
||||||
|
self.__stdin_thread.start()
|
||||||
|
|
||||||
|
if self.__process.stdout:
|
||||||
|
self.__stdout_thread = threading.Thread(
|
||||||
|
name="stdout-thread",
|
||||||
|
target=self.__reader, args=(self.__collected_outdata,
|
||||||
|
self.__process.stdout))
|
||||||
|
self.__stdout_thread.setDaemon(True)
|
||||||
|
self.__stdout_thread.start()
|
||||||
|
|
||||||
|
if self.__process.stderr:
|
||||||
|
self.__stderr_thread = threading.Thread(
|
||||||
|
name="stderr-thread",
|
||||||
|
target=self.__reader, args=(self.__collected_errdata,
|
||||||
|
self.__process.stderr))
|
||||||
|
self.__stderr_thread.setDaemon(True)
|
||||||
|
self.__stderr_thread.start()
|
||||||
|
|
||||||
|
def pid(self):
|
||||||
|
return self.__process.pid
|
||||||
|
|
||||||
|
def kill(self, signal):
|
||||||
|
self.__process.send_signal(signal)
|
||||||
|
|
||||||
|
# check on subprocess (pass in 'nowait') to act like poll
|
||||||
|
def wait(self, flag):
|
||||||
|
if flag.lower() == 'nowait':
|
||||||
|
rc = self.__process.poll()
|
||||||
|
else:
|
||||||
|
rc = self.__process.wait()
|
||||||
|
if rc != None:
|
||||||
|
if self.__process.stdin:
|
||||||
|
self.closeinput()
|
||||||
|
if self.__process.stdout:
|
||||||
|
self.__stdout_thread.join()
|
||||||
|
if self.__process.stderr:
|
||||||
|
self.__stderr_thread.join()
|
||||||
|
return self.__process.returncode
|
||||||
|
|
||||||
|
def terminate(self):
|
||||||
|
if self.__process.stdin:
|
||||||
|
self.closeinput()
|
||||||
|
self.__process.terminate()
|
||||||
|
|
||||||
|
# thread gets data from subprocess stdout
|
||||||
|
def __reader(self, collector, source):
|
||||||
|
while True:
|
||||||
|
data = os.read(source.fileno(), 65536)
|
||||||
|
self.__lock.acquire()
|
||||||
|
collector.append(data)
|
||||||
|
self.__lock.release()
|
||||||
|
if data == "":
|
||||||
|
source.close()
|
||||||
|
break
|
||||||
|
return
|
||||||
|
|
||||||
|
# thread feeds data to subprocess stdin
|
||||||
|
def __feeder(self, pending, drain):
|
||||||
|
while True:
|
||||||
|
self.__inputsem.acquire()
|
||||||
|
self.__lock.acquire()
|
||||||
|
if not pending and self.__quit:
|
||||||
|
drain.close()
|
||||||
|
self.__lock.release()
|
||||||
|
break
|
||||||
|
data = pending.pop(0)
|
||||||
|
self.__lock.release()
|
||||||
|
drain.write(data)
|
||||||
|
|
||||||
|
# non-blocking read of data from subprocess stdout
|
||||||
|
def read(self):
|
||||||
|
self.__lock.acquire()
|
||||||
|
outdata = "".join(self.__collected_outdata)
|
||||||
|
del self.__collected_outdata[:]
|
||||||
|
self.__lock.release()
|
||||||
|
return outdata
|
||||||
|
|
||||||
|
# non-blocking read of data from subprocess stderr
|
||||||
|
def readerr(self):
|
||||||
|
self.__lock.acquire()
|
||||||
|
errdata = "".join(self.__collected_errdata)
|
||||||
|
del self.__collected_errdata[:]
|
||||||
|
self.__lock.release()
|
||||||
|
return errdata
|
||||||
|
|
||||||
|
# non-blocking write to stdin of subprocess
|
||||||
|
def write(self, data):
|
||||||
|
if self.__process.stdin is None:
|
||||||
|
raise ValueError("Writing to process with stdin not a pipe")
|
||||||
|
self.__lock.acquire()
|
||||||
|
self.__pending_input.append(data)
|
||||||
|
self.__inputsem.release()
|
||||||
|
self.__lock.release()
|
||||||
|
|
||||||
|
# close stdinput of subprocess
|
||||||
|
def closeinput(self):
|
||||||
|
self.__lock.acquire()
|
||||||
|
self.__quit = True
|
||||||
|
self.__inputsem.release()
|
||||||
|
self.__lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
# interface to needed routines in openssl's libcrypto
|
||||||
|
def _load_crypto_libcrypto():
|
||||||
|
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
||||||
|
Structure, c_ulong, create_string_buffer, addressof, string_at, cast
|
||||||
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
if libcrypto is None:
|
||||||
|
raise K4MDrmException('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)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
|
||||||
|
|
||||||
|
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
|
||||||
|
|
||||||
|
PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
|
||||||
|
[c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
||||||
|
|
||||||
|
class LibCrypto(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._blocksize = 0
|
||||||
|
self._keyctx = None
|
||||||
|
self.iv = 0
|
||||||
|
|
||||||
|
def set_decrypt_key(self, userkey, iv):
|
||||||
|
self._blocksize = len(userkey)
|
||||||
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
|
raise K4MDrmException('AES improper key used')
|
||||||
|
return
|
||||||
|
keyctx = self._keyctx = AES_KEY()
|
||||||
|
self.iv = iv
|
||||||
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
|
||||||
|
if rv < 0:
|
||||||
|
raise K4MDrmException('Failed to initialize AES key')
|
||||||
|
|
||||||
|
def decrypt(self, data):
|
||||||
|
out = create_string_buffer(len(data))
|
||||||
|
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
|
||||||
|
if rv == 0:
|
||||||
|
raise K4MDrmException('AES decryption failed')
|
||||||
|
return out.raw
|
||||||
|
|
||||||
|
def keyivgen(self, passwd):
|
||||||
|
salt = '16743'
|
||||||
|
saltlen = 5
|
||||||
|
passlen = len(passwd)
|
||||||
|
iter = 0x3e8
|
||||||
|
keylen = 80
|
||||||
|
out = create_string_buffer(keylen)
|
||||||
|
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
|
||||||
|
return out.raw
|
||||||
|
return LibCrypto
|
||||||
|
|
||||||
|
def _load_crypto():
|
||||||
|
LibCrypto = None
|
||||||
|
try:
|
||||||
|
LibCrypto = _load_crypto_libcrypto()
|
||||||
|
except (ImportError, K4MDrmException):
|
||||||
|
pass
|
||||||
|
return LibCrypto
|
||||||
|
|
||||||
|
LibCrypto = _load_crypto()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Utility Routines
|
||||||
|
#
|
||||||
|
|
||||||
|
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||||
|
# returns with the serial number of drive whose BSD Name is "disk0"
|
||||||
|
def GetVolumeSerialNumber():
|
||||||
|
sernum = os.getenv('MYSERIALNUMBER')
|
||||||
|
if sernum != None:
|
||||||
|
return sernum
|
||||||
|
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
||||||
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
|
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||||
|
poll = p.wait('wait')
|
||||||
|
results = p.read()
|
||||||
|
reslst = results.split('\n')
|
||||||
|
cnt = len(reslst)
|
||||||
|
bsdname = None
|
||||||
|
sernum = None
|
||||||
|
foundIt = False
|
||||||
|
for j in xrange(cnt):
|
||||||
|
resline = reslst[j]
|
||||||
|
pp = resline.find('"Serial Number" = "')
|
||||||
|
if pp >= 0:
|
||||||
|
sernum = resline[pp+19:-1]
|
||||||
|
sernum = sernum.strip()
|
||||||
|
bb = resline.find('"BSD Name" = "')
|
||||||
|
if bb >= 0:
|
||||||
|
bsdname = resline[bb+14:-1]
|
||||||
|
bsdname = bsdname.strip()
|
||||||
|
if (bsdname == 'disk0') and (sernum != None):
|
||||||
|
foundIt = True
|
||||||
|
break
|
||||||
|
if not foundIt:
|
||||||
|
sernum = '9999999999'
|
||||||
|
return sernum
|
||||||
|
|
||||||
|
# uses unix env to get username instead of using sysctlbyname
|
||||||
|
def GetUserName():
|
||||||
|
username = os.getenv('USER')
|
||||||
|
return username
|
||||||
|
|
||||||
|
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||||||
|
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||||
|
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
|
||||||
|
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||||
|
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
|
||||||
|
def encode(data, map):
|
||||||
|
result = ""
|
||||||
|
for char in data:
|
||||||
|
value = ord(char)
|
||||||
|
Q = (value ^ 0x80) // len(map)
|
||||||
|
R = value % len(map)
|
||||||
|
result += map[Q]
|
||||||
|
result += map[R]
|
||||||
|
return result
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
def SHA256(message):
|
||||||
|
ctx = hashlib.sha256()
|
||||||
|
ctx.update(message)
|
||||||
|
return ctx.digest()
|
||||||
|
|
||||||
|
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||||
|
def CryptUnprotectData(encryptedData):
|
||||||
|
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
|
||||||
|
passwdData = encode(SHA256(sp),charMap1)
|
||||||
|
crp = LibCrypto()
|
||||||
|
key_iv = crp.keyivgen(passwdData)
|
||||||
|
key = key_iv[0:32]
|
||||||
|
iv = key_iv[32:48]
|
||||||
|
crp.set_decrypt_key(key,iv)
|
||||||
|
cleartext = crp.decrypt(encryptedData)
|
||||||
|
return cleartext
|
||||||
|
|
||||||
|
# Locate and open the .kindle-info file
|
||||||
|
def openKindleInfo(kInfoFile=None):
|
||||||
|
if kInfoFile == None:
|
||||||
|
home = os.getenv('HOME')
|
||||||
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
|
||||||
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
|
p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||||
|
poll = p1.wait('wait')
|
||||||
|
results = p1.read()
|
||||||
|
reslst = results.split('\n')
|
||||||
|
kinfopath = 'NONE'
|
||||||
|
cnt = len(reslst)
|
||||||
|
for j in xrange(cnt):
|
||||||
|
resline = reslst[j]
|
||||||
|
pp = resline.find('.kindle-info')
|
||||||
|
if pp >= 0:
|
||||||
|
kinfopath = resline
|
||||||
|
break
|
||||||
|
if not os.path.exists(kinfopath):
|
||||||
|
raise K4MDrmException('Error: .kindle-info file can not be found')
|
||||||
|
return open(kinfopath,'r')
|
||||||
|
else:
|
||||||
|
return open(kInfoFile, 'r')
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# K4PC Windows specific routines
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
import _winreg as winreg
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
MAX_PATH = 255
|
||||||
|
|
||||||
|
kernel32 = windll.kernel32
|
||||||
|
advapi32 = windll.advapi32
|
||||||
|
crypt32 = windll.crypt32
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||||||
|
#
|
||||||
|
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||||
|
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
||||||
|
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||||
|
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Exceptions for all the problems that might happen during the script
|
||||||
|
#
|
||||||
|
class DrmException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DataBlob(Structure):
|
||||||
|
_fields_ = [('cbData', c_uint),
|
||||||
|
('pbData', c_void_p)]
|
||||||
|
DataBlob_p = POINTER(DataBlob)
|
||||||
|
|
||||||
|
|
||||||
|
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 = GetSystemDirectory().split('\\')[0] + '\\'):
|
||||||
|
vsn = c_uint(0)
|
||||||
|
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
|
||||||
|
return str(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()
|
||||||
|
|
||||||
|
|
||||||
|
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 DrmException("Failed to Unprotect Data")
|
||||||
|
return string_at(outdata.pbData, outdata.cbData)
|
||||||
|
return CryptUnprotectData
|
||||||
|
CryptUnprotectData = CryptUnprotectData()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Locate and open the Kindle.info file.
|
||||||
|
#
|
||||||
|
def openKindleInfo(kInfoFile=None):
|
||||||
|
if kInfoFile == None:
|
||||||
|
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
||||||
|
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||||
|
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
|
||||||
|
else:
|
||||||
|
return open(kInfoFile, 'r')
|
||||||
@@ -3,14 +3,6 @@
|
|||||||
# This is a python script. You need a Python interpreter to run it.
|
# This is a python script. You need a Python interpreter to run it.
|
||||||
# For example, ActiveState Python, which exists for windows.
|
# For example, ActiveState Python, which exists for windows.
|
||||||
#
|
#
|
||||||
# It can run standalone to convert files, or it can be installed as a
|
|
||||||
# plugin for Calibre (http://calibre-ebook.com/about) so that
|
|
||||||
# importing files with DRM 'Just Works'.
|
|
||||||
#
|
|
||||||
# To create a Calibre plugin, rename this file so that the filename
|
|
||||||
# ends in '_plugin.py', put it into a ZIP file and import that Calibre
|
|
||||||
# using its plugin configuration GUI.
|
|
||||||
#
|
|
||||||
# Changelog
|
# Changelog
|
||||||
# 0.01 - Initial version
|
# 0.01 - Initial version
|
||||||
# 0.02 - Huffdic compressed books were not properly decrypted
|
# 0.02 - Huffdic compressed books were not properly decrypted
|
||||||
@@ -37,10 +29,19 @@
|
|||||||
# in utf8 file are encrypted. (Although neither kind gets compressed.)
|
# in utf8 file are encrypted. (Although neither kind gets compressed.)
|
||||||
# This knowledge leads to a simplification of the test for the
|
# This knowledge leads to a simplification of the test for the
|
||||||
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
|
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
|
||||||
# 0.15 - Now outputs 'hearbeat', and is also quicker for long files.
|
# 0.15 - Now outputs 'heartbeat', and is also quicker for long files.
|
||||||
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
|
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
|
||||||
|
# 0.17 - added modifications to support its use as an imported python module
|
||||||
|
# both inside calibre and also in other places (ie K4DeDRM tools)
|
||||||
|
# 0.17a- disabled the standalone plugin feature since a plugin can not import
|
||||||
|
# a plugin
|
||||||
|
# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
|
||||||
|
# Removed the disabled Calibre plug-in code
|
||||||
|
# Permit use of 8-digit PIDs
|
||||||
|
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
|
||||||
|
# 0.20 - Corretion: It seems that multibyte entries are encrypted in a v6 file.
|
||||||
|
|
||||||
__version__ = '0.16'
|
__version__ = '0.20'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
@@ -123,10 +124,11 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
|
|||||||
if testflags & 1:
|
if testflags & 1:
|
||||||
num += getSizeOfTrailingDataEntry(ptr, size - num)
|
num += getSizeOfTrailingDataEntry(ptr, size - num)
|
||||||
testflags >>= 1
|
testflags >>= 1
|
||||||
# Multibyte data, if present, is included in the encryption, so
|
# Check the low bit to see if there's multibyte data present.
|
||||||
# we do not need to check the low bit.
|
# if multibyte data is included in the encryped data, we'll
|
||||||
# if flags & 1:
|
# have already cleared this flag.
|
||||||
# num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
if flags & 1:
|
||||||
|
num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
||||||
return num
|
return num
|
||||||
|
|
||||||
class DrmStripper:
|
class DrmStripper:
|
||||||
@@ -177,9 +179,14 @@ class DrmStripper:
|
|||||||
return found_key
|
return found_key
|
||||||
|
|
||||||
def __init__(self, data_file, pid):
|
def __init__(self, data_file, pid):
|
||||||
if checksumPid(pid[0:-2]) != pid:
|
if len(pid)==10:
|
||||||
raise DrmException("invalid PID checksum")
|
if checksumPid(pid[0:-2]) != pid:
|
||||||
pid = pid[0:-2]
|
raise DrmException("invalid PID checksum")
|
||||||
|
pid = pid[0:-2]
|
||||||
|
elif len(pid)==8:
|
||||||
|
print "PID without checksum given. With checksum PID is "+checksumPid(pid)
|
||||||
|
else:
|
||||||
|
raise DrmException("Invalid PID length")
|
||||||
|
|
||||||
self.data_file = data_file
|
self.data_file = data_file
|
||||||
header = data_file[0:72]
|
header = data_file[0:72]
|
||||||
@@ -202,6 +209,10 @@ class DrmStripper:
|
|||||||
if (mobi_length >= 0xE4) and (mobi_version >= 5):
|
if (mobi_length >= 0xE4) and (mobi_version >= 5):
|
||||||
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
|
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
|
||||||
print "Extra Data Flags = %d" %extra_data_flags
|
print "Extra Data Flags = %d" %extra_data_flags
|
||||||
|
if mobi_version < 7:
|
||||||
|
# multibyte utf8 data is included in the encryption for mobi_version 6 and below
|
||||||
|
# so clear that byte so that we leave it to be decrypted.
|
||||||
|
extra_data_flags &= 0xFFFE
|
||||||
|
|
||||||
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
|
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
|
||||||
if crypto_type == 0:
|
if crypto_type == 0:
|
||||||
@@ -248,63 +259,33 @@ class DrmStripper:
|
|||||||
def getResult(self):
|
def getResult(self):
|
||||||
return self.data_file
|
return self.data_file
|
||||||
|
|
||||||
if not __name__ == "__main__":
|
def getUnencryptedBook(infile,pid):
|
||||||
from calibre.customize import FileTypePlugin
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
|
data_file = file(infile, 'rb').read()
|
||||||
|
strippedFile = DrmStripper(data_file, pid)
|
||||||
|
return strippedFile.getResult()
|
||||||
|
|
||||||
class MobiDeDRM(FileTypePlugin):
|
def main(argv=sys.argv):
|
||||||
name = 'MobiDeDRM' # Name of the plugin
|
|
||||||
description = 'Removes DRM from secure Mobi files'
|
|
||||||
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
|
|
||||||
author = 'The Dark Reverser' # The author of this plugin
|
|
||||||
version = (0, 1, 6) # The version number of this plugin
|
|
||||||
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
|
|
||||||
on_import = True # Run this plugin during the import
|
|
||||||
|
|
||||||
def run(self, path_to_ebook):
|
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
|
||||||
from PyQt4.Qt import QMessageBox
|
|
||||||
PID = self.site_customization
|
|
||||||
data_file = file(path_to_ebook, 'rb').read()
|
|
||||||
ar = PID.split(',')
|
|
||||||
for i in ar:
|
|
||||||
try:
|
|
||||||
unlocked_file = DrmStripper(data_file, i).getResult()
|
|
||||||
except DrmException:
|
|
||||||
# ignore the error
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
of = self.temporary_file('.mobi')
|
|
||||||
of.write(unlocked_file)
|
|
||||||
of.close()
|
|
||||||
return of.name
|
|
||||||
if is_ok_to_use_qt():
|
|
||||||
d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
|
|
||||||
d.show()
|
|
||||||
d.raise_()
|
|
||||||
d.exec_()
|
|
||||||
return path_to_ebook
|
|
||||||
|
|
||||||
def customization_help(self, gui=False):
|
|
||||||
return 'Enter PID (separate multiple PIDs with comma)'
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.stdout=Unbuffered(sys.stdout)
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
print ('MobiDeDrm v%(__version__)s. '
|
print ('MobiDeDrm v%(__version__)s. '
|
||||||
'Copyright 2008-2010 The Dark Reverser.' % globals())
|
'Copyright 2008-2010 The Dark Reverser.' % globals())
|
||||||
if len(sys.argv)<4:
|
if len(argv)<4:
|
||||||
print "Removes protection from Mobipocket books"
|
print "Removes protection from Mobipocket books"
|
||||||
print "Usage:"
|
print "Usage:"
|
||||||
print " %s <infile> <outfile> <PID>" % sys.argv[0]
|
print " %s <infile> <outfile> <PID>" % sys.argv[0]
|
||||||
sys.exit(1)
|
return 1
|
||||||
else:
|
else:
|
||||||
infile = sys.argv[1]
|
infile = argv[1]
|
||||||
outfile = sys.argv[2]
|
outfile = argv[2]
|
||||||
pid = sys.argv[3]
|
pid = argv[3]
|
||||||
data_file = file(infile, 'rb').read()
|
|
||||||
try:
|
try:
|
||||||
strippedFile = DrmStripper(data_file, pid)
|
stripped_file = getUnencryptedBook(infile, pid)
|
||||||
file(outfile, 'wb').write(strippedFile.getResult())
|
file(outfile, 'wb').write(stripped_file)
|
||||||
except DrmException, e:
|
except DrmException, e:
|
||||||
print "Error: %s" % e
|
print "Error: %s" % e
|
||||||
sys.exit(1)
|
return 1
|
||||||
sys.exit(0)
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
|
# implement just enough of des from openssl to make erdr2pml.py happy
|
||||||
|
|
||||||
|
def load_libcrypto():
|
||||||
|
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_char, c_int, c_long, \
|
||||||
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
|
from ctypes.util import find_library
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
|
||||||
|
if libcrypto is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
|
||||||
|
# typedef struct DES_ks
|
||||||
|
# {
|
||||||
|
# union
|
||||||
|
# {
|
||||||
|
# DES_cblock cblock;
|
||||||
|
# /* make sure things are correct size on machines with
|
||||||
|
# * 8 byte longs */
|
||||||
|
# DES_LONG deslong[2];
|
||||||
|
# } ks[16];
|
||||||
|
# } DES_key_schedule;
|
||||||
|
|
||||||
|
# just create a big enough place to hold everything
|
||||||
|
# it will have alignment of structure so we should be okay (16 byte aligned?)
|
||||||
|
class DES_KEY_SCHEDULE(Structure):
|
||||||
|
_fields_ = [('DES_cblock1', c_char * 16),
|
||||||
|
('DES_cblock2', c_char * 16),
|
||||||
|
('DES_cblock3', c_char * 16),
|
||||||
|
('DES_cblock4', c_char * 16),
|
||||||
|
('DES_cblock5', c_char * 16),
|
||||||
|
('DES_cblock6', c_char * 16),
|
||||||
|
('DES_cblock7', c_char * 16),
|
||||||
|
('DES_cblock8', c_char * 16),
|
||||||
|
('DES_cblock9', c_char * 16),
|
||||||
|
('DES_cblock10', c_char * 16),
|
||||||
|
('DES_cblock11', c_char * 16),
|
||||||
|
('DES_cblock12', c_char * 16),
|
||||||
|
('DES_cblock13', c_char * 16),
|
||||||
|
('DES_cblock14', c_char * 16),
|
||||||
|
('DES_cblock15', c_char * 16),
|
||||||
|
('DES_cblock16', c_char * 16)]
|
||||||
|
|
||||||
|
DES_KEY_SCHEDULE_p = POINTER(DES_KEY_SCHEDULE)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
DES_set_key = F(None, 'DES_set_key',[c_char_p, DES_KEY_SCHEDULE_p])
|
||||||
|
DES_ecb_encrypt = F(None, 'DES_ecb_encrypt',[c_char_p, c_char_p, DES_KEY_SCHEDULE_p, c_int])
|
||||||
|
|
||||||
|
|
||||||
|
class DES(object):
|
||||||
|
def __init__(self, key):
|
||||||
|
if len(key) != 8 :
|
||||||
|
raise Error('DES improper key used')
|
||||||
|
return
|
||||||
|
self.key = key
|
||||||
|
self.keyschedule = DES_KEY_SCHEDULE()
|
||||||
|
DES_set_key(self.key, self.keyschedule)
|
||||||
|
def desdecrypt(self, data):
|
||||||
|
ob = create_string_buffer(len(data))
|
||||||
|
DES_ecb_encrypt(data, ob, self.keyschedule, 0)
|
||||||
|
return ob.raw
|
||||||
|
def decrypt(self, data):
|
||||||
|
if not data:
|
||||||
|
return ''
|
||||||
|
i = 0
|
||||||
|
result = []
|
||||||
|
while i < len(data):
|
||||||
|
block = data[i:i+8]
|
||||||
|
processed_block = self.desdecrypt(block)
|
||||||
|
result.append(processed_block)
|
||||||
|
i += 8
|
||||||
|
return ''.join(result)
|
||||||
|
|
||||||
|
return DES
|
||||||
|
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
#!/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 zinfo in self.inzip.infolist():
|
||||||
|
data = None
|
||||||
|
nzinfo = zinfo
|
||||||
|
try:
|
||||||
|
data = self.inzip.read(zinfo.filename)
|
||||||
|
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())
|
||||||
|
|
||||||
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
#include <cstdlib>
|
|
||||||
#include <iostream>
|
|
||||||
#include <conio.h>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
// Variables
|
|
||||||
int TopazTrue = 0;
|
|
||||||
int strlength = 0;
|
|
||||||
char uinfile[80];
|
|
||||||
char outfile[80];
|
|
||||||
char command[80];
|
|
||||||
char buffer[80];
|
|
||||||
|
|
||||||
// String initialization
|
|
||||||
strcpy(uinfile,"");
|
|
||||||
strcpy(outfile,"");
|
|
||||||
strcpy(buffer,"");
|
|
||||||
strcpy(command,"skindle "); // string preloaded with "skindle "
|
|
||||||
|
|
||||||
|
|
||||||
cout << "\n\n\n Please enter the name of the book to be converted:\n\n ";
|
|
||||||
cout << " Don't forget the prc file extension!\n\n ";
|
|
||||||
cout << " Watch out for zeros and Os. Zeros are skinny and Os are fat.\n\n\n ";
|
|
||||||
|
|
||||||
cin >> uinfile; // get file name of the book to be converted from user
|
|
||||||
|
|
||||||
|
|
||||||
ifstream infile(uinfile);
|
|
||||||
infile.getline(buffer,4);
|
|
||||||
|
|
||||||
|
|
||||||
if (strncmp (buffer,"TPZ",3)==0) // open file and test first 3 char if TPZ then book is topaz
|
|
||||||
{
|
|
||||||
TopazTrue = 1; // This is a Topaz file
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
strlength = strlen(uinfile);
|
|
||||||
|
|
||||||
if(strlength > 13)
|
|
||||||
{
|
|
||||||
strncat(outfile,uinfile,10); // Create output file name using first 10 char of input file name
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
strncat(outfile,uinfile, (strlength - 4)); // If file name is less than 10 characters
|
|
||||||
}
|
|
||||||
if(TopazTrue == 1) // This is Topaz Book
|
|
||||||
{
|
|
||||||
strcat(command,"-d "); // Add the topaz switch to the command line
|
|
||||||
|
|
||||||
strcat(outfile,".tpz"); // give tpz file extension to topaz output file
|
|
||||||
} // end of TopazTrue
|
|
||||||
else
|
|
||||||
{
|
|
||||||
strcat(outfile,".azw");
|
|
||||||
} // if not Topaz make it azw
|
|
||||||
|
|
||||||
strcat(command,"-i "); // Add the input switch to the command line
|
|
||||||
strcat(command,uinfile); // add the input file name to the command line
|
|
||||||
strcat(command," -o "); // add the output switch to the command line
|
|
||||||
strcat(command,outfile); // Add the output file name to the command line
|
|
||||||
|
|
||||||
cout << "\n\n The skindle program is called here.\n";
|
|
||||||
cout << " Any errors reported between here and \"The command line used was:\"\n";
|
|
||||||
cout << " Are errors from the skindle program. Not EZskindle4PC.\n\n";
|
|
||||||
|
|
||||||
|
|
||||||
system(command); // call skindle program to convert the book
|
|
||||||
|
|
||||||
|
|
||||||
cout << "\n\n The command line used was:\n\n";
|
|
||||||
cout << " " << command << "\n";
|
|
||||||
cout << "\n\n\n Please note the output file is created from the input";
|
|
||||||
cout << "\n file name. The file extension is changed to tpz for Topaz";
|
|
||||||
cout << "\n files and to azw for non-Topaz files. Also, _EBOK is removed ";
|
|
||||||
cout << "\n from the file name. This is to make it eaiser to identify ";
|
|
||||||
cout << "\n the file with no DRM.";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
system("PAUSE");
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,44 +0,0 @@
|
|||||||
LZskindle4PCv1_1 The Lazy skindle program for those who are typing impared
|
|
||||||
|
|
||||||
To setup:
|
|
||||||
|
|
||||||
1. Create a new folder: example C:\skindle
|
|
||||||
|
|
||||||
2. Place LZskindle4PCv1_1.exe and skindle.exe in this folder.
|
|
||||||
|
|
||||||
3. Create 2 subfolders: C:\skindle\input
|
|
||||||
and C:\skindle\output
|
|
||||||
|
|
||||||
|
|
||||||
To run:
|
|
||||||
|
|
||||||
1. Copy the book(s) you wish to remove DRM from into the input directory
|
|
||||||
(leave the originals in my kindle folder).
|
|
||||||
|
|
||||||
2. Double click on LZskindle4PCv1_0.exe
|
|
||||||
|
|
||||||
3. A DOS window will open and will show skindle being called for
|
|
||||||
each book in the input directory.
|
|
||||||
|
|
||||||
4. The books with the DRM removed will now be in the output directory.
|
|
||||||
|
|
||||||
Rev1_1
|
|
||||||
|
|
||||||
fixed program to allow any file extension. My testing indicates that
|
|
||||||
skindle does not care what file extension a file has. If it is a file type
|
|
||||||
that it can convert it will. If the file is not compatible it will close
|
|
||||||
and give an unknown file type message.
|
|
||||||
|
|
||||||
Rev1_0
|
|
||||||
|
|
||||||
If the program is run with no files in the input directory you will get a
|
|
||||||
<EFBFBD>File not Found<6E> error and the program will terminate.
|
|
||||||
|
|
||||||
PLEASE USE ONLY FOR YOUR PERSONAL USE <20> ON BOOKS YOU PAID FOR!!
|
|
||||||
|
|
||||||
This program is provided to allow you to archive your library in a format that
|
|
||||||
will outlast the kindle. Technology moves on and you should not have to reinvest
|
|
||||||
in an entire new library when the Kindle is obsolete. Please do not use this program
|
|
||||||
for piracy.
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
#include <cstdlib>
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
//#include <conio.h>
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
// Variable Declarations ??
|
|
||||||
char buffer[80];
|
|
||||||
int error = 0;
|
|
||||||
// int YesNo = 0;
|
|
||||||
// int exit = 0;
|
|
||||||
// Variables EZskindle4PC
|
|
||||||
int TopazTrue = 0;
|
|
||||||
int strlength = 0;
|
|
||||||
char uinfile[80];
|
|
||||||
char outfile[80];
|
|
||||||
char command[80];
|
|
||||||
char buffer2[20];
|
|
||||||
char tempfile[80];
|
|
||||||
|
|
||||||
// Initialize strings
|
|
||||||
strcpy(uinfile,"");
|
|
||||||
strcpy(outfile,"");
|
|
||||||
strcpy(buffer,"");
|
|
||||||
strcpy(buffer2,"");
|
|
||||||
strcpy(command,"skindle "); // string preloaded with "skindle "
|
|
||||||
|
|
||||||
|
|
||||||
//// Beginning of program code ////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
system("dir /b .\\input\\*.* > books.txt"); // Create txt file with list of books
|
|
||||||
// No testing of file type being done
|
|
||||||
// I am letting skindle determing if valid
|
|
||||||
// file type
|
|
||||||
// Read in the list of book file names
|
|
||||||
|
|
||||||
ifstream infile("books.txt");
|
|
||||||
|
|
||||||
do // while not end of file
|
|
||||||
{
|
|
||||||
infile.getline(buffer,50); // load the first 50 characters of the line to buffer
|
|
||||||
|
|
||||||
|
|
||||||
if(strcmp(buffer, "")!= 0) // If there is file name in the buffer do this on last loop buffer will be empty
|
|
||||||
{
|
|
||||||
strcpy(uinfile,buffer); // load file name from buffer
|
|
||||||
|
|
||||||
strcpy(tempfile,".\\input\\"); // load directory name for input files
|
|
||||||
strcat(tempfile,buffer); // load the file name
|
|
||||||
ifstream infile2(tempfile); // open the book file for reading
|
|
||||||
infile2.getline(buffer2,4); // load first 4 char from file
|
|
||||||
|
|
||||||
infile2.close(); // close the book file
|
|
||||||
|
|
||||||
|
|
||||||
if (strncmp (buffer2,"TPZ",3)==0) // open file and test first 3 char if TPZ then book is topaz
|
|
||||||
{
|
|
||||||
TopazTrue = 1; // This is a Topaz file
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
strlength = strlen(uinfile);
|
|
||||||
|
|
||||||
if(strlength > 13)
|
|
||||||
{
|
|
||||||
strncat(outfile,uinfile,10); // Create output file name using first 10 char of input file name
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
strncat(outfile,uinfile, (strlength - 4)); // If file name is less than 10 characters
|
|
||||||
}
|
|
||||||
if(TopazTrue == 1) // This is Topaz Book
|
|
||||||
{
|
|
||||||
strcat(command,"-d "); // Add the topaz switch to the command line
|
|
||||||
|
|
||||||
strcat(outfile,".tpz"); // give tpz file extension to topaz output file
|
|
||||||
} // end of TopazTrue
|
|
||||||
else
|
|
||||||
{
|
|
||||||
strcat(outfile,".azw");
|
|
||||||
} // if not Topaz make it azw
|
|
||||||
|
|
||||||
strcat(command,"-i "); // Add the input switch to the command line
|
|
||||||
strcat(command,".\\input\\"); // Add the input directory to the command line
|
|
||||||
strcat(command,uinfile); // add the input file name to the command line
|
|
||||||
strcat(command," -o "); // add the output switch to the command line
|
|
||||||
strcat(command,".\\output\\"); // Add directory for out files
|
|
||||||
strcat(command,outfile); // Add the output file name to the command line
|
|
||||||
|
|
||||||
cout << "\n\n The skindle program is called here.\n";
|
|
||||||
cout << " Any errors reported between here and \"The command line used was:\"\n";
|
|
||||||
cout << " Are errors from the skindle program. Not EZskindle4PC.\n\n";
|
|
||||||
|
|
||||||
|
|
||||||
system(command); // call skindle program to convert the book
|
|
||||||
|
|
||||||
|
|
||||||
cout << "\n\n The command line used was:\n\n";
|
|
||||||
cout << " " << command << "\n\n\n\n";
|
|
||||||
|
|
||||||
|
|
||||||
}// end of file name in the buffer required to prevent execution on EOF
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
strcpy(command,"skindle "); // reset strings and variables for next book
|
|
||||||
strcpy(outfile,"");
|
|
||||||
strcpy(uinfile,"");
|
|
||||||
strcpy(buffer,"");
|
|
||||||
strcpy(buffer2,"");
|
|
||||||
TopazTrue = 0;
|
|
||||||
strlength = 0;
|
|
||||||
|
|
||||||
}while (! infile.eof() ); // no more books in the file
|
|
||||||
|
|
||||||
infile.close(); // close books.txt
|
|
||||||
|
|
||||||
|
|
||||||
// cout << "\n\n\n Do you want to delete all of the books from the input directory?\n\n";
|
|
||||||
// cout << " DO NOT DELETE IF THESE ARE ONLY COPY OF YOUR BOOKS!!!!\n\n";
|
|
||||||
// cout << " Y or N: ";
|
|
||||||
|
|
||||||
|
|
||||||
// do { // while not yes or no
|
|
||||||
// YesNo = getch(); // This is a DOS/Windows console command not standard C may not be
|
|
||||||
// // Usable under Unix or Mac implementations
|
|
||||||
//
|
|
||||||
// if((YesNo == 121)||(YesNo == 89)) // y or Y is true
|
|
||||||
// {
|
|
||||||
// exit = 1; // valid input exit do while loop
|
|
||||||
// cout << "\n\n";
|
|
||||||
// system("del .\\input\\*.*"); // delete everything in the input directory
|
|
||||||
// cout << "\n\n";
|
|
||||||
// }
|
|
||||||
// if((YesNo == 110)||(YesNo == 78)) // n or N is true
|
|
||||||
// {
|
|
||||||
// exit = 1; // valid input exit do while loop
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }while (exit != 1);
|
|
||||||
// cout << "\n\nYesNo = " << YesNo << "\n\n";
|
|
||||||
|
|
||||||
system("PAUSE");
|
|
||||||
|
|
||||||
system("del books.txt"); // Delete txt file with list of books
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,27 +0,0 @@
|
|||||||
Ezskindle4PC.exe
|
|
||||||
|
|
||||||
This executable program makes using skindle easier for people using Windows PC<50>s.
|
|
||||||
|
|
||||||
I do not know if it will work under any other operating system, however, I have included
|
|
||||||
the source code should anyone want to port it into other operating systems.
|
|
||||||
|
|
||||||
To use this program:
|
|
||||||
|
|
||||||
1. Copy the ezskindle4PC.exe into the same directory with the skindle files.
|
|
||||||
2. Copy the kindle book into the same directory.
|
|
||||||
3. double click the EZskindle4PCv1_0.exe file.
|
|
||||||
a. A DOS window will open and you will be asked for the name of the file you want to work with.
|
|
||||||
4. Type in the book<6F>s file name. (it will look something like B000WCTBTA_EBOK.prc)
|
|
||||||
5. The program will then check if it is a Topaz file and then create the output file name using the
|
|
||||||
first part of the input file name. It will use <20>tpz<70> file extension for Topaz books and will use <20>azw<7A>
|
|
||||||
for non topaz books. The files with the <20>azw<7A> format can be converted to other ebook formats using
|
|
||||||
Calibre. If you want to convert Topaz books to other formats you need to use Topaz tools not skindle.
|
|
||||||
6. The program will then create a command line and call the skindle program to process the book and
|
|
||||||
remove the DRM.
|
|
||||||
7. The program will pause and allow you to see the result of the skindle process.
|
|
||||||
8. Press any key to close the program.
|
|
||||||
|
|
||||||
version 1.1
|
|
||||||
Ok
|
|
||||||
|
|
||||||
Found a new 32 bit compiler and I think I have worked out the kinks.
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
Readme.txt
|
|
||||||
|
|
||||||
- LZskindle4PC is a front end for running skindle that makes it easier for some people who do not like command lines to use it.
|
|
||||||
|
|
||||||
- skindle has completely reverse engineered how the book specific PID is generated for Kindle4PC so it can be very useful when new version of Kindle4PC are released and unswindle has not yet been updated. Unfortunately, skindle has some minor bugs that can actually result in corrupted eBooks. No one has yet tracked them down and fixed them. Until they do, use at your own risk.
|
|
||||||
|
|
||||||
- unswindle can be used to find the book specific PID but it needs to be updated for each version of Kindle4PC that Amazon releases (and therefore is also useful for Linux users who have Wine). This program “patches” the Kindle4PC executable and therefore is very release specific.
|
|
||||||
Unfortunately unswindle v7 the latest, has not been updated to work with the latest version of Kindle for PC. You will need to find one of the older versions of Kindle4PC and prevent later updates in order to use this tool.
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
|
|
||||||
OBJS=skindle.o md5.o sha1.o b64.o skinutils.o cbuf.o mobi.o tpz.o
|
|
||||||
|
|
||||||
CC=gcc
|
|
||||||
LD=gcc
|
|
||||||
EXE=skindle
|
|
||||||
EXTRALIBS=libz.a -lCrypt32 -lAdvapi32
|
|
||||||
CFLAGS=-mno-cygwin
|
|
||||||
|
|
||||||
#use the following to strip your binary
|
|
||||||
LDFLAGS=-s -mno-cygwin
|
|
||||||
#LDFLAGS=-mno-cygwin
|
|
||||||
|
|
||||||
all: $(EXE)
|
|
||||||
|
|
||||||
%.o: %.c
|
|
||||||
$(CC) -c $(CFLAGS) -g $(INC) $< -o $@
|
|
||||||
|
|
||||||
$(EXE): $(OBJS)
|
|
||||||
$(LD) $(LDFLAGS) -o $@ -g $(OBJS) $(EXTRALIBS)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
-@rm *.o
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
|
|
||||||
/*
|
|
||||||
Copyright 2010 BartSimpson aka skindle
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Dependencies: zlib (included)
|
|
||||||
* build on cygwin using make and the included make file
|
|
||||||
* A fully functionaly windows executable is included
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* MUST be run on the computer on which KindleForPC is installed
|
|
||||||
* under the account that was used to purchase DRM'ed content.
|
|
||||||
* Requires your kindle.info file which can be found in something like:
|
|
||||||
* <User home>\...\Amazon\Kindle For PC\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}
|
|
||||||
* where ... varies by platform but is "Local Settings\Application Data" on XP
|
|
||||||
* skindle will attempt to find this file automatically.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
What: KindleForPC DRM removal utility to preserve your fair use rights!
|
|
||||||
Why: Fair use is a well established doctrine, and I am no fan of vendor
|
|
||||||
lockin.
|
|
||||||
How: This utility implements the PID extraction, DRM key generation and
|
|
||||||
decryption algorithms employed by the KindleForPC application. This
|
|
||||||
is a stand alone app that does not require you to determine a PID on
|
|
||||||
your own, and it does not need to run KindleForPC in order to extract
|
|
||||||
any data from memory.
|
|
||||||
|
|
||||||
Shoutz: The DarkReverser - thanks for mobidedrm! The last part of this
|
|
||||||
is just a C port of mobidedrm.
|
|
||||||
labba and I<3cabbages for motivating me to do this the right way.
|
|
||||||
You guys shouldn't need to spend all your time responding to all the
|
|
||||||
changes Amazon is going to force you to make in unswindle each time
|
|
||||||
the release a new version.
|
|
||||||
CMBDTC - nice work on the topaz break!
|
|
||||||
Lawrence Lessig - You are my hero. 'Nuff said.
|
|
||||||
Cory Doctorow - A voice of reason in a sea of insanity
|
|
||||||
Thumbs down: Disney, MPAA, RIAA - you guys suck. Making billions off
|
|
||||||
of the exploitation of works out of copyright while vigourously
|
|
||||||
pushing copyright extension to prevent others from doing the same
|
|
||||||
is the height of hypocrasy.
|
|
||||||
Congress - you guys suck too. Why you arrogant pricks think you
|
|
||||||
are smarter than the founding fathers is beyond me.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Rationale:
|
|
||||||
Need a tool to enable fair use of purchased ebook content.
|
|
||||||
Need a tool that is not dependent on any particular version of
|
|
||||||
KindleForPC and that does not need to run KindleForPC in order to
|
|
||||||
extract a PID. The tool documents the structure of the kindle.info
|
|
||||||
file and the data and algorthims that are used to derive per book
|
|
||||||
PID values.
|
|
||||||
|
|
||||||
Installing:
|
|
||||||
A compiled binary is included. Though it was built using cygwin, it
|
|
||||||
should not require a cygwin installation in order to run it. To build
|
|
||||||
from source, you will need cygwin with gcc and make.
|
|
||||||
This has not been tested with Visual Studio, though you may be able to
|
|
||||||
pile all the files into a project and add the Crypt32.lib, Advapi32 and
|
|
||||||
zlib1 dependencies to build it.
|
|
||||||
|
|
||||||
usage: ./skindle [-d] [-v] -i <ebook file> -o <output file> [-k kindle.info file] [-p pid]
|
|
||||||
-d optional, for topaz files only, produce a decompressed output file
|
|
||||||
-i required name of the input mobi or topaz file
|
|
||||||
-o required name of the output file to generate
|
|
||||||
-k optional kindle.info path
|
|
||||||
-v dump the contents of kindle.info
|
|
||||||
-p additional PID values to attempt (can specifiy multiple times)
|
|
||||||
|
|
||||||
You only need to specify a kindle.info path if skindle can't find
|
|
||||||
your kindle.info file automatically
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
/*********************************************************************\
|
|
||||||
LICENCE: Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
|
||||||
obtaining a copy of this software and associated
|
|
||||||
documentation files (the "Software"), to deal in the
|
|
||||||
Software without restriction, including without limitation
|
|
||||||
the rights to use, copy, modify, merge, publish, distribute,
|
|
||||||
sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so,
|
|
||||||
subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall
|
|
||||||
be included in all copies or substantial portions of the
|
|
||||||
Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
||||||
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
||||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
|
||||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
||||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
||||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
VERSION HISTORY:
|
|
||||||
Bob Trower 08/04/01 -- Create Version 0.00.00B
|
|
||||||
|
|
||||||
\******************************************************************* */
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Translation Table as described in RFC1113
|
|
||||||
*/
|
|
||||||
static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
||||||
|
|
||||||
/*
|
|
||||||
** encodeblock
|
|
||||||
**
|
|
||||||
** encode 3 8-bit binary bytes as 4 '6-bit' characters
|
|
||||||
*/
|
|
||||||
void encodeblock(unsigned char in[3], unsigned char out[4], int len) {
|
|
||||||
out[0] = cb64[ in[0] >> 2 ];
|
|
||||||
out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ];
|
|
||||||
out[2] = (unsigned char) (len > 1 ? cb64[ ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '=');
|
|
||||||
out[3] = (unsigned char) (len > 2 ? cb64[ in[2] & 0x3f ] : '=');
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
** encode
|
|
||||||
**
|
|
||||||
** base64 encode a stream adding padding and line breaks as per spec.
|
|
||||||
*/
|
|
||||||
unsigned int base64(unsigned char *inbuf, unsigned int len, unsigned char *outbuf) {
|
|
||||||
unsigned char in[3], out[4];
|
|
||||||
int c;
|
|
||||||
unsigned int i = 0;
|
|
||||||
unsigned int outlen = 0;
|
|
||||||
while (i < len) {
|
|
||||||
int n = 0;
|
|
||||||
for(c = 0; c < 3; c++, i++) {
|
|
||||||
if (i < len) {
|
|
||||||
in[c] = inbuf[i];
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
in[c] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (n) {
|
|
||||||
encodeblock(in, out, n);
|
|
||||||
for(c = 0; c < 4; c++) {
|
|
||||||
outbuf[outlen++] = out[c];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return outlen;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 BartSimpson aka skindle
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "cbuf.h"
|
|
||||||
|
|
||||||
cbuf *b_new(unsigned int size) {
|
|
||||||
cbuf *b = (cbuf*)calloc(sizeof(cbuf), 1);
|
|
||||||
if (b) {
|
|
||||||
b->buf = (unsigned char *)malloc(size);
|
|
||||||
b->size = b->buf ? size : 0;
|
|
||||||
}
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
void b_free(cbuf *b) {
|
|
||||||
if (b) {
|
|
||||||
free(b->buf);
|
|
||||||
free(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void b_add_byte(cbuf *b, unsigned char ch) {
|
|
||||||
if (b == NULL) return;
|
|
||||||
if (b->idx == b->size) {
|
|
||||||
unsigned char *p = realloc(b->buf, b->size * 2);
|
|
||||||
if (p) {
|
|
||||||
b->buf = p;
|
|
||||||
b->size = b->size * 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (b->idx < b->size) {
|
|
||||||
b->buf[b->idx++] = ch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void b_add_buf(cbuf *b, unsigned char *buf, unsigned int len) {
|
|
||||||
if (b == NULL) return;
|
|
||||||
unsigned int new_sz = b->idx + len;
|
|
||||||
while (b->size < new_sz) {
|
|
||||||
unsigned char *p = realloc(b->buf, b->size * 2);
|
|
||||||
if (p) {
|
|
||||||
b->buf = p;
|
|
||||||
b->size = b->size * 2;
|
|
||||||
}
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
if ((b->idx + len) <= b->size) {
|
|
||||||
memcpy(b->buf + b->idx, buf, len);
|
|
||||||
b->idx += len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void b_add_str(cbuf *b, const char *buf) {
|
|
||||||
if (b == NULL) return;
|
|
||||||
unsigned int len = strlen(buf);
|
|
||||||
unsigned int new_sz = b->idx + len;
|
|
||||||
while (b->size < new_sz) {
|
|
||||||
unsigned char *p = realloc(b->buf, b->size * 2);
|
|
||||||
if (p) {
|
|
||||||
b->buf = p;
|
|
||||||
b->size = b->size * 2;
|
|
||||||
}
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
if ((b->idx + len) <= b->size) {
|
|
||||||
memcpy(b->buf + b->idx, buf, len);
|
|
||||||
b->idx += len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 BartSimpson aka skindle
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __CBUF_H
|
|
||||||
#define __CBUF_H
|
|
||||||
|
|
||||||
typedef struct _cbuf {
|
|
||||||
unsigned int size; //current size
|
|
||||||
unsigned int idx; //current position
|
|
||||||
unsigned char *buf;
|
|
||||||
} cbuf;
|
|
||||||
|
|
||||||
cbuf *b_new(unsigned int size);
|
|
||||||
void b_free(cbuf *b);
|
|
||||||
void b_add_byte(cbuf *b, unsigned char ch);
|
|
||||||
void b_add_buf(cbuf *b, unsigned char *buf, unsigned int len);
|
|
||||||
void b_add_str(cbuf *b, const char *buf);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
Binary file not shown.
@@ -1,381 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved.
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any damages
|
|
||||||
arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
L. Peter Deutsch
|
|
||||||
ghost@aladdin.com
|
|
||||||
|
|
||||||
*/
|
|
||||||
/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
|
|
||||||
/*
|
|
||||||
Independent implementation of MD5 (RFC 1321).
|
|
||||||
|
|
||||||
This code implements the MD5 Algorithm defined in RFC 1321, whose
|
|
||||||
text is available at
|
|
||||||
http://www.ietf.org/rfc/rfc1321.txt
|
|
||||||
The code is derived from the text of the RFC, including the test suite
|
|
||||||
(section A.5) but excluding the rest of Appendix A. It does not include
|
|
||||||
any code or documentation that is identified in the RFC as being
|
|
||||||
copyrighted.
|
|
||||||
|
|
||||||
The original and principal author of md5.c is L. Peter Deutsch
|
|
||||||
<ghost@aladdin.com>. Other authors are noted in the change history
|
|
||||||
that follows (in reverse chronological order):
|
|
||||||
|
|
||||||
2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
|
|
||||||
either statically or dynamically; added missing #include <string.h>
|
|
||||||
in library.
|
|
||||||
2002-03-11 lpd Corrected argument list for main(), and added int return
|
|
||||||
type, in test program and T value program.
|
|
||||||
2002-02-21 lpd Added missing #include <stdio.h> in test program.
|
|
||||||
2000-07-03 lpd Patched to eliminate warnings about "constant is
|
|
||||||
unsigned in ANSI C, signed in traditional"; made test program
|
|
||||||
self-checking.
|
|
||||||
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
|
|
||||||
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
|
|
||||||
1999-05-03 lpd Original version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "md5.h"
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
|
|
||||||
#ifdef ARCH_IS_BIG_ENDIAN
|
|
||||||
# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
|
|
||||||
#else
|
|
||||||
# define BYTE_ORDER 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define T_MASK ((md5_word_t)~0)
|
|
||||||
#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
|
|
||||||
#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
|
|
||||||
#define T3 0x242070db
|
|
||||||
#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
|
|
||||||
#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
|
|
||||||
#define T6 0x4787c62a
|
|
||||||
#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
|
|
||||||
#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
|
|
||||||
#define T9 0x698098d8
|
|
||||||
#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
|
|
||||||
#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
|
|
||||||
#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
|
|
||||||
#define T13 0x6b901122
|
|
||||||
#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
|
|
||||||
#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
|
|
||||||
#define T16 0x49b40821
|
|
||||||
#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
|
|
||||||
#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
|
|
||||||
#define T19 0x265e5a51
|
|
||||||
#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
|
|
||||||
#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
|
|
||||||
#define T22 0x02441453
|
|
||||||
#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
|
|
||||||
#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
|
|
||||||
#define T25 0x21e1cde6
|
|
||||||
#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
|
|
||||||
#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
|
|
||||||
#define T28 0x455a14ed
|
|
||||||
#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
|
|
||||||
#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
|
|
||||||
#define T31 0x676f02d9
|
|
||||||
#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
|
|
||||||
#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
|
|
||||||
#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
|
|
||||||
#define T35 0x6d9d6122
|
|
||||||
#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
|
|
||||||
#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
|
|
||||||
#define T38 0x4bdecfa9
|
|
||||||
#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
|
|
||||||
#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
|
|
||||||
#define T41 0x289b7ec6
|
|
||||||
#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
|
|
||||||
#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
|
|
||||||
#define T44 0x04881d05
|
|
||||||
#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
|
|
||||||
#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
|
|
||||||
#define T47 0x1fa27cf8
|
|
||||||
#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
|
|
||||||
#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
|
|
||||||
#define T50 0x432aff97
|
|
||||||
#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
|
|
||||||
#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
|
|
||||||
#define T53 0x655b59c3
|
|
||||||
#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
|
|
||||||
#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
|
|
||||||
#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
|
|
||||||
#define T57 0x6fa87e4f
|
|
||||||
#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
|
|
||||||
#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
|
|
||||||
#define T60 0x4e0811a1
|
|
||||||
#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
|
|
||||||
#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
|
|
||||||
#define T63 0x2ad7d2bb
|
|
||||||
#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
|
|
||||||
{
|
|
||||||
md5_word_t
|
|
||||||
a = pms->abcd[0], b = pms->abcd[1],
|
|
||||||
c = pms->abcd[2], d = pms->abcd[3];
|
|
||||||
md5_word_t t;
|
|
||||||
#if BYTE_ORDER > 0
|
|
||||||
/* Define storage only for big-endian CPUs. */
|
|
||||||
md5_word_t X[16];
|
|
||||||
#else
|
|
||||||
/* Define storage for little-endian or both types of CPUs. */
|
|
||||||
md5_word_t xbuf[16];
|
|
||||||
const md5_word_t *X;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
{
|
|
||||||
#if BYTE_ORDER == 0
|
|
||||||
/*
|
|
||||||
* Determine dynamically whether this is a big-endian or
|
|
||||||
* little-endian machine, since we can use a more efficient
|
|
||||||
* algorithm on the latter.
|
|
||||||
*/
|
|
||||||
static const int w = 1;
|
|
||||||
|
|
||||||
if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
|
|
||||||
#endif
|
|
||||||
#if BYTE_ORDER <= 0 /* little-endian */
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* On little-endian machines, we can process properly aligned
|
|
||||||
* data without copying it.
|
|
||||||
*/
|
|
||||||
if (!((data - (const md5_byte_t *)0) & 3)) {
|
|
||||||
/* data are properly aligned */
|
|
||||||
X = (const md5_word_t *)data;
|
|
||||||
} else {
|
|
||||||
/* not aligned */
|
|
||||||
memcpy(xbuf, data, 64);
|
|
||||||
X = xbuf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if BYTE_ORDER == 0
|
|
||||||
else /* dynamic big-endian */
|
|
||||||
#endif
|
|
||||||
#if BYTE_ORDER >= 0 /* big-endian */
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* On big-endian machines, we must arrange the bytes in the
|
|
||||||
* right order.
|
|
||||||
*/
|
|
||||||
const md5_byte_t *xp = data;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
# if BYTE_ORDER == 0
|
|
||||||
X = xbuf; /* (dynamic only) */
|
|
||||||
# else
|
|
||||||
# define xbuf X /* (static only) */
|
|
||||||
# endif
|
|
||||||
for (i = 0; i < 16; ++i, xp += 4)
|
|
||||||
xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
|
|
||||||
|
|
||||||
/* Round 1. */
|
|
||||||
/* Let [abcd k s i] denote the operation
|
|
||||||
a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
|
|
||||||
#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
|
|
||||||
#define SET(a, b, c, d, k, s, Ti)\
|
|
||||||
t = a + F(b,c,d) + X[k] + Ti;\
|
|
||||||
a = ROTATE_LEFT(t, s) + b
|
|
||||||
/* Do the following 16 operations. */
|
|
||||||
SET(a, b, c, d, 0, 7, T1);
|
|
||||||
SET(d, a, b, c, 1, 12, T2);
|
|
||||||
SET(c, d, a, b, 2, 17, T3);
|
|
||||||
SET(b, c, d, a, 3, 22, T4);
|
|
||||||
SET(a, b, c, d, 4, 7, T5);
|
|
||||||
SET(d, a, b, c, 5, 12, T6);
|
|
||||||
SET(c, d, a, b, 6, 17, T7);
|
|
||||||
SET(b, c, d, a, 7, 22, T8);
|
|
||||||
SET(a, b, c, d, 8, 7, T9);
|
|
||||||
SET(d, a, b, c, 9, 12, T10);
|
|
||||||
SET(c, d, a, b, 10, 17, T11);
|
|
||||||
SET(b, c, d, a, 11, 22, T12);
|
|
||||||
SET(a, b, c, d, 12, 7, T13);
|
|
||||||
SET(d, a, b, c, 13, 12, T14);
|
|
||||||
SET(c, d, a, b, 14, 17, T15);
|
|
||||||
SET(b, c, d, a, 15, 22, T16);
|
|
||||||
#undef SET
|
|
||||||
|
|
||||||
/* Round 2. */
|
|
||||||
/* Let [abcd k s i] denote the operation
|
|
||||||
a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
|
|
||||||
#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
|
|
||||||
#define SET(a, b, c, d, k, s, Ti)\
|
|
||||||
t = a + G(b,c,d) + X[k] + Ti;\
|
|
||||||
a = ROTATE_LEFT(t, s) + b
|
|
||||||
/* Do the following 16 operations. */
|
|
||||||
SET(a, b, c, d, 1, 5, T17);
|
|
||||||
SET(d, a, b, c, 6, 9, T18);
|
|
||||||
SET(c, d, a, b, 11, 14, T19);
|
|
||||||
SET(b, c, d, a, 0, 20, T20);
|
|
||||||
SET(a, b, c, d, 5, 5, T21);
|
|
||||||
SET(d, a, b, c, 10, 9, T22);
|
|
||||||
SET(c, d, a, b, 15, 14, T23);
|
|
||||||
SET(b, c, d, a, 4, 20, T24);
|
|
||||||
SET(a, b, c, d, 9, 5, T25);
|
|
||||||
SET(d, a, b, c, 14, 9, T26);
|
|
||||||
SET(c, d, a, b, 3, 14, T27);
|
|
||||||
SET(b, c, d, a, 8, 20, T28);
|
|
||||||
SET(a, b, c, d, 13, 5, T29);
|
|
||||||
SET(d, a, b, c, 2, 9, T30);
|
|
||||||
SET(c, d, a, b, 7, 14, T31);
|
|
||||||
SET(b, c, d, a, 12, 20, T32);
|
|
||||||
#undef SET
|
|
||||||
|
|
||||||
/* Round 3. */
|
|
||||||
/* Let [abcd k s t] denote the operation
|
|
||||||
a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
|
|
||||||
#define H(x, y, z) ((x) ^ (y) ^ (z))
|
|
||||||
#define SET(a, b, c, d, k, s, Ti)\
|
|
||||||
t = a + H(b,c,d) + X[k] + Ti;\
|
|
||||||
a = ROTATE_LEFT(t, s) + b
|
|
||||||
/* Do the following 16 operations. */
|
|
||||||
SET(a, b, c, d, 5, 4, T33);
|
|
||||||
SET(d, a, b, c, 8, 11, T34);
|
|
||||||
SET(c, d, a, b, 11, 16, T35);
|
|
||||||
SET(b, c, d, a, 14, 23, T36);
|
|
||||||
SET(a, b, c, d, 1, 4, T37);
|
|
||||||
SET(d, a, b, c, 4, 11, T38);
|
|
||||||
SET(c, d, a, b, 7, 16, T39);
|
|
||||||
SET(b, c, d, a, 10, 23, T40);
|
|
||||||
SET(a, b, c, d, 13, 4, T41);
|
|
||||||
SET(d, a, b, c, 0, 11, T42);
|
|
||||||
SET(c, d, a, b, 3, 16, T43);
|
|
||||||
SET(b, c, d, a, 6, 23, T44);
|
|
||||||
SET(a, b, c, d, 9, 4, T45);
|
|
||||||
SET(d, a, b, c, 12, 11, T46);
|
|
||||||
SET(c, d, a, b, 15, 16, T47);
|
|
||||||
SET(b, c, d, a, 2, 23, T48);
|
|
||||||
#undef SET
|
|
||||||
|
|
||||||
/* Round 4. */
|
|
||||||
/* Let [abcd k s t] denote the operation
|
|
||||||
a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
|
|
||||||
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
|
|
||||||
#define SET(a, b, c, d, k, s, Ti)\
|
|
||||||
t = a + I(b,c,d) + X[k] + Ti;\
|
|
||||||
a = ROTATE_LEFT(t, s) + b
|
|
||||||
/* Do the following 16 operations. */
|
|
||||||
SET(a, b, c, d, 0, 6, T49);
|
|
||||||
SET(d, a, b, c, 7, 10, T50);
|
|
||||||
SET(c, d, a, b, 14, 15, T51);
|
|
||||||
SET(b, c, d, a, 5, 21, T52);
|
|
||||||
SET(a, b, c, d, 12, 6, T53);
|
|
||||||
SET(d, a, b, c, 3, 10, T54);
|
|
||||||
SET(c, d, a, b, 10, 15, T55);
|
|
||||||
SET(b, c, d, a, 1, 21, T56);
|
|
||||||
SET(a, b, c, d, 8, 6, T57);
|
|
||||||
SET(d, a, b, c, 15, 10, T58);
|
|
||||||
SET(c, d, a, b, 6, 15, T59);
|
|
||||||
SET(b, c, d, a, 13, 21, T60);
|
|
||||||
SET(a, b, c, d, 4, 6, T61);
|
|
||||||
SET(d, a, b, c, 11, 10, T62);
|
|
||||||
SET(c, d, a, b, 2, 15, T63);
|
|
||||||
SET(b, c, d, a, 9, 21, T64);
|
|
||||||
#undef SET
|
|
||||||
|
|
||||||
/* Then perform the following additions. (That is increment each
|
|
||||||
of the four registers by the value it had before this block
|
|
||||||
was started.) */
|
|
||||||
pms->abcd[0] += a;
|
|
||||||
pms->abcd[1] += b;
|
|
||||||
pms->abcd[2] += c;
|
|
||||||
pms->abcd[3] += d;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
md5_init(md5_state_t *pms)
|
|
||||||
{
|
|
||||||
pms->count[0] = pms->count[1] = 0;
|
|
||||||
pms->abcd[0] = 0x67452301;
|
|
||||||
pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
|
|
||||||
pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
|
|
||||||
pms->abcd[3] = 0x10325476;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
|
|
||||||
{
|
|
||||||
const md5_byte_t *p = data;
|
|
||||||
int left = nbytes;
|
|
||||||
int offset = (pms->count[0] >> 3) & 63;
|
|
||||||
md5_word_t nbits = (md5_word_t)(nbytes << 3);
|
|
||||||
|
|
||||||
if (nbytes <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Update the message length. */
|
|
||||||
pms->count[1] += nbytes >> 29;
|
|
||||||
pms->count[0] += nbits;
|
|
||||||
if (pms->count[0] < nbits)
|
|
||||||
pms->count[1]++;
|
|
||||||
|
|
||||||
/* Process an initial partial block. */
|
|
||||||
if (offset) {
|
|
||||||
int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
|
|
||||||
|
|
||||||
memcpy(pms->buf + offset, p, copy);
|
|
||||||
if (offset + copy < 64)
|
|
||||||
return;
|
|
||||||
p += copy;
|
|
||||||
left -= copy;
|
|
||||||
md5_process(pms, pms->buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Process full blocks. */
|
|
||||||
for (; left >= 64; p += 64, left -= 64)
|
|
||||||
md5_process(pms, p);
|
|
||||||
|
|
||||||
/* Process a final partial block. */
|
|
||||||
if (left)
|
|
||||||
memcpy(pms->buf, p, left);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
md5_finish(md5_state_t *pms, md5_byte_t digest[16])
|
|
||||||
{
|
|
||||||
static const md5_byte_t pad[64] = {
|
|
||||||
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
||||||
};
|
|
||||||
md5_byte_t data[8];
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/* Save the length before padding. */
|
|
||||||
for (i = 0; i < 8; ++i)
|
|
||||||
data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
|
|
||||||
/* Pad to 56 bytes mod 64. */
|
|
||||||
md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
|
|
||||||
/* Append the length. */
|
|
||||||
md5_append(pms, data, 8);
|
|
||||||
for (i = 0; i < 16; ++i)
|
|
||||||
digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any damages
|
|
||||||
arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
L. Peter Deutsch
|
|
||||||
ghost@aladdin.com
|
|
||||||
|
|
||||||
*/
|
|
||||||
/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
|
|
||||||
/*
|
|
||||||
Independent implementation of MD5 (RFC 1321).
|
|
||||||
|
|
||||||
This code implements the MD5 Algorithm defined in RFC 1321, whose
|
|
||||||
text is available at
|
|
||||||
http://www.ietf.org/rfc/rfc1321.txt
|
|
||||||
The code is derived from the text of the RFC, including the test suite
|
|
||||||
(section A.5) but excluding the rest of Appendix A. It does not include
|
|
||||||
any code or documentation that is identified in the RFC as being
|
|
||||||
copyrighted.
|
|
||||||
|
|
||||||
The original and principal author of md5.h is L. Peter Deutsch
|
|
||||||
<ghost@aladdin.com>. Other authors are noted in the change history
|
|
||||||
that follows (in reverse chronological order):
|
|
||||||
|
|
||||||
2002-04-13 lpd Removed support for non-ANSI compilers; removed
|
|
||||||
references to Ghostscript; clarified derivation from RFC 1321;
|
|
||||||
now handles byte order either statically or dynamically.
|
|
||||||
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
|
|
||||||
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
|
|
||||||
added conditionalization for C++ compilation from Martin
|
|
||||||
Purschke <purschke@bnl.gov>.
|
|
||||||
1999-05-03 lpd Original version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef md5_INCLUDED
|
|
||||||
# define md5_INCLUDED
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This package supports both compile-time and run-time determination of CPU
|
|
||||||
* byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
|
|
||||||
* compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
|
|
||||||
* defined as non-zero, the code will be compiled to run only on big-endian
|
|
||||||
* CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
|
|
||||||
* run on either big- or little-endian CPUs, but will run slightly less
|
|
||||||
* efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
|
|
||||||
*/
|
|
||||||
|
|
||||||
typedef unsigned char md5_byte_t; /* 8-bit byte */
|
|
||||||
typedef unsigned int md5_word_t; /* 32-bit word */
|
|
||||||
|
|
||||||
/* Define the state of the MD5 Algorithm. */
|
|
||||||
typedef struct md5_state_s {
|
|
||||||
md5_word_t count[2]; /* message length in bits, lsw first */
|
|
||||||
md5_word_t abcd[4]; /* digest buffer */
|
|
||||||
md5_byte_t buf[64]; /* accumulate block */
|
|
||||||
} md5_state_t;
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Initialize the algorithm. */
|
|
||||||
void md5_init(md5_state_t *pms);
|
|
||||||
|
|
||||||
/* Append a string to the message. */
|
|
||||||
void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
|
|
||||||
|
|
||||||
/* Finish the message and return the digest. */
|
|
||||||
void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} /* end extern "C" */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* md5_INCLUDED */
|
|
||||||
@@ -1,365 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 BartSimpson aka skindle
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
#include "mobi.h"
|
|
||||||
|
|
||||||
unsigned char *getExthData(MobiFile *book, unsigned int type, unsigned int *len) {
|
|
||||||
unsigned int i;
|
|
||||||
unsigned int exthRecords = bswap_l(book->exth->recordCount);
|
|
||||||
ExthRecHeader *erh = book->exth->records;
|
|
||||||
|
|
||||||
*len = 0;
|
|
||||||
|
|
||||||
for (i = 0; i < exthRecords; i++) {
|
|
||||||
unsigned int recType = bswap_l(erh->type);
|
|
||||||
unsigned int recLen = bswap_l(erh->len);
|
|
||||||
|
|
||||||
if (recLen < 8) {
|
|
||||||
fprintf(stderr, "Invalid exth record length %d\n", i);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recType == type) {
|
|
||||||
*len = recLen - 8;
|
|
||||||
return (unsigned char*)(erh + 1);
|
|
||||||
}
|
|
||||||
erh = (ExthRecHeader*)(recLen + (char*)erh);
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void enumExthRecords(ExthHeader *eh) {
|
|
||||||
unsigned int exthRecords = bswap_l(eh->recordCount);
|
|
||||||
unsigned int i;
|
|
||||||
unsigned char *data;
|
|
||||||
ExthRecHeader *erh = eh->records;
|
|
||||||
|
|
||||||
for (i = 0; i < exthRecords; i++) {
|
|
||||||
unsigned int recType = bswap_l(erh->type);
|
|
||||||
unsigned int recLen = bswap_l(erh->len);
|
|
||||||
|
|
||||||
fprintf(stderr, "%d: type - %d, len %d\n", i, recType, recLen);
|
|
||||||
|
|
||||||
if (recLen < 8) {
|
|
||||||
fprintf(stderr, "Invalid exth record length %d\n", i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = (unsigned char*)(erh + 1);
|
|
||||||
switch (recType) {
|
|
||||||
case 1: //drm_server_id
|
|
||||||
fprintf(stderr, "drm_server_id: %s\n", data);
|
|
||||||
break;
|
|
||||||
case 2: //drm_commerce_id
|
|
||||||
fprintf(stderr, "drm_commerce_id: %s\n", data);
|
|
||||||
break;
|
|
||||||
case 3: //drm_ebookbase_book_id
|
|
||||||
fprintf(stderr, "drm_ebookbase_book_id: %s\n", data);
|
|
||||||
break;
|
|
||||||
case 100: //author
|
|
||||||
fprintf(stderr, "author: %s\n", data);
|
|
||||||
break;
|
|
||||||
case 101: //publisher
|
|
||||||
fprintf(stderr, "publisher: %s\n", data);
|
|
||||||
break;
|
|
||||||
case 106: //publishingdate
|
|
||||||
fprintf(stderr, "publishingdate: %s\n", data);
|
|
||||||
break;
|
|
||||||
case 113: //asin
|
|
||||||
fprintf(stderr, "asin: %s\n", data);
|
|
||||||
break;
|
|
||||||
case 208: //book unique drm key
|
|
||||||
fprintf(stderr, "book drm key: %s\n", data);
|
|
||||||
break;
|
|
||||||
case 503: //updatedtitle
|
|
||||||
fprintf(stderr, "updatedtitle: %s\n", data);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
erh = (ExthRecHeader*)(recLen + (char*)erh);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//implementation of Pukall Cipher 1
|
|
||||||
unsigned char *PC1(unsigned char *key, unsigned int klen, unsigned char *src,
|
|
||||||
unsigned char *dest, unsigned int len, int decryption) {
|
|
||||||
unsigned int sum1 = 0;
|
|
||||||
unsigned int sum2 = 0;
|
|
||||||
unsigned int keyXorVal = 0;
|
|
||||||
unsigned short wkey[8];
|
|
||||||
unsigned int i;
|
|
||||||
if (klen != 16) {
|
|
||||||
fprintf(stderr, "Bad key length!\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
for (i = 0; i < 8; i++) {
|
|
||||||
wkey[i] = (key[i * 2] << 8) | key[i * 2 + 1];
|
|
||||||
}
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
unsigned int temp1 = 0;
|
|
||||||
unsigned int byteXorVal = 0;
|
|
||||||
unsigned int j, curByte;
|
|
||||||
for (j = 0; j < 8; j++) {
|
|
||||||
temp1 ^= wkey[j];
|
|
||||||
sum2 = (sum2 + j) * 20021 + sum1;
|
|
||||||
sum1 = (temp1 * 346) & 0xFFFF;
|
|
||||||
sum2 = (sum2 + sum1) & 0xFFFF;
|
|
||||||
temp1 = (temp1 * 20021 + 1) & 0xFFFF;
|
|
||||||
byteXorVal ^= temp1 ^ sum2;
|
|
||||||
}
|
|
||||||
curByte = src[i];
|
|
||||||
if (!decryption) {
|
|
||||||
keyXorVal = curByte * 257;
|
|
||||||
}
|
|
||||||
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF;
|
|
||||||
if (decryption) {
|
|
||||||
keyXorVal = curByte * 257;
|
|
||||||
}
|
|
||||||
for (j = 0; j < 8; j++) {
|
|
||||||
wkey[j] ^= keyXorVal;
|
|
||||||
}
|
|
||||||
dest[i] = curByte;
|
|
||||||
}
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int getSizeOfTrailingDataEntry(unsigned char *ptr, unsigned int size) {
|
|
||||||
unsigned int bitpos = 0;
|
|
||||||
unsigned int result = 0;
|
|
||||||
if (size <= 0) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
while (1) {
|
|
||||||
unsigned int v = ptr[size - 1];
|
|
||||||
result |= (v & 0x7F) << bitpos;
|
|
||||||
bitpos += 7;
|
|
||||||
size -= 1;
|
|
||||||
if ((v & 0x80) != 0 || (bitpos >= 28) || (size == 0)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int getSizeOfTrailingDataEntries(unsigned char *ptr, unsigned int size, unsigned int flags) {
|
|
||||||
unsigned int num = 0;
|
|
||||||
unsigned int testflags = flags >> 1;
|
|
||||||
while (testflags) {
|
|
||||||
if (testflags & 1) {
|
|
||||||
num += getSizeOfTrailingDataEntry(ptr, size - num);
|
|
||||||
}
|
|
||||||
testflags >>= 1;
|
|
||||||
}
|
|
||||||
if (flags & 1) {
|
|
||||||
num += (ptr[size - num - 1] & 0x3) + 1;
|
|
||||||
}
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char *parseDRM(unsigned char *data, unsigned int count, unsigned char *pid, unsigned int pidlen) {
|
|
||||||
unsigned int i;
|
|
||||||
unsigned char temp_key_sum = 0;
|
|
||||||
unsigned char *found_key = NULL;
|
|
||||||
unsigned char *keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96";
|
|
||||||
unsigned char temp_key[16];
|
|
||||||
|
|
||||||
memset(temp_key, 0, 16);
|
|
||||||
memcpy(temp_key, pid, 8);
|
|
||||||
PC1(keyvec1, 16, temp_key, temp_key, 16, 0);
|
|
||||||
|
|
||||||
for (i = 0; i < 16; i++) {
|
|
||||||
temp_key_sum += temp_key[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < count; i++) {
|
|
||||||
unsigned char kk[32];
|
|
||||||
vstruct *v = (vstruct*)(data + i * 0x30);
|
|
||||||
kstruct *k = (kstruct*)PC1(temp_key, 16, v->cookie, kk, 32, 1);
|
|
||||||
|
|
||||||
if (v->verification == k->ver && v->cksum[0] == temp_key_sum &&
|
|
||||||
(bswap_l(k->flags) & 0x1F) == 1) {
|
|
||||||
found_key = (unsigned char*)malloc(16);
|
|
||||||
memcpy(found_key, k->finalkey, 16);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return found_key;
|
|
||||||
}
|
|
||||||
|
|
||||||
void freeMobiFile(MobiFile *book) {
|
|
||||||
free(book->hr);
|
|
||||||
free(book->record0);
|
|
||||||
free(book);
|
|
||||||
}
|
|
||||||
|
|
||||||
MobiFile *parseMobiHeader(FILE *f) {
|
|
||||||
unsigned int numRecs, i, magic;
|
|
||||||
MobiFile *book = (MobiFile*)calloc(sizeof(MobiFile), 1);
|
|
||||||
book->f = f;
|
|
||||||
if (fread(&book->pdb, sizeof(PDB), 1, f) != 1) {
|
|
||||||
fprintf(stderr, "Failed to read Palm headers\n");
|
|
||||||
free(book);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
//do BOOKMOBI check
|
|
||||||
if (book->pdb.type != 0x4b4f4f42 || book->pdb.creator != 0x49424f4d) {
|
|
||||||
fprintf(stderr, "Invalid header type or creator\n");
|
|
||||||
free(book);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
book->recs = bswap_s(book->pdb.numRecs);
|
|
||||||
|
|
||||||
book->hr = (HeaderRec*)malloc(book->recs * sizeof(HeaderRec));
|
|
||||||
if (fread(book->hr, sizeof(HeaderRec), book->recs, f) != book->recs) {
|
|
||||||
fprintf(stderr, "Failed read of header record\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
book->record0_offset = bswap_l(book->hr[0].offset);
|
|
||||||
book->record0_size = bswap_l(book->hr[1].offset) - book->record0_offset;
|
|
||||||
|
|
||||||
if (fseek(f, book->record0_offset, SEEK_SET) == -1) {
|
|
||||||
fprintf(stderr, "bad seek to header record offset\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
book->record0 = (unsigned char*)malloc(book->record0_size);
|
|
||||||
|
|
||||||
if (fread(book->record0, book->record0_size, 1, f) != 1) {
|
|
||||||
fprintf(stderr, "bad read of record0\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
book->pdh = (PalmDocHeader*)(book->record0);
|
|
||||||
if (bswap_s(book->pdh->encryptionType) != 2) {
|
|
||||||
fprintf(stderr, "MOBI BOOK is not encrypted\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
book->textRecs = bswap_s(book->pdh->recordCount);
|
|
||||||
|
|
||||||
book->mobi = (MobiHeader*)(book->pdh + 1);
|
|
||||||
if (book->mobi->id != 0x49424f4d) {
|
|
||||||
fprintf(stderr, "MOBI header not found\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
book->mobiLen = bswap_l(book->mobi->hdrLen);
|
|
||||||
book->extra_data_flags = 0;
|
|
||||||
|
|
||||||
if (book->mobiLen >= 0xe4) {
|
|
||||||
book->extra_data_flags = bswap_s(book->mobi->extra_flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((bswap_l(book->mobi->exthFlags) & 0x40) == 0) {
|
|
||||||
fprintf(stderr, "Missing exth header\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
book->exth = (ExthHeader*)(book->mobiLen + (char*)(book->mobi));
|
|
||||||
if (book->exth->id != 0x48545845) {
|
|
||||||
fprintf(stderr, "EXTH header not found\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
//if you want a list of EXTH records, uncomment the following
|
|
||||||
// enumExthRecords(exth);
|
|
||||||
|
|
||||||
book->drmCount = bswap_l(book->mobi->drmCount);
|
|
||||||
|
|
||||||
if (book->drmCount == 0) {
|
|
||||||
fprintf(stderr, "no PIDs found in this file\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return book;
|
|
||||||
}
|
|
||||||
|
|
||||||
int writeMobiOutputFile(MobiFile *book, FILE *out, unsigned char *key,
|
|
||||||
unsigned int drmOffset, unsigned int drm_len) {
|
|
||||||
int i;
|
|
||||||
struct stat statbuf;
|
|
||||||
|
|
||||||
fstat(fileno(book->f), &statbuf);
|
|
||||||
|
|
||||||
// kill the drm keys
|
|
||||||
memset(book->record0 + drmOffset, 0, drm_len);
|
|
||||||
// kill the drm pointers
|
|
||||||
book->mobi->drmOffset = 0xffffffff;
|
|
||||||
book->mobi->drmCount = book->mobi->drmSize = book->mobi->drmFlags = 0;
|
|
||||||
// clear the crypto type
|
|
||||||
book->pdh->encryptionType = 0;
|
|
||||||
|
|
||||||
fwrite(&book->pdb, sizeof(PDB), 1, out);
|
|
||||||
fwrite(book->hr, sizeof(HeaderRec), book->recs, out);
|
|
||||||
fwrite("\x00\x00", 1, 2, out);
|
|
||||||
fwrite(book->record0, book->record0_size, 1, out);
|
|
||||||
|
|
||||||
//need to zero out exth 209 data
|
|
||||||
for (i = 1; i < book->recs; i++) {
|
|
||||||
unsigned int offset = bswap_l(book->hr[i].offset);
|
|
||||||
unsigned int len, extra_size = 0;
|
|
||||||
unsigned char *rec;
|
|
||||||
if (i == (book->recs - 1)) { //last record extends to end of file
|
|
||||||
len = statbuf.st_size - offset;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
len = bswap_l(book->hr[i + 1].offset) - offset;
|
|
||||||
}
|
|
||||||
//make sure we are at proper offset
|
|
||||||
while (ftell(out) < offset) {
|
|
||||||
fwrite("\x00", 1, 1, out);
|
|
||||||
}
|
|
||||||
rec = (unsigned char *)malloc(len);
|
|
||||||
if (fseek(book->f, offset, SEEK_SET) != 0) {
|
|
||||||
fprintf(stderr, "Failed record seek on input\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
free(rec);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (fread(rec, len, 1, book->f) != 1) {
|
|
||||||
fprintf(stderr, "Failed record read on input\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
free(rec);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i <= book->textRecs) { //decrypt if necessary
|
|
||||||
extra_size = getSizeOfTrailingDataEntries(rec, len, book->extra_data_flags);
|
|
||||||
PC1(key, 16, rec, rec, len - extra_size, 1);
|
|
||||||
}
|
|
||||||
fwrite(rec, len, 1, out);
|
|
||||||
free(rec);
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 BartSimpson aka skindle
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __MOBI_H
|
|
||||||
#define __MOBI_H
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include "skinutils.h"
|
|
||||||
|
|
||||||
#pragma pack(2)
|
|
||||||
typedef struct _PDB {
|
|
||||||
char name[32];
|
|
||||||
unsigned short attrib;
|
|
||||||
unsigned short version;
|
|
||||||
unsigned int created;
|
|
||||||
unsigned int modified;
|
|
||||||
unsigned int backup;
|
|
||||||
unsigned int modNum;
|
|
||||||
unsigned int appInfoID;
|
|
||||||
unsigned int sortInfoID;
|
|
||||||
unsigned int type;
|
|
||||||
unsigned int creator;
|
|
||||||
unsigned int uniqueIDseed;
|
|
||||||
unsigned int nextRecordListID;
|
|
||||||
unsigned short numRecs;
|
|
||||||
} PDB;
|
|
||||||
|
|
||||||
typedef struct _HeaderRec {
|
|
||||||
unsigned int offset;
|
|
||||||
unsigned int attribId;
|
|
||||||
} HeaderRec;
|
|
||||||
|
|
||||||
#define attrib(x) ((x)&0xFF)
|
|
||||||
#define id(x) (bswap_l((x) & 0xFFFFFF00))
|
|
||||||
|
|
||||||
typedef struct _PalmDocHeader {
|
|
||||||
unsigned short compression;
|
|
||||||
unsigned short reserverd1;
|
|
||||||
unsigned int textLength;
|
|
||||||
unsigned short recordCount;
|
|
||||||
unsigned short recordSize;
|
|
||||||
unsigned short encryptionType;
|
|
||||||
unsigned short reserved2;
|
|
||||||
} PalmDocHeader;
|
|
||||||
|
|
||||||
|
|
||||||
//checked lengths are 24, 116, 208, 228
|
|
||||||
typedef struct _MobiHeader {
|
|
||||||
unsigned int id;
|
|
||||||
unsigned int hdrLen;
|
|
||||||
unsigned int type;
|
|
||||||
unsigned int encoding;
|
|
||||||
unsigned int uniqueId;
|
|
||||||
unsigned int generator;
|
|
||||||
unsigned char reserved1[40];
|
|
||||||
unsigned int firstNonBookIdx;
|
|
||||||
unsigned int nameOffset;
|
|
||||||
unsigned int nameLength;
|
|
||||||
unsigned int language;
|
|
||||||
unsigned int inputLang;
|
|
||||||
unsigned int outputLang;
|
|
||||||
unsigned int formatVersion;
|
|
||||||
unsigned int firstImageIdx;
|
|
||||||
unsigned char unknown1[16];
|
|
||||||
unsigned int exthFlags;
|
|
||||||
unsigned char unknown2[36];
|
|
||||||
unsigned int drmOffset;
|
|
||||||
unsigned int drmCount;
|
|
||||||
unsigned int drmSize;
|
|
||||||
unsigned int drmFlags;
|
|
||||||
unsigned char unknown3[58];
|
|
||||||
unsigned short extra_flags;
|
|
||||||
} MobiHeader;
|
|
||||||
|
|
||||||
typedef struct _ExthRecHeader {
|
|
||||||
unsigned int type;
|
|
||||||
unsigned int len;
|
|
||||||
} ExthRecHeader;
|
|
||||||
|
|
||||||
typedef struct _ExthHeader {
|
|
||||||
unsigned int id;
|
|
||||||
unsigned int hdrLen;
|
|
||||||
unsigned int recordCount;
|
|
||||||
ExthRecHeader records[1];
|
|
||||||
} ExthHeader;
|
|
||||||
|
|
||||||
typedef struct _vstruct {
|
|
||||||
unsigned int verification;
|
|
||||||
unsigned int size;
|
|
||||||
unsigned int type;
|
|
||||||
unsigned char cksum[4];
|
|
||||||
unsigned char cookie[32];
|
|
||||||
} vstruct;
|
|
||||||
|
|
||||||
typedef struct _kstruct {
|
|
||||||
unsigned int ver;
|
|
||||||
unsigned int flags;
|
|
||||||
unsigned char finalkey[16];
|
|
||||||
unsigned int expiry;
|
|
||||||
unsigned int expiry2;
|
|
||||||
} kstruct;
|
|
||||||
|
|
||||||
typedef struct _MobiFile {
|
|
||||||
FILE *f;
|
|
||||||
PDB pdb;
|
|
||||||
HeaderRec *hr;
|
|
||||||
PalmDocHeader *pdh;
|
|
||||||
MobiHeader *mobi;
|
|
||||||
ExthHeader *exth;
|
|
||||||
unsigned char *record0;
|
|
||||||
unsigned int record0_offset;
|
|
||||||
unsigned int record0_size;
|
|
||||||
unsigned int mobiLen;
|
|
||||||
unsigned int extra_data_flags;
|
|
||||||
unsigned int recs;
|
|
||||||
unsigned int drmCount;
|
|
||||||
unsigned int textRecs;
|
|
||||||
PidList *pids; //extra pids to try from command line
|
|
||||||
} MobiFile;
|
|
||||||
|
|
||||||
unsigned char *getExthData(MobiFile *book, unsigned int type, unsigned int *len);
|
|
||||||
void enumExthRecords(ExthHeader *eh);
|
|
||||||
unsigned char *PC1(unsigned char *key, unsigned int klen, unsigned char *src,
|
|
||||||
unsigned char *dest, unsigned int len, int decryption);
|
|
||||||
unsigned int getSizeOfTrailingDataEntry(unsigned char *ptr, unsigned int size);
|
|
||||||
unsigned int getSizeOfTrailingDataEntries(unsigned char *ptr, unsigned int size, unsigned int flags);
|
|
||||||
unsigned char *parseDRM(unsigned char *data, unsigned int count, unsigned char *pid, unsigned int pidlen);
|
|
||||||
|
|
||||||
void freeMobiFile(MobiFile *book);
|
|
||||||
MobiFile *parseMobiHeader(FILE *f);
|
|
||||||
int writeMobiOutputFile(MobiFile *book, FILE *out, unsigned char *key,
|
|
||||||
unsigned int drmOffset, unsigned int drm_len);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
/*
|
|
||||||
sha1.c: Implementation of SHA-1 Secure Hash Algorithm-1
|
|
||||||
|
|
||||||
Based upon: NIST FIPS180-1 Secure Hash Algorithm-1
|
|
||||||
http://www.itl.nist.gov/fipspubs/fip180-1.htm
|
|
||||||
|
|
||||||
Non-official Japanese Translation by HIRATA Yasuyuki:
|
|
||||||
http://yasu.asuka.net/translations/SHA-1.html
|
|
||||||
|
|
||||||
Copyright (C) 2002 vi@nwr.jp. All rights reserved.
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any damages
|
|
||||||
arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgement in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as beging the original software.
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
The copyright notice above is copied from md5.h by L. Peter Deutsch
|
|
||||||
<ghost@aladdin.com>. Thank him since I'm not a good speaker of English. :)
|
|
||||||
*/
|
|
||||||
#include <string.h>
|
|
||||||
#include "sha1.h"
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Packing bytes to a word
|
|
||||||
*
|
|
||||||
* Should not assume p is aligned to word boundary
|
|
||||||
*/
|
|
||||||
static sha1_word_t packup(sha1_byte_t *p)
|
|
||||||
{
|
|
||||||
/* Portable, but slow */
|
|
||||||
return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3] << 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Unpacking a word to bytes
|
|
||||||
*
|
|
||||||
* Should not assume p is aligned to word boundary
|
|
||||||
*/
|
|
||||||
static void unpackup(sha1_byte_t *p, sha1_word_t q)
|
|
||||||
{
|
|
||||||
p[0] = (q >> 24) & 0xff;
|
|
||||||
p[1] = (q >> 16) & 0xff;
|
|
||||||
p[2] = (q >> 8) & 0xff;
|
|
||||||
p[3] = (q >> 0) & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Processing a block
|
|
||||||
*/
|
|
||||||
static void sha1_update_now(sha1_state_s *pms, sha1_byte_t *bp)
|
|
||||||
{
|
|
||||||
sha1_word_t tmp, a, b, c, d, e, w[16+16];
|
|
||||||
int i, s;
|
|
||||||
|
|
||||||
/* pack 64 bytes into 16 words */
|
|
||||||
for(i = 0; i < 16; i++) {
|
|
||||||
w[i] = packup(bp + i * sizeof(sha1_word_t));
|
|
||||||
}
|
|
||||||
memcpy(w + 16, w + 0, sizeof(sha1_word_t) * 16);
|
|
||||||
|
|
||||||
a = pms->sha1_h[0], b = pms->sha1_h[1], c = pms->sha1_h[2], d = pms->sha1_h[3], e = pms->sha1_h[4];
|
|
||||||
|
|
||||||
#define rot(x,n) (((x) << n) | ((x) >> (32-n)))
|
|
||||||
#define f0(b, c, d) ((b&c)|(~b&d))
|
|
||||||
#define f1(b, c, d) (b^c^d)
|
|
||||||
#define f2(b, c, d) ((b&c)|(b&d)|(c&d))
|
|
||||||
#define f3(b, c, d) (b^c^d)
|
|
||||||
#define k0 0x5a827999
|
|
||||||
#define k1 0x6ed9eba1
|
|
||||||
#define k2 0x8f1bbcdc
|
|
||||||
#define k3 0xca62c1d6
|
|
||||||
|
|
||||||
/* t=0-15 */
|
|
||||||
s = 0;
|
|
||||||
for(i = 0; i < 16; i++) {
|
|
||||||
tmp = rot(a, 5) + f0(b, c, d) + e + w[s] + k0;
|
|
||||||
e = d; d = c; c = rot(b, 30); b = a; a = tmp;
|
|
||||||
s = (s + 1) % 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* t=16-19 */
|
|
||||||
for(i = 16; i < 20; i++) {
|
|
||||||
w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1);
|
|
||||||
w[s+16] = w[s];
|
|
||||||
tmp = rot(a, 5) + f0(b, c, d) + e + w[s] + k0;
|
|
||||||
e = d; d = c; c = rot(b, 30); b = a; a = tmp;
|
|
||||||
s = (s + 1) % 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* t=20-39 */
|
|
||||||
for(i = 0; i < 20; i++) {
|
|
||||||
w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1);
|
|
||||||
w[s+16] = w[s];
|
|
||||||
tmp = rot(a, 5) + f1(b, c, d) + e + w[s] + k1;
|
|
||||||
e = d; d = c; c = rot(b, 30); b = a; a = tmp;
|
|
||||||
s = (s + 1) % 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* t=40-59 */
|
|
||||||
for(i = 0; i < 20; i++) {
|
|
||||||
w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1);
|
|
||||||
w[s+16] = w[s];
|
|
||||||
tmp = rot(a, 5) + f2(b, c, d) + e + w[s] + k2;
|
|
||||||
e = d; d = c; c = rot(b, 30); b = a; a = tmp;
|
|
||||||
s = (s + 1) % 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* t=60-79 */
|
|
||||||
for(i = 0; i < 20; i++) {
|
|
||||||
w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1);
|
|
||||||
w[s+16] = w[s];
|
|
||||||
tmp = rot(a, 5) + f3(b, c, d) + e + w[s] + k3;
|
|
||||||
e = d; d = c; c = rot(b, 30); b = a; a = tmp;
|
|
||||||
s = (s + 1) % 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
pms->sha1_h[0] += a, pms->sha1_h[1] += b, pms->sha1_h[2] += c, pms->sha1_h[3] += d, pms->sha1_h[4] += e;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Increment sha1_size1, sha1_size2 field of sha1_state_s
|
|
||||||
*/
|
|
||||||
static void incr(sha1_state_s *pms, int v)
|
|
||||||
{
|
|
||||||
sha1_word_t q;
|
|
||||||
|
|
||||||
q = pms->sha1_size1 + v * BITS;
|
|
||||||
if(q < pms->sha1_size1) {
|
|
||||||
pms->sha1_size2++;
|
|
||||||
}
|
|
||||||
pms->sha1_size1 = q;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize sha1_state_s as FIPS specifies
|
|
||||||
*/
|
|
||||||
void sha1_init(sha1_state_s *pms)
|
|
||||||
{
|
|
||||||
memset(pms, 0, sizeof(*pms));
|
|
||||||
pms->sha1_h[0] = 0x67452301; /* Initialize H[0]-H[4] */
|
|
||||||
pms->sha1_h[1] = 0xEFCDAB89;
|
|
||||||
pms->sha1_h[2] = 0x98BADCFE;
|
|
||||||
pms->sha1_h[3] = 0x10325476;
|
|
||||||
pms->sha1_h[4] = 0xC3D2E1F0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Fill block and update output when needed
|
|
||||||
*/
|
|
||||||
void sha1_update(sha1_state_s *pms, sha1_byte_t *bufp, int length)
|
|
||||||
{
|
|
||||||
/* Is the buffer partially filled? */
|
|
||||||
if(pms->sha1_count != 0) {
|
|
||||||
if(pms->sha1_count + length >= (signed) sizeof(pms->sha1_buf)) { /* buffer is filled enough */
|
|
||||||
int fil = sizeof(pms->sha1_buf) - pms->sha1_count; /* length to copy */
|
|
||||||
|
|
||||||
memcpy(pms->sha1_buf + pms->sha1_count, bufp, fil);
|
|
||||||
sha1_update_now(pms, pms->sha1_buf);
|
|
||||||
length -= fil;
|
|
||||||
bufp += fil;
|
|
||||||
pms->sha1_count = 0;
|
|
||||||
incr(pms, fil);
|
|
||||||
} else {
|
|
||||||
memcpy(pms->sha1_buf + pms->sha1_count, bufp, length);
|
|
||||||
pms->sha1_count += length;
|
|
||||||
incr(pms, length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loop to update state */
|
|
||||||
for(;;) {
|
|
||||||
if(length < (signed) sizeof(pms->sha1_buf)) { /* Short to fill up the buffer */
|
|
||||||
if(length) {
|
|
||||||
memcpy(pms->sha1_buf, bufp, length);
|
|
||||||
}
|
|
||||||
pms->sha1_count = length;
|
|
||||||
incr(pms, length);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sha1_update_now(pms, bufp);
|
|
||||||
length -= sizeof(pms->sha1_buf);
|
|
||||||
bufp += sizeof(pms->sha1_buf);
|
|
||||||
incr(pms, sizeof(pms->sha1_buf));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sha1_finish(sha1_state_s *pms, sha1_byte_t output[SHA1_OUTPUT_SIZE])
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
sha1_byte_t buf[1];
|
|
||||||
|
|
||||||
/* fill a bit */
|
|
||||||
buf[0] = 0x80;
|
|
||||||
sha1_update(pms, buf, 1);
|
|
||||||
|
|
||||||
/* Decrement sha1_size1, sha1_size2 */
|
|
||||||
if((pms->sha1_size1 -= BITS) == 0) {
|
|
||||||
pms->sha1_size2--;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fill zeros */
|
|
||||||
if(pms->sha1_count > (signed) (sizeof(pms->sha1_buf) - 2 * sizeof(sha1_word_t))) {
|
|
||||||
memset(pms->sha1_buf + pms->sha1_count, 0, sizeof(pms->sha1_buf) - pms->sha1_count);
|
|
||||||
sha1_update_now(pms, pms->sha1_buf);
|
|
||||||
pms->sha1_count = 0;
|
|
||||||
}
|
|
||||||
memset(pms->sha1_buf + pms->sha1_count, 0,
|
|
||||||
sizeof(pms->sha1_buf) - pms->sha1_count - sizeof(sha1_word_t) * 2);
|
|
||||||
|
|
||||||
/* fill last length */
|
|
||||||
unpackup(pms->sha1_buf + sizeof(pms->sha1_buf) - sizeof(sha1_word_t) * 2, pms->sha1_size2);
|
|
||||||
unpackup(pms->sha1_buf + sizeof(pms->sha1_buf) - sizeof(sha1_word_t) * 1, pms->sha1_size1);
|
|
||||||
|
|
||||||
/* final update */
|
|
||||||
sha1_update_now(pms, pms->sha1_buf);
|
|
||||||
|
|
||||||
/* move hash value to output byte array */
|
|
||||||
for(i = 0; i < (signed) (sizeof(pms->sha1_h)/sizeof(sha1_word_t)); i++) {
|
|
||||||
unpackup(output + i * sizeof(sha1_word_t), pms->sha1_h[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
sha1.h: Implementation of SHA-1 Secure Hash Algorithm-1
|
|
||||||
|
|
||||||
Based upon: NIST FIPS180-1 Secure Hash Algorithm-1
|
|
||||||
http://www.itl.nist.gov/fipspubs/fip180-1.htm
|
|
||||||
|
|
||||||
Non-official Japanese Translation by HIRATA Yasuyuki:
|
|
||||||
http://yasu.asuka.net/translations/SHA-1.html
|
|
||||||
|
|
||||||
Copyright (C) 2002 vi@nwr.jp. All rights reserved.
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any damages
|
|
||||||
arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgement in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as beging the original software.
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
The copyright notice above is copied from md5.h by L. Petet Deutsch
|
|
||||||
<ghost@aladdin.com>. Thank him since I'm not a good speaker of English. :)
|
|
||||||
*/
|
|
||||||
#ifndef SHA1_H
|
|
||||||
#define SHA1_H
|
|
||||||
|
|
||||||
typedef unsigned int sha1_word_t; /* 32bits unsigned integer */
|
|
||||||
typedef unsigned char sha1_byte_t; /* 8bits unsigned integer */
|
|
||||||
#define BITS 8
|
|
||||||
|
|
||||||
/* Define the state of SHA-1 algorithm */
|
|
||||||
typedef struct {
|
|
||||||
sha1_byte_t sha1_buf[64]; /* 512 bits */
|
|
||||||
int sha1_count; /* How many bytes are used */
|
|
||||||
sha1_word_t sha1_size1; /* Length counter Lower Word */
|
|
||||||
sha1_word_t sha1_size2; /* Length counter Upper Word */
|
|
||||||
sha1_word_t sha1_h[5]; /* Hash output */
|
|
||||||
} sha1_state_s;
|
|
||||||
#define SHA1_OUTPUT_SIZE 20 /* in bytes */
|
|
||||||
|
|
||||||
/* External Functions */
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Initialize SHA-1 algorithm */
|
|
||||||
void sha1_init(sha1_state_s *pms);
|
|
||||||
|
|
||||||
/* Append a string to SHA-1 algorithm */
|
|
||||||
void sha1_update(sha1_state_s *pms, sha1_byte_t *input_buffer, int length);
|
|
||||||
|
|
||||||
/* Finish the SHA-1 algorithm and return the hash */
|
|
||||||
void sha1_finish(sha1_state_s *pms, sha1_byte_t output[SHA1_OUTPUT_SIZE]);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,461 +0,0 @@
|
|||||||
|
|
||||||
/*
|
|
||||||
Copyright 2010 BartSimpson aka skindle
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Dependencies: none
|
|
||||||
* build on cygwin:
|
|
||||||
* gcc -o skindle skindle.c md5.c sha1.c b64.c -lCrypt32
|
|
||||||
* or gcc -o skindle skindle.c md5.c sha1.c b64.c -lCrypt32 -mno-cygwin
|
|
||||||
* Under cygwin, you can just type make to build it.
|
|
||||||
* The code should compile with Visual Studio, just add all the files to
|
|
||||||
* a project and add the Crypt32.lib dependency and it should build as a
|
|
||||||
* Win32 console app.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* MUST be run on the computer on which KindleForPC is installed
|
|
||||||
* under the account that was used to purchase DRM'ed content.
|
|
||||||
* Requires your kindle.info file which can be found in something like:
|
|
||||||
* <User home>\...\Amazon\Kindle For PC\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}
|
|
||||||
* where ... varies by platform but is "Local Settings\Application Data" on XP
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
What: KindleForPC DRM removal utility to preserve your fair use rights!
|
|
||||||
Why: Fair use is a well established doctrine, and I am no fan of vendor
|
|
||||||
lockin.
|
|
||||||
How: This utility implements the PID extraction, DRM key generation and
|
|
||||||
decryption algorithms employed by the KindleForPC application. This
|
|
||||||
is a stand alone app that does not require you to determine a PID on
|
|
||||||
your own, and it does not need to run KindleForPC in order to extract
|
|
||||||
any data from memory.
|
|
||||||
|
|
||||||
Shoutz: The DarkReverser - thanks for mobidedrm! The last part of this
|
|
||||||
is just a C port of mobidedrm.
|
|
||||||
labba and I<3cabbages for motivating me to do this the right way.
|
|
||||||
You guys shouldn't need to spend all your time responding to all the
|
|
||||||
changes Amazon is going to force you to make in unswindle each time
|
|
||||||
the release a new version.
|
|
||||||
Lawrence Lessig - You are my hero. 'Nuff said.
|
|
||||||
Thumbs down: Disney, MPAA, RIAA - you guys suck. Making billions off
|
|
||||||
of the exploitation of works out of copyright while vigourously
|
|
||||||
pushing copyright extension to prevent others from doing the same
|
|
||||||
is the height of hypocrasy.
|
|
||||||
Congress - you guys suck too. Why you arrogant pricks think you
|
|
||||||
are smarter than the founding fathers is beyond me.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <Wincrypt.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
#include "skinutils.h"
|
|
||||||
#include "cbuf.h"
|
|
||||||
#include "mobi.h"
|
|
||||||
#include "tpz.h"
|
|
||||||
|
|
||||||
#include "zlib.h"
|
|
||||||
|
|
||||||
int processTopaz(FILE *in, char *outFile, int explode, PidList *extraPids) {
|
|
||||||
//had to pile all these up here to please VS2009
|
|
||||||
cbuf *tpzHeaders, *tpzBody;
|
|
||||||
struct stat statbuf;
|
|
||||||
FILE *out;
|
|
||||||
unsigned int i;
|
|
||||||
char *keysRecord, *keysRecordRecord;
|
|
||||||
TopazFile *topaz;
|
|
||||||
char *pid;
|
|
||||||
|
|
||||||
fstat(fileno(in), &statbuf);
|
|
||||||
|
|
||||||
topaz = parseTopazHeader(in);
|
|
||||||
if (topaz == NULL) {
|
|
||||||
fprintf(stderr, "Failed to parse topaz headers\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
topaz->pids = extraPids;
|
|
||||||
|
|
||||||
tpzHeaders = b_new(topaz->bodyOffset);
|
|
||||||
tpzBody = b_new(statbuf.st_size);
|
|
||||||
|
|
||||||
parseMetadata(topaz);
|
|
||||||
|
|
||||||
// dumpMap(bookMetadata);
|
|
||||||
|
|
||||||
keysRecord = getMetadata(topaz, "keys");
|
|
||||||
if (keysRecord == NULL) {
|
|
||||||
//fail
|
|
||||||
}
|
|
||||||
keysRecordRecord = getMetadata(topaz, keysRecord);
|
|
||||||
if (keysRecordRecord == NULL) {
|
|
||||||
//fail
|
|
||||||
}
|
|
||||||
|
|
||||||
pid = getBookPid(keysRecord, strlen(keysRecord), keysRecordRecord, strlen(keysRecordRecord));
|
|
||||||
|
|
||||||
if (pid == NULL) {
|
|
||||||
fprintf(stderr, "Failed to extract pid automatically\n");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
char *title = getMetadata(topaz, "Title");
|
|
||||||
fprintf(stderr, "PID for %s is: %s\n", title ? title : "UNK", pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
unique pid is computed as:
|
|
||||||
base64(sha1(idArray . kindleToken . 209_data . 209_tokens))
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
|
||||||
// Decrypt book key
|
|
||||||
//
|
|
||||||
|
|
||||||
Payload *dkey = getBookPayloadRecord(topaz, "dkey", 0, 0);
|
|
||||||
|
|
||||||
if (dkey == NULL) {
|
|
||||||
fprintf(stderr, "No dkey record found\n");
|
|
||||||
freeTopazFile(topaz);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pid) {
|
|
||||||
topaz->bookKey = decryptDkeyRecords(dkey, pid);
|
|
||||||
free(pid);
|
|
||||||
}
|
|
||||||
if (topaz->bookKey == NULL) {
|
|
||||||
if (extraPids) {
|
|
||||||
int p;
|
|
||||||
freePayload(dkey);
|
|
||||||
for (p = 0; p < extraPids->numPids; p++) {
|
|
||||||
dkey = getBookPayloadRecord(topaz, "dkey", 0, 0);
|
|
||||||
topaz->bookKey = decryptDkeyRecords(dkey, extraPids->pidList[p]);
|
|
||||||
if (topaz->bookKey) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (topaz->bookKey == NULL) {
|
|
||||||
fprintf(stderr, "No valid pids available, failed to find DRM key\n");
|
|
||||||
freeTopazFile(topaz);
|
|
||||||
freePayload(dkey);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "Found a DRM key!\n");
|
|
||||||
for (i = 0; i < 8; i++) {
|
|
||||||
fprintf(stderr, "%02x", topaz->bookKey[i]);
|
|
||||||
}
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
|
|
||||||
out = fopen(outFile, "wb");
|
|
||||||
if (out == NULL) {
|
|
||||||
fprintf(stderr, "Failed to open output file, quitting\n");
|
|
||||||
freeTopazFile(topaz);
|
|
||||||
freePayload(dkey);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeTopazOutputFile(topaz, out, tpzHeaders, tpzBody, explode);
|
|
||||||
fwrite(tpzHeaders->buf, tpzHeaders->idx, 1, out);
|
|
||||||
fwrite(tpzBody->buf, tpzBody->idx, 1, out);
|
|
||||||
fclose(out);
|
|
||||||
b_free(tpzHeaders);
|
|
||||||
b_free(tpzBody);
|
|
||||||
|
|
||||||
freePayload(dkey);
|
|
||||||
|
|
||||||
freeTopazFile(topaz);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int processMobi(FILE *prc, char *outFile, PidList *extraPids) {
|
|
||||||
//had to pile all these up here to please VS2009
|
|
||||||
PDB header;
|
|
||||||
cbuf *keyBuf;
|
|
||||||
char *pid;
|
|
||||||
FILE *out;
|
|
||||||
unsigned int i, keyPtrLen;
|
|
||||||
unsigned char *keyPtr;
|
|
||||||
unsigned int drmOffset, drm_len;
|
|
||||||
unsigned char *drm, *found_key = NULL;
|
|
||||||
MobiFile *book;
|
|
||||||
int result;
|
|
||||||
|
|
||||||
book = parseMobiHeader(prc);
|
|
||||||
|
|
||||||
if (book == NULL) {
|
|
||||||
fprintf(stderr, "Failed to read mobi headers\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
book->pids = extraPids;
|
|
||||||
keyPtr = getExthData(book, 209, &keyPtrLen);
|
|
||||||
|
|
||||||
keyBuf = b_new(128);
|
|
||||||
if (keyPtr != NULL) {
|
|
||||||
unsigned int idx;
|
|
||||||
for (idx = 0; idx < keyPtrLen; idx += 5) {
|
|
||||||
unsigned char *rec;
|
|
||||||
unsigned int dlen;
|
|
||||||
unsigned int rtype = bswap_l(*(unsigned int*)(keyPtr + idx + 1));
|
|
||||||
rec = getExthData(book, rtype, &dlen);
|
|
||||||
if (rec != NULL) {
|
|
||||||
b_add_buf(keyBuf, rec, dlen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pid = getBookPid(keyPtr, keyPtrLen, keyBuf->buf, keyBuf->idx);
|
|
||||||
|
|
||||||
b_free(keyBuf);
|
|
||||||
|
|
||||||
if (pid == NULL) {
|
|
||||||
fprintf(stderr, "Failed to extract pid automatically\n");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "PID for %s is: %s\n", book->pdb.name, pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
unique pid is computed as:
|
|
||||||
base64(sha1(idArray . kindleToken . 209_data . 209_tokens))
|
|
||||||
*/
|
|
||||||
|
|
||||||
drmOffset = bswap_l(book->mobi->drmOffset);
|
|
||||||
|
|
||||||
drm_len = bswap_l(book->mobi->drmSize);
|
|
||||||
drm = book->record0 + drmOffset;
|
|
||||||
|
|
||||||
if (pid) {
|
|
||||||
found_key = parseDRM(drm, book->drmCount, pid, 8);
|
|
||||||
free(pid);
|
|
||||||
}
|
|
||||||
if (found_key == NULL) {
|
|
||||||
if (extraPids) {
|
|
||||||
int p;
|
|
||||||
for (p = 0; p < extraPids->numPids; p++) {
|
|
||||||
found_key = parseDRM(drm, book->drmCount, extraPids->pidList[p], 8);
|
|
||||||
if (found_key) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (found_key == NULL) {
|
|
||||||
fprintf(stderr, "No valid pids available, failed to find DRM key\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(stderr, "Found a DRM key!\n");
|
|
||||||
|
|
||||||
out = fopen(outFile, "wb");
|
|
||||||
if (out == NULL) {
|
|
||||||
fprintf(stderr, "Failed to open output file, quitting\n");
|
|
||||||
freeMobiFile(book);
|
|
||||||
free(found_key);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = writeMobiOutputFile(book, out, found_key, drmOffset, drm_len);
|
|
||||||
|
|
||||||
fclose(out);
|
|
||||||
if (result == 0) {
|
|
||||||
_unlink(outFile);
|
|
||||||
}
|
|
||||||
freeMobiFile(book);
|
|
||||||
free(found_key);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum {
|
|
||||||
FileTypeUnk,
|
|
||||||
FileTypeMobi,
|
|
||||||
FileTypeTopaz
|
|
||||||
};
|
|
||||||
|
|
||||||
int getFileType(FILE *in) {
|
|
||||||
PDB p;
|
|
||||||
int type = FileTypeUnk;
|
|
||||||
fseek(in, 0, SEEK_SET);
|
|
||||||
fread(&p, sizeof(p), 1, in);
|
|
||||||
if (p.type == 0x4b4f4f42 && p.creator == 0x49424f4d) {
|
|
||||||
type = FileTypeMobi;
|
|
||||||
}
|
|
||||||
else if (strncmp(p.name, "TPZ0", 4) == 0) {
|
|
||||||
type = FileTypeTopaz;
|
|
||||||
}
|
|
||||||
fseek(in, 0, SEEK_SET);
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
void usage() {
|
|
||||||
fprintf(stderr, "usage: ./skindle [-d] [-v] -i <ebook file> -o <output file> [-k kindle.info file] [-p pid]\n");
|
|
||||||
fprintf(stderr, " -d optional, for topaz files only, produce a decompressed output file\n");
|
|
||||||
fprintf(stderr, " -i required name of the input mobi or topaz file\n");
|
|
||||||
fprintf(stderr, " -o required name of the output file to generate\n");
|
|
||||||
fprintf(stderr, " -k optional kindle.info path\n");
|
|
||||||
fprintf(stderr, " -v dump the contents of kindle.info\n");
|
|
||||||
fprintf(stderr, " -p additional PID values to attempt (can specifiy multiple times)\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
extern char *optarg;
|
|
||||||
extern int optind;
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
//had to pile all these up here to please VS2009
|
|
||||||
FILE *in;
|
|
||||||
int type, explode = 0;
|
|
||||||
int result = 0;
|
|
||||||
int firstArg = 1;
|
|
||||||
int opt;
|
|
||||||
PidList *pids = NULL;
|
|
||||||
char *infile = NULL, *outfile = NULL, *kinfo = NULL;
|
|
||||||
int dump = 0;
|
|
||||||
|
|
||||||
while ((opt = getopt(argc, argv, "vdp:i:o:k:")) != -1) {
|
|
||||||
switch (opt) {
|
|
||||||
case 'v':
|
|
||||||
dump = 1;
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
explode = 1;
|
|
||||||
break;
|
|
||||||
case 'p': {
|
|
||||||
int l = strlen(optarg);
|
|
||||||
if (l == 10) {
|
|
||||||
if (!verifyPidChecksum(optarg)) {
|
|
||||||
fprintf(stderr, "Invalid pid %s, skipping\n", optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
optarg[8] = 0;
|
|
||||||
}
|
|
||||||
else if (l != 8) {
|
|
||||||
fprintf(stderr, "Invalid pid length for %s, skipping\n", optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (pids == NULL) {
|
|
||||||
pids = (PidList*)malloc(sizeof(PidList));
|
|
||||||
pids->numPids = 1;
|
|
||||||
pids->pidList[0] = optarg;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
pids = (PidList*)realloc(pids, sizeof(PidList) + pids->numPids * sizeof(unsigned char*));
|
|
||||||
pids->pidList[pids->numPids++] = optarg;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'k':
|
|
||||||
kinfo = optarg;
|
|
||||||
break;
|
|
||||||
case 'i':
|
|
||||||
infile = optarg;
|
|
||||||
break;
|
|
||||||
case 'o':
|
|
||||||
outfile = optarg;
|
|
||||||
break;
|
|
||||||
default: /* '?' */
|
|
||||||
usage();
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (optind != argc) {
|
|
||||||
fprintf(stderr, "Extra options ignored\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!buildKindleMap(kinfo)) {
|
|
||||||
fprintf(stderr, "buildKindleMap failed\n");
|
|
||||||
usage();
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//The following loop dumps the contents of your kindle.info file
|
|
||||||
if (dump) {
|
|
||||||
MapList *ml;
|
|
||||||
// dumpKindleMap();
|
|
||||||
fprintf(stderr, "\nDumping kindle.info contents:\n");
|
|
||||||
for (ml = kindleMap; ml; ml = ml->next) {
|
|
||||||
DATA_BLOB DataIn;
|
|
||||||
DATA_BLOB DataOut;
|
|
||||||
DataIn.pbData = mazamaDecode(ml->value, (int*)&DataIn.cbData);
|
|
||||||
if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, 1, &DataOut)) {
|
|
||||||
fprintf(stderr, "%s ==> %s\n", ml->key, translateKindleKey(ml->key));
|
|
||||||
fwrite(DataOut.pbData, DataOut.cbData, 1, stderr);
|
|
||||||
fprintf(stderr, "\n\n");
|
|
||||||
LocalFree(DataOut.pbData);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "CryptUnprotectData failed\n");
|
|
||||||
}
|
|
||||||
free(DataIn.pbData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (infile == NULL && outfile == NULL) {
|
|
||||||
//special case, user just wants to see kindle.info
|
|
||||||
freeMap(kindleMap);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (infile == NULL) {
|
|
||||||
fprintf(stderr, "Missing input file name\n");
|
|
||||||
usage();
|
|
||||||
freeMap(kindleMap);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outfile == NULL) {
|
|
||||||
fprintf(stderr, "Missing output file name\n");
|
|
||||||
usage();
|
|
||||||
freeMap(kindleMap);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
in = fopen(infile, "rb");
|
|
||||||
if (in == NULL) {
|
|
||||||
fprintf(stderr, "%s bad open, quitting\n", infile);
|
|
||||||
freeMap(kindleMap);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
type = getFileType(in);
|
|
||||||
if (type == FileTypeTopaz) {
|
|
||||||
result = processTopaz(in, outfile, explode, pids);
|
|
||||||
}
|
|
||||||
else if (type == FileTypeMobi) {
|
|
||||||
result = processMobi(in, outfile, pids);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "%s file type unknown, quitting\n", infile);
|
|
||||||
fclose(in);
|
|
||||||
freeMap(kindleMap);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(in);
|
|
||||||
if (result) {
|
|
||||||
fprintf(stderr, "Success! Enjoy!\n");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "An error occurred, unable to process input file!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
freeMap(kindleMap);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,539 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 BartSimpson aka skindle
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <Wincrypt.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
#include "skinutils.h"
|
|
||||||
|
|
||||||
/* The kindle.info file created when you install KindleForPC is a set
|
|
||||||
* of key:value pairs delimited by '{'. The keys and values are encoded
|
|
||||||
* in a variety of ways. Keys are the mazama64 encoded md5 hash of the
|
|
||||||
* key name, while values are the mazama64 encoding of the blob returned
|
|
||||||
* by the Windows CryptProtectData function. The use of CryptProtectData
|
|
||||||
* is what locks things to a particular user/machine
|
|
||||||
|
|
||||||
* kindle.info layout
|
|
||||||
|
|
||||||
* Key:AbaZZ6z4a7ZxzLzkZcaqauZMZjZ_Ztz6 ("kindle.account.tokens")
|
|
||||||
* Value: mazama64Encode(CryptProtectData(some sha1 hash))
|
|
||||||
|
|
||||||
* Key:AsAWa4ZJAQaCZ7A3zrZSaZavZMarZFAw ("kindle.cookie.item")
|
|
||||||
* Value: mazama64Encode(CryptProtectData(base64(144 bytes of data)))
|
|
||||||
|
|
||||||
* Key:ZHatAla4a-zTzWA-AvaeAvZQzKZ-agAz ("eulaVersionAccepted")
|
|
||||||
* Value: mazama64Encode(CryptProtectData(kindle version?))
|
|
||||||
|
|
||||||
* Key:ZiajZga7Z9zjZRz7AfZ-zRzUANZNZJzP ("login_date")
|
|
||||||
* Value: mazama64Encode(CryptProtectData(registration date))
|
|
||||||
|
|
||||||
* Key:ZkzeAUA-Z2ZYA2Z_ayA_ahZEATaEAOaG ("kindle.token.item")
|
|
||||||
* Value: mazama64Encode(CryptProtectData(multi-field crypto data))
|
|
||||||
* {enc:xxx}{iv:xxx}{key:xxx}{name:xxx}{serial:xxx}
|
|
||||||
* enc:base64(binary blob)
|
|
||||||
* iv:base64(16 bytes)
|
|
||||||
* key:base64(256 bytes)
|
|
||||||
* name:base64("ADPTokenEncryptionKey")
|
|
||||||
* serial:base64("1")
|
|
||||||
|
|
||||||
* Key:aVzrzRAFZ7aIzmASZOzVzIAGAKawzwaU ("login")
|
|
||||||
* Value: mazama64Encode(CryptProtectData(your amazon email))
|
|
||||||
|
|
||||||
* Key:avalzbzkAcAPAQA5ApZgaOZPzQZzaiaO mazama64Encode(md5("MazamaRandomNumber"))
|
|
||||||
* Value: mazama64Encode(CryptProtectData(mazama32Encode(32 bytes random data)))
|
|
||||||
|
|
||||||
* Key:zgACzqAjZ2zzAmAJa6ZFaZALaYAlZrz- ("kindle.key.item")
|
|
||||||
* Value: mazama64Encode(CryptProtectData(RSA private key)) no password
|
|
||||||
|
|
||||||
* Key:zga-aIANZPzbzfZ1zHZWZcA4afZMZcA_ ("kindle.name.info")
|
|
||||||
* Value: mazama64Encode(CryptProtectData(your name))
|
|
||||||
|
|
||||||
* Key:zlZ9afz1AfAVZjacaqa-ZHa1aIa_ajz7 ("kindle.device.info");
|
|
||||||
* Value: mazama64Encode(CryptProtectData(the name of your kindle))
|
|
||||||
*/
|
|
||||||
|
|
||||||
char *kindleKeys[] = {
|
|
||||||
"AbaZZ6z4a7ZxzLzkZcaqauZMZjZ_Ztz6", "kindle.account.tokens",
|
|
||||||
"AsAWa4ZJAQaCZ7A3zrZSaZavZMarZFAw", "kindle.cookie.item",
|
|
||||||
"ZHatAla4a-zTzWA-AvaeAvZQzKZ-agAz", "eulaVersionAccepted",
|
|
||||||
"ZiajZga7Z9zjZRz7AfZ-zRzUANZNZJzP", "login_date",
|
|
||||||
"ZkzeAUA-Z2ZYA2Z_ayA_ahZEATaEAOaG", "kindle.token.item",
|
|
||||||
"aVzrzRAFZ7aIzmASZOzVzIAGAKawzwaU", "login",
|
|
||||||
"avalzbzkAcAPAQA5ApZgaOZPzQZzaiaO", "MazamaRandomNumber",
|
|
||||||
"zgACzqAjZ2zzAmAJa6ZFaZALaYAlZrz-", "kindle.key.item",
|
|
||||||
"zga-aIANZPzbzfZ1zHZWZcA4afZMZcA_", "kindle.name.info",
|
|
||||||
"zlZ9afz1AfAVZjacaqa-ZHa1aIa_ajz7", "kindle.device.info"
|
|
||||||
};
|
|
||||||
|
|
||||||
MapList *kindleMap;
|
|
||||||
|
|
||||||
unsigned short bswap_s(unsigned short s) {
|
|
||||||
return (s >> 8) | (s << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int bswap_l(unsigned int s) {
|
|
||||||
unsigned int u = bswap_s(s);
|
|
||||||
unsigned int l = bswap_s(s >> 16);
|
|
||||||
return (u << 16) | l;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *translateKindleKey(char *key) {
|
|
||||||
int n = sizeof(kindleKeys) / sizeof(char*);
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < n; i += 2) {
|
|
||||||
if (strcmp(key, kindleKeys[i]) == 0) {
|
|
||||||
return kindleKeys[i + 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
MapList *findNode(MapList *map, char *key) {
|
|
||||||
MapList *l;
|
|
||||||
for (l = map; l; l = l->next) {
|
|
||||||
if (strcmp(key, l->key) == 0) {
|
|
||||||
return l;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
MapList *findKindleNode(char *key) {
|
|
||||||
return findNode(kindleMap, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *getNodeValue(MapList *map, char *key) {
|
|
||||||
MapList *l;
|
|
||||||
for (l = map; l; l = l->next) {
|
|
||||||
if (strcmp(key, l->key) == 0) {
|
|
||||||
return l->value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *getKindleValue(char *key) {
|
|
||||||
return getNodeValue(kindleMap, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
MapList *addMapNode(MapList *map, char *key, char *value) {
|
|
||||||
MapList *ml;
|
|
||||||
ml = findNode(map, key);
|
|
||||||
if (ml) {
|
|
||||||
free(ml->value);
|
|
||||||
ml->value = value;
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ml = (MapList*)malloc(sizeof(MapList));
|
|
||||||
ml->key = key;
|
|
||||||
ml->value = value;
|
|
||||||
ml->next = map;
|
|
||||||
return ml;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dumpMap(MapList *m) {
|
|
||||||
MapList *l;
|
|
||||||
for (l = m; l; l = l->next) {
|
|
||||||
fprintf(stderr, "%s:%s\n", l->key, l->value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void freeMap(MapList *m) {
|
|
||||||
MapList *n;
|
|
||||||
while (m) {
|
|
||||||
n = m;
|
|
||||||
m = m->next;
|
|
||||||
free(n->key);
|
|
||||||
free(n->value);
|
|
||||||
free(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseLine(char *line) {
|
|
||||||
char *colon = strchr(line, ':');
|
|
||||||
if (colon) {
|
|
||||||
char *key, *value;
|
|
||||||
int len = colon - line;
|
|
||||||
key = (char*)malloc(len + 1);
|
|
||||||
*colon++ = 0;
|
|
||||||
strcpy(key, line);
|
|
||||||
len = strlen(colon);
|
|
||||||
value = (char*)malloc(len + 1);
|
|
||||||
strcpy(value, colon);
|
|
||||||
value[len] = 0;
|
|
||||||
kindleMap = addMapNode(kindleMap, key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dumpKindleMap() {
|
|
||||||
dumpMap(kindleMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
int buildKindleMap(char *infoFile) {
|
|
||||||
int result = 0;
|
|
||||||
struct stat statbuf;
|
|
||||||
char ki[512];
|
|
||||||
DWORD len = sizeof(ki);
|
|
||||||
if (infoFile == NULL) {
|
|
||||||
HKEY regkey;
|
|
||||||
fprintf(stderr, "Attempting to locate kindle.info\n");
|
|
||||||
if (RegOpenKey(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\", ®key) != ERROR_SUCCESS) {
|
|
||||||
fprintf(stderr, "Unable to locate kindle.info, please specify path on command line\n");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (RegGetValue(regkey, "Local AppData", NULL, NULL, ki, &len) != ERROR_SUCCESS) {
|
|
||||||
if (RegQueryValueEx(regkey, "Local AppData", NULL, NULL, ki, &len) != ERROR_SUCCESS) {
|
|
||||||
RegCloseKey(regkey);
|
|
||||||
fprintf(stderr, "Unable to locate kindle.info, please specify path on command line\n");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
ki[len] = 0;
|
|
||||||
strncat(ki, "\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info", sizeof(ki) - 1 - strlen(ki));
|
|
||||||
infoFile = ki;
|
|
||||||
fprintf(stderr, "Found kindle.info location\n");
|
|
||||||
}
|
|
||||||
if (stat(infoFile, &statbuf) == 0) {
|
|
||||||
FILE *fd = fopen(infoFile, "rb");
|
|
||||||
char *infoBuf = (char*)malloc(statbuf.st_size + 1);
|
|
||||||
infoBuf[statbuf.st_size] = 0;
|
|
||||||
if (fread(infoBuf, statbuf.st_size, 1, fd) == 1) {
|
|
||||||
char *end = infoBuf + statbuf.st_size;
|
|
||||||
char *b = infoBuf, *e;
|
|
||||||
while (e = strchr(b, '{')) {
|
|
||||||
*e = 0;
|
|
||||||
if ((e - b) > 2) {
|
|
||||||
parseLine(b);
|
|
||||||
}
|
|
||||||
e++;
|
|
||||||
b = e;
|
|
||||||
}
|
|
||||||
if (b < end) {
|
|
||||||
parseLine(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "short read on info file\n");
|
|
||||||
}
|
|
||||||
free(infoBuf);
|
|
||||||
fclose(fd);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned int crc_table[256];
|
|
||||||
|
|
||||||
void png_crc_table_init() {
|
|
||||||
unsigned int i;
|
|
||||||
if (crc_table[255]) return;
|
|
||||||
for (i = 0; i < 256; i++) {
|
|
||||||
unsigned int n = i;
|
|
||||||
unsigned int j;
|
|
||||||
for (j = 0; j < 8; j++) {
|
|
||||||
if (n & 1) {
|
|
||||||
n = 0xEDB88320 ^ (n >> 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
n >>= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
crc_table[i] = n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int do_crc(unsigned char *input, unsigned int len) {
|
|
||||||
unsigned int crc = 0;
|
|
||||||
unsigned int i;
|
|
||||||
png_crc_table_init();
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
unsigned int v = (input[i] ^ crc) & 0xff;
|
|
||||||
crc = crc_table[v] ^ (crc >> 8);
|
|
||||||
}
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *decodeString = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
|
|
||||||
|
|
||||||
void doPngDecode(unsigned char *input, unsigned int len, unsigned char *output) {
|
|
||||||
// unsigned int crc_table[256];
|
|
||||||
unsigned int crc, i, x = 0;
|
|
||||||
unsigned int *out = (unsigned int*)output;
|
|
||||||
crc = bswap_l(do_crc(input, len));
|
|
||||||
memset(output, 0, 8);
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
output[x++] ^= input[i];
|
|
||||||
if (x == 8) x = 0;
|
|
||||||
}
|
|
||||||
out[0] ^= crc;
|
|
||||||
out[1] ^= crc;
|
|
||||||
for (i = 0; i < 8; i++) {
|
|
||||||
unsigned char v = output[i];
|
|
||||||
output[i] = decodeString[((((v >> 5) & 3) ^ v) & 0x1F) + (v >> 7)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *string_32 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M";
|
|
||||||
static char *string_64 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_";
|
|
||||||
|
|
||||||
char *mazamaEncode32(unsigned char *input, unsigned int len) {
|
|
||||||
return mazamaEncode(input, len, 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *mazamaEncode64(unsigned char *input, unsigned int len) {
|
|
||||||
return mazamaEncode(input, len, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *mazamaEncode(unsigned char *input, unsigned int len, unsigned char choice) {
|
|
||||||
unsigned int i;
|
|
||||||
char *enc, *out;
|
|
||||||
if (choice == 0x20) enc = string_32;
|
|
||||||
else if (choice == 0x40) enc = string_64;
|
|
||||||
else return NULL;
|
|
||||||
out = (char*)malloc(len * 2 + 1);
|
|
||||||
out[len * 2] = 0;
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
unsigned char v = input[i] + 128;
|
|
||||||
unsigned char q = v / choice;
|
|
||||||
unsigned char m = v % choice;
|
|
||||||
out[i * 2] = enc[q];
|
|
||||||
out[i * 2 + 1] = enc[m];
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char *mazamaDecode(char *input, int *outlen) {
|
|
||||||
unsigned char *out;
|
|
||||||
int len = strlen(input);
|
|
||||||
char *dec = NULL;
|
|
||||||
int i, choice = 0x20;
|
|
||||||
*outlen = 0;
|
|
||||||
for (i = 0; i < 8 && i < len; i++) {
|
|
||||||
if (*input == string_32[i]) {
|
|
||||||
dec = string_32;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dec == NULL) {
|
|
||||||
for (i = 0; i < 4 && i < len; i++) {
|
|
||||||
if (*input == string_64[i]) {
|
|
||||||
dec = string_64;
|
|
||||||
choice = 0x40;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dec == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
out = (unsigned char*)malloc(len / 2 + 1);
|
|
||||||
out[len / 2] = 0;
|
|
||||||
for (i = 0; i < len; i += 2) {
|
|
||||||
int q, m, v;
|
|
||||||
char *p = strchr(dec, input[i]);
|
|
||||||
if (p == NULL) break;
|
|
||||||
q = p - dec;
|
|
||||||
p = strchr(dec, input[i + 1]);
|
|
||||||
if (p == NULL) break;
|
|
||||||
m = p - dec;
|
|
||||||
v = (choice * q + m) - 128;
|
|
||||||
out[(*outlen)++] = (unsigned char)v;
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef HEADER_MD5_H
|
|
||||||
|
|
||||||
void md5(unsigned char *in, int len, unsigned char *md) {
|
|
||||||
MD5_CTX s;
|
|
||||||
MD5_Init(&s);
|
|
||||||
MD5_Update(&s, in, len);
|
|
||||||
MD5_Final(md, &s);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HEADER_SHA_H
|
|
||||||
|
|
||||||
void sha1(unsigned char *in, int len, unsigned char *md) {
|
|
||||||
SHA_CTX s;
|
|
||||||
SHA1_Init(&s);
|
|
||||||
SHA1_Update(&s, in, len);
|
|
||||||
SHA1_Final(md, &s);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
char *getBookPid(unsigned char *keys, unsigned int klen, unsigned char *keysValue, unsigned int kvlen) {
|
|
||||||
unsigned char *vsn, *username, *mrn_key, *kat_key, *pid;
|
|
||||||
char drive[256];
|
|
||||||
char name[256];
|
|
||||||
DWORD nlen = sizeof(name);
|
|
||||||
char *d;
|
|
||||||
char volumeName[256];
|
|
||||||
DWORD volumeSerialNumber;
|
|
||||||
char fileSystemNameBuffer[256];
|
|
||||||
char volumeID[32];
|
|
||||||
unsigned char md5sum[MD5_DIGEST_LENGTH];
|
|
||||||
unsigned char sha1sum[SHA_DIGEST_LENGTH];
|
|
||||||
SHA_CTX sha1_ctx;
|
|
||||||
char *mv;
|
|
||||||
|
|
||||||
if (GetUserName(name, &nlen) == 0) {
|
|
||||||
fprintf(stderr, "GetUserName failed\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "Using UserName = \"%s\"\n", name);
|
|
||||||
|
|
||||||
d = getenv("SystemDrive");
|
|
||||||
if (d) {
|
|
||||||
strcpy(drive, d);
|
|
||||||
strcat(drive, "\\");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
strcpy(drive, "c:\\");
|
|
||||||
}
|
|
||||||
fprintf(stderr, "Using SystemDrive = \"%s\"\n", drive);
|
|
||||||
if (GetVolumeInformation(drive, volumeName, sizeof(volumeName), &volumeSerialNumber,
|
|
||||||
NULL, NULL, fileSystemNameBuffer, sizeof(fileSystemNameBuffer))) {
|
|
||||||
sprintf(volumeID, "%u", volumeSerialNumber);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
strcpy(volumeID, "9999999999");
|
|
||||||
}
|
|
||||||
fprintf(stderr, "Using VolumeSerialNumber = \"%s\"\n", volumeID);
|
|
||||||
MD5(volumeID, strlen(volumeID), md5sum);
|
|
||||||
vsn = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x20);
|
|
||||||
|
|
||||||
MD5(name, strlen(name), md5sum);
|
|
||||||
username = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x20);
|
|
||||||
|
|
||||||
MD5("MazamaRandomNumber", 18, md5sum);
|
|
||||||
mrn_key = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x40);
|
|
||||||
|
|
||||||
MD5("kindle.account.tokens", 21, md5sum);
|
|
||||||
kat_key = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x40);
|
|
||||||
|
|
||||||
SHA1_Init(&sha1_ctx);
|
|
||||||
|
|
||||||
mv = getKindleValue(mrn_key);
|
|
||||||
if (mv) {
|
|
||||||
DATA_BLOB DataIn;
|
|
||||||
DATA_BLOB DataOut;
|
|
||||||
DataIn.pbData = mazamaDecode(mv, (int*)&DataIn.cbData);
|
|
||||||
if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, 1, &DataOut)) {
|
|
||||||
char *devId = (char*)malloc(DataOut.cbData + 4 * MD5_DIGEST_LENGTH + 1);
|
|
||||||
char *finalDevId;
|
|
||||||
unsigned char pidbuf[10];
|
|
||||||
|
|
||||||
// fprintf(stderr, "CryptUnprotectData success\n");
|
|
||||||
// fwrite(DataOut.pbData, DataOut.cbData, 1, stderr);
|
|
||||||
// fprintf(stderr, "\n");
|
|
||||||
|
|
||||||
memcpy(devId, DataOut.pbData, DataOut.cbData);
|
|
||||||
strcpy(devId + DataOut.cbData, vsn);
|
|
||||||
strcat(devId + DataOut.cbData, username);
|
|
||||||
|
|
||||||
// fprintf(stderr, "Computing sha1 over %d bytes\n", DataOut.cbData + 4 * MD5_DIGEST_LENGTH);
|
|
||||||
sha1(devId, DataOut.cbData + 4 * MD5_DIGEST_LENGTH, sha1sum);
|
|
||||||
finalDevId = mazamaEncode(sha1sum, SHA_DIGEST_LENGTH, 0x20);
|
|
||||||
// fprintf(stderr, "finalDevId: %s\n", finalDevId);
|
|
||||||
|
|
||||||
SHA1_Update(&sha1_ctx, finalDevId, strlen(finalDevId));
|
|
||||||
|
|
||||||
pidbuf[8] = 0;
|
|
||||||
doPngDecode(finalDevId, 4, (unsigned char*)pidbuf);
|
|
||||||
fprintf(stderr, "Device PID: %s\n", pidbuf);
|
|
||||||
|
|
||||||
LocalFree(DataOut.pbData);
|
|
||||||
free(devId);
|
|
||||||
free(finalDevId);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "CryptUnprotectData failed, quitting\n");
|
|
||||||
free(kat_key);
|
|
||||||
free(mrn_key);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(DataIn.pbData);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "Failed to find map node: %s\n", mrn_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
mv = getKindleValue(kat_key);
|
|
||||||
if (mv) {
|
|
||||||
DATA_BLOB DataIn;
|
|
||||||
DATA_BLOB DataOut;
|
|
||||||
DataIn.pbData = mazamaDecode(mv, (int*)&DataIn.cbData);
|
|
||||||
if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, 1, &DataOut)) {
|
|
||||||
// fprintf(stderr, "CryptUnprotectData success\n");
|
|
||||||
// fwrite(DataOut.pbData, DataOut.cbData, 1, stderr);
|
|
||||||
// fprintf(stderr, "\n");
|
|
||||||
|
|
||||||
SHA1_Update(&sha1_ctx, DataOut.pbData, DataOut.cbData);
|
|
||||||
|
|
||||||
LocalFree(DataOut.pbData);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "CryptUnprotectData failed, quitting\n");
|
|
||||||
free(kat_key);
|
|
||||||
free(mrn_key);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(DataIn.pbData);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "Failed to find map node: %s\n", kat_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
SHA1_Update(&sha1_ctx, keys, klen);
|
|
||||||
SHA1_Update(&sha1_ctx, keysValue, kvlen);
|
|
||||||
SHA1_Final(sha1sum, &sha1_ctx);
|
|
||||||
|
|
||||||
pid = (char*)malloc(SHA_DIGEST_LENGTH * 2);
|
|
||||||
base64(sha1sum, SHA_DIGEST_LENGTH, pid);
|
|
||||||
|
|
||||||
pid[8] = 0;
|
|
||||||
|
|
||||||
free(mrn_key);
|
|
||||||
free(kat_key);
|
|
||||||
free(vsn);
|
|
||||||
free(username);
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
|
|
||||||
|
|
||||||
int verifyPidChecksum(char *pid) {
|
|
||||||
int l = strlen(letters);
|
|
||||||
unsigned int crc = ~do_crc(pid, 8);
|
|
||||||
unsigned char b;
|
|
||||||
crc = crc ^ (crc >> 16);
|
|
||||||
b = crc & 0xff;
|
|
||||||
if (pid[8] != letters[((b / l) ^ (b % l)) % l]) return 0;
|
|
||||||
crc >>= 8;
|
|
||||||
b = crc & 0xff;
|
|
||||||
if (pid[9] != letters[((b / l) ^ (b % l)) % l]) return 0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user