mirror of
				https://github.com/noDRM/DeDRM_tools.git
				synced 2025-10-23 23:07:47 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			172 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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 prc, 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(argv=sys.argv):
 | |
|   print "The Kindleizer v0.2. 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"
 | |
|       return 1
 | |
|   fname = sys.argv[1]
 | |
|   pid = sys.argv[2]
 | |
|   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
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     sys.exit(main())
 | 
