mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2024-11-05 13:36:10 +06:00
682 lines
22 KiB
Python
682 lines
22 KiB
Python
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# This is a WINDOWS python script. You need a Python interpreter to run it.
|
||
|
# For example, ActiveState Python, which exists for windows.
|
||
|
#
|
||
|
# It can run standalone to convert K4PC files, or it can be installed as a
|
||
|
# plugin for Calibre (http://calibre-ebook.com/about) so that importing
|
||
|
# K4PC files with DRM is no londer a multi-step process.
|
||
|
#
|
||
|
# ***NOTE*** Calibre and K4PC must be installed on the same windows machine
|
||
|
# for the plugin version to function properly.
|
||
|
#
|
||
|
# To create a Calibre plugin, rename this file so that the filename
|
||
|
# ends in '_plugin.py', put it into a ZIP file and import that ZIP into Calibre
|
||
|
# using its plugin configuration GUI.
|
||
|
#
|
||
|
# Thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump from
|
||
|
# which this script steals most unashamedly.
|
||
|
#
|
||
|
# Changelog
|
||
|
# 0.01 - Initial version - Utilizes skindle and CMBDTC method of obtaining
|
||
|
# book specific pids from K4PC books. If Calibre and K4PC are installed
|
||
|
# on the same windows machine, Calibre plugin functionality is once
|
||
|
# again restored.
|
||
|
|
||
|
|
||
|
"""
|
||
|
|
||
|
Comprehensive Mazama Book DRM with Topaz Cryptography V2.0
|
||
|
|
||
|
-----BEGIN PUBLIC KEY-----
|
||
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
|
||
|
M8hYfnNEI0yQmn5Ti+W8biT7EatpauE/5jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
|
||
|
B62UCxeu/fpfnvNHDN/wPWWH4jynZ2M6cdcnE5LQ+FfeKqZn7gnG2No1U9h7oOHx
|
||
|
y2/pHuYme7U1TsgSjwIDAQAB
|
||
|
-----END PUBLIC KEY-----
|
||
|
|
||
|
"""
|
||
|
|
||
|
from __future__ import with_statement
|
||
|
|
||
|
import csv
|
||
|
import sys
|
||
|
import os
|
||
|
import getopt
|
||
|
import zlib
|
||
|
import binascii
|
||
|
from struct import pack
|
||
|
from struct import unpack
|
||
|
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
||
|
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
||
|
string_at, Structure, c_void_p, cast
|
||
|
import _winreg as winreg
|
||
|
import traceback
|
||
|
import hashlib
|
||
|
|
||
|
__version__ = '0.01'
|
||
|
|
||
|
global kindleDatabase
|
||
|
MAX_PATH = 255
|
||
|
kernel32 = windll.kernel32
|
||
|
advapi32 = windll.advapi32
|
||
|
crypt32 = windll.crypt32
|
||
|
|
||
|
|
||
|
#
|
||
|
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||
|
#
|
||
|
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||
|
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
||
|
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||
|
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||
|
|
||
|
|
||
|
#
|
||
|
# Exceptions for all the problems that might happen during the script
|
||
|
#
|
||
|
class DrmException(Exception):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class DataBlob(Structure):
|
||
|
_fields_ = [('cbData', c_uint),
|
||
|
('pbData', c_void_p)]
|
||
|
DataBlob_p = POINTER(DataBlob)
|
||
|
|
||
|
|
||
|
def GetSystemDirectory():
|
||
|
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
|
||
|
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
|
||
|
GetSystemDirectoryW.restype = c_uint
|
||
|
def GetSystemDirectory():
|
||
|
buffer = create_unicode_buffer(MAX_PATH + 1)
|
||
|
GetSystemDirectoryW(buffer, len(buffer))
|
||
|
return buffer.value
|
||
|
return GetSystemDirectory
|
||
|
GetSystemDirectory = GetSystemDirectory()
|
||
|
|
||
|
|
||
|
def GetVolumeSerialNumber():
|
||
|
GetVolumeInformationW = kernel32.GetVolumeInformationW
|
||
|
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
|
||
|
POINTER(c_uint), POINTER(c_uint),
|
||
|
POINTER(c_uint), c_wchar_p, c_uint]
|
||
|
GetVolumeInformationW.restype = c_uint
|
||
|
def GetVolumeSerialNumber(path):
|
||
|
vsn = c_uint(0)
|
||
|
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
|
||
|
return vsn.value
|
||
|
return GetVolumeSerialNumber
|
||
|
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||
|
|
||
|
|
||
|
def GetUserName():
|
||
|
GetUserNameW = advapi32.GetUserNameW
|
||
|
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
|
||
|
GetUserNameW.restype = c_uint
|
||
|
def GetUserName():
|
||
|
buffer = create_unicode_buffer(32)
|
||
|
size = c_uint(len(buffer))
|
||
|
while not GetUserNameW(buffer, byref(size)):
|
||
|
buffer = create_unicode_buffer(len(buffer) * 2)
|
||
|
size.value = len(buffer)
|
||
|
return buffer.value.encode('utf-16-le')[::2]
|
||
|
return GetUserName
|
||
|
GetUserName = GetUserName()
|
||
|
|
||
|
|
||
|
def CryptUnprotectData():
|
||
|
_CryptUnprotectData = crypt32.CryptUnprotectData
|
||
|
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
|
||
|
c_void_p, c_void_p, c_uint, DataBlob_p]
|
||
|
_CryptUnprotectData.restype = c_uint
|
||
|
def CryptUnprotectData(indata, entropy):
|
||
|
indatab = create_string_buffer(indata)
|
||
|
indata = DataBlob(len(indata), cast(indatab, c_void_p))
|
||
|
entropyb = create_string_buffer(entropy)
|
||
|
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
|
||
|
outdata = DataBlob()
|
||
|
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
|
||
|
None, None, 0, byref(outdata)):
|
||
|
raise DrmException("Failed to Unprotect Data")
|
||
|
return string_at(outdata.pbData, outdata.cbData)
|
||
|
return CryptUnprotectData
|
||
|
CryptUnprotectData = CryptUnprotectData()
|
||
|
|
||
|
|
||
|
#
|
||
|
# Returns the MD5 digest of "message"
|
||
|
#
|
||
|
def MD5(message):
|
||
|
ctx = hashlib.md5()
|
||
|
ctx.update(message)
|
||
|
return ctx.digest()
|
||
|
|
||
|
|
||
|
#
|
||
|
# Returns the MD5 digest of "message"
|
||
|
#
|
||
|
def SHA1(message):
|
||
|
ctx = hashlib.sha1()
|
||
|
ctx.update(message)
|
||
|
return ctx.digest()
|
||
|
|
||
|
|
||
|
#
|
||
|
# Locate and open the Kindle.info file.
|
||
|
#
|
||
|
def openKindleInfo():
|
||
|
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
||
|
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||
|
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
|
||
|
|
||
|
|
||
|
#
|
||
|
# Parse the Kindle.info file and return the records as a list of key-values
|
||
|
#
|
||
|
def parseKindleInfo():
|
||
|
DB = {}
|
||
|
infoReader = openKindleInfo()
|
||
|
infoReader.read(1)
|
||
|
data = infoReader.read()
|
||
|
items = data.split('{')
|
||
|
|
||
|
for item in items:
|
||
|
splito = item.split(':')
|
||
|
DB[splito[0]] =splito[1]
|
||
|
return DB
|
||
|
|
||
|
|
||
|
#
|
||
|
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
|
||
|
#
|
||
|
def findNameForHash(hash):
|
||
|
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||
|
result = ""
|
||
|
for name in names:
|
||
|
if hash == encodeHash(name, charMap2):
|
||
|
result = name
|
||
|
break
|
||
|
return name
|
||
|
|
||
|
|
||
|
#
|
||
|
# Print all the records from the kindle.info file.
|
||
|
#
|
||
|
def printKindleInfo():
|
||
|
for record in kindleDatabase:
|
||
|
name = findNameForHash(record)
|
||
|
if name != "" :
|
||
|
print (name)
|
||
|
print ("--------------------------\n")
|
||
|
else :
|
||
|
print ("Unknown Record")
|
||
|
print getKindleInfoValueForHash(record)
|
||
|
print "\n"
|
||
|
|
||
|
|
||
|
#
|
||
|
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
|
||
|
#
|
||
|
def getKindleInfoValueForHash(hashedKey):
|
||
|
global kindleDatabase
|
||
|
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
|
||
|
return CryptUnprotectData(encryptedValue,"")
|
||
|
|
||
|
|
||
|
#
|
||
|
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
|
||
|
#
|
||
|
def getKindleInfoValueForKey(key):
|
||
|
return getKindleInfoValueForHash(encodeHash(key,charMap2))
|
||
|
|
||
|
|
||
|
#
|
||
|
# 8 bits to six bits encoding from hash to generate PID string
|
||
|
#
|
||
|
def encodePID(hash):
|
||
|
global charMap3
|
||
|
PID = ""
|
||
|
for position in range (0,8):
|
||
|
PID += charMap3[getSixBitsFromBitField(hash,position)]
|
||
|
return PID
|
||
|
|
||
|
|
||
|
#
|
||
|
# Hash the bytes in data and then encode the digest with the characters in map
|
||
|
#
|
||
|
def encodeHash(data,map):
|
||
|
return encode(MD5(data),map)
|
||
|
|
||
|
|
||
|
#
|
||
|
# Encode the bytes in data with the characters in map
|
||
|
#
|
||
|
def encode(data, map):
|
||
|
result = ""
|
||
|
for char in data:
|
||
|
value = ord(char)
|
||
|
Q = (value ^ 0x80) // len(map)
|
||
|
R = value % len(map)
|
||
|
result += map[Q]
|
||
|
result += map[R]
|
||
|
return result
|
||
|
|
||
|
|
||
|
#
|
||
|
# Decode the string in data with the characters in map. Returns the decoded bytes
|
||
|
#
|
||
|
def decode(data,map):
|
||
|
result = ""
|
||
|
for i in range (0,len(data),2):
|
||
|
high = map.find(data[i])
|
||
|
low = map.find(data[i+1])
|
||
|
value = (((high * 0x40) ^ 0x80) & 0xFF) + low
|
||
|
result += pack("B",value)
|
||
|
return result
|
||
|
|
||
|
|
||
|
#
|
||
|
# Encryption table used to generate the device PID
|
||
|
#
|
||
|
def generatePidEncryptionTable() :
|
||
|
table = []
|
||
|
for counter1 in range (0,0x100):
|
||
|
value = counter1
|
||
|
for counter2 in range (0,8):
|
||
|
if (value & 1 == 0) :
|
||
|
value = value >> 1
|
||
|
else :
|
||
|
value = value >> 1
|
||
|
value = value ^ 0xEDB88320
|
||
|
table.append(value)
|
||
|
return table
|
||
|
|
||
|
|
||
|
#
|
||
|
# Seed value used to generate the device PID
|
||
|
#
|
||
|
def generatePidSeed(table,dsn) :
|
||
|
value = 0
|
||
|
for counter in range (0,4) :
|
||
|
index = (ord(dsn[counter]) ^ value) &0xFF
|
||
|
value = (value >> 8) ^ table[index]
|
||
|
return value
|
||
|
|
||
|
|
||
|
#
|
||
|
# Generate the device PID
|
||
|
#
|
||
|
def generateDevicePID(table,dsn,nbRoll):
|
||
|
seed = generatePidSeed(table,dsn)
|
||
|
pidAscii = ""
|
||
|
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
|
||
|
index = 0
|
||
|
|
||
|
for counter in range (0,nbRoll):
|
||
|
pid[index] = pid[index] ^ ord(dsn[counter])
|
||
|
index = (index+1) %8
|
||
|
|
||
|
for counter in range (0,8):
|
||
|
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
||
|
pidAscii += charMap4[index]
|
||
|
return pidAscii
|
||
|
|
||
|
|
||
|
#
|
||
|
# Returns two bit at offset from a bit field
|
||
|
#
|
||
|
def getTwoBitsFromBitField(bitField,offset):
|
||
|
byteNumber = offset // 4
|
||
|
bitPosition = 6 - 2*(offset % 4)
|
||
|
|
||
|
return ord(bitField[byteNumber]) >> bitPosition & 3
|
||
|
|
||
|
|
||
|
#
|
||
|
# Returns the six bits at offset from a bit field
|
||
|
#
|
||
|
def getSixBitsFromBitField(bitField,offset):
|
||
|
offset *= 3
|
||
|
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
|
||
|
return value
|
||
|
|
||
|
|
||
|
#
|
||
|
# MobiDeDrm-0.16 Stuff
|
||
|
#
|
||
|
class Unbuffered:
|
||
|
def __init__(self, stream):
|
||
|
self.stream = stream
|
||
|
def write(self, data):
|
||
|
self.stream.write(data)
|
||
|
self.stream.flush()
|
||
|
def __getattr__(self, attr):
|
||
|
return getattr(self.stream, attr)
|
||
|
|
||
|
|
||
|
# Implementation of Pukall Cipher 1
|
||
|
def PC1(key, src, decryption=True):
|
||
|
sum1 = 0;
|
||
|
sum2 = 0;
|
||
|
keyXorVal = 0;
|
||
|
if len(key)!=16:
|
||
|
print "Bad key length!"
|
||
|
return None
|
||
|
wkey = []
|
||
|
for i in xrange(8):
|
||
|
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
||
|
|
||
|
dst = ""
|
||
|
for i in xrange(len(src)):
|
||
|
temp1 = 0;
|
||
|
byteXorVal = 0;
|
||
|
for j in xrange(8):
|
||
|
temp1 ^= wkey[j]
|
||
|
sum2 = (sum2+j)*20021 + sum1
|
||
|
sum1 = (temp1*346)&0xFFFF
|
||
|
sum2 = (sum2+sum1)&0xFFFF
|
||
|
temp1 = (temp1*20021+1)&0xFFFF
|
||
|
byteXorVal ^= temp1 ^ sum2
|
||
|
curByte = ord(src[i])
|
||
|
if not decryption:
|
||
|
keyXorVal = curByte * 257;
|
||
|
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
||
|
if decryption:
|
||
|
keyXorVal = curByte * 257;
|
||
|
for j in xrange(8):
|
||
|
wkey[j] ^= keyXorVal;
|
||
|
dst+=chr(curByte)
|
||
|
return dst
|
||
|
|
||
|
|
||
|
def getSizeOfTrailingDataEntries(ptr, size, flags):
|
||
|
def getSizeOfTrailingDataEntry(ptr, size):
|
||
|
bitpos, result = 0, 0
|
||
|
if size <= 0:
|
||
|
return result
|
||
|
while True:
|
||
|
v = ord(ptr[size-1])
|
||
|
result |= (v & 0x7F) << bitpos
|
||
|
bitpos += 7
|
||
|
size -= 1
|
||
|
if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
|
||
|
return result
|
||
|
num = 0
|
||
|
testflags = flags >> 1
|
||
|
while testflags:
|
||
|
if testflags & 1:
|
||
|
num += getSizeOfTrailingDataEntry(ptr, size - num)
|
||
|
testflags >>= 1
|
||
|
# Multibyte data, if present, is included in the encryption, so
|
||
|
# we do not need to check the low bit.
|
||
|
# if flags & 1:
|
||
|
# num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
||
|
return num
|
||
|
|
||
|
|
||
|
#
|
||
|
# This class does all the heavy lifting.
|
||
|
#
|
||
|
class DrmStripper:
|
||
|
def loadSection(self, section):
|
||
|
if (section + 1 == self.num_sections):
|
||
|
endoff = len(self.data_file)
|
||
|
else:
|
||
|
endoff = self.sections[section + 1][0]
|
||
|
off = self.sections[section][0]
|
||
|
return self.data_file[off:endoff]
|
||
|
|
||
|
def patch(self, off, new):
|
||
|
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
|
||
|
|
||
|
def patchSection(self, section, new, in_off = 0):
|
||
|
if (section + 1 == self.num_sections):
|
||
|
endoff = len(self.data_file)
|
||
|
else:
|
||
|
endoff = self.sections[section + 1][0]
|
||
|
off = self.sections[section][0]
|
||
|
assert off + in_off + len(new) <= endoff
|
||
|
self.patch(off + in_off, new)
|
||
|
|
||
|
def parseDRM(self, data, count, pid):
|
||
|
pid = pid.ljust(16,'\0')
|
||
|
keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
|
||
|
temp_key = PC1(keyvec1, pid, False)
|
||
|
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
||
|
found_key = None
|
||
|
for i in xrange(count):
|
||
|
verification, size, type, cksum, cookie = unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
||
|
cookie = PC1(temp_key, cookie)
|
||
|
ver,flags,finalkey,expiry,expiry2 = unpack('>LL16sLL', cookie)
|
||
|
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
|
||
|
found_key = finalkey
|
||
|
break
|
||
|
if not found_key:
|
||
|
# Then try the default encoding that doesn't require a PID
|
||
|
temp_key = keyvec1
|
||
|
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
||
|
for i in xrange(count):
|
||
|
verification, size, type, cksum, cookie = unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
||
|
cookie = PC1(temp_key, cookie)
|
||
|
ver,flags,finalkey,expiry,expiry2 = unpack('>LL16sLL', cookie)
|
||
|
if verification == ver and cksum == temp_key_sum:
|
||
|
found_key = finalkey
|
||
|
break
|
||
|
return found_key
|
||
|
|
||
|
def __init__(self, data_file):
|
||
|
self.data_file = data_file
|
||
|
header = data_file[0:72]
|
||
|
if header[0x3C:0x3C+8] != 'BOOKMOBI':
|
||
|
raise DrmException("invalid file format")
|
||
|
self.num_sections, = unpack('>H', data_file[76:78])
|
||
|
|
||
|
self.sections = []
|
||
|
for i in xrange(self.num_sections):
|
||
|
offset, a1,a2,a3,a4 = unpack('>LBBBB', data_file[78+i*8:78+i*8+8])
|
||
|
flags, val = a1, a2<<16|a3<<8|a4
|
||
|
self.sections.append( (offset, flags, val) )
|
||
|
|
||
|
sect = self.loadSection(0)
|
||
|
records, = unpack('>H', sect[0x8:0x8+2])
|
||
|
mobi_length, = unpack('>L',sect[0x14:0x18])
|
||
|
mobi_version, = unpack('>L',sect[0x68:0x6C])
|
||
|
extra_data_flags = 0
|
||
|
print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length)
|
||
|
if (mobi_length >= 0xE4) and (mobi_version >= 5):
|
||
|
extra_data_flags, = unpack('>H', sect[0xF2:0xF4])
|
||
|
print "Extra Data Flags = %d" %extra_data_flags
|
||
|
|
||
|
crypto_type, = unpack('>H', sect[0xC:0xC+2])
|
||
|
if crypto_type == 0:
|
||
|
print "This book is not encrypted."
|
||
|
else:
|
||
|
if crypto_type == 1:
|
||
|
raise DrmException("cannot decode Mobipocket encryption type 1")
|
||
|
if crypto_type != 2:
|
||
|
raise DrmException("unknown encryption type: %d" % crypto_type)
|
||
|
|
||
|
# determine the EXTH Offset.
|
||
|
exth_off = unpack('>I', sect[20:24])[0] + 16 + self.sections[0][0]
|
||
|
# Grab the entire EXTH block and feed it to the getK4PCPids function.
|
||
|
exth = data_file[exth_off:self.sections[0+1][0]]
|
||
|
pid = getK4PCPids(exth)
|
||
|
|
||
|
# calculate the keys
|
||
|
drm_ptr, drm_count, drm_size, drm_flags = unpack('>LLLL', sect[0xA8:0xA8+16])
|
||
|
if drm_count == 0:
|
||
|
raise DrmException("no PIDs found in this file")
|
||
|
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
|
||
|
if not found_key:
|
||
|
raise DrmException("no key found. maybe the PID is incorrect")
|
||
|
|
||
|
# kill the drm keys
|
||
|
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
||
|
# kill the drm pointers
|
||
|
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
|
||
|
# clear the crypto type
|
||
|
self.patchSection(0, "\0" * 2, 0xC)
|
||
|
|
||
|
# decrypt sections
|
||
|
print "\nDecrypting. Please wait . . .",
|
||
|
new_data = self.data_file[:self.sections[1][0]]
|
||
|
for i in xrange(1, records+1):
|
||
|
data = self.loadSection(i)
|
||
|
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
|
||
|
if i%100 == 0:
|
||
|
print ".",
|
||
|
# print "record %d, extra_size %d" %(i,extra_size)
|
||
|
new_data += PC1(found_key, data[0:len(data) - extra_size])
|
||
|
if extra_size > 0:
|
||
|
new_data += data[-extra_size:]
|
||
|
#self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
|
||
|
if self.num_sections > records+1:
|
||
|
new_data += self.data_file[self.sections[records+1][0]:]
|
||
|
self.data_file = new_data
|
||
|
print "done!"
|
||
|
print "\nPlease only use your new-found powers for good."
|
||
|
|
||
|
def getResult(self):
|
||
|
return self.data_file
|
||
|
|
||
|
|
||
|
#
|
||
|
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
|
||
|
# file to calculate the book pid.
|
||
|
#
|
||
|
def getK4PCPids(exth):
|
||
|
global kindleDatabase
|
||
|
try:
|
||
|
kindleDatabase = parseKindleInfo()
|
||
|
except Exception as message:
|
||
|
print(message)
|
||
|
|
||
|
if kindleDatabase != None :
|
||
|
|
||
|
# Get the Mazama Random number
|
||
|
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
|
||
|
|
||
|
# Get the HDD serial
|
||
|
encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1)
|
||
|
|
||
|
# Get the current user name
|
||
|
encodedUsername = encodeHash(GetUserName(),charMap1)
|
||
|
|
||
|
# concat, hash and encode to calculate the DSN
|
||
|
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
|
||
|
|
||
|
print("\nDSN: " + DSN)
|
||
|
|
||
|
|
||
|
# Compute the device PID (for which I can tell, is used for nothing).
|
||
|
# But hey, stuff being printed out is apparently cool.
|
||
|
table = generatePidEncryptionTable()
|
||
|
devicePID = generateDevicePID(table,DSN,4)
|
||
|
|
||
|
print("Device PID: " + devicePID)
|
||
|
|
||
|
# Compute book PID
|
||
|
exth_records = {}
|
||
|
nitems, = unpack('>I', exth[8:12])
|
||
|
pos = 12
|
||
|
# Parse the EXTH records, storing data indexed by type
|
||
|
for i in xrange(nitems):
|
||
|
type, size = unpack('>II', exth[pos: pos + 8])
|
||
|
content = exth[pos + 8: pos + size]
|
||
|
|
||
|
exth_records[type] = content
|
||
|
pos += size
|
||
|
|
||
|
# Grab the contents of the type 209 exth record
|
||
|
if exth_records[209] != None:
|
||
|
data = exth_records[209]
|
||
|
else:
|
||
|
raise DrmException("\nNo EXTH record type 209 - Perhaps not a K4PC file?")
|
||
|
|
||
|
# Parse the 209 data to find the the exth record with the token data.
|
||
|
# The last character of the 209 data points to the record with the token.
|
||
|
# Always 208 from my experience, but I'll leave the logic in case that changes.
|
||
|
for i in xrange(len(data)):
|
||
|
if ord(data[i]) != 0:
|
||
|
if exth_records[ord(data[i])] != None:
|
||
|
token = exth_records[ord(data[i])]
|
||
|
|
||
|
# Get the kindle account token
|
||
|
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
|
||
|
|
||
|
print("Account Token: " + kindleAccountToken)
|
||
|
|
||
|
pidHash = SHA1(DSN+kindleAccountToken+exth_records[209]+token)
|
||
|
|
||
|
bookPID = encodePID(pidHash)
|
||
|
|
||
|
if exth_records[503] != None:
|
||
|
print "Pid for " + exth_records[503] + ": " + bookPID
|
||
|
else:
|
||
|
print ("Book PID: " + bookPID )
|
||
|
|
||
|
return bookPID
|
||
|
|
||
|
raise DrmException("\nCould not access K4PC data - Perhaps K4PC is not installed/configured?")
|
||
|
return null
|
||
|
|
||
|
if not __name__ == "__main__":
|
||
|
from calibre.customize import FileTypePlugin
|
||
|
|
||
|
class K4PCDeDRM(FileTypePlugin):
|
||
|
name = 'K4PCDeDRM' # Name of the plugin
|
||
|
description = 'Removes DRM from K4PC files'
|
||
|
supported_platforms = ['windows'] # Platforms this plugin will run on
|
||
|
author = 'DiapDealer' # The author of this plugin
|
||
|
version = (0, 0, 1) # The version number of this plugin
|
||
|
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
|
||
|
on_import = True # Run this plugin during the import
|
||
|
|
||
|
def run(self, path_to_ebook):
|
||
|
from calibre.gui2 import is_ok_to_use_qt
|
||
|
from PyQt4.Qt import QMessageBox
|
||
|
data_file = file(path_to_ebook, 'rb').read()
|
||
|
|
||
|
try:
|
||
|
unlocked_file = DrmStripper(data_file).getResult()
|
||
|
except DrmException:
|
||
|
# ignore the error
|
||
|
pass
|
||
|
else:
|
||
|
of = self.temporary_file('.mobi')
|
||
|
of.write(unlocked_file)
|
||
|
of.close()
|
||
|
return of.name
|
||
|
|
||
|
if is_ok_to_use_qt():
|
||
|
d = QMessageBox(QMessageBox.Warning, "K4PCDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
|
||
|
d.show()
|
||
|
d.raise_()
|
||
|
d.exec_()
|
||
|
return path_to_ebook
|
||
|
|
||
|
#def customization_help(self, gui=False):
|
||
|
# return 'Enter PID (separate multiple PIDs with comma)'
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
sys.stdout=Unbuffered(sys.stdout)
|
||
|
print ('K4PCDeDrm v%(__version__)s '
|
||
|
'provided DiapDealer.' % globals())
|
||
|
if len(sys.argv)<3:
|
||
|
print "Removes DRM protection from K4PC books"
|
||
|
print "Usage:"
|
||
|
print " %s <infile> <outfile>" % sys.argv[0]
|
||
|
sys.exit(1)
|
||
|
else:
|
||
|
infile = sys.argv[1]
|
||
|
outfile = sys.argv[2]
|
||
|
data_file = file(infile, 'rb').read()
|
||
|
try:
|
||
|
strippedFile = DrmStripper(data_file)
|
||
|
file(outfile, 'wb').write(strippedFile.getResult())
|
||
|
except DrmException, e:
|
||
|
print "Error: %s" % e
|
||
|
sys.exit(1)
|
||
|
sys.exit(0)
|