tools v2.3

First appearance of DeDRM AppleScript application
This commit is contained in:
Apprentice Alf
2010-12-02 18:10:46 +00:00
parent 506d97d5f0
commit 9162698f89
62 changed files with 5694 additions and 907 deletions

View File

@@ -180,6 +180,7 @@ class MainDialog(Tkinter.Frame):
# actually ready to run the subprocess and get its output
def convertit(self):
self.status['text'] = ''
# now disable the button to prevent multiple launches
self.sbotton.configure(state='disabled')
mobipath = self.mobipath.get()
@@ -187,7 +188,7 @@ class MainDialog(Tkinter.Frame):
altinfopath = self.altinfopath.get()
pidnums = self.pidinfo.get()
if not mobipath or not os.path.exists(mobipath):
if not mobipath or not os.path.exists(mobipath) or not os.path.isfile(mobipath):
self.status['text'] = 'Specified K4PC, K4M or Mobi eBook file does not exist'
self.sbotton.configure(state='normal')
return

View File

@@ -0,0 +1,333 @@
# engine to remove drm from Kindle for Mac books
# for personal use for archiving and converting your ebooks
# PLEASE DO NOT PIRATE!
# We want all authors and Publishers, and eBook stores to live long and prosperous lives
#
# it borrows heavily from works by CMBDTC, IHeartCabbages, skindle,
# unswindle, DiapDealer, some_updates and many many others
from __future__ import with_statement
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
import sys
sys.stdout=Unbuffered(sys.stdout)
import os, csv, getopt
from struct import pack
from struct import unpack
import zlib
# for handling sub processes
import subprocess
from subprocess import Popen, PIPE, STDOUT
import subasyncio
from subasyncio import Process
#Exception Handling
class K4MDEDRMError(Exception):
pass
class K4MDEDRMFatal(Exception):
pass
#
# crypto routines
#
import hashlib
def MD5(message):
ctx = hashlib.md5()
ctx.update(message)
return ctx.digest()
def SHA1(message):
ctx = hashlib.sha1()
ctx.update(message)
return ctx.digest()
def SHA256(message):
ctx = hashlib.sha256()
ctx.update(message)
return ctx.digest()
# interface to needed routines in openssl's libcrypto
def _load_crypto_libcrypto():
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, addressof, string_at, cast
from ctypes.util import find_library
libcrypto = find_library('crypto')
if libcrypto is None:
raise K4MDEDRMError('libcrypto not found')
libcrypto = CDLL(libcrypto)
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
[c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
class LibCrypto(object):
def __init__(self):
self._blocksize = 0
self._keyctx = None
self.iv = 0
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise K4MDEDRMError('AES improper key used')
return
keyctx = self._keyctx = AES_KEY()
self.iv = iv
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
raise K4MDEDRMError('Failed to initialize AES key')
def decrypt(self, data):
out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
if rv == 0:
raise K4MDEDRMError('AES decryption failed')
return out.raw
def keyivgen(self, passwd):
salt = '16743'
saltlen = 5
passlen = len(passwd)
iter = 0x3e8
keylen = 80
out = create_string_buffer(keylen)
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
return out.raw
return LibCrypto
def _load_crypto():
LibCrypto = None
try:
LibCrypto = _load_crypto_libcrypto()
except (ImportError, K4MDEDRMError):
pass
return LibCrypto
LibCrypto = _load_crypto()
#
# Utility Routines
#
# uses a sub process to get the Hard Drive Serial Number using ioreg
# returns with the first found serial number in that class
def GetVolumeSerialNumber():
sernum = os.getenv('MYSERIALNUMBER')
if sernum != None:
return sernum
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
poll = p.wait('wait')
results = p.read()
reslst = results.split('\n')
cnt = len(reslst)
bsdname = None
sernum = None
foundIt = False
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('"Serial Number" = "')
if pp >= 0:
sernum = resline[pp+19:-1]
sernum = sernum.strip()
bb = resline.find('"BSD Name" = "')
if bb >= 0:
bsdname = resline[bb+14:-1]
bsdname = bsdname.strip()
if (bsdname == 'disk0') and (sernum != None):
foundIt = True
break
if not foundIt:
sernum = '9999999999'
return sernum
# uses unix env to get username instead of using sysctlbyname
def GetUserName():
username = os.getenv('USER')
return username
MAX_PATH = 255
#
# start of Kindle specific routines
#
global kindleDatabase
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
# Encode the bytes in data with the characters in map
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
# Hash the bytes in data and then encode the digest with the characters in map
def encodeHash(data,map):
return encode(MD5(data),map)
# Decode the string in data with the characters in map. Returns the decoded bytes
def decode(data,map):
result = ""
for i in range (0,len(data)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
if (high == -1) or (low == -1) :
break
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
result += pack("B",value)
return result
# implements an Pseudo Mac Version of Windows built-in Crypto routine
def CryptUnprotectData(encryptedData):
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1)
crp = LibCrypto()
key_iv = crp.keyivgen(passwdData)
key = key_iv[0:32]
iv = key_iv[32:48]
crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData)
return cleartext
# Locate and open the .kindle-info file
def openKindleInfo():
home = os.getenv('HOME')
kinfopath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info'
if not os.path.exists(kinfopath):
kinfopath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info'
if not os.path.exists(kinfopath):
raise K4MDEDRMError('Error: .kindle-info file can not be found')
return open(kinfopath,'r')
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo():
DB = {}
infoReader = openKindleInfo()
infoReader.read(1)
data = infoReader.read()
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey):
global kindleDatabase
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
cleartext = CryptUnprotectData(encryptedValue)
return decode(cleartext, charMap1)
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
def getKindleInfoValueForKey(key):
return getKindleInfoValueForHash(encodeHash(key,charMap2))
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string.
def findNameForHash(hash):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
result = ""
for name in names:
if hash == encodeHash(name, charMap2):
result = name
break
return result
# Print all the records from the kindle.info file (option -i)
def printKindleInfo():
for record in kindleDatabase:
name = findNameForHash(record)
if name != "" :
print (name)
print ("--------------------------")
else :
print ("Unknown Record")
print getKindleInfoValueForHash(record)
print "\n"
#
# PID generation routines
#
# Returns two bit at offset from a bit field
def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4)
return ord(bitField[byteNumber]) >> bitPosition & 3
# Returns the six bits at offset from a bit field
def getSixBitsFromBitField(bitField,offset):
offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value
# 8 bits to six bits encoding from hash to generate PID string
def encodePID(hash):
global charMap3
PID = ""
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
#
# Main
#
def main(argv=sys.argv):
global kindleDatabase
kindleDatabase = None
#
# Read the encrypted database
#
try:
kindleDatabase = parseKindleInfo()
except Exception, message:
print(message)
if kindleDatabase != None :
printKindleInfo()
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -28,7 +28,7 @@
from __future__ import with_statement
__version__ = '1.1'
__version__ = '1.2'
class Unbuffered:
def __init__(self, stream):
@@ -284,7 +284,7 @@ def getK4Pids(exth, title, kInfoFile=None):
global kindleDatabase
try:
kindleDatabase = parseKindleInfo(kInfoFile)
except Exception as message:
except Exception, message:
print(message)
if kindleDatabase != None :
@@ -313,6 +313,8 @@ def getK4Pids(exth, title, kInfoFile=None):
exth_records = {}
nitems, = unpack('>I', exth[8:12])
pos = 12
exth_records[209] = None
# Parse the exth records, storing data indexed by type
for i in xrange(nitems):
type, size = unpack('>II', exth[pos: pos + 8])
@@ -397,6 +399,7 @@ def main(argv=sys.argv):
kindleDatabase = None
infile = args[0]
outfile = args[1]
DecodeErrorString = ""
try:
# first try with K4PC/K4M
ex = MobiPeek(infile)
@@ -405,11 +408,15 @@ def main(argv=sys.argv):
return 2
title = ex.getBookTitle()
exth = ex.getexthData()
if exth=='':
raise DrmException("Not a Kindle Mobipocket file")
pid = getK4Pids(exth, title)
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
except DrmException:
except DrmException, e:
DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n"
pass
except mobidedrm.DrmException:
except mobidedrm.DrmException, e:
DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n"
pass
else:
file(outfile, 'wb').write(unlocked_file)
@@ -422,11 +429,15 @@ def main(argv=sys.argv):
try:
title = ex.getBookTitle()
exth = ex.getexthData()
if exth=='':
raise DrmException("Not a Kindle Mobipocket file")
pid = getK4Pids(exth, title, infoFile)
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
except DrmException:
except DrmException, e:
DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n"
pass
except mobidedrm.DrmException:
except mobidedrm.DrmException, e:
DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n"
pass
else:
file(outfile, 'wb').write(unlocked_file)
@@ -445,6 +456,7 @@ def main(argv=sys.argv):
return 0
# we could not unencrypt book
print DecodeErrorString
print "Error: Could Not Unencrypt Book"
return 1
@@ -463,7 +475,7 @@ if not __name__ == "__main__" and inCalibre:
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 1, 1) # The version number of this plugin
version = (0, 1, 2) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
@@ -509,6 +521,8 @@ if not __name__ == "__main__" and inCalibre:
return path_to_ebook
title = ex.getBookTitle()
exth = ex.getexthData()
if exth=='':
raise DrmException("Not a Kindle Mobipocket file")
pid = getK4Pids(exth, title)
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
except DrmException:
@@ -528,6 +542,8 @@ if not __name__ == "__main__" and inCalibre:
try:
title = ex.getBookTitle()
exth = ex.getexthData()
if exth=='':
raise DrmException("Not a Kindle Mobipocket file")
pid = getK4Pids(exth, title, infoFile)
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
except DrmException:

