tools v3.1

This commit is contained in:
Apprentice Alf 2011-01-06 07:10:38 +00:00
parent 694dfafd39
commit 00ac669f76
71 changed files with 3915 additions and 624 deletions

View File

@ -1,6 +1,6 @@
From Apprentice Alf's Blog From Apprentice Alf's Blog
Adobe Adept ePub and PDF, .epub, .pdf Adobe Adept ePub, .epub
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. 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.
@ -11,20 +11,8 @@ 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.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 first is called ineptkey_vX.X.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.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. The second is called in ineptepub_vX.X.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.
The latest version of ineptpdf to use is version 8.4.42, which improves support for some PDF files.
ineptpdf version 8.4.42 can be found here:
http://pastebin.com/kuKMXXsC
It is not included in the tools archive.
If that link is down, please check out the following website for some of the latest releases of these tools:
http://ainept.freewebspace.com/

View File

@ -1,7 +1,7 @@
#! /usr/bin/python #! /usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ineptepub.pyw, version 5.4 # ineptepub.pyw, version 5.5
# Copyright © 2009-2010 i♥cabbages # Copyright © 2009-2010 i♥cabbages
# 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
@ -26,6 +26,7 @@
# 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 # 5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o
# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml # 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
# 5.5 - On Windows try PyCrypto first, OpenSSL next
""" """
Decrypt Adobe ADEPT-encrypted EPUB books. Decrypt Adobe ADEPT-encrypted EPUB books.
@ -259,7 +260,10 @@ def _load_crypto_pycrypto():
def _load_crypto(): def _load_crypto():
AES = RSA = None AES = RSA = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try: try:
AES, RSA = loader() AES, RSA = loader()
break break

View File

@ -1,7 +1,7 @@
#! /usr/bin/python #! /usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ineptkey.pyw, version 5 # ineptkey.pyw, version 5.3
# Copyright © 2009-2010 i♥cabbages # Copyright © 2009-2010 i♥cabbages
# 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
@ -32,6 +32,7 @@
# 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.1 - add support for using OpenSSL on Windows in place of PyCrypto
# 5.2 - added support for output of key to a particular file # 5.2 - added support for output of key to a particular file
# 5.3 - On Windows try PyCrypto first, OpenSSL next
""" """
Retrieve Adobe ADEPT user key. Retrieve Adobe ADEPT user key.
@ -115,7 +116,7 @@ if sys.platform.startswith('win'):
def _load_crypto(): def _load_crypto():
AES = None AES = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): for loader in (_load_crypto_pycrypto, _load_crypto_libcrypto):
try: try:
AES = loader() AES = loader()
break break

View File

@ -0,0 +1,18 @@
From Apprentice Alf's Blog
Adobe Adept PDF, .pdf
This directory includes modified versions of the I♥CABBAGES Adobe Adept inept scripts for pdfs. These scripts have been modified to work with OpenSSL on Windows as well as Linux and Mac OS X. 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 Adobes 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:
http://i-u2665-cabbages.blogspot.com/2009_02_01_archive.html
There are two scripts:
The first is called ineptkey_vX.X.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 ineptpdf_vX.X.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.

View File

@ -1,7 +1,7 @@
#! /usr/bin/python #! /usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ineptkey.pyw, version 5 # ineptkey.pyw, version 5.3
# Copyright © 2009-2010 i♥cabbages # Copyright © 2009-2010 i♥cabbages
# 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
@ -32,6 +32,7 @@
# 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.1 - add support for using OpenSSL on Windows in place of PyCrypto
# 5.2 - added support for output of key to a particular file # 5.2 - added support for output of key to a particular file
# 5.3 - On Windows try PyCrypto first, OpenSSL next
""" """
Retrieve Adobe ADEPT user key. Retrieve Adobe ADEPT user key.
@ -115,7 +116,7 @@ if sys.platform.startswith('win'):
def _load_crypto(): def _load_crypto():
AES = None AES = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): for loader in (_load_crypto_pycrypto, _load_crypto_libcrypto):
try: try:
AES = loader() AES = loader()
break break

View File

@ -1,5 +1,5 @@
#! /usr/bin/env python #! /usr/bin/env python
# ineptpdf.pyw, version 7.6 # ineptpdf.pyw, version 7.7
# To run this program install Python 2.6 from http://www.python.org/download/ # To run this program install Python 2.6 from http://www.python.org/download/
# and OpenSSL (already installed on Mac OS X and Linux) OR # and OpenSSL (already installed on Mac OS X and Linux) OR
@ -29,6 +29,7 @@
# implemented ARC4 interface to OpenSSL # implemented ARC4 interface to OpenSSL
# fixed minor typos # fixed minor typos
# 7.6 - backported AES and other fixes from version 8.4.48 # 7.6 - backported AES and other fixes from version 8.4.48
# 7.7 - On Windows try PyCrypto first and OpenSSL next
""" """
Decrypts Adobe ADEPT-encrypted PDF files. Decrypts Adobe ADEPT-encrypted PDF files.
@ -319,7 +320,10 @@ def _load_crypto_pycrypto():
def _load_crypto(): def _load_crypto():
ARC4 = RSA = AES = None ARC4 = RSA = AES = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try: try:
ARC4, RSA, AES = loader() ARC4, RSA, AES = loader()
break break

View File

@ -5,13 +5,13 @@ Barnes and Noble EPUB ebooks use a form of Social DRM which requires information
For more info, see the author's blog: For more info, see the author's blog:
http://i-u2665-cabbages.blogspot.com/2009_12_01_archive.html http://i-u2665-cabbages.blogspot.com/2009_12_01_archive.html
The original scripts by IHeartCabbages are available here as well. These scripts have been modified to allow the use of OpenSSL in place of PyCrypto to make them easier to run on Linux and Mac OS X. The original scripts by IHeartCabbages are available here as well. These scripts have been modified to allow the use of OpenSSL in place of PyCrypto to make them easier to run on Linux and Mac OS X, as well as to fix some minor bugs/
There are 2 scripts: There are 2 scripts:
The first is ignoblekeygen_v2.pyw. Double-click to launch it and provide the required information, and this program will generate a key file needed to remove the DRM from the books. This key file need only be generated once unless either you change your credit card number or your name on the credit card (or if you use a different credit card to purchase your book). The first is ignoblekeygen_vX.X.pyw. Double-click to launch it and provide the required information, and this program will generate a key file needed to remove the DRM from the books. This key file need only be generated once unless either you change your credit card number or your name on the credit card (or if you use a different credit card to purchase your book).
The second is ignobleepub_v3.pyw. Double-click it and it will ask for your key file and the path to the book to remove the DRM from. The second is ignobleepub_vX.X.pyw. Double-click it and it will ask for your key file and the path to the book to remove the DRM from.
All 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. All 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.

View File

@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# ignobleepub.pyw, version 3 # ignobleepub.pyw, version 3.3
# To run this program install Python 2.6 from <http://www.python.org/download/> # 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 # and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
@ -12,6 +12,9 @@
# 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 # 3.1 - Allow Windows versions of libcrypto to be found
# 3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml
# 3.3 - On Windows try PyCrypto first and OpenSSL next
from __future__ import with_statement from __future__ import with_statement
@ -105,15 +108,18 @@ def _load_crypto_pycrypto():
def _load_crypto(): def _load_crypto():
AES = None AES = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try: try:
AES = loader() AES = loader()
break break
except (ImportError, IGNOBLEError): except (ImportError, IGNOBLEError):
pass pass
return AES return AES
AES = _load_crypto()
AES = _load_crypto()

View File

@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# ignoblekeygen.pyw, version 2 # ignoblekeygen.pyw, version 2.2
# To run this program install Python 2.6 from <http://www.python.org/download/> # 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 # and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
@ -11,7 +11,7 @@
# 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 # 2.1 - Allow Windows versions of libcrypto to be found
# 2.2 - On Windows try PyCrypto first and then OpenSSL next
""" """
Generate Barnes & Noble EPUB user key from name and credit card number. Generate Barnes & Noble EPUB user key from name and credit card number.
""" """
@ -102,11 +102,12 @@ def _load_crypto_pycrypto():
return AES return AES
def _load_crypto(): def _load_crypto():
AES = None AES = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try: try:
AES = loader() AES = loader()
break break

View File

@ -83,7 +83,8 @@ def parseKindleInfo(kInfoFile):
DB[splito[0]] =splito[1] DB[splito[0]] =splito[1]
return DB return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record # 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): def getKindleInfoValueForHash(hashedKey):
global kindleDatabase global kindleDatabase
global charMap1 global charMap1
@ -95,12 +96,14 @@ def getKindleInfoValueForHash(hashedKey):
cleartext = CryptUnprotectData(encryptedValue) cleartext = CryptUnprotectData(encryptedValue)
return decode(cleartext, charMap1) return decode(cleartext, charMap1)
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record # Get a record from the Kindle.info file for the string in "key" (plaintext).
# Return the decoded and decrypted record
def getKindleInfoValueForKey(key): def getKindleInfoValueForKey(key):
global charMap2 global charMap2
return getKindleInfoValueForHash(encodeHash(key,charMap2)) 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. # 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): def findNameForHash(hash):
global charMap2 global charMap2
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
@ -222,7 +225,7 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. # Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum): def getKindlePid(pidlst, rec209, token, serialnum):
if rec209 != None: if rec209 != None and token != None:
# Compute book PID # Compute book PID
pidHash = SHA1(serialnum+rec209+token) pidHash = SHA1(serialnum+rec209+token)
bookPID = encodePID(pidHash) bookPID = encodePID(pidHash)
@ -248,6 +251,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
kindleDatabase = parseKindleInfo(kInfoFile) kindleDatabase = parseKindleInfo(kInfoFile)
except Exception, message: except Exception, message:
print(message) print(message)
kindleDatabase = None
pass pass
if kindleDatabase == None : if kindleDatabase == None :
@ -272,8 +276,8 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
pidlst.append(devicePID) pidlst.append(devicePID)
# Compute book PID # Compute book PID
if rec209 == None: if rec209 == None or token == None:
print "\nNo EXTH record type 209 - Perhaps not a K4 file?" print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?"
return pidlst return pidlst
# Get the kindle account token # Get the kindle account token

View File

@ -164,9 +164,10 @@ class TopazBook:
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
keysRecord = None keysRecord = None
KeysRecordRecord = None keysRecordRecord = None
if 'keys' in self.bookMetadata: if 'keys' in self.bookMetadata:
keysRecord = self.bookMetadata['keys'] keysRecord = self.bookMetadata['keys']
if keysRecord in self.bookMetadata:
keysRecordRecord = self.bookMetadata[keysRecord] keysRecordRecord = self.bookMetadata[keysRecord]
return keysRecord, keysRecordRecord return keysRecord, keysRecordRecord
@ -395,6 +396,7 @@ def main(argv=sys.argv):
myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
zipUpDir(myzip, tempdir, '') zipUpDir(myzip, tempdir, '')
myzip.close() myzip.close()
shutil.rmtree(tempdir, True)
return 1 return 1
print " Creating HTML ZIP Archive" print " Creating HTML ZIP Archive"
@ -424,7 +426,7 @@ def main(argv=sys.argv):
zipUpDir(myzip3, tempdir, 'img') zipUpDir(myzip3, tempdir, 'img')
myzip3.close() myzip3.close()
shutil.rmtree(tempdir) shutil.rmtree(tempdir, True)
return 0 return 0

View File

@ -10,6 +10,9 @@ Installation:
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (ineptpdf_vXX_plugin.zip) and click the 'Add' button. you're done. Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (ineptpdf_vXX_plugin.zip) and click the 'Add' button. you're done.
Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
Configuration: 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 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

View File

@ -7,6 +7,8 @@ This plugin is meant to remove the DRM from .prc, .azw, .azw1, and .tpz ebooks.
Installation: 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. 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.
Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
Configuration: 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. Include in this list (again separated by commas) any 16 digit serial numbers the standalone Kindles you may have (these typically begin "B0...") This is not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books. 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. Include in this list (again separated by commas) any 16 digit serial numbers the standalone Kindles you may have (these typically begin "B0...") This is not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books.

View File

@ -7,6 +7,8 @@ This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ fil
Installation: 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. 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.
Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
Configuration: 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 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

View File

@ -12,6 +12,8 @@ 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 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. click the 'Add' button. you're done.
Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
Configuration: 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 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

View File

@ -10,6 +10,9 @@ 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. 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.
Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
Configuration: 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 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

View File

@ -42,7 +42,7 @@ class eRdrDeDRM(FileTypePlugin):
Credit given to The Dark Reverser for the original standalone script.' Credit given to The Dark Reverser for the original standalone script.'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'DiapDealer' # The author of this plugin author = 'DiapDealer' # The author of this plugin
version = (0, 0, 3) # The version number of this plugin version = (0, 0, 4) # The version number of this plugin
file_types = set(['pdb']) # The file types that this plugin will be applied to file_types = set(['pdb']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
@ -76,7 +76,6 @@ class eRdrDeDRM(FileTypePlugin):
if pmlfilepath and pmlfilepath != 1: if pmlfilepath and pmlfilepath != 1:
import zipfile import zipfile
import shutil
print " Creating PMLZ file" print " Creating PMLZ file"
myZipFile = zipfile.ZipFile(pmlzfile.name,'w',zipfile.ZIP_STORED, False) myZipFile = zipfile.ZipFile(pmlzfile.name,'w',zipfile.ZIP_STORED, False)
list = os.listdir(outdir) list = os.listdir(outdir)

View File

@ -56,32 +56,9 @@
# 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 # 0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
# 0.17 - added support for pycrypto's DES as well # 0.17 - added support for pycrypto's DES as well
# 0.18 - on Windows try PyCrypto first and OpenSSL next
Des = None __version__='0.18'
import openssl_des
Des = openssl_des.load_libcrypto()
# if that did not work then try pycrypto version of DES
if Des == None:
import pycrypto_des
Des = pycrypto_des.load_pycrypto()
# 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:
# http://psyco.sourceforge.net
import psyco
psyco.full()
except ImportError:
pass
__version__='0.17'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@ -97,6 +74,37 @@ sys.stdout=Unbuffered(sys.stdout)
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
Des = None
if sys.platform.startswith('win'):
# first try with pycrypto
import pycrypto_des
Des = pycrypto_des.load_pycrypto()
if Des == None:
# they try with openssl
import openssl_des
Des = openssl_des.load_libcrypto()
else:
# first try with openssl
import openssl_des
Des = openssl_des.load_libcrypto()
if Des == None:
# then try with pycrypto
import pycrypto_des
Des = pycrypto_des.load_pycrypto()
# 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:
# http://psyco.sourceforge.net
import psyco
psyco.full()
except ImportError:
pass
try: try:
from hashlib import sha1 from hashlib import sha1
except ImportError: except ImportError:
@ -460,7 +468,7 @@ def main(argv=None):
myZipFile.write(imagePath, localname) myZipFile.write(imagePath, localname)
myZipFile.close() myZipFile.close()
# remove temporary directory # remove temporary directory
shutil.rmtree(outdir) shutil.rmtree(outdir, True)
end_time = time.time() end_time = time.time()
search_time = end_time - start_time search_time = end_time - start_time

