Compare commits

...

2 Commits

Author SHA1 Message Date
NoDRM a553a71f45 Fix font decryption with multiple IDs (#347) 11 months ago
NoDRM 740b46546f Try to add support for new K4PC
Co-authored-by: Andrew Innes <andrew.c12@gmail.com>
Co-authored-by: Satsuoni <satsuoni@hotmail.com>
11 months ago

@ -89,3 +89,5 @@ List of changes since the fork of Apprentice Harper's repository:
- PDF: Ignore invalid PDF objids unless the script is running in strict mode. Fixes some PDFs, apparently. Fixes #233.
- Bugfix: EPUBs with remaining content in the encryption.xml after decryption weren't written correctly.
- Support for Adobe's 'aes128-cbc-uncompressed' encryption method (fixes #242).
- Two bugfixes for Amazon DeDRM from Satuoni ( https://github.com/noDRM/DeDRM_tools/issues/315#issuecomment-1508305428 ) and andrewc12 ( https://github.com/andrewc12/DeDRM_tools/commit/d9233d61f00d4484235863969919059f4d0b2057 ) that might make the plugin work with newer versions.
- Fix font decryption not working with some books (fixes #347), thanks for the patch @bydioeds.

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# epubfontdecrypt.py
# Copyright © 2021 by noDRM
# Copyright © 2021-2023 by noDRM
# Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/>
@ -10,6 +10,7 @@
# Revision history:
# 1 - Initial release
# 2 - Bugfix for multiple book IDs, reported at #347
"""
Decrypts / deobfuscates font files in EPUB files
@ -18,7 +19,7 @@ Decrypts / deobfuscates font files in EPUB files
from __future__ import print_function
__license__ = 'GPL v3'
__version__ = "1"
__version__ = "2"
import os
import traceback
@ -193,9 +194,10 @@ def decryptFontsBook(inpath, outpath):
pass
try:
identify_element = container.find(packageNS("metadata")).find(metadataDCNS("identifier"))
if (secret_key_name is None or secret_key_name == identify_element.get("id")):
font_master_key = identify_element.text
identify_elements = container.find(packageNS("metadata")).findall(metadataDCNS("identifier"))
for element in identify_elements:
if (secret_key_name is None or secret_key_name == element.get("id")):
font_master_key = element.text
except:
pass

@ -835,6 +835,107 @@ def obfuscate(secret, version):
return obfuscated
# scramble() and obfuscate2() from https://github.com/andrewc12/DeDRM_tools/commit/d9233d61f00d4484235863969919059f4d0b2057
def scramble(st,magic):
ret=bytearray(len(st))
padlen=len(st)
for counter in range(len(st)):
ivar2=(padlen//2)-2*(counter%magic)+magic+counter-1
ret[ivar2%padlen]=st[counter]
return ret
def obfuscate2(secret, version):
if version == 1: # v1 does not use obfuscation
return secret
magic, word = OBFUSCATION_TABLE["V%d" % version]
# extend secret so that its length is divisible by the magic number
if len(secret) % magic != 0:
secret = secret + b'\x00' * (magic - len(secret) % magic)
obfuscated = bytearray(len(secret))
wordhash = bytearray(hashlib.sha256(word).digest()[16:])
#print(wordhash.hex())
shuffled = bytearray(scramble(secret,magic))
for i in range(0, len(secret)):
obfuscated[i] = shuffled[i] ^ wordhash[i % 16]
return obfuscated
# scramble3() and obfuscate3() from https://github.com/Satsuoni/DeDRM_tools/commit/da6b6a0c911b6d45fe1b13042b690daebc1cc22f
def scramble3(st,magic):
ret=bytearray(len(st))
padlen=len(st)
divs = padlen // magic
cntr = 0
iVar6 = 0
offset = 0
if (0 < ((magic - 1) + divs)):
while True:
if (offset & 1) == 0 :
uVar4 = divs - 1
if offset < divs:
iVar3 = 0
uVar4 = offset
else:
iVar3 = (offset - divs) + 1
if uVar4>=0:
iVar5 = uVar4 * magic
index = ((padlen - 1) - cntr)
while True:
if (magic <= iVar3): break
ret[index] = st[iVar3 + iVar5]
iVar3 = iVar3 + 1
cntr = cntr + 1
uVar4 = uVar4 - 1
iVar5 = iVar5 - magic
index -= 1
if uVar4<=-1: break
else:
if (offset < magic):
iVar3 = 0
else :
iVar3 = (offset - magic) + 1
if (iVar3 < divs):
uVar4 = offset
if (magic <= offset):
uVar4 = magic - 1
index = ((padlen - 1) - cntr)
iVar5 = iVar3 * magic
while True:
if (uVar4 < 0) : break
iVar3 += 1
ret[index] = st[uVar4 + iVar5]
uVar4 -= 1
index=index-1
iVar5 = iVar5 + magic;
cntr += 1;
if iVar3>=divs: break
offset = offset + 1
if offset >= ((magic - 1) + divs) :break
return ret
#not sure if the third variant is used anywhere, but it is in Kindle, so I tried to add it
def obfuscate3(secret, version):
if version == 1: # v1 does not use obfuscation
return secret
magic, word = OBFUSCATION_TABLE["V%d" % version]
# extend secret so that its length is divisible by the magic number
if len(secret) % magic != 0:
secret = secret + b'\x00' * (magic - len(secret) % magic)
#secret = bytearray(secret)
obfuscated = bytearray(len(secret))
wordhash = bytearray(hashlib.sha256(word).digest())
#print(wordhash.hex())
shuffled=bytearray(scramble3(secret,magic))
#print(shuffled)
# shuffle secret and xor it with the first half of the word hash
for i in range(0, len(secret)):
obfuscated[i] = shuffled[i] ^ wordhash[i % 16]
return obfuscated
class DrmIonVoucher(object):
envelope = None
version = None
@ -878,12 +979,25 @@ class DrmIonVoucher(object):
else:
_assert(False, "Unknown lock parameter: %s" % param)
sharedsecret = obfuscate(shared, self.version)
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
b = aes.decrypt(self.ciphertext)
b = pkcs7unpad(b, 16)
sharedsecrets = [obfuscate(shared, self.version),obfuscate2(shared, self.version),obfuscate3(shared, self.version)]
decrypted=False
ex=None
for sharedsecret in sharedsecrets:
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
try:
b = aes.decrypt(self.ciphertext)
b = pkcs7unpad(b, 16)
decrypted=True
print("Decryption succeeded")
break
except Exception as ex:
print("Decryption failed, trying next fallback ")
if not decrypted:
raise ex
sharedsecret = obfuscate(shared, self.version)
self.drmkey = BinaryIonParser(BytesIO(b))
addprottable(self.drmkey)

Loading…
Cancel
Save