View File

@@ -237,24 +237,36 @@ LibCrypto = _load_crypto()
#
# uses a sub process to get the Hard Drive Serial Number using ioreg
# returns with the first found serial number in that class
# returns with the serial number of drive whose BSD Name is "disk0"
def GetVolumeSerialNumber():
cmdline = '/usr/sbin/ioreg -r -c AppleAHCIDiskDriver'
sernum = os.getenv('MYSERIALNUMBER')
if sernum != None:
return sernum
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
poll = p.wait('wait')
results = p.read()
reslst = results.split('\n')
sernum = '9999999999'
cnt = len(reslst)
bsdname = None
sernum = None
foundIt = False
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('"Serial Number" = "')
if pp >= 0:
sernum = resline[pp+19:]
sernum = sernum[:-1]
sernum = sernum.lstrip()
break
sernum = resline[pp+19:-1]
sernum = sernum.strip()
bb = resline.find('"BSD Name" = "')
if bb >= 0:
bsdname = resline[bb+14:-1]
bsdname = bsdname.strip()
if (bsdname == 'disk0') and (sernum != None):
foundIt = True
break
if not foundIt:
sernum = '9999999999'
return sernum
# uses unix env to get username instead of using sysctlbyname
@@ -319,4 +331,4 @@ def openKindleInfo(kInfoFile=None):
raise K4MDrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r')
else:
return open(kInfoFile, 'r')
return open(kInfoFile, 'r')