View File

@ -45,6 +45,7 @@
# 0.1.1 - Allow Windows users to make use of openssl if they have it installed. # 0.1.1 - Allow Windows users to make use of openssl if they have it installed.
# - Incorporated SomeUpdates zipfix routine. # - Incorporated SomeUpdates zipfix routine.
# 0.1.2 - bug fix for non-ascii file names in encryption.xml # 0.1.2 - bug fix for non-ascii file names in encryption.xml
# 0.1.3 - Try PyCrypto on Windows first
""" """
Decrypt Barnes & Noble ADEPT encrypted EPUB books. Decrypt Barnes & Noble ADEPT encrypted EPUB books.
@ -169,7 +170,10 @@ def _load_crypto_pycrypto():
def _load_crypto(): def _load_crypto():
_aes = _aes2 = None _aes = _aes2 = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try: try:
_aes, _aes2 = loader() _aes, _aes2 = loader()
break break
@ -267,7 +271,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, 2) version = (0, 1, 3)
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

Binary file not shown.

View File

@ -79,7 +79,7 @@ if iswindows:
def _load_crypto(): def _load_crypto():
AES = None AES = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): for loader in (_load_crypto_pycrypto, _load_crypto_libcrypto):
try: try:
AES = loader() AES = loader()
break break

View File

@ -46,7 +46,7 @@
# 0.1.2 - Removed Carbon dependency for Mac users. Fixes an issue that was a # 0.1.2 - Removed Carbon dependency for Mac users. Fixes an issue that was a
# result of Calibre changing to python 2.7. # result of Calibre changing to python 2.7.
# 0.1.3 - bug fix for epubs with non-ascii chars in file names # 0.1.3 - bug fix for epubs with non-ascii chars in file names
# 0.1.4 - default to try PyCrypto first on Windows
""" """
@ -285,7 +285,10 @@ def _load_crypto_pycrypto():
def _load_crypto(): def _load_crypto():
_aes = _rsa = None _aes = _rsa = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try: try:
_aes, _rsa = loader() _aes, _rsa = loader()
break break
@ -368,7 +371,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, 3) version = (0, 1, 4)
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

Binary file not shown.

View File

@ -79,7 +79,7 @@ if iswindows:
def _load_crypto(): def _load_crypto():
AES = None AES = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): for loader in (_load_crypto_pycrypto, _load_crypto_libcrypto):
try: try:
AES = loader() AES = loader()
break break

View File

@ -336,7 +336,10 @@ def _load_crypto_pycrypto():
def _load_crypto(): def _load_crypto():
ARC4 = RSA = AES = None ARC4 = RSA = AES = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try: try:
ARC4, RSA, AES = loader() ARC4, RSA, AES = loader()
break break
@ -2113,7 +2116,7 @@ class IneptPDFDeDRM(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, 1) 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(['pdf']) file_types = set(['pdf'])
on_import = True on_import = True

View File

@ -28,7 +28,7 @@
from __future__ import with_statement from __future__ import with_statement
__version__ = '1.4' __version__ = '1.9'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@ -163,6 +163,7 @@ def main(argv=sys.argv):
myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
zipUpDir(myzip, tempdir, '') zipUpDir(myzip, tempdir, '')
myzip.close() myzip.close()
shutil.rmtree(tempdir, True)
return 1 return 1
if mobi: if mobi:
@ -198,7 +199,7 @@ def main(argv=sys.argv):
zipUpDir(myzip3, tempdir, 'img') zipUpDir(myzip3, tempdir, 'img')
myzip3.close() myzip3.close()
shutil.rmtree(tempdir) shutil.rmtree(tempdir, True)
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
@ -214,7 +215,7 @@ if not __name__ == "__main__" and inCalibre:
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' 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 supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 1, 7) # The version number of this plugin version = (0, 1, 9) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm

View File

@ -6,7 +6,7 @@ import os
import subprocess import subprocess
class K4MDrmException(Exception): class DrmException(Exception):
pass pass
@ -18,7 +18,7 @@ def _load_crypto_libcrypto():
libcrypto = find_library('crypto') libcrypto = find_library('crypto')
if libcrypto is None: if libcrypto is None:
raise K4MDrmException('libcrypto not found') raise DrmException('libcrypto not found')
libcrypto = CDLL(libcrypto) libcrypto = CDLL(libcrypto)
AES_MAXNR = 14 AES_MAXNR = 14
@ -51,19 +51,19 @@ def _load_crypto_libcrypto():
def set_decrypt_key(self, userkey, iv): def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey) self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise K4MDrmException('AES improper key used') raise DrmException('AES improper key used')
return return
keyctx = self._keyctx = AES_KEY() keyctx = self._keyctx = AES_KEY()
self.iv = iv self.iv = iv
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0: if rv < 0:
raise K4MDrmException('Failed to initialize AES key') raise DrmException('Failed to initialize AES key')
def decrypt(self, data): def decrypt(self, data):
out = create_string_buffer(len(data)) out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0) rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
if rv == 0: if rv == 0:
raise K4MDrmException('AES decryption failed') raise DrmException('AES decryption failed')
return out.raw return out.raw
def keyivgen(self, passwd): def keyivgen(self, passwd):
@ -81,7 +81,7 @@ def _load_crypto():
LibCrypto = None LibCrypto = None
try: try:
LibCrypto = _load_crypto_libcrypto() LibCrypto = _load_crypto_libcrypto()
except (ImportError, K4MDrmException): except (ImportError, DrmException):
pass pass
return LibCrypto return LibCrypto
@ -185,8 +185,10 @@ def openKindleInfo(kInfoFile=None):
if pp >= 0: if pp >= 0:
kinfopath = resline kinfopath = resline
break break
if not os.path.exists(kinfopath): if not os.path.isfile(kinfopath):
raise K4MDrmException('Error: .kindle-info file can not be found') raise DrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r') return open(kinfopath,'r')
else: else:
if not os.path.isfile(kinfoFile):
raise DrmException('Error: kindle-info file can not be found')
return open(kInfoFile, 'r') return open(kInfoFile, 'r')

View File

@ -99,7 +99,12 @@ CryptUnprotectData = CryptUnprotectData()
def openKindleInfo(kInfoFile=None): def openKindleInfo(kInfoFile=None):
if kInfoFile == None: if kInfoFile == None:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r') kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath):
raise DrmException('Error: kindle.info file can not be found')
return open(kinfopath,'r')
else: else:
if not os.path.isfile(kInfoFile):
raise DrmException('Error: kindle.info file can not be found')
return open(kInfoFile, 'r') return open(kInfoFile, 'r')

View File

@ -42,8 +42,10 @@
# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file. # 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
# 0.21 - Added support for multiple pids # 0.21 - Added support for multiple pids
# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface # 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
# 0.23 - fixed problem with older files with no EXTH section
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
__version__ = '0.22' __version__ = '0.24'
import sys import sys
@ -57,6 +59,7 @@ class Unbuffered:
return getattr(self.stream, attr) return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout) sys.stdout=Unbuffered(sys.stdout)
import os
import struct import struct
import binascii import binascii
@ -154,8 +157,10 @@ class MobiBook:
# initial sanity check on file # initial sanity check on file
self.data_file = file(infile, 'rb').read() self.data_file = file(infile, 'rb').read()
self.header = self.data_file[0:78] self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI': if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
raise DrmException("invalid file format") raise DrmException("invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
# build up section offset and flag info # build up section offset and flag info
self.num_sections, = struct.unpack('>H', self.header[76:78]) self.num_sections, = struct.unpack('>H', self.header[76:78])
@ -168,6 +173,14 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_version = -1
self.meta_array = {}
return
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
@ -182,18 +195,23 @@ class MobiBook:
# if exth region exists parse it for metadata array # if exth region exists parse it for metadata array
self.meta_array = {} self.meta_array = {}
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) try:
exth = '' exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
if exth_flag & 0x40: exth = 'NONE'
exth = self.sect[16 + self.mobi_length:] if exth_flag & 0x40:
nitems, = struct.unpack('>I', exth[8:12]) exth = self.sect[16 + self.mobi_length:]
pos = 12 if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
for i in xrange(nitems): nitems, = struct.unpack('>I', exth[8:12])
type, size = struct.unpack('>II', exth[pos: pos + 8]) pos = 12
content = exth[pos + 8: pos + size] for i in xrange(nitems):
self.meta_array[type] = content type, size = struct.unpack('>II', exth[pos: pos + 8])
pos += size content = exth[pos + 8: pos + size]
self.meta_array[type] = content
pos += size
except:
self.meta_array = {}
pass
def getBookTitle(self): def getBookTitle(self):
title = '' title = ''
if 503 in self.meta_array: if 503 in self.meta_array:
@ -269,12 +287,12 @@ class MobiBook:
def processBook(self, pidlist): def processBook(self, pidlist):
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
print 'Crypto Type is: ', crypto_type
self.crypto_type = crypto_type
if crypto_type == 0: if crypto_type == 0:
print "This book is not encrypted." print "This book is not encrypted."
return self.data_file return self.data_file
if crypto_type == 1: if crypto_type != 2 and crypto_type != 1:
raise DrmException("Cannot decode Mobipocket encryption type 1")
if crypto_type != 2:
raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
goodpids = [] goodpids = []
@ -286,23 +304,32 @@ class MobiBook:
elif len(pid)==8: elif len(pid)==8:
goodpids.append(pid) goodpids.append(pid)
# calculate the keys if self.crypto_type == 1:
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) t1_keyvec = "QDCVEPMU675RUBSZ"
if drm_count == 0: if self.magic == 'TEXtREAd':
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") bookkey_data = self.sect[0x0E:0x0E+16]
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) else:
if not found_key: bookkey_data = self.sect[0x90:0x90+16]
raise DrmException("No key found. Most likely the correct PID has not been given.") pid = "00000000"
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key:
raise DrmException("No key found. Most likely the correct PID has not been given.")
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
if pid=="00000000": if pid=="00000000":
print "File has default encryption, no specific PID." print "File has default encryption, no specific PID."
else: else:
print "File is encoded with PID "+checksumPid(pid)+"." print "File is encoded with PID "+checksumPid(pid)+"."
# 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 # clear the crypto type
self.patchSection(0, "\0" * 2, 0xC) self.patchSection(0, "\0" * 2, 0xC)

View File

