From fa62e11f8c42f26b793280675a0c2b4d00a03b6f Mon Sep 17 00:00:00 2001 From: Igor Skochinsky Date: Wed, 12 Dec 2007 17:38:30 +0000 Subject: [PATCH] First version of kindlepid and kindlefix scripts --- Kindle_Mobi_Tools/lib/kindlefix.py | 156 +++++++++++++++++++++++++++++ Kindle_Mobi_Tools/lib/kindlepid.py | 45 +++++++++ Kindle_Mobi_Tools/lib/readme.txt | 34 +++++++ 3 files changed, 235 insertions(+) create mode 100644 Kindle_Mobi_Tools/lib/kindlefix.py create mode 100644 Kindle_Mobi_Tools/lib/kindlepid.py create mode 100644 Kindle_Mobi_Tools/lib/readme.txt diff --git a/Kindle_Mobi_Tools/lib/kindlefix.py b/Kindle_Mobi_Tools/lib/kindlefix.py new file mode 100644 index 0000000..7281915 --- /dev/null +++ b/Kindle_Mobi_Tools/lib/kindlefix.py @@ -0,0 +1,156 @@ +import prc, sys, struct +from binascii import hexlify + +def strByte(s,off=0): + return struct.unpack(">B",s[off])[0]; + +def strSWord(s,off=0): + return struct.unpack(">h",s[off:off+2])[0]; + +def strWord(s,off=0): + return struct.unpack(">H",s[off:off+2])[0]; + +def strDWord(s,off=0): + return struct.unpack(">L",s[off:off+4])[0]; + +def strPutDWord(s,off,i): + return s[:off]+struct.pack(">L",i)+s[off+4:]; + +keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" + +#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 find_key(rec0, pid): + off1 = strDWord(rec0, 0xA8) + if off1==0xFFFFFFFF or off1==0: + print "No DRM" + return None + size1 = strDWord(rec0, 0xB0) + cnt = strDWord(rec0, 0xAC) + flag = strDWord(rec0, 0xB4) + + temp_key = PC1(keyvec1, pid.ljust(16,'\0'), False) + cksum = 0 + #print pid, "->", hexlify(temp_key) + for i in xrange(len(temp_key)): + cksum += ord(temp_key[i]) + cksum &= 0xFF + temp_key = temp_key.ljust(16,'\0') + #print "pid cksum: %02X"%cksum + + #print "Key records: %02X-%02X, count: %d, flag: %02X"%(off1, off1+size1, cnt, flag) + iOff = off1 + drm_key = None + for i in xrange(cnt): + dwCheck = strDWord(rec0, iOff) + dwSize = strDWord(rec0, iOff+4) + dwType = strDWord(rec0, iOff+8) + nCksum = strByte(rec0, iOff+0xC) + #print "Key record %d: check=%08X, size=%d, type=%d, cksum=%02X"%(i, dwCheck, dwSize, dwType, nCksum) + if nCksum==cksum: + drmInfo = PC1(temp_key, rec0[iOff+0x10:iOff+0x30]) + dw0, dw4, dw18, dw1c = struct.unpack(">II16xII", drmInfo) + #print "Decrypted drmInfo:", "%08X, %08X, %s, %08X, %08X"%(dw0, dw4, hexlify(drmInfo[0x8:0x18]), dw18, dw1c) + #print "Decrypted drmInfo:", hexlify(drmInfo) + if dw0==dwCheck: + print "Found the matching record; setting the CustomDRM flag for Kindle" + drmInfo = strPutDWord(drmInfo,4,(dw4|0x800)) + dw0, dw4, dw18, dw1c = struct.unpack(">II16xII", drmInfo) + #print "Updated drmInfo:", "%08X, %08X, %s, %08X, %08X"%(dw0, dw4, hexlify(drmInfo[0x8:0x18]), dw18, dw1c) + return rec0[:iOff+0x10] + PC1(temp_key, drmInfo, False) + rec0[:iOff+0x30] + iOff += dwSize + return None + +def replaceext(filename, newext): + nameparts = filename.split(".") + if len(nameparts)>1: + return (".".join(nameparts[:-1]))+newext + else: + return nameparts[0]+newext + +def main(fname, pid): + if len(pid)==10 and pid[-3]=='*': + pid = pid[:-2] + if len(pid)!=8 or pid[-1]!='*': + print "PID is not valid! (should be in format AAAAAAA*DD)" + return 3 + db = prc.File(fname) + #print dir(db) + if db.getDBInfo()["creator"]!='MOBI': + print "Not a Mobi file!" + return 1 + rec0 = db.getRecord(0)[0] + enc = strSWord(rec0, 0xC) + print "Encryption:", enc + if enc!=2: + print "Unknown encryption type" + return 1 + + if len(rec0)<0x28 or rec0[0x10:0x14] != 'MOBI': + print "bad file format" + return 1 + print "Mobi publication type:", strDWord(rec0, 0x18) + formatVer = strDWord(rec0, 0x24) + print "Mobi format version:", formatVer + last_rec = strWord(rec0, 8) + dwE0 = 0 + if formatVer>=4: + new_rec0 = find_key(rec0, pid) + if new_rec0: + db.setRecordIdx(0,new_rec0) + else: + print "PID doesn't match this file" + return 2 + else: + print "Wrong Mobi format version" + return 1 + + outfname = replaceext(fname, ".azw") + if outfname==fname: + outfname = replaceext(fname, "_fixed.azw") + db.save(outfname) + print "Output written to "+outfname + return 0 + +print "The Kindleizer v0.1. Copyright (c) 2007 Igor Skochinsky" +if len(sys.argv)<3: + print "Fixes encrypted Mobipocket books to be readable by Kindle" + print "Usage: kindlefix.py file.mobi PID" +else: + fname = sys.argv[1] + sys.exit(main(fname, sys.argv[2])) diff --git a/Kindle_Mobi_Tools/lib/kindlepid.py b/Kindle_Mobi_Tools/lib/kindlepid.py new file mode 100644 index 0000000..70a3f35 --- /dev/null +++ b/Kindle_Mobi_Tools/lib/kindlepid.py @@ -0,0 +1,45 @@ +import sys, binascii + +letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" + +def crc32(s): + return (~binascii.crc32(s,-1))&0xFFFFFFFF + +def checksumPid(s): + crc = crc32(s) + crc = crc ^ (crc >> 16) + res = s + l = len(letters) + for i in (0,1): + b = crc & 0xff + pos = (b // l) ^ (b % l) + res += letters[pos%l] + crc >>= 8 + + return res + + +def pidFromSerial(s, l): + crc = crc32(s) + + arr1 = [0]*l + for i in xrange(len(s)): + arr1[i%l] ^= ord(s[i]) + + crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff] + for i in xrange(l): + arr1[i] ^= crc_bytes[i&3] + + pid = "" + for i in xrange(l): + b = arr1[i] & 0xff + pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))] + + return pid + +print "Mobipocket PID calculator for Amazon Kindle. Copyright (c) 2007 Igor Skochinsky " +if len(sys.argv)>1: + pid = pidFromSerial(sys.argv[1],7)+"*" + print "Mobipocked PID for Kindle serial# "+sys.argv[1]+" is "+checksumPid(pid) +else: + print "Usage: kindlepid.py " diff --git a/Kindle_Mobi_Tools/lib/readme.txt b/Kindle_Mobi_Tools/lib/readme.txt new file mode 100644 index 0000000..d4eb89d --- /dev/null +++ b/Kindle_Mobi_Tools/lib/readme.txt @@ -0,0 +1,34 @@ +Kindle Mobipocket tools 0.1 +Copyright (c) 2007 Igor Skochinsky + +These scripts allow one to read legally purchased Secure Mobipocket books +on Amazon Kindle. + +* kindlepid.py + This script generates Mobipocket PID from the Kindle serial number. That + PID can then be added at a Mobi retailer site and used for downloading + books locked to the Kindle. + + Example: + + > kindlepid.py B001BAB012345678 + Mobipocket PID calculator for Amazon Kindle. Copyright (c) 2007 Igor Skochinsky + Mobipocked PID for Kindle serial# B001BAB012345678 is V176CXM*FZ + +* kindlefix.py + This script adds a "CustomDRM" flag necessary for opening Secure + Mobipocket books on Kindle. The book has to be enabled for Kindle's PID + (generated by kindlepid.py). The "fixed" book is written with + extension ".azw". That file can then be uploaded to Kindle for reading. + + Example: + > kindlefix.py MyBook.mobi V176CXM*FZ + The Kindleizer v0.1. Copyright (c) 2007 Igor Skochinsky + Encryption: 2 + Mobi publication type: 2 + Mobi format version: 4 + Found the matching record; setting the CustomDRM flag for Kindle + Output written to MyBook.azw + +* History + 2007-12-12 Initial release