View File

@@ -3,14 +3,6 @@
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# It can run standalone to convert files, or it can be installed as a
# plugin for Calibre (http://calibre-ebook.com/about) so that
# importing files with DRM 'Just Works'.
#
# To create a Calibre plugin, rename this file so that the filename
# ends in '_plugin.py', put it into a ZIP file and import that Calibre
# using its plugin configuration GUI.
#
# Changelog
# 0.01 - Initial version
# 0.02 - Huffdic compressed books were not properly decrypted
@@ -46,8 +38,9 @@
# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
# Removed the disabled Calibre plug-in code
# Permit use of 8-digit PIDs
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
__version__ = '0.18'
__version__ = '0.19'
import sys
import struct
@@ -215,8 +208,8 @@ class DrmStripper:
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 5 (& 6?)
if mobi_version <= 5:
# multibyte utf8 data is included in the encryption for mobi_version 5 and below
# so clear that byte so that we leave it to be decrypted.
extra_data_flags &= 0xFFFE

View File

@@ -0,0 +1,18 @@
set verbose 0
break * 0x00d3dac4
commands 1
printf "PID is %s\n", $eax
continue
end
break * 0x0130a384
commands 2
printf "File is %s\n", $eax
continue
end
condition 2 $eax != 0
break * 0x00f0f306
commands 3
printf "TOPAZ PID is %s\n", *(long*)($esp+12)
continue
end
run