@ -55,7 +55,7 @@ on GetTools()
set AdobePDFTool to POSIX path of file (path to me as text) & "Contents/Resources/ineptpdf.pyw" set AdobePDFTool to POSIX path of file (path to me as text) & "Contents/Resources/ineptpdf.pyw"
set ZipFixTool to POSIX path of file (path to me as text) & "Contents/Resources/zipfix.py" set ZipFixTool to POSIX path of file (path to me as text) & "Contents/Resources/zipfix.py"
set ProgressApp to POSIX path of file ((path to resource "DeDRM Progress.app") as string) set ProgressApp to POSIX path of file ((path to resource "DeDRM Progress.app") as string)
if not fileexists(eReaderTool) or not fileexists(MobipocketTool) or not fileexists(BNKeyGenTool) or not fileexists(BNePubTool) or not fileexists(AdobeKeyGenTool) or not fileexists(AdobeePubTool) or not folderexists(ProgressApp) then if not fileexists(eReaderTool) or not fileexists(MobipocketTool) or not fileexists(BNKeyGenTool) or not fileexists(BNePubTool) or not fileexists(AdobeKeyGenTool) or not fileexists(AdobePDFTool) or not fileexists(AdobeePubTool) or not folderexists(ProgressApp) then
display dialog "De-drm scripts or support files are missing from this package. Get a fresh copy." buttons {"Bother"} default button 1 with title "DeDRM Applescript" with icon stop display dialog "De-drm scripts or support files are missing from this package. Get a fresh copy." buttons {"Bother"} default button 1 with title "DeDRM Applescript" with icon stop
return false return false
end if end if
@ -68,7 +68,7 @@ on unlockmobifile(encryptedFile)
try try
set BOOKMOBI to read file encryptedFile from 61 for 8 set BOOKMOBI to read file encryptedFile from 61 for 8
end try end try
if BOOKMOBI is not "BOOKMOBI" then if BOOKMOBI is not "BOOKMOBI" and BOOKMOBI is not "TEXtREAd" then
set TOPAZ to "NOT" set TOPAZ to "NOT"
try try
set TOPAZ to read file encryptedFile from 1 for 4 set TOPAZ to read file encryptedFile from 1 for 4
@ -98,9 +98,9 @@ on unlockmobifile(encryptedFile)
set shellcommand to shellcommand & " -k " & quoted form of KindleInfoPath set shellcommand to shellcommand & " -k " & quoted form of KindleInfoPath
end repeat end repeat
set Serialstring to GetSerialstring() set Serialstring to GetSerialstring()
if Serialstring is not "" then set shellcommand to shellcommand & " -s " & Serialstring if Serialstring is not "" then set shellcommand to shellcommand & " -s " & quoted form of Serialstring
set PIDstring to GetPIDstring() set PIDstring to GetPIDstring()
if PIDstring is not "" then set shellcommand to shellcommand & " -p " & PIDstring if PIDstring is not "" then set shellcommand to shellcommand & " -p " & quoted form of PIDstring
set shellcommand to shellcommand & " " & (quoted form of encryptedFilePath) & " " & (quoted form of unlockedFileParentFolderPath) set shellcommand to shellcommand & " " & (quoted form of encryptedFilePath) & " " & (quoted form of unlockedFileParentFolderPath)
--display dialog "shellcommand: " default answer shellcommand buttons {"OK"} default button 1 giving up after 10 --display dialog "shellcommand: " default answer shellcommand buttons {"OK"} default button 1 giving up after 10
try try
@ -108,8 +108,34 @@ on unlockmobifile(encryptedFile)
on error errmsg on error errmsg
set shellresult to errmsg set shellresult to errmsg
end try end try
--display dialog shellresult
try try
if (offset of "Error" in shellresult) > 0 then repeat
if (totalebooks > 1) or (offset of "No key found" in shellresult) is 0 then
exit repeat
end if
-- ask for another PID as we're only doing one ebook
set newPID to "None"
set DialogPrompt to "Couldn't decode " & fileName & ". Do you have another PID to try?"
try
set dialogresult to (display dialog DialogPrompt default answer "" buttons {"Try This One", "No Extra PID"} with title "DeDRM Applescript" default button 2)
if button returned of dialogresult is "Try This One" then
set newPID to text returned of dialogresult
end if
end try
if newPID is "None" or (length of newPID is not 10 and length of newPID is not 8) then
exit repeat
end if
set shellcommand to "python " & (quoted form of MobipocketTool) & " -p " & quoted form of newPID & " " & (quoted form of encryptedFilePath) & " " & (quoted form of unlockedFileParentFolderPath)
--display dialog "shellcommand: " default answer shellcommand buttons {"OK"} default button 1 giving up after 10
try
set shellresult to do shell script shellcommand
on error errmsg
set shellresult to errmsg
end try
--display dialog shellresult
end repeat
if (offset of "Error" in shellresult) > 0 or (offset of "No key found" in shellresult) > 0 then
set ErrorCount to ErrorCount + 1 set ErrorCount to ErrorCount + 1
set ErrorList to (ErrorList & fileName & fileExtension & " couldn't be decoded: set ErrorList to (ErrorList & fileName & fileExtension & " couldn't be decoded:
" & shellresult as text) & " " & shellresult as text) & "
@ -322,6 +348,7 @@ on unlockepubfile(encryptedFile)
end repeat end repeat
if decoded is "NO" then if decoded is "NO" then
set shellresult to "no keys"
-- now try Adobe ePub -- now try Adobe ePub
repeat with AdeptKey in AdeptKeyList repeat with AdeptKey in AdeptKeyList
set shellcommand to "python " & (quoted form of AdobeePubTool) & " " & (quoted form of AdeptKey) & " " & (quoted form of fixedFilePath) & " " & (quoted form of unlockedFilePath) set shellcommand to "python " & (quoted form of AdobeePubTool) & " " & (quoted form of AdeptKey) & " " & (quoted form of fixedFilePath) & " " & (quoted form of unlockedFilePath)
@ -350,6 +377,11 @@ on unlockepubfile(encryptedFile)
set ErrorCount to ErrorCount + 1 set ErrorCount to ErrorCount + 1
set ErrorList to (ErrorList & fileName & fileExtension & " couldn't be decoded: no keys. set ErrorList to (ErrorList & fileName & fileExtension & " couldn't be decoded: no keys.
")
else if (offset of "not an ADEPT EPUB" in shellresult) is not 0 then
set WarningCount to WarningCount + 1
set WarningList to (WarningList & fileName & " doesn't seem to be encrypted.
") ")
else else
set ErrorCount to ErrorCount + 1 set ErrorCount to ErrorCount + 1
@ -392,7 +424,7 @@ on unlockpdffile(encryptedFile)
set decoded to "NO" set decoded to "NO"
-- first we must check we have a PDF script -- first we must check we have a PDF script
GetIneptPDF(false) --GetIneptPDF(false)
if not fileexists(AdobePDFTool) then if not fileexists(AdobePDFTool) then
set ErrorCount to ErrorCount + 1 set ErrorCount to ErrorCount + 1
set ErrorList to ErrorList & encryptedFile & " is a PDF file and no ineptpdf script found. set ErrorList to ErrorList & encryptedFile & " is a PDF file and no ineptpdf script found.
@ -591,7 +623,7 @@ on GetPIDs()
Enter any additional Mobipocket PIDs for your Mobipocket books one at a time:" Enter any additional Mobipocket PIDs for your Mobipocket books one at a time:"
set FinishedButton to "No More" set FinishedButton to "No More"
end if end if
set dialogresult to (display dialog DialogPrompt default answer "" buttons {"Delete All", "Add", FinishedButton} with title "DeDRM Applescript 2/6" default button 2) set dialogresult to (display dialog DialogPrompt default answer "" buttons {"Delete All", "Add", FinishedButton} with title "DeDRM Applescript 2/5" default button 2)
if button returned of dialogresult is "Add" then if button returned of dialogresult is "Add" then
set PID to text returned of dialogresult set PID to text returned of dialogresult
set PIDlength to length of PID set PIDlength to length of PID
@ -627,7 +659,7 @@ on GetSerials()
Enter any additional Kindle Serial Numbers one at a time:" Enter any additional Kindle Serial Numbers one at a time:"
set FinishedButton to "No More" set FinishedButton to "No More"
end if end if
set dialogresult to (display dialog DialogPrompt default answer "" buttons {"Delete All", "Add", FinishedButton} with title "DeDRM Applescript 3/6" default button 2) set dialogresult to (display dialog DialogPrompt default answer "" buttons {"Delete All", "Add", FinishedButton} with title "DeDRM Applescript 3/5" default button 2)
if button returned of dialogresult is "Add" then if button returned of dialogresult is "Add" then
set Serial to text returned of dialogresult set Serial to text returned of dialogresult
set Seriallength to length of Serial set Seriallength to length of Serial
@ -735,7 +767,7 @@ Please enter any additional "
set DialogPrompt to DialogPrompt & "eReader/Barnes & Noble Name,Number key pairs one at a time. If you're only decoding eReader files, the last 8 digits of the Number will do. The full 15 or 16 are only needed for Barnes & Noble ePubs. Only the last eight will be stored or displayed. Please separate the name and number with a comma and click \"Add\". Or to add a an already generated .b64 file, just click \"Add\" with nothing in the text field." set DialogPrompt to DialogPrompt & "eReader/Barnes & Noble Name,Number key pairs one at a time. If you're only decoding eReader files, the last 8 digits of the Number will do. The full 15 or 16 are only needed for Barnes & Noble ePubs. Only the last eight will be stored or displayed. Please separate the name and number with a comma and click \"Add\". Or to add a an already generated .b64 file, just click \"Add\" with nothing in the text field."
set dialogtitle to "DeDRM Applescript" set dialogtitle to "DeDRM Applescript"
if (running) then if (running) then
set dialogtitle to dialogtitle & " 4/6" set dialogtitle to dialogtitle & " 4/5"
end if end if
set dialogresult to (display dialog DialogPrompt default answer bnKeyText buttons {"Delete All", "Add", FinishedButton} with title dialogtitle default button 2) set dialogresult to (display dialog DialogPrompt default answer bnKeyText buttons {"Delete All", "Add", FinishedButton} with title dialogtitle default button 2)
if button returned of dialogresult is "Add" then if button returned of dialogresult is "Add" then
@ -823,7 +855,7 @@ on GetAdeptKeyFiles()
To add extra key files (.der), click the AddÉ button." To add extra key files (.der), click the AddÉ button."
set FinishedButton to "No More" set FinishedButton to "No More"
end if end if
set dialogresult to (display dialog DialogPrompt buttons {"Forget All", "AddÉ", FinishedButton} with title "DeDRM Applescript 5/6" default button 2) set dialogresult to (display dialog DialogPrompt buttons {"Forget All", "AddÉ", FinishedButton} with title "DeDRM Applescript 5/5" default button 2)
if button returned of dialogresult is "AddÉ" then if button returned of dialogresult is "AddÉ" then
try try
set newFile to (choose file with prompt "Please select an Adept key file") as text set newFile to (choose file with prompt "Please select an Adept key file") as text
@ -1023,9 +1055,9 @@ on ReadPrefs()
try try
set AdeptKeyList to value of property list item "AdeptKeys" of property list file preferencesFilePath set AdeptKeyList to value of property list item "AdeptKeys" of property list file preferencesFilePath
end try end try
try --try
set AdobePDFTool to value of property list item "IneptPDF" of property list file preferencesFilePath --set AdobePDFTool to value of property list item "IneptPDF" of property list file preferencesFilePath
end try --end try
end tell end tell
end if end if
set newList to {} set newList to {}
@ -1053,7 +1085,7 @@ on WritePrefs()
make new property list item at end of property list items of contents of myPrefs with properties {kind:list, name:"KindleInfoFiles", value:KindleInfoList} make new property list item at end of property list items of contents of myPrefs with properties {kind:list, name:"KindleInfoFiles", value:KindleInfoList}
make new property list item at end of property list items of contents of myPrefs with properties {kind:list, name:"bnKeys", value:bnKeys} make new property list item at end of property list items of contents of myPrefs with properties {kind:list, name:"bnKeys", value:bnKeys}
make new property list item at end of property list items of contents of myPrefs with properties {kind:list, name:"AdeptKeys", value:AdeptKeyList} make new property list item at end of property list items of contents of myPrefs with properties {kind:list, name:"AdeptKeys", value:AdeptKeyList}
make new property list item at end of property list items of contents of myPrefs with properties {kind:string, name:"IneptPDF", value:AdobePDFTool} --make new property list item at end of property list items of contents of myPrefs with properties {kind:string, name:"IneptPDF", value:AdobePDFTool}
end tell end tell
end WritePrefs end WritePrefs
@ -1103,9 +1135,9 @@ on run
if GetTools() then if GetTools() then
display dialog "Drag&Drop encrypted ebooks onto this AppleScript's icon in the Finder to decode them after you have finished configuring it and it has quit. display dialog "Drag&Drop encrypted ebooks onto this AppleScript's icon in the Finder to decode them after you have finished configuring it and it has quit.
Click the Continue button to enter any PIDs for Mobipocket ebooks; serial numbers for Kindle ebooks; name,number key pairs for Barnes & Noble/eReader ebooks; to select extra Barnes & Noble .b64 key files; to select extra Adobe Adept .der key files; and to find th optional ineptpdf.pyw script. Click the Continue button to enter any PIDs for Mobipocket ebooks; serial numbers for Kindle ebooks; name,number key pairs for Barnes & Noble/eReader ebooks; to select extra Barnes & Noble .b64 key files; or to select extra Adobe Adept .der key files.
***You do not need to enter any extra info if decoding ebooks downloaded to your installation of Kindle for Mac, or Adobe Digital Editions. If you do not have any PIDS; serial numbers; name,number keys, .b64 or .der files to add or want to decode PDF files, just click the Cancel button.*** ***You do not need to enter any extra info if decoding ebooks downloaded to your installation of Kindle for Mac, or Adobe Digital Editions. If you do not have any PIDS; serial numbers; name,number keys, .b64 or .der files, just click the Cancel button.***
Please only use to get access to your own books. Authors, publishers and ebook stores need to make money to produce more ebooks. Don't cheat them. Please only use to get access to your own books. Authors, publishers and ebook stores need to make money to produce more ebooks. Don't cheat them.
@ -1117,7 +1149,7 @@ Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
For more information, please refer to For more information, please refer to
<http://unlicense.org/> <http://unlicense.org/>
" with title "DeDRM Applescript 1/6" buttons {"Cancel", "Continue"} default button 2 " with title "DeDRM Applescript 1/5" buttons {"Cancel", "Continue"} default button 2
ReadPrefs() ReadPrefs()
GetAdeptKey(true) GetAdeptKey(true)
@ -1125,7 +1157,7 @@ For more information, please refer to
GetSerials() GetSerials()
GetKeys(true) GetKeys(true)
GetAdeptKeyFiles() GetAdeptKeyFiles()
GetIneptPDF(true) --GetIneptPDF(true)
--GetKindleInfoFiles() --GetKindleInfoFiles()
WritePrefs() WritePrefs()
end if end if

View File

@ -24,7 +24,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>droplet</string> <string>droplet</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>DeDRM 1.4, Copyright © 2010 by Apprentice Alf.</string> <string>DeDRM 1.6, Copyright © 20102011 by Apprentice Alf.</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>droplet</string> <string>droplet</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
@ -34,11 +34,11 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.4</string> <string>1.6</string>
<key>LSMinimumSystemVersion</key>
<string>10.5.0</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>
@ -46,9 +46,9 @@
<key>name</key> <key>name</key>
<string>ScriptWindowState</string> <string>ScriptWindowState</string>
<key>positionOfDivider</key> <key>positionOfDivider</key>
<real>739</real> <real>686</real>
<key>savedFrame</key> <key>savedFrame</key>
<string>1533 -24 1262 818 1440 -150 1680 1050 </string> <string>2161 -75 907 765 1440 -150 1680 1050 </string>
<key>selectedTabView</key> <key>selectedTabView</key>
<string>result</string> <string>result</string>
</dict> </dict>

View File

@ -55,29 +55,10 @@
# 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 # 0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
# 0.17 - added support for pycrypto's DES as well
# 0.18 - on Windows try PyCrypto first and OpenSSL next
Des = None __version__='0.18'
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: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@ -93,6 +74,37 @@ sys.stdout=Unbuffered(sys.stdout)
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
Des = None
if sys.platform.startswith('win'):
# first try with pycrypto
import pycrypto_des
Des = pycrypto_des.load_pycrypto()
if Des == None:
# they try with openssl
import openssl_des
Des = openssl_des.load_libcrypto()
else:
# first try with openssl
import openssl_des
Des = openssl_des.load_libcrypto()
if Des == None:
# then try with pycrypto
import pycrypto_des
Des = pycrypto_des.load_pycrypto()
# 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:
# http://psyco.sourceforge.net
import psyco
psyco.full()
except ImportError:
pass
try: try:
from hashlib import sha1 from hashlib import sha1
except ImportError: except ImportError:
@ -456,7 +468,7 @@ def main(argv=None):
myZipFile.write(imagePath, localname) myZipFile.write(imagePath, localname)
myZipFile.close() myZipFile.close()
# remove temporary directory # remove temporary directory
shutil.rmtree(outdir) shutil.rmtree(outdir, True)
end_time = time.time() end_time = time.time()
search_time = end_time - start_time search_time = end_time - start_time

View File

@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# ignobleepub.pyw, version 3 # ignobleepub.pyw, version 3.3
# To run this program install Python 2.6 from <http://www.python.org/download/> # 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 # and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
@ -12,6 +12,9 @@
# 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 # 3.1 - Allow Windows versions of libcrypto to be found
# 3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml
# 3.3 - On Windows try PyCrypto first and OpenSSL next
from __future__ import with_statement from __future__ import with_statement
@ -105,15 +108,18 @@ def _load_crypto_pycrypto():
def _load_crypto(): def _load_crypto():
AES = None AES = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try: try:
AES = loader() AES = loader()
break break
except (ImportError, IGNOBLEError): except (ImportError, IGNOBLEError):
pass pass
return AES return AES
AES = _load_crypto()
AES = _load_crypto()

View File

@ -1,6 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# ignoblekeygen.pyw, version 2 # ignoblekeygen.pyw, version 2.2
# To run this program install Python 2.6 from <http://www.python.org/download/> # 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 # and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
@ -11,7 +11,7 @@
# 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 # 2.1 - Allow Windows versions of libcrypto to be found
# 2.2 - On Windows try PyCrypto first and then OpenSSL next
""" """
Generate Barnes & Noble EPUB user key from name and credit card number. Generate Barnes & Noble EPUB user key from name and credit card number.
""" """
@ -102,11 +102,12 @@ def _load_crypto_pycrypto():
return AES return AES
def _load_crypto(): def _load_crypto():
AES = None AES = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try: try:
AES = loader() AES = loader()
break break

View File

@ -1,7 +1,7 @@
#! /usr/bin/python #! /usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ineptepub.pyw, version 5.4 # ineptepub.pyw, version 5.5
# Copyright © 2009-2010 i♥cabbages # Copyright © 2009-2010 i♥cabbages
# 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
@ -26,6 +26,7 @@
# 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 # 5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o
# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml # 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
# 5.5 - On Windows try PyCrypto first, OpenSSL next
""" """
Decrypt Adobe ADEPT-encrypted EPUB books. Decrypt Adobe ADEPT-encrypted EPUB books.
@ -259,7 +260,10 @@ def _load_crypto_pycrypto():
def _load_crypto(): def _load_crypto():
AES = RSA = None AES = RSA = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
if sys.platform.startswith('win'):
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
for loader in cryptolist:
try: try:
AES, RSA = loader() AES, RSA = loader()
break break

View File

@ -1,7 +1,7 @@
#! /usr/bin/python #! /usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ineptkey.pyw, version 5 # ineptkey.pyw, version 5.3
# Copyright © 2009-2010 i♥cabbages # Copyright © 2009-2010 i♥cabbages
# 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
@ -32,6 +32,7 @@
# 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.1 - add support for using OpenSSL on Windows in place of PyCrypto
# 5.2 - added support for output of key to a particular file # 5.2 - added support for output of key to a particular file
# 5.3 - On Windows try PyCrypto first, OpenSSL next
""" """
Retrieve Adobe ADEPT user key. Retrieve Adobe ADEPT user key.
@ -115,7 +116,7 @@ if sys.platform.startswith('win'):
def _load_crypto(): def _load_crypto():
AES = None AES = None
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto): for loader in (_load_crypto_pycrypto, _load_crypto_libcrypto):
try: try:
AES = loader() AES = loader()
break break

File diff suppressed because it is too large Load Diff

View File

@ -163,6 +163,7 @@ def main(argv=sys.argv):
myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
zipUpDir(myzip, tempdir, '') zipUpDir(myzip, tempdir, '')
myzip.close() myzip.close()
shutil.rmtree(tempdir, True)
return 1 return 1
if mobi: if mobi:
@ -198,7 +199,7 @@ def main(argv=sys.argv):
zipUpDir(myzip3, tempdir, 'img') zipUpDir(myzip3, tempdir, 'img')
myzip3.close() myzip3.close()
shutil.rmtree(tempdir) shutil.rmtree(tempdir, True)
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -6,7 +6,7 @@ import os
import subprocess import subprocess
class K4MDrmException(Exception): class DrmException(Exception):
pass pass
@ -18,7 +18,7 @@ def _load_crypto_libcrypto():
libcrypto = find_library('crypto') libcrypto = find_library('crypto')
if libcrypto is None: if libcrypto is None:
raise K4MDrmException('libcrypto not found') raise DrmException('libcrypto not found')
libcrypto = CDLL(libcrypto) libcrypto = CDLL(libcrypto)
AES_MAXNR = 14 AES_MAXNR = 14
@ -51,19 +51,19 @@ def _load_crypto_libcrypto():
def set_decrypt_key(self, userkey, iv): def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey) self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise K4MDrmException('AES improper key used') raise DrmException('AES improper key used')
return return
keyctx = self._keyctx = AES_KEY() keyctx = self._keyctx = AES_KEY()
self.iv = iv self.iv = iv
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0: if rv < 0:
raise K4MDrmException('Failed to initialize AES key') raise DrmException('Failed to initialize AES key')
def decrypt(self, data): def decrypt(self, data):
out = create_string_buffer(len(data)) out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0) rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
if rv == 0: if rv == 0:
raise K4MDrmException('AES decryption failed') raise DrmException('AES decryption failed')
return out.raw return out.raw
def keyivgen(self, passwd): def keyivgen(self, passwd):
@ -81,7 +81,7 @@ def _load_crypto():
LibCrypto = None LibCrypto = None
try: try:
LibCrypto = _load_crypto_libcrypto() LibCrypto = _load_crypto_libcrypto()
except (ImportError, K4MDrmException): except (ImportError, DrmException):
pass pass
return LibCrypto return LibCrypto
@ -185,8 +185,10 @@ def openKindleInfo(kInfoFile=None):
if pp >= 0: if pp >= 0:
kinfopath = resline kinfopath = resline
break break
if not os.path.exists(kinfopath): if not os.path.isfile(kinfopath):
raise K4MDrmException('Error: .kindle-info file can not be found') raise DrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r') return open(kinfopath,'r')
else: else:
if not os.path.isfile(kinfoFile):
raise DrmException('Error: kindle-info file can not be found')
return open(kInfoFile, 'r') return open(kInfoFile, 'r')

