mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-20 04:58:56 +00:00
Compare commits
6 Commits
fb8b003444
...
autoreleas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ae77c438f | ||
|
|
abc5de018e | ||
|
|
133e67fa03 | ||
|
|
f86cff285b | ||
|
|
a553a71f45 | ||
|
|
740b46546f |
44
.github/workflows/main.yml
vendored
44
.github/workflows/main.yml
vendored
@@ -9,8 +9,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Package
|
||||
run: python3 make_release.py
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@@ -18,3 +20,45 @@ jobs:
|
||||
path: |
|
||||
DeDRM_tools_*.zip
|
||||
DeDRM_tools.zip
|
||||
|
||||
- name: Delete old release
|
||||
uses: cb80/delrel@latest
|
||||
with:
|
||||
tag: autorelease
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: Delete old tag
|
||||
uses: dev-drprasad/delete-tag-and-release@v1.0
|
||||
with:
|
||||
tag_name: autorelease
|
||||
github_token: ${{ github.token }}
|
||||
delete_release: true
|
||||
|
||||
- name: Prepare release
|
||||
run: cp DeDRM_tools.zip DeDRM_alpha_${{ github.sha }}.zip
|
||||
|
||||
- name: Auto-release
|
||||
id: autorelease
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: autorelease
|
||||
token: ${{ github.token }}
|
||||
name: Automatic alpha release with latest changes
|
||||
body: |
|
||||
This release is automatically generated by Github for each commit.
|
||||
|
||||
This means, every time a change is made to this repo, this release will be updated to contain an untested copy of the plugin at that stage. This will contain the most up-to-date code, but it's not tested at all and may be broken.
|
||||
|
||||
Last update based on Git commit ${{ github.sha }}.
|
||||
prerelease: true
|
||||
draft: true
|
||||
files: DeDRM_alpha_${{ github.sha }}.zip
|
||||
|
||||
- name: Make release public
|
||||
uses: irongut/EditRelease@v1.2.0
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
id: ${{ steps.autorelease.outputs.id }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
|
||||
|
||||
@@ -89,3 +89,6 @@ 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.
|
||||
- Fix a couple unicode errors for Python2 in Kindle and Nook code.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -54,15 +54,26 @@ def getNookLogFiles():
|
||||
paths = set()
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
if sys.version_info[0] == 2:
|
||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
else:
|
||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
if 'USERPROFILE' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Local"
|
||||
if sys.version_info[0] == 2:
|
||||
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Local"
|
||||
else:
|
||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Local"
|
||||
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Roaming"
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming"
|
||||
else:
|
||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Roaming"
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
# User Shell Folders show take precedent over Shell Folders if present
|
||||
|
||||
@@ -57,6 +57,7 @@ except ImportError:
|
||||
# Windows-friendly choice: pylzma wheels
|
||||
import pylzma as lzma
|
||||
|
||||
from kfxtables import *
|
||||
|
||||
TID_NULL = 0
|
||||
TID_BOOLEAN = 1
|
||||
@@ -769,6 +770,7 @@ def pkcs7unpad(msg, blocklen):
|
||||
|
||||
|
||||
# every VoucherEnvelope version has a corresponding "word" and magic number, used in obfuscating the shared secret
|
||||
# 4-digit versions use their own obfuscation/scramble. It does not seem to depend on the "word" and number
|
||||
OBFUSCATION_TABLE = {
|
||||
"V1": (0x00, None),
|
||||
"V2": (0x05, b'Antidisestablishmentarianism'),
|
||||
@@ -779,26 +781,26 @@ OBFUSCATION_TABLE = {
|
||||
"V7": (0x05, b'\x10\x1bJ\x18\nh!\x10"\x03>Z\'\r\x01]W\x06\x1c\x1e?\x0f\x13'),
|
||||
"V8": (0x09, b"K\x0c6\x1d\x1a\x17pO}Rk\x1d'w1^\x1f$\x1c{C\x02Q\x06\x1d`"),
|
||||
"V9": (0x05, b'X.\x0eW\x1c*K\x12\x12\t\n\n\x17Wx\x01\x02Yf\x0f\x18\x1bVXPi\x01'),
|
||||
"V10": (0x07, b'z3\n\x039\x12\x13`\x06=v,\x02MTK\x1e%}L\x1c\x1f\x15\x0c\x11\x02\x0c\n8\x17p'),
|
||||
"V10": (0x07, b'z3\n\x039\x12\x13`\x06=v;\x02MTK\x1e%}L\x1c\x1f\x15\x0c\x11\x02\x0c\n8\x17p'),
|
||||
"V11": (0x05, b'L=\nhVm\x07go\n6\x14\x06\x16L\r\x02\x0b\x0c\x1b\x04#p\t'),
|
||||
"V12": (0x06, b',n\x1d\rl\x13\x1c\x13\x16p\x14\x07U\x0c\x1f\x19w\x16\x16\x1d5T'),
|
||||
"V12": (0x06, b';n\x1d\rl\x13\x1c\x13\x16p\x14\x07U\x0c\x1f\x19w\x16\x16\x1d5T'),
|
||||
"V13": (0x07, b'I\x05\t\x08\x03r)\x01$N\x0fr3n\x0b062D\x0f\x13'),
|
||||
"V14": (0x05, b"\x03\x02\x1c9\x19\x15\x15q\x1057\x08\x16\x0cF\x1b.Fw\x01\x12\x03\x13\x02\x17S'hk6"),
|
||||
"V15": (0x0A, b'&,4B\x1dcI\x0bU\x03I\x07\x04\x1c\t\x05c\x07%ws\x0cj\t\x1a\x08\x0f'),
|
||||
"V16": (0x0A, b'\x06\x18`h,b><\x06PqR\x02Zc\x034\n\x16\x1e\x18\x06#e'),
|
||||
"V16": (0x0A, b'\x06\x18`h;b><\x06PqR\x02Zc\x034\n\x16\x1e\x18\x06#e'),
|
||||
"V17": (0x07, b'y\r\x12\x08fw.[\x02\t\n\x13\x11\x0c\x11b\x1e8L\x10(\x13<Jx6c\x0f'),
|
||||
"V18": (0x07, b'I\x0b\x0e,\x19\x1aIa\x10s\x19g\\\x1b\x11!\x18yf\x0f\t\x1d7[bSp\x03'),
|
||||
"V18": (0x07, b'I\x0b\x0e;\x19\x1aIa\x10s\x19g\\\x1b\x11!\x18yf\x0f\t\x1d7[bSp\x03'),
|
||||
"V19": (0x05, b'\n6>)N\x02\x188\x016s\x13\x14\x1b\x16jeN\n\x146\x04\x18\x1c\x0c\x19\x1f,\x02]'),
|
||||
"V20": (0x08, b'_\r\x01\x12]\\\x14*\x17i\x14\r\t!\x1e,~hZ\x12jK\x17\x1e*1'),
|
||||
"V20": (0x08, b'_\r\x01\x12]\\\x14*\x17i\x14\r\t!\x1e;~hZ\x12jK\x17\x1e*1'),
|
||||
"V21": (0x07, b'e\x1d\x19|\ty\x1di|N\x13\x0e\x04\x1bj<h\x13\x15k\x12\x08=\x1f\x16~\x13l'),
|
||||
"V22": (0x08, b'?\x17yi$k7Pc\tEo\x0c\x07\x07\t\x1f,*i\x12\x0cI0\x10I\x1a?2\x04'),
|
||||
"V23": (0x08, b'\x16+db\x13\x04\x18\rc%\x14\x17\x0f\x13F\x0c[\t9\x1ay\x01\x1eH'),
|
||||
"V24": (0x06, b'|6\\\x1a\r\x10\nP\x07\x0fu\x1f\t,\rr`uv\\~55\x11]N'),
|
||||
"V25": (0x09, b'\x07\x14w\x1e,^y\x01:\x08\x07\x1fr\tU#j\x16\x12\x1eB\x04\x16=\x06fZ\x07\x02\x06'),
|
||||
"V24": (0x06, b'|6\\\x1a\r\x10\nP\x07\x0fu\x1f\t;\rr`uv\\~55\x11]N'),
|
||||
"V25": (0x09, b'\x07\x14w\x1e;^y\x01:\x08\x07\x1fr\tU#j\x16\x12\x1eB\x04\x16=\x06fZ\x07\x02\x06'),
|
||||
"V26": (0x06, b'\x03IL\x1e"K\x1f\x0f\x1fp0\x01`X\x02z0`\x03\x0eN\x07'),
|
||||
"V27": (0x07, b'Xk\x10y\x02\x18\x10\x17\x1d,\x0e\x05e\x10\x15"e\x0fh(\x06s\x1c\x08I\x0c\x1b\x0e'),
|
||||
"V28": (0x0A, b'6P\x1bs\x0f\x06V.\x1cM\x14\x02\n\x1b\x07{P0:\x18zaU\x05'),
|
||||
"V9708": (0x05, b'\x1diIm\x08a\x17\x1e!am\x1d\x1aQ.\x16!\x06*\}x04\x11\t\x06\x04?'),
|
||||
"V9708": (0x05, b'\x1diIm\x08a\x17\x1e!am\x1d\x1aQ.\x16!\x06*\x04\x11\t\x06\x04?'),
|
||||
"V1031": (0x08, b'Antidisestablishmentarianism'),
|
||||
"V2069": (0x07, b'Floccinaucinihilipilification'),
|
||||
"V9041": (0x06, b'>\x14\x0c\x12\x10-\x13&\x18U\x1d\x05Rlt\x03!\x19\x1b\x13\x04]Y\x19,\t\x1b'),
|
||||
@@ -807,10 +809,367 @@ OBFUSCATION_TABLE = {
|
||||
"V9479": (0x09, b'\x10\x1bJ\x18\nh!\x10"\x03>Z\'\r\x01]W\x06\x1c\x1e?\x0f\x13'),
|
||||
"V9888": (0x05, b"K\x0c6\x1d\x1a\x17pO}Rk\x1d'w1^\x1f$\x1c{C\x02Q\x06\x1d`"),
|
||||
"V4648": (0x07, b'X.\x0eW\x1c*K\x12\x12\t\n\n\x17Wx\x01\x02Yf\x0f\x18\x1bVXPi\x01'),
|
||||
"V5683": (0x05, b'z3\n\x039\x12\x13`\x06=v,\x02MTK\x1e%}L\x1c\x1f\x15\x0c\x11\x02\x0c\n8\x17p'),
|
||||
"V5683": (0x05, b'z3\n\x039\x12\x13`\x06=v;\x02MTK\x1e%}L\x1c\x1f\x15\x0c\x11\x02\x0c\n8\x17p'),
|
||||
}
|
||||
|
||||
|
||||
#common str: "PIDv3AESAES/CBC/PKCS5PaddingHmacSHA256"
|
||||
class workspace(object):
|
||||
def __init__(self,initial_list):
|
||||
self.work=initial_list
|
||||
def shuffle(self,shuflist):
|
||||
ll=len(shuflist)
|
||||
rt=[]
|
||||
for i in range(ll):
|
||||
rt.append(self.work[shuflist[i]])
|
||||
self.work=rt
|
||||
def sbox(self,table,matrix,skplist=[]): #table is list of 4-byte integers
|
||||
offset=0
|
||||
nwork=list(self.work)
|
||||
wo=0
|
||||
toff=0
|
||||
while offset<0x6000:
|
||||
uv5=table[toff+nwork[wo+0]]
|
||||
uv1=table[toff+nwork[wo+1]+0x100]
|
||||
uv2=table[toff+nwork[wo+2]+0x200]
|
||||
uv3=table[toff+nwork[wo+3]+0x300]
|
||||
moff=0
|
||||
if 0 in skplist:
|
||||
moff+=0x400
|
||||
else:
|
||||
nib1=matrix[moff+offset+(uv1>>0x1c)|( (uv5>>0x18)&0xf0)]
|
||||
moff+=0x100
|
||||
nib2=matrix[moff+offset+(uv3>>0x1c)|( (uv2>>0x18)&0xf0)]
|
||||
moff+=0x100
|
||||
nib3=matrix[moff+offset+((uv1>>0x18)&0xf) |( (uv5>>0x14)&0xf0)]
|
||||
moff+=0x100
|
||||
nib4=matrix[moff+offset+((uv3>>0x18)&0xf) |( (uv2>>0x14)&0xf0)]
|
||||
moff+=0x100
|
||||
rnib1=matrix[moff+offset+nib1*0x10+nib2]
|
||||
moff+=0x100
|
||||
rnib2=matrix[moff+offset+nib3*0x10+nib4]
|
||||
moff+=0x100
|
||||
nwork[wo+0]=rnib1*0x10+rnib2
|
||||
if 1 in skplist:
|
||||
moff+=0x400
|
||||
else:
|
||||
nib1=matrix[moff+offset+((uv1>>0x14)&0xf)|( (uv5>>0x10)&0xf0)]
|
||||
moff+=0x100
|
||||
nib2=matrix[moff+offset+((uv3>>0x14)&0xf)|( (uv2>>0x10)&0xf0)]
|
||||
moff+=0x100
|
||||
nib3=matrix[moff+offset+((uv1>>0x10)&0xf) |( (uv5>>0xc)&0xf0)]
|
||||
moff+=0x100
|
||||
nib4=matrix[moff+offset+((uv3>>0x10)&0xf) |( (uv2>>0xc)&0xf0)]
|
||||
moff+=0x100
|
||||
|
||||
rnib1=matrix[moff+offset+nib1*0x10+nib2]
|
||||
moff+=0x100
|
||||
rnib2=matrix[moff+offset+nib3*0x10+nib4]
|
||||
moff+=0x100
|
||||
nwork[wo+1]=rnib1*0x10+rnib2
|
||||
if 2 in skplist:
|
||||
moff+=0x400
|
||||
else:
|
||||
nib1=matrix[moff+offset+((uv1>>0xc)&0xf)|( (uv5>>0x8)&0xf0)]
|
||||
moff+=0x100
|
||||
nib2=matrix[moff+offset+((uv3>>0xc)&0xf)|( (uv2>>0x8)&0xf0)]
|
||||
moff+=0x100
|
||||
nib3=matrix[moff+offset+((uv1>>0x8)&0xf) |( (uv5>>0x4)&0xf0)]
|
||||
moff+=0x100
|
||||
nib4=matrix[moff+offset+((uv3>>0x8)&0xf) |( (uv2>>0x4)&0xf0)]
|
||||
moff+=0x100
|
||||
rnib1=matrix[moff+offset+nib1*0x10+nib2]
|
||||
moff+=0x100
|
||||
rnib2=matrix[moff+offset+nib3*0x10+nib4]
|
||||
moff+=0x100
|
||||
nwork[wo+2]=rnib1*0x10+rnib2
|
||||
if 3 in skplist:
|
||||
moff+=0x400
|
||||
else:
|
||||
nib1=matrix[moff+offset+((uv1>>0x4)&0xf)|( (uv5)&0xf0)]
|
||||
moff+=0x100
|
||||
nib2=matrix[moff+offset+((uv3>>0x4)&0xf)|( (uv2)&0xf0)]
|
||||
moff+=0x100
|
||||
nib3=matrix[moff+offset+((uv1)&0xf)|( (uv5<<4)&0xf0) ]
|
||||
moff+=0x100
|
||||
nib4=matrix[moff+offset+((uv3)&0xf)|( (uv2<<4)&0xf0) ]
|
||||
moff+=0x100
|
||||
##############
|
||||
rnib1=matrix[moff+offset+nib1*0x10+nib2]
|
||||
moff+=0x100
|
||||
rnib2=matrix[moff+offset+nib3*0x10+nib4]
|
||||
moff+=0x100
|
||||
nwork[wo+3]=rnib1*0x10+rnib2
|
||||
offset = offset + 0x1800
|
||||
wo+=4
|
||||
toff+=0x400
|
||||
self.work=nwork
|
||||
def lookup(self,ltable):
|
||||
for a in range(len(self.work)):
|
||||
self.work[a]=ltable[a]
|
||||
def exlookup(self,ltable):
|
||||
lookoffs=0
|
||||
for a in range(len(self.work)):
|
||||
self.work[a]=ltable[self.work[a]+lookoffs]
|
||||
lookoffs+=0x100
|
||||
def mask(self, chunk):
|
||||
out=[]
|
||||
for a in range(len(chunk)):
|
||||
self.work[a]=self.work[a]^chunk[a]
|
||||
out.append(self.work[a])
|
||||
return out
|
||||
|
||||
def process_V9708(st):
|
||||
#e9c457a7dae6aa24365e7ef219b934b17ed58ee7d5329343fc3aea7860ed51f9a73de14351c9
|
||||
ws=workspace([0x11]*16)
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a06ea70,d0x6a0dab50)
|
||||
ws.sbox(d0x6a073a70,d0x6a0dab50)
|
||||
ws.shuffle(repl)
|
||||
ws.exlookup(d0x6a072a70)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16;
|
||||
return bytes(out)
|
||||
|
||||
def process_V1031(st):
|
||||
#d53efea7fdd0fda3e1e0ebbae87cad0e8f5ef413c471c3ae81f39222a9ec8b8ed582e045918c
|
||||
ws=workspace([0x06,0x18,0x60,0x68,0x3b,0x62,0x3e,0x3c,0x06,0x50,0x71,0x52,0x02,0x5a,0x63,0x03])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0797c0,d0x6a0dab50,[3])
|
||||
ws.sbox(d0x6a07e7c0,d0x6a0dab50,[3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0797c0,d0x6a0dab50,[3])
|
||||
ws.sbox(d0x6a07e7c0,d0x6a0dab50,[3])
|
||||
ws.exlookup(d0x6a07d7c0)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
#break
|
||||
return bytes(out)
|
||||
|
||||
def process_V2069(st):
|
||||
#8e6196d754a304c9354e91b5d79f07b048026d31c7373a8691e513f2c802c706742731caa858
|
||||
ws=workspace([0x79,0x0d,0x12,0x08,0x66,0x77,0x2e,0x5b,0x02,0x09,0x0a,0x13,0x11,0x0c,0x11,0x62])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a084498,d0x6a0dab50,[2])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a089498,d0x6a0dab50,[2])
|
||||
ws.sbox(d0x6a089498,d0x6a0dab50,[2])
|
||||
ws.sbox(d0x6a084498,d0x6a0dab50,[2])
|
||||
ws.shuffle(repl)
|
||||
ws.exlookup(d0x6a088498)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def process_V9041(st):
|
||||
#11f7db074b24e560dfa6fae3252b383c3b936e51f6ded570dc936cb1da9f4fc4a97ec686e7d8
|
||||
ws=workspace([0x49,0x0b,0x0e,0x3b,0x19,0x1a,0x49,0x61,0x10,0x73,0x19,0x67,0x5c,0x1b,0x11,0x21])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a094170,d0x6a0dab50,[1])
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a08f170,d0x6a0dab50,[1])
|
||||
ws.sbox(d0x6a08f170,d0x6a0dab50,[1])
|
||||
ws.sbox(d0x6a094170,d0x6a0dab50,[1])
|
||||
|
||||
ws.exlookup(d0x6a093170)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
#break
|
||||
return bytes(out)
|
||||
|
||||
def process_V3646(st):
|
||||
#d468aa362b44479282291983243b38197c4b4aa24c2c58e62c76ec4b81e08556ca0c54301664
|
||||
ws=workspace([0x0a,0x36,0x3e,0x29,0x4e,0x02,0x18,0x38,0x01,0x36,0x73,0x13,0x14,0x1b,0x16,0x6a])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a099e48,d0x6a0dab50,[2,3])
|
||||
ws.sbox(d0x6a09ee48,d0x6a0dab50,[2,3])
|
||||
ws.sbox(d0x6a09ee48,d0x6a0dab50,[2,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a099e48,d0x6a0dab50,[2,3])
|
||||
ws.sbox(d0x6a099e48,d0x6a0dab50,[2,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a09ee48,d0x6a0dab50,[2,3])
|
||||
ws.exlookup(d0x6a09de48)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def process_V6052(st):
|
||||
#d683c8c4e4f46ae45812196f37e218eabce0fae08994f25fabb01d3e569b8bf3866b99d36f57
|
||||
ws=workspace([0x5f,0x0d,0x01,0x12,0x5d,0x5c,0x14,0x2a,0x17,0x69,0x14,0x0d,0x09,0x21,0x1e,0x3b])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0a4b20,d0x6a0dab50,[1,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0a4b20,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0a9b20,d0x6a0dab50,[1,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0a9b20,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0a9b20,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0a4b20,d0x6a0dab50,[1,3])
|
||||
|
||||
ws.exlookup(d0x6a0a8b20)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
def process_V9479(st):
|
||||
#925635db434bccd3f4791eb87b89d2dfc7c93be06e794744eb9de58e6d721e696980680ab551
|
||||
ws=workspace([0x65,0x1d,0x19,0x7c,0x09,0x79,0x1d,0x69,0x7c,0x4e,0x13,0x0e,0x04,0x1b,0x6a,0x3c ])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a0af7f8,d0x6a0dab50,[1,2,3])
|
||||
ws.sbox(d0x6a0af7f8,d0x6a0dab50,[1,2,3])
|
||||
ws.sbox(d0x6a0b47f8,d0x6a0dab50,[1,2,3])
|
||||
ws.sbox(d0x6a0af7f8,d0x6a0dab50,[1,2,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0b47f8,d0x6a0dab50,[1,2,3])
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0b47f8,d0x6a0dab50,[1,2,3])
|
||||
ws.exlookup(d0x6a0b37f8)
|
||||
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
def process_V9888(st):
|
||||
#54c470723f8c105ba0186b6319050869de673ce31a5ec15d4439921d4cd05c5e860cb2a41fea
|
||||
ws=workspace([0x3f,0x17,0x79,0x69,0x24,0x6b,0x37,0x50,0x63,0x09,0x45,0x6f,0x0c,0x07,0x07,0x09])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
||||
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
||||
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
||||
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
||||
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
||||
ws.exlookup(d0x6a0be4d0)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
def process_V4648(st):
|
||||
#705bd4cd8b61d4596ef4ca40774d68e71f1f846c6e94bd23fd26e5c127e0beaa650a50171f1b
|
||||
ws=workspace([0x16,0x2b,0x64,0x62,0x13,0x04,0x18,0x0d,0x63,0x25,0x14,0x17,0x0f,0x13,0x46,0x0c])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a0ca1a8,d0x6a0dab50,[1,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0ca1a8,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0c51a8,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0ca1a8,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0c51a8,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0c51a8,d0x6a0dab50,[1,3])
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.exlookup(d0x6a0c91a8)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
def process_V5683(st):
|
||||
#1f5af733423e5104afb9d5594e682ecf839a776257f33747c9beee671c57ab3f84943f69d8fd
|
||||
ws=workspace([0x7c,0x36,0x5c,0x1a,0x0d,0x10,0x0a,0x50,0x07,0x0f,0x75,0x1f,0x09,0x3b,0x0d,0x72])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a0d4e80,d0x6a0dab50,[])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0cfe80,d0x6a0dab50,[])
|
||||
ws.sbox(d0x6a0d4e80,d0x6a0dab50,[])
|
||||
ws.sbox(d0x6a0cfe80,d0x6a0dab50,[])
|
||||
ws.sbox(d0x6a0d4e80,d0x6a0dab50,[])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0cfe80,d0x6a0dab50,[])
|
||||
ws.shuffle(repl)
|
||||
ws.exlookup(d0x6a0d3e80)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
|
||||
# def a2hex(arr):
|
||||
# ax=[]
|
||||
# ha="0123456789abcdef"
|
||||
# for a in arr:
|
||||
# if a<0: a=256+a
|
||||
# ax.append(ha[(a>>4)]+ha[a%16])
|
||||
# return "".join(ax)
|
||||
#
|
||||
# def memhex(adr,sz):
|
||||
# emu=EmulatorHelper(currentProgram)
|
||||
# arr=emu.readMemory(getAddress(adr),sz)
|
||||
# return a2hex(arr)
|
||||
#
|
||||
|
||||
|
||||
|
||||
|
||||
# obfuscate shared secret according to the VoucherEnvelope version
|
||||
def obfuscate(secret, version):
|
||||
if version == 1: # v1 does not use obfuscation
|
||||
@@ -835,6 +1194,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,18 +1338,34 @@ 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)
|
||||
# i know that version maps to scramble pretty much 1 to 1, but there was precendent where they changed it, so...
|
||||
sharedsecrets = [obfuscate(shared, self.version),obfuscate2(shared, self.version),obfuscate3(shared, self.version),
|
||||
process_V9708(shared), process_V1031(shared), process_V2069(shared), process_V9041(shared),
|
||||
process_V3646(shared), process_V6052(shared), process_V9479(shared), process_V9888(shared),
|
||||
process_V4648(shared), process_V5683(shared)]
|
||||
|
||||
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)
|
||||
self.drmkey = BinaryIonParser(BytesIO(b))
|
||||
addprottable(self.drmkey)
|
||||
|
||||
self.drmkey = BinaryIonParser(BytesIO(b))
|
||||
addprottable(self.drmkey)
|
||||
_assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
|
||||
"Expected KeySet, got %s" % self.drmkey.gettypename())
|
||||
decrypted=True
|
||||
|
||||
_assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
|
||||
"Expected KeySet, got %s" % self.drmkey.gettypename())
|
||||
print("Decryption succeeded")
|
||||
break
|
||||
except Exception as ex:
|
||||
print("Decryption failed, trying next fallback ")
|
||||
if not decrypted:
|
||||
raise ex
|
||||
|
||||
self.drmkey.stepin()
|
||||
while self.drmkey.hasnext():
|
||||
|
||||
5771
DeDRM_plugin/kfxtables.py
Normal file
5771
DeDRM_plugin/kfxtables.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -279,7 +279,10 @@ if iswindows:
|
||||
path = ""
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
if sys.version_info[0] == 2:
|
||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
else:
|
||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
# this is just another alternative.
|
||||
# path = getEnvironmentVariable('LOCALAPPDATA')
|
||||
if not os.path.isdir(path):
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
# topazextract.py
|
||||
# Mostly written by some_updates based on code from many others
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ DeDRM tools for ebooks
|
||||
|
||||
This is a fork of Apprentice Harper's version of the DeDRM tools. Apprentice Harper said that the original version of the plugin [is no longer maintained](https://github.com/apprenticeharper/DeDRM_tools#no-longer-maintained), so I've taken over, merged a bunch of open PRs, and added a ton more features and bugfixes.
|
||||
|
||||
The latest stable (released) version is v10.0.3 which [can be downloaded here](https://github.com/noDRM/DeDRM_tools/releases/tag/v10.0.3). The latest `master` build (will be automatically updated with every code change, may be unstable) [can be found here](https://github.com/noDRM/DeDRM_tools/releases/tag/autorelease).
|
||||
|
||||
Take a look at [the CHANGELOG](https://github.com/noDRM/DeDRM_tools/blob/master/CHANGELOG.md) to see a list of changes since the last version by Apprentice Harper (v7.2.1). This plugin will start with version v10.0.0.
|
||||
|
||||
The v10.0.0 versions of this plugin should both work with Calibre 5.x (Python 3) as well as Calibre 4.x and lower (Python 2). If you encounter issues with this plugin in Calibre 4.x or lower, please open a bug report.
|
||||
|
||||
Reference in New Issue
Block a user