View File

@@ -0,0 +1,18 @@
set verbose 0
break * 0x00d8f72a
commands 1
printf "PID is %s\n", *(long*)($esp+4)
continue
end
break * 0x0127498c
commands 2
printf "File is %s\n", $eax
continue
end
condition 2 $eax != 0
break * 0x00e9aec0
commands 3
printf "TOPAZ PID is %s\n", **(long**)($esp+8)
continue
end
run

View File

@@ -3,14 +3,6 @@
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# It can run standalone to convert files, or it can be installed as a
# plugin for Calibre (http://calibre-ebook.com/about) so that
# importing files with DRM 'Just Works'.
#
# To create a Calibre plugin, rename this file so that the filename
# ends in '_plugin.py', put it into a ZIP file and import that Calibre
# using its plugin configuration GUI.
#
# Changelog
# 0.01 - Initial version
# 0.02 - Huffdic compressed books were not properly decrypted
@@ -46,8 +38,9 @@
# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
# Removed the disabled Calibre plug-in code
# Permit use of 8-digit PIDs
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
__version__ = '0.18'
__version__ = '0.19'
import sys
import struct
@@ -215,8 +208,8 @@ class DrmStripper:
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 5 (& 6?)
if mobi_version <= 5:
# multibyte utf8 data is included in the encryption for mobi_version 5 and below
# so clear that byte so that we leave it to be decrypted.
extra_data_flags &= 0xFFFE

View File

@@ -3,14 +3,6 @@
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# It can run standalone to convert files, or it can be installed as a
# plugin for Calibre (http://calibre-ebook.com/about) so that
# importing files with DRM 'Just Works'.
#
# To create a Calibre plugin, rename this file so that the filename
# ends in '_plugin.py', put it into a ZIP file and import that Calibre
# using its plugin configuration GUI.
#
# Changelog
# 0.01 - Initial version
# 0.02 - Huffdic compressed books were not properly decrypted
@@ -46,8 +38,9 @@
# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
# Removed the disabled Calibre plug-in code
# Permit use of 8-digit PIDs
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
__version__ = '0.18'
__version__ = '0.19'
import sys
import struct
@@ -215,8 +208,8 @@ class DrmStripper:
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 5 (& 6?)
if mobi_version <= 5:
# multibyte utf8 data is included in the encryption for mobi_version 5 and below
# so clear that byte so that we leave it to be decrypted.
extra_data_flags &= 0xFFFE

View File

@@ -3,14 +3,6 @@
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# It can run standalone to convert files, or it can be installed as a
# plugin for Calibre (http://calibre-ebook.com/about) so that
# importing files with DRM 'Just Works'.
#
# To create a Calibre plugin, rename this file so that the filename
# ends in '_plugin.py', put it into a ZIP file and import that Calibre
# using its plugin configuration GUI.
#
# Changelog
# 0.01 - Initial version
# 0.02 - Huffdic compressed books were not properly decrypted
@@ -46,8 +38,9 @@
# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
# Removed the disabled Calibre plug-in code
# Permit use of 8-digit PIDs
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
__version__ = '0.18'
__version__ = '0.19'
import sys
import struct
@@ -215,8 +208,8 @@ class DrmStripper:
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 5 (& 6?)
if mobi_version <= 5:
# multibyte utf8 data is included in the encryption for mobi_version 5 and below
# so clear that byte so that we leave it to be decrypted.
extra_data_flags &= 0xFFFE