View File

@ -19,17 +19,12 @@ advapi32 = windll.advapi32
crypt32 = windll.crypt32 crypt32 = windll.crypt32
#
# Various character maps used to decrypt books. Probably supposed to act as obfuscation # Various character maps used to decrypt books. Probably supposed to act as obfuscation
#
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_" charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
#
# Exceptions for all the problems that might happen during the script
#
class DrmException(Exception): class DrmException(Exception):
pass pass
@ -104,7 +99,12 @@ CryptUnprotectData = CryptUnprotectData()
def openKindleInfo(kInfoFile=None): def openKindleInfo(kInfoFile=None):
if kInfoFile == None: if kInfoFile == None:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r') kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath):
raise DrmException('Error: kindle.info file can not be found')
return open(kinfopath,'r')
else: else:
if not os.path.isfile(kInfoFile):
raise DrmException('Error: kindle.info file can not be found')
return open(kInfoFile, 'r') return open(kInfoFile, 'r')

View File

@ -83,7 +83,8 @@ def parseKindleInfo(kInfoFile):
DB[splito[0]] =splito[1] DB[splito[0]] =splito[1]
return DB return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record # 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): def getKindleInfoValueForHash(hashedKey):
global kindleDatabase global kindleDatabase
global charMap1 global charMap1
@ -95,12 +96,14 @@ def getKindleInfoValueForHash(hashedKey):
cleartext = CryptUnprotectData(encryptedValue) cleartext = CryptUnprotectData(encryptedValue)
return decode(cleartext, charMap1) return decode(cleartext, charMap1)
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record # Get a record from the Kindle.info file for the string in "key" (plaintext).
# Return the decoded and decrypted record
def getKindleInfoValueForKey(key): def getKindleInfoValueForKey(key):
global charMap2 global charMap2
return getKindleInfoValueForHash(encodeHash(key,charMap2)) 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. # 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): def findNameForHash(hash):
global charMap2 global charMap2
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
@ -222,7 +225,7 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. # Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum): def getKindlePid(pidlst, rec209, token, serialnum):
if rec209 != None: if rec209 != None and token != None:
# Compute book PID # Compute book PID
pidHash = SHA1(serialnum+rec209+token) pidHash = SHA1(serialnum+rec209+token)
bookPID = encodePID(pidHash) bookPID = encodePID(pidHash)
@ -248,6 +251,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
kindleDatabase = parseKindleInfo(kInfoFile) kindleDatabase = parseKindleInfo(kInfoFile)
except Exception, message: except Exception, message:
print(message) print(message)
kindleDatabase = None
pass pass
if kindleDatabase == None : if kindleDatabase == None :
@ -272,8 +276,8 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
pidlst.append(devicePID) pidlst.append(devicePID)
# Compute book PID # Compute book PID
if rec209 == None: if rec209 == None or token == None:
print "\nNo EXTH record type 209 - Perhaps not a K4 file?" print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?"
return pidlst return pidlst
# Get the kindle account token # Get the kindle account token

View File

@ -42,8 +42,10 @@
# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file. # 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
# 0.21 - Added support for multiple pids # 0.21 - Added support for multiple pids
# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface # 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
# 0.23 - fixed problem with older files with no EXTH section
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
__version__ = '0.22' __version__ = '0.24'
import sys import sys
@ -57,6 +59,7 @@ class Unbuffered:
return getattr(self.stream, attr) return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout) sys.stdout=Unbuffered(sys.stdout)
import os
import struct import struct
import binascii import binascii
@ -154,8 +157,10 @@ class MobiBook:
# initial sanity check on file # initial sanity check on file
self.data_file = file(infile, 'rb').read() self.data_file = file(infile, 'rb').read()
self.header = self.data_file[0:78] self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI': if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
raise DrmException("invalid file format") raise DrmException("invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
# build up section offset and flag info # build up section offset and flag info
self.num_sections, = struct.unpack('>H', self.header[76:78]) self.num_sections, = struct.unpack('>H', self.header[76:78])
@ -168,6 +173,14 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_version = -1
self.meta_array = {}
return
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
@ -182,18 +195,23 @@ class MobiBook:
# if exth region exists parse it for metadata array # if exth region exists parse it for metadata array
self.meta_array = {} self.meta_array = {}
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) try:
exth = '' exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
if exth_flag & 0x40: exth = 'NONE'
exth = self.sect[16 + self.mobi_length:] if exth_flag & 0x40:
nitems, = struct.unpack('>I', exth[8:12]) exth = self.sect[16 + self.mobi_length:]
pos = 12 if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
for i in xrange(nitems): nitems, = struct.unpack('>I', exth[8:12])
type, size = struct.unpack('>II', exth[pos: pos + 8]) pos = 12
content = exth[pos + 8: pos + size] for i in xrange(nitems):
self.meta_array[type] = content type, size = struct.unpack('>II', exth[pos: pos + 8])
pos += size content = exth[pos + 8: pos + size]
self.meta_array[type] = content
pos += size
except:
self.meta_array = {}
pass
def getBookTitle(self): def getBookTitle(self):
title = '' title = ''
if 503 in self.meta_array: if 503 in self.meta_array:
@ -269,12 +287,12 @@ class MobiBook:
def processBook(self, pidlist): def processBook(self, pidlist):
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
print 'Crypto Type is: ', crypto_type
self.crypto_type = crypto_type
if crypto_type == 0: if crypto_type == 0:
print "This book is not encrypted." print "This book is not encrypted."
return self.data_file return self.data_file
if crypto_type == 1: if crypto_type != 2 and crypto_type != 1:
raise DrmException("Cannot decode Mobipocket encryption type 1")
if crypto_type != 2:
raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
goodpids = [] goodpids = []
@ -286,23 +304,32 @@ class MobiBook:
elif len(pid)==8: elif len(pid)==8:
goodpids.append(pid) goodpids.append(pid)
# calculate the keys if self.crypto_type == 1:
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) t1_keyvec = "QDCVEPMU675RUBSZ"
if drm_count == 0: if self.magic == 'TEXtREAd':
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") bookkey_data = self.sect[0x0E:0x0E+16]
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) else:
if not found_key: bookkey_data = self.sect[0x90:0x90+16]
raise DrmException("No key found. Most likely the correct PID has not been given.") pid = "00000000"
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key:
raise DrmException("No key found. Most likely the correct PID has not been given.")
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
if pid=="00000000": if pid=="00000000":
print "File has default encryption, no specific PID." print "File has default encryption, no specific PID."
else: else:
print "File is encoded with PID "+checksumPid(pid)+"." print "File is encoded with PID "+checksumPid(pid)+"."
# 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 # clear the crypto type
self.patchSection(0, "\0" * 2, 0xC) self.patchSection(0, "\0" * 2, 0xC)

View File

@ -164,9 +164,10 @@ class TopazBook:
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
keysRecord = None keysRecord = None
KeysRecordRecord = None keysRecordRecord = None
if 'keys' in self.bookMetadata: if 'keys' in self.bookMetadata:
keysRecord = self.bookMetadata['keys'] keysRecord = self.bookMetadata['keys']
if keysRecord in self.bookMetadata:
keysRecordRecord = self.bookMetadata[keysRecord] keysRecordRecord = self.bookMetadata[keysRecord]
return keysRecord, keysRecordRecord return keysRecord, keysRecordRecord
@ -395,6 +396,7 @@ def main(argv=sys.argv):
myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
zipUpDir(myzip, tempdir, '') zipUpDir(myzip, tempdir, '')
myzip.close() myzip.close()
shutil.rmtree(tempdir, True)
return 1 return 1
print " Creating HTML ZIP Archive" print " Creating HTML ZIP Archive"
@ -424,7 +426,7 @@ def main(argv=sys.argv):
zipUpDir(myzip3, tempdir, 'img') zipUpDir(myzip3, tempdir, 'img')
myzip3.close() myzip3.close()
shutil.rmtree(tempdir) shutil.rmtree(tempdir, True)
return 0 return 0

View File

@ -110,6 +110,8 @@ class MainDialog(Tkinter.Frame):
def showCmdOutput(self, msg): def showCmdOutput(self, msg):
if msg and msg !='': if msg and msg !='':
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
if sys.platform.startswith('win'):
msg = msg.replace('\r\n','\n')
self.stext.insert(Tkconstants.END,msg) self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END) self.stext.yview_pickplace(Tkconstants.END)
return return

View File

@ -28,7 +28,7 @@
from __future__ import with_statement from __future__ import with_statement
__version__ = '1.4' __version__ = '1.9'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@ -163,6 +163,7 @@ def main(argv=sys.argv):
myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
zipUpDir(myzip, tempdir, '') zipUpDir(myzip, tempdir, '')
myzip.close() myzip.close()
shutil.rmtree(tempdir, True)
return 1 return 1
if mobi: if mobi:
@ -198,7 +199,7 @@ def main(argv=sys.argv):
zipUpDir(myzip3, tempdir, 'img') zipUpDir(myzip3, tempdir, 'img')
myzip3.close() myzip3.close()
shutil.rmtree(tempdir) shutil.rmtree(tempdir, True)
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
@ -214,7 +215,7 @@ if not __name__ == "__main__" and inCalibre:
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' 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 supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 1, 7) # The version number of this plugin version = (0, 1, 9) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm

View File

@ -6,7 +6,7 @@ import os
import subprocess import subprocess
class K4MDrmException(Exception): class DrmException(Exception):
pass pass
@ -18,7 +18,7 @@ def _load_crypto_libcrypto():
libcrypto = find_library('crypto') libcrypto = find_library('crypto')
if libcrypto is None: if libcrypto is None:
raise K4MDrmException('libcrypto not found') raise DrmException('libcrypto not found')
libcrypto = CDLL(libcrypto) libcrypto = CDLL(libcrypto)
AES_MAXNR = 14 AES_MAXNR = 14
@ -51,19 +51,19 @@ def _load_crypto_libcrypto():
def set_decrypt_key(self, userkey, iv): def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey) self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise K4MDrmException('AES improper key used') raise DrmException('AES improper key used')
return return
keyctx = self._keyctx = AES_KEY() keyctx = self._keyctx = AES_KEY()
self.iv = iv self.iv = iv
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0: if rv < 0:
raise K4MDrmException('Failed to initialize AES key') raise DrmException('Failed to initialize AES key')
def decrypt(self, data): def decrypt(self, data):
out = create_string_buffer(len(data)) out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0) rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
if rv == 0: if rv == 0:
raise K4MDrmException('AES decryption failed') raise DrmException('AES decryption failed')
return out.raw return out.raw
def keyivgen(self, passwd): def keyivgen(self, passwd):
@ -81,7 +81,7 @@ def _load_crypto():
LibCrypto = None LibCrypto = None
try: try:
LibCrypto = _load_crypto_libcrypto() LibCrypto = _load_crypto_libcrypto()
except (ImportError, K4MDrmException): except (ImportError, DrmException):
pass pass
return LibCrypto return LibCrypto
@ -185,8 +185,10 @@ def openKindleInfo(kInfoFile=None):
if pp >= 0: if pp >= 0:
kinfopath = resline kinfopath = resline
break break
if not os.path.exists(kinfopath): if not os.path.isfile(kinfopath):
raise K4MDrmException('Error: .kindle-info file can not be found') raise DrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r') return open(kinfopath,'r')
else: else:
if not os.path.isfile(kinfoFile):
raise DrmException('Error: kindle-info file can not be found')
return open(kInfoFile, 'r') return open(kInfoFile, 'r')

View File

@ -99,7 +99,12 @@ CryptUnprotectData = CryptUnprotectData()
def openKindleInfo(kInfoFile=None): def openKindleInfo(kInfoFile=None):
if kInfoFile == None: if kInfoFile == None:
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r') kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath):
raise DrmException('Error: kindle.info file can not be found')
return open(kinfopath,'r')
else: else:
if not os.path.isfile(kInfoFile):
raise DrmException('Error: kindle.info file can not be found')
return open(kInfoFile, 'r') return open(kInfoFile, 'r')

View File

@ -83,7 +83,8 @@ def parseKindleInfo(kInfoFile):
DB[splito[0]] =splito[1] DB[splito[0]] =splito[1]
return DB return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record # 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): def getKindleInfoValueForHash(hashedKey):
global kindleDatabase global kindleDatabase
global charMap1 global charMap1
@ -95,12 +96,14 @@ def getKindleInfoValueForHash(hashedKey):
cleartext = CryptUnprotectData(encryptedValue) cleartext = CryptUnprotectData(encryptedValue)
return decode(cleartext, charMap1) return decode(cleartext, charMap1)
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record # Get a record from the Kindle.info file for the string in "key" (plaintext).
# Return the decoded and decrypted record
def getKindleInfoValueForKey(key): def getKindleInfoValueForKey(key):
global charMap2 global charMap2
return getKindleInfoValueForHash(encodeHash(key,charMap2)) 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. # 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): def findNameForHash(hash):
global charMap2 global charMap2
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"] names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
@ -222,7 +225,7 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. # Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum): def getKindlePid(pidlst, rec209, token, serialnum):
if rec209 != None: if rec209 != None and token != None:
# Compute book PID # Compute book PID
pidHash = SHA1(serialnum+rec209+token) pidHash = SHA1(serialnum+rec209+token)
bookPID = encodePID(pidHash) bookPID = encodePID(pidHash)
@ -248,6 +251,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
kindleDatabase = parseKindleInfo(kInfoFile) kindleDatabase = parseKindleInfo(kInfoFile)
except Exception, message: except Exception, message:
print(message) print(message)
kindleDatabase = None
pass pass
if kindleDatabase == None : if kindleDatabase == None :
@ -272,8 +276,8 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
pidlst.append(devicePID) pidlst.append(devicePID)
# Compute book PID # Compute book PID
if rec209 == None: if rec209 == None or token == None:
print "\nNo EXTH record type 209 - Perhaps not a K4 file?" print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?"
return pidlst return pidlst
# Get the kindle account token # Get the kindle account token

View File

@ -42,8 +42,10 @@
# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file. # 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
# 0.21 - Added support for multiple pids # 0.21 - Added support for multiple pids
# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface # 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
# 0.23 - fixed problem with older files with no EXTH section
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
__version__ = '0.22' __version__ = '0.24'
import sys import sys
@ -57,6 +59,7 @@ class Unbuffered:
return getattr(self.stream, attr) return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout) sys.stdout=Unbuffered(sys.stdout)
import os
import struct import struct
import binascii import binascii
@ -154,8 +157,10 @@ class MobiBook:
# initial sanity check on file # initial sanity check on file
self.data_file = file(infile, 'rb').read() self.data_file = file(infile, 'rb').read()
self.header = self.data_file[0:78] self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI': if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
raise DrmException("invalid file format") raise DrmException("invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
# build up section offset and flag info # build up section offset and flag info
self.num_sections, = struct.unpack('>H', self.header[76:78]) self.num_sections, = struct.unpack('>H', self.header[76:78])
@ -168,6 +173,14 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_version = -1
self.meta_array = {}
return
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
@ -182,18 +195,23 @@ class MobiBook:
# if exth region exists parse it for metadata array # if exth region exists parse it for metadata array
self.meta_array = {} self.meta_array = {}
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) try:
exth = '' exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
if exth_flag & 0x40: exth = 'NONE'
exth = self.sect[16 + self.mobi_length:] if exth_flag & 0x40:
nitems, = struct.unpack('>I', exth[8:12]) exth = self.sect[16 + self.mobi_length:]
pos = 12 if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
for i in xrange(nitems): nitems, = struct.unpack('>I', exth[8:12])
type, size = struct.unpack('>II', exth[pos: pos + 8]) pos = 12
content = exth[pos + 8: pos + size] for i in xrange(nitems):
self.meta_array[type] = content type, size = struct.unpack('>II', exth[pos: pos + 8])
pos += size content = exth[pos + 8: pos + size]
self.meta_array[type] = content
pos += size
except:
self.meta_array = {}
pass
def getBookTitle(self): def getBookTitle(self):
title = '' title = ''
if 503 in self.meta_array: if 503 in self.meta_array:
@ -269,12 +287,12 @@ class MobiBook:
def processBook(self, pidlist): def processBook(self, pidlist):
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
print 'Crypto Type is: ', crypto_type
self.crypto_type = crypto_type
if crypto_type == 0: if crypto_type == 0:
print "This book is not encrypted." print "This book is not encrypted."
return self.data_file return self.data_file
if crypto_type == 1: if crypto_type != 2 and crypto_type != 1:
raise DrmException("Cannot decode Mobipocket encryption type 1")
if crypto_type != 2:
raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
goodpids = [] goodpids = []
@ -286,23 +304,32 @@ class MobiBook:
elif len(pid)==8: elif len(pid)==8:
goodpids.append(pid) goodpids.append(pid)
# calculate the keys if self.crypto_type == 1:
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) t1_keyvec = "QDCVEPMU675RUBSZ"
if drm_count == 0: if self.magic == 'TEXtREAd':
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") bookkey_data = self.sect[0x0E:0x0E+16]
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) else:
if not found_key: bookkey_data = self.sect[0x90:0x90+16]
raise DrmException("No key found. Most likely the correct PID has not been given.") pid = "00000000"
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key:
raise DrmException("No key found. Most likely the correct PID has not been given.")
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
if pid=="00000000": if pid=="00000000":
print "File has default encryption, no specific PID." print "File has default encryption, no specific PID."
else: else:
print "File is encoded with PID "+checksumPid(pid)+"." print "File is encoded with PID "+checksumPid(pid)+"."
# 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 # clear the crypto type
self.patchSection(0, "\0" * 2, 0xC) self.patchSection(0, "\0" * 2, 0xC)

View File

@ -164,9 +164,10 @@ class TopazBook:
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
keysRecord = None keysRecord = None
KeysRecordRecord = None keysRecordRecord = None
if 'keys' in self.bookMetadata: if 'keys' in self.bookMetadata:
keysRecord = self.bookMetadata['keys'] keysRecord = self.bookMetadata['keys']
if keysRecord in self.bookMetadata:
keysRecordRecord = self.bookMetadata[keysRecord] keysRecordRecord = self.bookMetadata[keysRecord]
return keysRecord, keysRecordRecord return keysRecord, keysRecordRecord
@ -395,6 +396,7 @@ def main(argv=sys.argv):
myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False) myzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
zipUpDir(myzip, tempdir, '') zipUpDir(myzip, tempdir, '')
myzip.close() myzip.close()
shutil.rmtree(tempdir, True)
return 1 return 1
print " Creating HTML ZIP Archive" print " Creating HTML ZIP Archive"
@ -424,7 +426,7 @@ def main(argv=sys.argv):
zipUpDir(myzip3, tempdir, 'img') zipUpDir(myzip3, tempdir, 'img')
myzip3.close() myzip3.close()
shutil.rmtree(tempdir) shutil.rmtree(tempdir, True)
return 0 return 0

View File

@ -24,7 +24,7 @@
# 0.14 - Working out when the extra data flags are present has been problematic # 0.14 - Working out when the extra data flags are present has been problematic
# Versions 7 through 9 have tried to tweak the conditions, but have been # Versions 7 through 9 have tried to tweak the conditions, but have been
# only partially successful. Closer examination of lots of sample # only partially successful. Closer examination of lots of sample
# files reveals that a confusin has arisen because trailing data entries # files reveals that a confusion has arisen because trailing data entries
# are not encrypted, but it turns out that the multibyte entries # are not encrypted, but it turns out that the multibyte entries
# 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
@ -39,13 +39,15 @@
# Removed the disabled Calibre plug-in code # Removed the disabled Calibre plug-in code
# Permit use of 8-digit PIDs # Permit use of 8-digit PIDs
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. # 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. # 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
# 0.21 - Added support for multiple pids
# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
# 0.23 - fixed problem with older files with no EXTH section
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
__version__ = '0.20' __version__ = '0.24'
import sys import sys
import struct
import binascii
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@ -55,10 +57,20 @@ class Unbuffered:
self.stream.flush() self.stream.flush()
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout)
import os
import struct
import binascii
class DrmException(Exception): class DrmException(Exception):
pass pass
#
# MobiBook Utility Routines
#
# Implementation of Pukall Cipher 1 # Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True): def PC1(key, src, decryption=True):
sum1 = 0; sum1 = 0;
@ -70,7 +82,6 @@ def PC1(key, src, decryption=True):
wkey = [] wkey = []
for i in xrange(8): for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = "" dst = ""
for i in xrange(len(src)): for i in xrange(len(src)):
temp1 = 0; temp1 = 0;
@ -131,7 +142,9 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
num += (ord(ptr[size - num - 1]) & 0x3) + 1 num += (ord(ptr[size - num - 1]) & 0x3) + 1
return num return num
class DrmStripper:
class MobiBook:
def loadSection(self, section): def loadSection(self, section):
if (section + 1 == self.num_sections): if (section + 1 == self.num_sections):
endoff = len(self.data_file) endoff = len(self.data_file)
@ -140,6 +153,93 @@ class DrmStripper:
off = self.sections[section][0] off = self.sections[section][0]
return self.data_file[off:endoff] return self.data_file[off:endoff]
def __init__(self, infile):
# initial sanity check on file
self.data_file = file(infile, 'rb').read()
self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
raise DrmException("invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
# build up section offset and flag info
self.num_sections, = struct.unpack('>H', self.header[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
flags, val = a1, a2<<16|a3<<8|a4
self.sections.append( (offset, flags, val) )
# parse information from section 0
self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_version = -1
self.meta_array = {}
return
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
self.extra_data_flags = 0
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags
if self.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.
self.extra_data_flags &= 0xFFFE
# if exth region exists parse it for metadata array
self.meta_array = {}
try:
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
exth = 'NONE'
if exth_flag & 0x40:
exth = self.sect[16 + self.mobi_length:]
if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
nitems, = struct.unpack('>I', exth[8:12])
pos = 12
for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
self.meta_array[type] = content
pos += size
except:
self.meta_array = {}
pass
def getBookTitle(self):
title = ''
if 503 in self.meta_array:
title = self.meta_array[503]
else :
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
if title == '':
title = self.header[:32]
title = title.split("\0")[0]
return title
def getPIDMetaInfo(self):
rec209 = None
token = None
if 209 in self.meta_array:
rec209 = self.meta_array[209]
data = rec209
# 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 self.meta_array[ord(data[i])] != None:
token = self.meta_array[ord(data[i])]
return rec209, token
def patch(self, off, new): def patch(self, off, new):
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
@ -152,134 +252,131 @@ class DrmStripper:
assert off + in_off + len(new) <= endoff assert off + in_off + len(new) <= endoff
self.patch(off + in_off, new) self.patch(off + in_off, new)
def parseDRM(self, data, count, pid): def parseDRM(self, data, count, pidlist):
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 found_key = None
for i in xrange(count): keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) for pid in pidlist:
cookie = PC1(temp_key, cookie) bigpid = pid.ljust(16,'\0')
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) temp_key = PC1(keyvec1, bigpid, False)
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1: temp_key_sum = sum(map(ord,temp_key)) & 0xff
found_key = finalkey found_key = None
for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
if cksum == temp_key_sum:
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
if verification == ver and (flags & 0x1F) == 1:
found_key = finalkey
break
if found_key != None:
break break
if not found_key: if not found_key:
# Then try the default encoding that doesn't require a PID # Then try the default encoding that doesn't require a PID
pid = "00000000"
temp_key = keyvec1 temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count): for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie) if cksum == temp_key_sum:
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) cookie = PC1(temp_key, cookie)
if verification == ver and cksum == temp_key_sum: ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
found_key = finalkey if verification == ver:
break found_key = finalkey
return found_key break
return [found_key,pid]
def __init__(self, data_file, pid): def processBook(self, pidlist):
if len(pid)==10: crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
if checksumPid(pid[0:-2]) != pid: print 'Crypto Type is: ', crypto_type
raise DrmException("invalid PID checksum") self.crypto_type = crypto_type
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
header = data_file[0:72]
if header[0x3C:0x3C+8] != 'BOOKMOBI':
raise DrmException("invalid file format")
self.num_sections, = struct.unpack('>H', data_file[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = struct.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, = struct.unpack('>H', sect[0x8:0x8+2])
mobi_length, = struct.unpack('>L',sect[0x14:0x18])
mobi_version, = struct.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, = struct.unpack('>H', sect[0xF2:0xF4])
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])
if crypto_type == 0: if crypto_type == 0:
print "This book is not encrypted." print "This book is not encrypted."
else: return self.data_file
if crypto_type == 1: if crypto_type != 2 and crypto_type != 1:
raise DrmException("cannot decode Mobipocket encryption type 1") raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
if crypto_type != 2:
raise DrmException("unknown encryption type: %d" % crypto_type)
goodpids = []
for pid in pidlist:
if len(pid)==10:
if checksumPid(pid[0:-2]) != pid:
print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2])
goodpids.append(pid[0:-2])
elif len(pid)==8:
goodpids.append(pid)
if self.crypto_type == 1:
t1_keyvec = "QDCVEPMU675RUBSZ"
if self.magic == 'TEXtREAd':
bookkey_data = self.sect[0x0E:0x0E+16]
else:
bookkey_data = self.sect[0x90:0x90+16]
pid = "00000000"
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys # calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16]) drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0: if drm_count == 0:
raise DrmException("no PIDs found in this file") raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid) found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key: if not found_key:
raise DrmException("no key found. maybe the PID is incorrect") raise DrmException("No key found. Most likely the correct PID has not been given.")
# kill the drm keys # kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr) self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers # kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC) if pid=="00000000":
print "File has default encryption, no specific PID."
else:
print "File is encoded with PID "+checksumPid(pid)+"."
# decrypt sections # clear the crypto type
print "Decrypting. Please wait . . .", self.patchSection(0, "\0" * 2, 0xC)
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"
def getResult(self): # decrypt sections
print "Decrypting. Please wait . . .",
new_data = self.data_file[:self.sections[1][0]]
for i in xrange(1, self.records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.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:]
if self.num_sections > self.records+1:
new_data += self.data_file[self.sections[self.records+1][0]:]
self.data_file = new_data
print "done"
return self.data_file return self.data_file
def getUnencryptedBook(infile,pid): def getUnencryptedBook(infile,pid):
sys.stdout=Unbuffered(sys.stdout) if not os.path.isfile(infile):
data_file = file(infile, 'rb').read() raise DrmException('Input File Not Found')
strippedFile = DrmStripper(data_file, pid) book = MobiBook(infile)
return strippedFile.getResult() return book.processBook([pid])
def getUnencryptedBookWithList(infile,pidlist):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
return book.processBook(pidlist)
def main(argv=sys.argv): def main(argv=sys.argv):
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(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> <Comma separated list of PIDs to try>" % sys.argv[0]
return 1 return 1
else: else:
infile = argv[1] infile = argv[1]
outfile = argv[2] outfile = argv[2]
pid = argv[3] pidlist = argv[3].split(',')
try: try:
stripped_file = getUnencryptedBook(infile, pid) stripped_file = getUnencryptedBookWithList(infile, pidlist)
file(outfile, 'wb').write(stripped_file) file(outfile, 'wb').write(stripped_file)
except DrmException, e: except DrmException, e:
print "Error: %s" % e print "Error: %s" % e

View File

@ -24,7 +24,7 @@
# 0.14 - Working out when the extra data flags are present has been problematic # 0.14 - Working out when the extra data flags are present has been problematic
# Versions 7 through 9 have tried to tweak the conditions, but have been # Versions 7 through 9 have tried to tweak the conditions, but have been
# only partially successful. Closer examination of lots of sample # only partially successful. Closer examination of lots of sample
# files reveals that a confusin has arisen because trailing data entries # files reveals that a confusion has arisen because trailing data entries
# are not encrypted, but it turns out that the multibyte entries # are not encrypted, but it turns out that the multibyte entries
# 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
@ -39,13 +39,15 @@
# Removed the disabled Calibre plug-in code # Removed the disabled Calibre plug-in code
# Permit use of 8-digit PIDs # Permit use of 8-digit PIDs
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. # 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. # 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
# 0.21 - Added support for multiple pids
# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
# 0.23 - fixed problem with older files with no EXTH section
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
__version__ = '0.20' __version__ = '0.24'
import sys import sys
import struct
import binascii
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@ -55,10 +57,20 @@ class Unbuffered:
self.stream.flush() self.stream.flush()
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout)
import os
import struct
import binascii
class DrmException(Exception): class DrmException(Exception):
pass pass
#
# MobiBook Utility Routines
#
# Implementation of Pukall Cipher 1 # Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True): def PC1(key, src, decryption=True):
sum1 = 0; sum1 = 0;
@ -70,7 +82,6 @@ def PC1(key, src, decryption=True):
wkey = [] wkey = []
for i in xrange(8): for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = "" dst = ""
for i in xrange(len(src)): for i in xrange(len(src)):
temp1 = 0; temp1 = 0;
@ -131,7 +142,9 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
num += (ord(ptr[size - num - 1]) & 0x3) + 1 num += (ord(ptr[size - num - 1]) & 0x3) + 1
return num return num
class DrmStripper:
class MobiBook:
def loadSection(self, section): def loadSection(self, section):
if (section + 1 == self.num_sections): if (section + 1 == self.num_sections):
endoff = len(self.data_file) endoff = len(self.data_file)
@ -140,6 +153,93 @@ class DrmStripper:
off = self.sections[section][0] off = self.sections[section][0]
return self.data_file[off:endoff] return self.data_file[off:endoff]
def __init__(self, infile):
# initial sanity check on file
self.data_file = file(infile, 'rb').read()
self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
raise DrmException("invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
# build up section offset and flag info
self.num_sections, = struct.unpack('>H', self.header[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
flags, val = a1, a2<<16|a3<<8|a4
self.sections.append( (offset, flags, val) )
# parse information from section 0
self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_version = -1
self.meta_array = {}
return
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
self.extra_data_flags = 0
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags
if self.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.
self.extra_data_flags &= 0xFFFE
# if exth region exists parse it for metadata array
self.meta_array = {}
try:
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
exth = 'NONE'
if exth_flag & 0x40:
exth = self.sect[16 + self.mobi_length:]
if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
nitems, = struct.unpack('>I', exth[8:12])
pos = 12
for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
self.meta_array[type] = content
pos += size
except:
self.meta_array = {}
pass
def getBookTitle(self):
title = ''
if 503 in self.meta_array:
title = self.meta_array[503]
else :
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
if title == '':
title = self.header[:32]
title = title.split("\0")[0]
return title
def getPIDMetaInfo(self):
rec209 = None
token = None
if 209 in self.meta_array:
rec209 = self.meta_array[209]
data = rec209
# 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 self.meta_array[ord(data[i])] != None:
token = self.meta_array[ord(data[i])]
return rec209, token
def patch(self, off, new): def patch(self, off, new):
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
@ -152,134 +252,131 @@ class DrmStripper:
assert off + in_off + len(new) <= endoff assert off + in_off + len(new) <= endoff
self.patch(off + in_off, new) self.patch(off + in_off, new)
def parseDRM(self, data, count, pid): def parseDRM(self, data, count, pidlist):
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 found_key = None
for i in xrange(count): keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) for pid in pidlist:
cookie = PC1(temp_key, cookie) bigpid = pid.ljust(16,'\0')
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) temp_key = PC1(keyvec1, bigpid, False)
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1: temp_key_sum = sum(map(ord,temp_key)) & 0xff
found_key = finalkey found_key = None
for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
if cksum == temp_key_sum:
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
if verification == ver and (flags & 0x1F) == 1:
found_key = finalkey
break
if found_key != None:
break break
if not found_key: if not found_key:
# Then try the default encoding that doesn't require a PID # Then try the default encoding that doesn't require a PID
pid = "00000000"
temp_key = keyvec1 temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count): for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie) if cksum == temp_key_sum:
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) cookie = PC1(temp_key, cookie)
if verification == ver and cksum == temp_key_sum: ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
found_key = finalkey if verification == ver:
break found_key = finalkey
return found_key break
return [found_key,pid]
def __init__(self, data_file, pid): def processBook(self, pidlist):
if len(pid)==10: crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
if checksumPid(pid[0:-2]) != pid: print 'Crypto Type is: ', crypto_type
raise DrmException("invalid PID checksum") self.crypto_type = crypto_type
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
header = data_file[0:72]
if header[0x3C:0x3C+8] != 'BOOKMOBI':
raise DrmException("invalid file format")
self.num_sections, = struct.unpack('>H', data_file[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = struct.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, = struct.unpack('>H', sect[0x8:0x8+2])
mobi_length, = struct.unpack('>L',sect[0x14:0x18])
mobi_version, = struct.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, = struct.unpack('>H', sect[0xF2:0xF4])
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])
if crypto_type == 0: if crypto_type == 0:
print "This book is not encrypted." print "This book is not encrypted."
else: return self.data_file
if crypto_type == 1: if crypto_type != 2 and crypto_type != 1:
raise DrmException("cannot decode Mobipocket encryption type 1") raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
if crypto_type != 2:
raise DrmException("unknown encryption type: %d" % crypto_type)
goodpids = []
for pid in pidlist:
if len(pid)==10:
if checksumPid(pid[0:-2]) != pid:
print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2])
goodpids.append(pid[0:-2])
elif len(pid)==8:
goodpids.append(pid)
if self.crypto_type == 1:
t1_keyvec = "QDCVEPMU675RUBSZ"
if self.magic == 'TEXtREAd':
bookkey_data = self.sect[0x0E:0x0E+16]
else:
bookkey_data = self.sect[0x90:0x90+16]
pid = "00000000"
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys # calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16]) drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0: if drm_count == 0:
raise DrmException("no PIDs found in this file") raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid) found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key: if not found_key:
raise DrmException("no key found. maybe the PID is incorrect") raise DrmException("No key found. Most likely the correct PID has not been given.")
# kill the drm keys # kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr) self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers # kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC) if pid=="00000000":
print "File has default encryption, no specific PID."
else:
print "File is encoded with PID "+checksumPid(pid)+"."
# decrypt sections # clear the crypto type
print "Decrypting. Please wait . . .", self.patchSection(0, "\0" * 2, 0xC)
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"
def getResult(self): # decrypt sections
print "Decrypting. Please wait . . .",
new_data = self.data_file[:self.sections[1][0]]
for i in xrange(1, self.records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.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:]
if self.num_sections > self.records+1:
new_data += self.data_file[self.sections[self.records+1][0]:]
self.data_file = new_data
print "done"
return self.data_file return self.data_file
def getUnencryptedBook(infile,pid): def getUnencryptedBook(infile,pid):
sys.stdout=Unbuffered(sys.stdout) if not os.path.isfile(infile):
data_file = file(infile, 'rb').read() raise DrmException('Input File Not Found')
strippedFile = DrmStripper(data_file, pid) book = MobiBook(infile)
return strippedFile.getResult() return book.processBook([pid])
def getUnencryptedBookWithList(infile,pidlist):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
return book.processBook(pidlist)
def main(argv=sys.argv): def main(argv=sys.argv):
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(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> <Comma separated list of PIDs to try>" % sys.argv[0]
return 1 return 1
else: else:
infile = argv[1] infile = argv[1]
outfile = argv[2] outfile = argv[2]
pid = argv[3] pidlist = argv[3].split(',')
try: try:
stripped_file = getUnencryptedBook(infile, pid) stripped_file = getUnencryptedBookWithList(infile, pidlist)
file(outfile, 'wb').write(stripped_file) file(outfile, 'wb').write(stripped_file)
except DrmException, e: except DrmException, e:
print "Error: %s" % e print "Error: %s" % e

View File

@ -42,9 +42,10 @@
# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file. # 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
# 0.21 - Added support for multiple pids # 0.21 - Added support for multiple pids
# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface # 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
# 0.23 - fixed problem with older files with no EXTH section # 0.23 - fixed problem with older files with no EXTH section
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
__version__ = '0.23' __version__ = '0.24'
import sys import sys
@ -58,6 +59,7 @@ class Unbuffered:
return getattr(self.stream, attr) return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout) sys.stdout=Unbuffered(sys.stdout)
import os
import struct import struct
import binascii import binascii
@ -155,8 +157,10 @@ class MobiBook:
# initial sanity check on file # initial sanity check on file
self.data_file = file(infile, 'rb').read() self.data_file = file(infile, 'rb').read()
self.header = self.data_file[0:78] self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI': if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
raise DrmException("invalid file format") raise DrmException("invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
# build up section offset and flag info # build up section offset and flag info
self.num_sections, = struct.unpack('>H', self.header[76:78]) self.num_sections, = struct.unpack('>H', self.header[76:78])
@ -169,6 +173,14 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_version = -1
self.meta_array = {}
return
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
@ -199,7 +211,7 @@ class MobiBook:
except: except:
self.meta_array = {} self.meta_array = {}
pass pass
def getBookTitle(self): def getBookTitle(self):
title = '' title = ''
if 503 in self.meta_array: if 503 in self.meta_array:
@ -275,12 +287,12 @@ class MobiBook:
def processBook(self, pidlist): def processBook(self, pidlist):
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
print 'Crypto Type is: ', crypto_type
self.crypto_type = crypto_type
if crypto_type == 0: if crypto_type == 0:
print "This book is not encrypted." print "This book is not encrypted."
return self.data_file return self.data_file
if crypto_type == 1: if crypto_type != 2 and crypto_type != 1:
raise DrmException("Cannot decode Mobipocket encryption type 1")
if crypto_type != 2:
raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
goodpids = [] goodpids = []
@ -292,23 +304,32 @@ class MobiBook:
elif len(pid)==8: elif len(pid)==8:
goodpids.append(pid) goodpids.append(pid)
# calculate the keys if self.crypto_type == 1:
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) t1_keyvec = "QDCVEPMU675RUBSZ"
if drm_count == 0: if self.magic == 'TEXtREAd':
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") bookkey_data = self.sect[0x0E:0x0E+16]
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) else:
if not found_key: bookkey_data = self.sect[0x90:0x90+16]
raise DrmException("No key found. Most likely the correct PID has not been given.") pid = "00000000"
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key:
raise DrmException("No key found. Most likely the correct PID has not been given.")
# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
if pid=="00000000": if pid=="00000000":
print "File has default encryption, no specific PID." print "File has default encryption, no specific PID."
else: else:
print "File is encoded with PID "+checksumPid(pid)+"." print "File is encoded with PID "+checksumPid(pid)+"."
# 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 # clear the crypto type
self.patchSection(0, "\0" * 2, 0xC) self.patchSection(0, "\0" * 2, 0xC)

View File

@ -0,0 +1,216 @@
#!/usr/bin/env python
# This is a simple tool to identify all Amazon Topaz ebooks in a specific directory.
# There always seems to be confusion since Topaz books downloaded to K4PC/Mac can have
# almost any extension (.azw, .azw1, .prc, tpz). While the .azw1 and .tpz extensions
# are fairly easy to indentify, the others are not (without opening the files in an editor).
# To run the tool with the GUI frontend, just double-click on the 'FindTopazFiles.pyw' file
# and select the folder where all of the ebooks in question are located. Then click 'Search'.
# The program will list the file names of the ebooks that are indentified as being Topaz.
# You can then isolate those books and use the Topaz tools to decrypt and convert them.
# You can also run the script from a command line... supplying the folder to search
# as a parameter: python FindTopazEbooks.pyw "C:\My Folder" (change appropriately for
# your particular O.S.)
# ** NOTE: This program does NOT decrypt or modify Topaz files in any way. It simply identifies them.
# 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
# Revision history:
# 1 - Initial release.
from __future__ import with_statement
__license__ = 'GPL v3'
import sys
import os
import re
import shutil
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
class ScrolledText(Tkinter.Text):
def __init__(self, master=None, **kw):
self.frame = Tkinter.Frame(master)
self.vbar = Tkinter.Scrollbar(self.frame)
self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y)
kw.update({'yscrollcommand': self.vbar.set})
Tkinter.Text.__init__(self, self.frame, **kw)
self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True)
self.vbar['command'] = self.yview
# Copy geometry methods of self.frame without overriding Text
# methods = hack!
text_meths = vars(Tkinter.Text).keys()
methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys()
methods = set(methods).difference(text_meths)
for m in methods:
if m[0] != '_' and m != 'config' and m != 'configure':
setattr(self, m, getattr(self.frame, m))
def __str__(self):
return str(self.frame)
def cli_main(argv=sys.argv, obj=None):
progname = os.path.basename(argv[0])
if len(argv) != 2:
print "usage: %s DIRECTORY" % (progname,)
return 1
if obj == None:
print "\nTopaz search results:\n"
else:
obj.stext.insert(Tkconstants.END,"Topaz search results:\n\n")
inpath = argv[1]
files = os.listdir(inpath)
filefilter = re.compile("(\.azw$)|(\.azw1$)|(\.prc$)|(\.tpz$)", re.IGNORECASE)
files = filter(filefilter.search, files)
if files:
topazcount = 0
totalcount = 0
for filename in files:
with open(os.path.join(inpath, filename), 'rb') as f:
try:
if f.read().startswith('TPZ'):
f.close()
basename, extension = os.path.splitext(filename)
if obj == None:
print " %s is a Topaz formatted ebook." % filename
"""
if extension == '.azw' or extension == '.prc':
print " renaming to %s" % (basename + '.tpz')
shutil.move(os.path.join(inpath, filename),
os.path.join(inpath, basename + '.tpz'))
"""
else:
msg1 = " %s is a Topaz formatted ebook.\n" % filename
obj.stext.insert(Tkconstants.END,msg1)
"""
if extension == '.azw' or extension == '.prc':
msg2 = " renaming to %s\n" % (basename + '.tpz')
obj.stext.insert(Tkconstants.END,msg2)
shutil.move(os.path.join(inpath, filename),
os.path.join(inpath, basename + '.tpz'))
"""
topazcount += 1
except:
if obj == None:
print " Error reading %s." % filename
else:
msg = " Error reading or %s.\n" % filename
obj.stext.insert(Tkconstants.END,msg)
pass
totalcount += 1
if topazcount == 0:
if obj == None:
print "\nNo Topaz books found in %s." % inpath
else:
msg = "\nNo Topaz books found in %s.\n\n" % inpath
obj.stext.insert(Tkconstants.END,msg)
else:
if obj == None:
print "\n%i Topaz books found in %s\n%i total books checked.\n" % (topazcount, inpath, totalcount)
else:
msg = "\n%i Topaz books found in %s\n%i total books checked.\n\n" %(topazcount, inpath, totalcount)
obj.stext.insert(Tkconstants.END,msg)
else:
if obj == None:
print "No typical Topaz file extensions found in %s.\n" % inpath
else:
msg = "No typical Topaz file extensions found in %s.\n\n" % inpath
obj.stext.insert(Tkconstants.END,msg)
return 0
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
ltext='Search a directory for Topaz eBooks\n'
self.status = Tkinter.Label(self, text=ltext)
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='Directory to Search').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)
msg1 = 'Topaz search results \n\n'
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE,
height=15, width=60, wrap=Tkconstants.WORD)
self.stext.grid(row=4, column=0, columnspan=2,sticky=sticky)
#self.stext.insert(Tkconstants.END,msg1)
buttons = Tkinter.Frame(self)
buttons.pack()
self.botton = Tkinter.Button(
buttons, text="Search", width=10, command=self.search)
self.botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
self.button = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quit)
self.button.pack(side=Tkconstants.RIGHT)
def get_inpath(self):
cwd = os.getcwdu()
cwd = cwd.encode('utf-8')
inpath = tkFileDialog.askdirectory(
parent=None, title='Directory to search',
initialdir=cwd, initialfile=None)
if inpath:
inpath = os.path.normpath(inpath)
self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath)
return
def search(self):
inpath = self.inpath.get()
if not inpath or not os.path.exists(inpath):
self.status['text'] = 'Specified directory does not exist'
return
argv = [sys.argv[0], inpath]
self.status['text'] = 'Searching...'
self.botton.configure(state='disabled')
cli_main(argv, self)
self.status['text'] = 'Search a directory for Topaz files'
self.botton.configure(state='normal')
return
def gui_main():
root = Tkinter.Tk()
root.title('Topaz eBook Finder')
root.resizable(True, False)
root.minsize(370, 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())

View File

@ -74,6 +74,8 @@ class MainDialog(Tkinter.Frame):
def showCmdOutput(self, msg): def showCmdOutput(self, msg):
if msg and msg !='': if msg and msg !='':
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
if sys.platform.startswith('win'):
msg = msg.replace('\r\n','\n')
self.stext.insert(Tkconstants.END,msg) self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END) self.stext.yview_pickplace(Tkconstants.END)
return return

View File

@ -83,6 +83,8 @@ class MainDialog(Tkinter.Frame):
def showCmdOutput(self, msg): def showCmdOutput(self, msg):
if msg and msg !='': if msg and msg !='':
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
if sys.platform.startswith('win'):
msg = msg.replace('\r\n','\n')
self.stext.insert(Tkconstants.END,msg) self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END) self.stext.yview_pickplace(Tkconstants.END)
return return

View File

@ -0,0 +1,199 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
import sys
sys.path.append('lib')
import os, os.path, urllib
import subprocess
from subprocess import Popen, PIPE, STDOUT
import subasyncio
from subasyncio import Process
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
from scrolltextwidget import ScrolledText
class MainDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.root = root
self.interval = 2000
self.p2 = None
self.status = Tkinter.Label(self, text='Remove Encryption from a Mobi eBook')
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='Mobi eBook input file').grid(row=0, sticky=Tkconstants.E)
self.mobipath = Tkinter.Entry(body, width=50)
self.mobipath.grid(row=0, column=1, sticky=sticky)
cwd = os.getcwdu()
cwd = cwd.encode('utf-8')
self.mobipath.insert(0, cwd)
button = Tkinter.Button(body, text="...", command=self.get_mobipath)
button.grid(row=0, column=2)
Tkinter.Label(body, text='Name for Unencrypted Output File').grid(row=1, sticky=Tkconstants.E)
self.outpath = Tkinter.Entry(body, width=50)
self.outpath.grid(row=1, column=1, sticky=sticky)
self.outpath.insert(0, '')
button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text='10 Character PID').grid(row=2, sticky=Tkconstants.E)
self.pidnum = Tkinter.StringVar()
self.pidinfo = Tkinter.Entry(body, width=12, textvariable=self.pidnum)
self.pidinfo.grid(row=2, column=1, sticky=sticky)
msg1 = 'Conversion Log \n\n'
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=15, width=60, wrap=Tkconstants.WORD)
self.stext.grid(row=3, column=0, columnspan=2,sticky=sticky)
self.stext.insert(Tkconstants.END,msg1)
buttons = Tkinter.Frame(self)
buttons.pack()
self.sbotton = Tkinter.Button(
buttons, text="Start", width=10, command=self.convertit)
self.sbotton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
self.qbutton = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quitting)
self.qbutton.pack(side=Tkconstants.RIGHT)
# read from subprocess pipe without blocking
# invoked every interval via the widget "after"
# option being used, so need to reset it for the next time
def processPipe(self):
poll = self.p2.wait('nowait')
if poll != None:
text = self.p2.readerr()
text += self.p2.read()
msg = text + '\n\n' + 'Encryption successfully removed\n'
if poll != 0:
msg = text + '\n\n' + 'Error: Encryption Removal Failed\n'
self.showCmdOutput(msg)
self.p2 = None
self.sbotton.configure(state='normal')
return
text = self.p2.readerr()
text += self.p2.read()
self.showCmdOutput(text)
# make sure we get invoked again by event loop after interval
self.stext.after(self.interval,self.processPipe)
return
# post output from subprocess in scrolled text widget
def showCmdOutput(self, msg):
if msg and msg !='':
msg = msg.encode('utf-8')
if sys.platform.startswith('win'):
msg = msg.replace('\r\n','\n')
self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END)
return
# run as a subprocess via pipes and collect stdout
def mobirdr(self, infile, outfile, pidnum):
# os.putenv('PYTHONUNBUFFERED', '1')
cmdline = 'python ./lib/mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnum + '"'
if sys.platform[0:3] == 'win':
search_path = os.environ['PATH']
search_path = search_path.lower()
if search_path.find('python') >= 0:
cmdline = 'python lib\mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnum + '"'
else :
cmdline = 'lib\mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnum + '"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
return p2
def get_mobipath(self):
mobipath = tkFileDialog.askopenfilename(
parent=None, title='Select Mobi eBook File',
defaultextension='.prc', filetypes=[('Mobi eBook File', '.prc'), ('Mobi eBook File', '.azw'),('Mobi eBook File', '.mobi'),
('All Files', '.*')])
if mobipath:
mobipath = os.path.normpath(mobipath)
self.mobipath.delete(0, Tkconstants.END)
self.mobipath.insert(0, mobipath)
return
def get_outpath(self):
mobipath = self.mobipath.get()
initname = os.path.basename(mobipath)
p = initname.find('.')
if p >= 0: initname = initname[0:p]
initname += '_nodrm.mobi'
outpath = tkFileDialog.asksaveasfilename(
parent=None, title='Select Unencrypted Mobi File to produce',
defaultextension='.mobi', initialfile=initname,
filetypes=[('Mobi files', '.mobi'), ('All files', '.*')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def quitting(self):
# kill any still running subprocess
if self.p2 != None:
if (self.p2.wait('nowait') == None):
self.p2.terminate()
self.root.destroy()
# actually ready to run the subprocess and get its output
def convertit(self):
# now disable the button to prevent multiple launches
self.sbotton.configure(state='disabled')
mobipath = self.mobipath.get()
outpath = self.outpath.get()
pidnum = self.pidinfo.get()
if not mobipath or not os.path.exists(mobipath):
self.status['text'] = 'Specified Mobi eBook file does not exist'
self.sbotton.configure(state='normal')
return
if not outpath:
self.status['text'] = 'No output file specified'
self.sbotton.configure(state='normal')
return
if not pidnum or pidnum == '':
self.status['text'] = 'No PID specified'
self.sbotton.configure(state='normal')
return
log = 'Command = "python mobidedrm.py"\n'
log += 'Mobi Path = "'+ mobipath + '"\n'
log += 'Output File = "' + outpath + '"\n'
log += 'PID = "' + pidnum + '"\n'
log += '\n\n'
log += 'Please Wait ...\n\n'
log = log.encode('utf-8')
self.stext.insert(Tkconstants.END,log)
self.p2 = self.mobirdr(mobipath, outpath, pidnum)
# python does not seem to allow you to create
# your own eventloop which every other gui does - strange
# so need to use the widget "after" command to force
# event loop to run non-gui events every interval
self.stext.after(self.interval,self.processPipe)
return
def main(argv=None):
root = Tkinter.Tk()
root.title('Mobi eBook Encryption Removal')
root.resizable(True, False)
root.minsize(300, 0)
MainDialog(root).pack(fill=Tkconstants.X, expand=1)
root.mainloop()
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -68,7 +68,7 @@ def main(argv=sys.argv):
print "Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>" print "Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>"
return 1 return 1
if len(serial)==16: if len(serial)==16:
if serial.startswith("B00"): if serial.startswith("B"):
print "Kindle serial number detected" print "Kindle serial number detected"
else: else:
print "Warning: unrecognized serial number. Please recheck input." print "Warning: unrecognized serial number. Please recheck input."

View File

@ -24,7 +24,7 @@
# 0.14 - Working out when the extra data flags are present has been problematic # 0.14 - Working out when the extra data flags are present has been problematic
# Versions 7 through 9 have tried to tweak the conditions, but have been # Versions 7 through 9 have tried to tweak the conditions, but have been
# only partially successful. Closer examination of lots of sample # only partially successful. Closer examination of lots of sample
# files reveals that a confusin has arisen because trailing data entries # files reveals that a confusion has arisen because trailing data entries
# are not encrypted, but it turns out that the multibyte entries # are not encrypted, but it turns out that the multibyte entries
# 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
@ -39,13 +39,15 @@
# Removed the disabled Calibre plug-in code # Removed the disabled Calibre plug-in code
# Permit use of 8-digit PIDs # Permit use of 8-digit PIDs
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. # 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. # 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
# 0.21 - Added support for multiple pids
# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
# 0.23 - fixed problem with older files with no EXTH section
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
__version__ = '0.20' __version__ = '0.24'
import sys import sys
import struct
import binascii
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@ -55,10 +57,20 @@ class Unbuffered:
self.stream.flush() self.stream.flush()
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout)
import os
import struct
import binascii
class DrmException(Exception): class DrmException(Exception):
pass pass
#
# MobiBook Utility Routines
#
# Implementation of Pukall Cipher 1 # Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True): def PC1(key, src, decryption=True):
sum1 = 0; sum1 = 0;
@ -70,7 +82,6 @@ def PC1(key, src, decryption=True):
wkey = [] wkey = []
for i in xrange(8): for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = "" dst = ""
for i in xrange(len(src)): for i in xrange(len(src)):
temp1 = 0; temp1 = 0;
@ -131,7 +142,9 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
num += (ord(ptr[size - num - 1]) & 0x3) + 1 num += (ord(ptr[size - num - 1]) & 0x3) + 1
return num return num
class DrmStripper:
class MobiBook:
def loadSection(self, section): def loadSection(self, section):
if (section + 1 == self.num_sections): if (section + 1 == self.num_sections):
endoff = len(self.data_file) endoff = len(self.data_file)
@ -140,6 +153,93 @@ class DrmStripper:
off = self.sections[section][0] off = self.sections[section][0]
return self.data_file[off:endoff] return self.data_file[off:endoff]
def __init__(self, infile):
# initial sanity check on file
self.data_file = file(infile, 'rb').read()
self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
raise DrmException("invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
# build up section offset and flag info
self.num_sections, = struct.unpack('>H', self.header[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
flags, val = a1, a2<<16|a3<<8|a4
self.sections.append( (offset, flags, val) )
# parse information from section 0
self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_version = -1
self.meta_array = {}
return
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
self.extra_data_flags = 0
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags
if self.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.
self.extra_data_flags &= 0xFFFE
# if exth region exists parse it for metadata array
self.meta_array = {}
try:
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
exth = 'NONE'
if exth_flag & 0x40:
exth = self.sect[16 + self.mobi_length:]
if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
nitems, = struct.unpack('>I', exth[8:12])
pos = 12
for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
self.meta_array[type] = content
pos += size
except:
self.meta_array = {}
pass
def getBookTitle(self):
title = ''
if 503 in self.meta_array:
title = self.meta_array[503]
else :
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
if title == '':
title = self.header[:32]
title = title.split("\0")[0]
return title
def getPIDMetaInfo(self):
rec209 = None
token = None
if 209 in self.meta_array:
rec209 = self.meta_array[209]
data = rec209
# 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 self.meta_array[ord(data[i])] != None:
token = self.meta_array[ord(data[i])]
return rec209, token
def patch(self, off, new): def patch(self, off, new):
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
@ -152,134 +252,131 @@ class DrmStripper:
assert off + in_off + len(new) <= endoff assert off + in_off + len(new) <= endoff
self.patch(off + in_off, new) self.patch(off + in_off, new)
def parseDRM(self, data, count, pid): def parseDRM(self, data, count, pidlist):
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 found_key = None
for i in xrange(count): keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) for pid in pidlist:
cookie = PC1(temp_key, cookie) bigpid = pid.ljust(16,'\0')
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) temp_key = PC1(keyvec1, bigpid, False)
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1: temp_key_sum = sum(map(ord,temp_key)) & 0xff
found_key = finalkey found_key = None
for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
if cksum == temp_key_sum:
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
if verification == ver and (flags & 0x1F) == 1:
found_key = finalkey
break
if found_key != None:
break break
if not found_key: if not found_key:
# Then try the default encoding that doesn't require a PID # Then try the default encoding that doesn't require a PID
pid = "00000000"
temp_key = keyvec1 temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count): for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie) if cksum == temp_key_sum:
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) cookie = PC1(temp_key, cookie)
if verification == ver and cksum == temp_key_sum: ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
found_key = finalkey if verification == ver:
break found_key = finalkey
return found_key break
return [found_key,pid]
def __init__(self, data_file, pid): def processBook(self, pidlist):
if len(pid)==10: crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
if checksumPid(pid[0:-2]) != pid: print 'Crypto Type is: ', crypto_type
raise DrmException("invalid PID checksum") self.crypto_type = crypto_type
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
header = data_file[0:72]
if header[0x3C:0x3C+8] != 'BOOKMOBI':
raise DrmException("invalid file format")
self.num_sections, = struct.unpack('>H', data_file[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = struct.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, = struct.unpack('>H', sect[0x8:0x8+2])
mobi_length, = struct.unpack('>L',sect[0x14:0x18])
mobi_version, = struct.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, = struct.unpack('>H', sect[0xF2:0xF4])
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])
if crypto_type == 0: if crypto_type == 0:
print "This book is not encrypted." print "This book is not encrypted."
else: return self.data_file
if crypto_type == 1: if crypto_type != 2 and crypto_type != 1:
raise DrmException("cannot decode Mobipocket encryption type 1") raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
if crypto_type != 2:
raise DrmException("unknown encryption type: %d" % crypto_type)
goodpids = []
for pid in pidlist:
if len(pid)==10:
if checksumPid(pid[0:-2]) != pid:
print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2])
goodpids.append(pid[0:-2])
elif len(pid)==8:
goodpids.append(pid)
if self.crypto_type == 1:
t1_keyvec = "QDCVEPMU675RUBSZ"
if self.magic == 'TEXtREAd':
bookkey_data = self.sect[0x0E:0x0E+16]
else:
bookkey_data = self.sect[0x90:0x90+16]
pid = "00000000"
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys # calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16]) drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0: if drm_count == 0:
raise DrmException("no PIDs found in this file") raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid) found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key: if not found_key:
raise DrmException("no key found. maybe the PID is incorrect") raise DrmException("No key found. Most likely the correct PID has not been given.")
# kill the drm keys # kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr) self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers # kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC) if pid=="00000000":
print "File has default encryption, no specific PID."
else:
print "File is encoded with PID "+checksumPid(pid)+"."
# decrypt sections # clear the crypto type
print "Decrypting. Please wait . . .", self.patchSection(0, "\0" * 2, 0xC)
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"
def getResult(self): # decrypt sections
print "Decrypting. Please wait . . .",
new_data = self.data_file[:self.sections[1][0]]
for i in xrange(1, self.records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.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:]
if self.num_sections > self.records+1:
new_data += self.data_file[self.sections[self.records+1][0]:]
self.data_file = new_data
print "done"
return self.data_file return self.data_file
def getUnencryptedBook(infile,pid): def getUnencryptedBook(infile,pid):
sys.stdout=Unbuffered(sys.stdout) if not os.path.isfile(infile):
data_file = file(infile, 'rb').read() raise DrmException('Input File Not Found')
strippedFile = DrmStripper(data_file, pid) book = MobiBook(infile)
return strippedFile.getResult() return book.processBook([pid])
def getUnencryptedBookWithList(infile,pidlist):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
return book.processBook(pidlist)
def main(argv=sys.argv): def main(argv=sys.argv):
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(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> <Comma separated list of PIDs to try>" % sys.argv[0]
return 1 return 1
else: else:
infile = argv[1] infile = argv[1]
outfile = argv[2] outfile = argv[2]
pid = argv[3] pidlist = argv[3].split(',')
try: try:
stripped_file = getUnencryptedBook(infile, pid) stripped_file = getUnencryptedBookWithList(infile, pidlist)
file(outfile, 'wb').write(stripped_file) file(outfile, 'wb').write(stripped_file)
except DrmException, e: except DrmException, e:
print "Error: %s" % e print "Error: %s" % e

96
ReadMe_First.txt Normal file
View File

@ -0,0 +1,96 @@
Welcome to the tools!
The set includes tools to remove DRM from eReader PDB books, Barnes and Noble ePubs, Adobe ePubs, Adobe PDFs, and Kindle/Mobi ebooks (including Topaz).
This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started.
Calibre Users (Mac OS X, Linux, Windows)
-------------
If you are a calibre user, the quickest and easiest way to remove DRM form your ebooks is to open the Calibre_Plugins folder and install each of the plugins following the instructions and configuration directions provided in each plugins README file.
Once installed and configured, you can simply import a DRM book into Calibre and end up with the DeDRM version in the Calibre database.
These plugins work for Windows, Mac OS X, and Linux
Mac OS X Users (Mac OS X 10.5 and 10.6)
--------------
Drag the DeDRM X.X.app droplet to your Desktop. Double-click on it once and it will guide you through collecting the data it needs to remove the DRM.
To use it simply drag a book onto the droplet, and a DeDRM version will appear. This tools supports dragging and dropping of folders of ebooks as well.
Not a Calibre or a Mac OS X DeDRM User?
----------------------------------------
There are a number of python based tools that have graphical user interfaces to make them easy to use. To use any of these tools, you need to have Python 2.5, 2.6, or 2.7 for 32 bits installed on your machine as well as a matching PyCrypto or OpenSSL for some tools.
On Mac OS X (10.5 and 10.6) and Linux (recent versions), your systems already have the proper Python and OpenSSL installed. So nothing need be done, you can already run these tools by double-clicking on the .pyw python scripts.
Users of Mac OS X 10.3 and 10.4, need to download and install the "32-bit Mac Installer disk Image (2.7.X) for OS X 10.3 and later from http://www.python.org/download/releases/2.7.1/
On Windows, you need to install a 32 bit version of Python (even on Windows 64) plus a matching 32 bit version of PyCrypto *OR* OpenSSL. See the end of this document for details.
The scripts are organized by type of ebook you need to remove the DRM from. Choose from among:
"Adobe_ePub_Tools"
"Adobe_PDF_Tools"
"Barnes_and_Noble_ePub_Tools"
"eReader_PDB_Tools"
"KindleBooks_Tools"
by simply opening that folder.
In the "KindleBooks_Tools" folder the primary tool is in the "KindleBooks" folder.
If you are a Windows user, or a Linux platform using Wine, or Mac OS X or have trouble running the KindleBooks tools, there are two other tools provided. These are called "Kindle_4_Mac_Unswindle" and "Kindle_4_PC_Unswindle".
Look for a README inside of the relevant folder to get you started.
Additional Tools
----------------------
Some additional tools are also provided in the "Mobi_Additional_Tools" folder. There are tools for working with "Kindle for iPhone/iPod_Touch/iPad", finding Topaz ebooks, unpacking Mobi ebooks (without DRM) to get to the Mobi markup language inside, and etc.
There is also an "ePub_Fixer" folder that can be used to fix broken DRM epubs that sometimes provided by Adobe and Barnes and Noble that actually violate the zip standard.
Check out their readmes for more info.
Windows and Python Tools
------------------------
We strongly recommend ActiveState's Active Python 2.6 or 2.7 Community Edition for Windows (x86) 32 bits. This can be downloaded for free from:
http://www.activestate.com/activepython/downloads
In addition, Windows Users need one of PyCrypto OR OpenSSL.
For OpenSSL:
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
For PyCrypto:
There are many places to get PyCrypto installers for Windows. One such place is:
http://www.voidspace.org.uk/python/modules.shtml
Please get the latest PyCrypto meant for Windows 32 bit that matches the version of Python you installed (2.7, or 2.6)
Once Windows users have installed Python 2.X for 32 bits, and the matching OpenSSL OR PyCrypto pieces, they too are ready to run the scripts.

View File

@ -1,6 +1,6 @@
ePub_Fixer ePub_Fixer
ePubs are specially crafted zip archives. Unfortunately, many of te DRM encoded Adobe Adept and Barnes & Noble ePubs are not "proper" zip archives in that the names of some files in the zip central directory do NOT match the local name given in archive itself. This type of zip archive is technically incorrect/corrupted and can not be read by many other programs. ePubs are specially crafted zip archives. Unfortunately, many of the DRM encoded Adobe Adept and Barnes & Noble ePubs are not "proper" zip archives in that the names of some files in the zip central directory do NOT match the local name given in archive itself. This type of zip archive is technically incorrect/corrupted and can not be read by many other programs.
ePub_Fixer was designed to fix improperly created zip archives of this type. ePub_Fixer was designed to fix improperly created zip archives of this type.

View File

@ -88,6 +88,8 @@ class MainDialog(Tkinter.Frame):
def showCmdOutput(self, msg): def showCmdOutput(self, msg):
if msg and msg !='': if msg and msg !='':
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
if sys.platform.startswith('win'):
msg = msg.replace('\r\n','\n')
self.stext.insert(Tkconstants.END,msg) self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END) self.stext.yview_pickplace(Tkconstants.END)
return return

View File

@ -85,6 +85,8 @@ class MainDialog(Tkinter.Frame):
def showCmdOutput(self, msg): def showCmdOutput(self, msg):
if msg and msg !='': if msg and msg !='':
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
if sys.platform.startswith('win'):
msg = msg.replace('\r\n','\n')
self.stext.insert(Tkconstants.END,msg) self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END) self.stext.yview_pickplace(Tkconstants.END)
return return

View File

@ -98,6 +98,8 @@ class MainDialog(Tkinter.Frame):
def showCmdOutput(self, msg): def showCmdOutput(self, msg):
if msg and msg !='': if msg and msg !='':
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
if sys.platform.startswith('win'):
msg = msg.replace('\r\n','\n')
self.stext.insert(Tkconstants.END,msg) self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END) self.stext.yview_pickplace(Tkconstants.END)
return return

View File

@ -89,6 +89,8 @@ class MainDialog(Tkinter.Frame):
def showCmdOutput(self, msg): def showCmdOutput(self, msg):
if msg and msg !='': if msg and msg !='':
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
if sys.platform.startswith('win'):
msg = msg.replace('\r\n','\n')
self.stext.insert(Tkconstants.END,msg) self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END) self.stext.yview_pickplace(Tkconstants.END)
return return

View File

@ -76,7 +76,6 @@ class eRdrDeDRM(FileTypePlugin):
if pmlfilepath and pmlfilepath != 1: if pmlfilepath and pmlfilepath != 1:
import zipfile import zipfile
import shutil
print " Creating PMLZ file" print " Creating PMLZ file"
myZipFile = zipfile.ZipFile(pmlzfile.name,'w',zipfile.ZIP_STORED, False) myZipFile = zipfile.ZipFile(pmlzfile.name,'w',zipfile.ZIP_STORED, False)
list = os.listdir(outdir) list = os.listdir(outdir)

View File

@ -56,32 +56,9 @@
# 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 # 0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
# 0.17 - added support for pycrypto's DES as well # 0.17 - added support for pycrypto's DES as well
# 0.18 - on Windows try PyCrypto first and OpenSSL next
Des = None __version__='0.18'
import openssl_des
Des = openssl_des.load_libcrypto()
# if that did not work then try pycrypto version of DES
if Des == None:
import pycrypto_des
Des = pycrypto_des.load_pycrypto()
# 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:
# http://psyco.sourceforge.net
import psyco
psyco.full()
except ImportError:
pass
__version__='0.17'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@ -97,6 +74,37 @@ sys.stdout=Unbuffered(sys.stdout)
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
Des = None
if sys.platform.startswith('win'):
# first try with pycrypto
import pycrypto_des
Des = pycrypto_des.load_pycrypto()
if Des == None:
# they try with openssl
import openssl_des
Des = openssl_des.load_libcrypto()
else:
# first try with openssl
import openssl_des
Des = openssl_des.load_libcrypto()
if Des == None:
# then try with pycrypto
import pycrypto_des
Des = pycrypto_des.load_pycrypto()
# 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:
# http://psyco.sourceforge.net
import psyco
psyco.full()
except ImportError:
pass
try: try:
from hashlib import sha1 from hashlib import sha1
except ImportError: except ImportError:
@ -460,7 +468,7 @@ def main(argv=None):
myZipFile.write(imagePath, localname) myZipFile.write(imagePath, localname)
myZipFile.close() myZipFile.close()
# remove temporary directory # remove temporary directory
shutil.rmtree(outdir) shutil.rmtree(outdir, True)
end_time = time.time() end_time = time.time()
search_time = end_time - start_time search_time = end_time - start_time