mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-20 04:58:56 +00:00
Compare commits
8 Commits
ed0a34cbff
...
d2808d83d9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2808d83d9 | ||
|
|
808dc7d29a | ||
|
|
2cd2792306 | ||
|
|
2e53d70e88 | ||
|
|
05fff5217b | ||
|
|
34c4c067e8 | ||
|
|
195ea69537 | ||
|
|
ad33aea18d |
@@ -255,7 +255,7 @@ class EreaderProcessor(object):
|
|||||||
encrypted_key = r[172:172+8]
|
encrypted_key = r[172:172+8]
|
||||||
encrypted_key_sha = r[56:56+20]
|
encrypted_key_sha = r[56:56+20]
|
||||||
self.content_key = des.decrypt(encrypted_key)
|
self.content_key = des.decrypt(encrypted_key)
|
||||||
if sha1(self.content_key).digest() != encrypted_key_sha:
|
if hashlib.sha1(self.content_key).digest() != encrypted_key_sha:
|
||||||
raise ValueError('Incorrect Name and/or Credit Card')
|
raise ValueError('Incorrect Name and/or Credit Card')
|
||||||
|
|
||||||
def getNumImages(self):
|
def getNumImages(self):
|
||||||
|
|||||||
@@ -1,25 +1,19 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
"""ion.py: Decrypt Kindle KFX files.
|
||||||
|
|
||||||
# ion.py
|
Revision history:
|
||||||
# Copyright © 2013-2020 Apprentice Harper et al.
|
Pascal implementation by lulzkabulz.
|
||||||
|
BinaryIon.pas + DrmIon.pas + IonSymbols.pas
|
||||||
__license__ = 'GPL v3'
|
1.0 - Python translation by apprenticenaomi.
|
||||||
__version__ = '3.0'
|
1.1 - DeDRM integration by anon.
|
||||||
|
1.2 - Added pylzma import fallback
|
||||||
# Revision history:
|
1.3 - Fixed lzma support for calibre 4.6+
|
||||||
# Pascal implementation by lulzkabulz.
|
2.0 - VoucherEnvelope v2/v3 support by apprenticesakuya.
|
||||||
# BinaryIon.pas + DrmIon.pas + IonSymbols.pas
|
3.0 - Added Python 3 compatibility for calibre 5.0
|
||||||
# 1.0 - Python translation by apprenticenaomi.
|
|
||||||
# 1.1 - DeDRM integration by anon.
|
|
||||||
# 1.2 - Added pylzma import fallback
|
|
||||||
# 1.3 - Fixed lzma support for calibre 4.6+
|
|
||||||
# 2.0 - VoucherEnvelope v2/v3 support by apprenticesakuya.
|
|
||||||
# 3.0 - Added Python 3 compatibility for calibre 5.0
|
|
||||||
|
|
||||||
|
Copyright © 2013-2020 Apprentice Harper et al.
|
||||||
"""
|
"""
|
||||||
Decrypt Kindle KFX files.
|
from __future__ import annotations
|
||||||
"""
|
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -30,6 +24,9 @@ import struct
|
|||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__version__ = '3.0'
|
||||||
|
|
||||||
#@@CALIBRE_COMPAT_CODE@@
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
|
|
||||||
@@ -1096,11 +1093,11 @@ def process_V9888(st):
|
|||||||
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
||||||
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
||||||
ws.shuffle(repl)
|
ws.shuffle(repl)
|
||||||
ws.shuffle(repl)
|
ws.shuffle(repl)
|
||||||
ws.shuffle(repl)
|
ws.shuffle(repl)
|
||||||
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
||||||
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
||||||
ws.exlookup(d0x6a0be4d0)
|
ws.exlookup(d0x6a0be4d0)
|
||||||
dat=ws.mask(st[sto:sto+16])
|
dat=ws.mask(st[sto:sto+16])
|
||||||
out+=dat
|
out+=dat
|
||||||
sto+=16
|
sto+=16
|
||||||
@@ -1124,7 +1121,7 @@ def process_V4648(st):
|
|||||||
ws.sbox(d0x6a0c51a8,d0x6a0dab50,[1,3])
|
ws.sbox(d0x6a0c51a8,d0x6a0dab50,[1,3])
|
||||||
ws.shuffle(repl)
|
ws.shuffle(repl)
|
||||||
ws.shuffle(repl)
|
ws.shuffle(repl)
|
||||||
ws.exlookup(d0x6a0c91a8)
|
ws.exlookup(d0x6a0c91a8)
|
||||||
dat=ws.mask(st[sto:sto+16])
|
dat=ws.mask(st[sto:sto+16])
|
||||||
out+=dat
|
out+=dat
|
||||||
sto+=16
|
sto+=16
|
||||||
@@ -1148,7 +1145,7 @@ def process_V5683(st):
|
|||||||
ws.shuffle(repl)
|
ws.shuffle(repl)
|
||||||
ws.sbox(d0x6a0cfe80,d0x6a0dab50,[])
|
ws.sbox(d0x6a0cfe80,d0x6a0dab50,[])
|
||||||
ws.shuffle(repl)
|
ws.shuffle(repl)
|
||||||
ws.exlookup(d0x6a0d3e80)
|
ws.exlookup(d0x6a0d3e80)
|
||||||
dat=ws.mask(st[sto:sto+16])
|
dat=ws.mask(st[sto:sto+16])
|
||||||
out+=dat
|
out+=dat
|
||||||
sto+=16
|
sto+=16
|
||||||
@@ -1163,12 +1160,12 @@ def process_V5683(st):
|
|||||||
# if a<0: a=256+a
|
# if a<0: a=256+a
|
||||||
# ax.append(ha[(a>>4)]+ha[a%16])
|
# ax.append(ha[(a>>4)]+ha[a%16])
|
||||||
# return "".join(ax)
|
# return "".join(ax)
|
||||||
#
|
#
|
||||||
# def memhex(adr,sz):
|
# def memhex(adr,sz):
|
||||||
# emu=EmulatorHelper(currentProgram)
|
# emu=EmulatorHelper(currentProgram)
|
||||||
# arr=emu.readMemory(getAddress(adr),sz)
|
# arr=emu.readMemory(getAddress(adr),sz)
|
||||||
# return a2hex(arr)
|
# return a2hex(arr)
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1254,7 +1251,7 @@ def scramble3(st,magic):
|
|||||||
iVar5 = iVar5 - magic
|
iVar5 = iVar5 - magic
|
||||||
index -= 1
|
index -= 1
|
||||||
if uVar4<=-1: break
|
if uVar4<=-1: break
|
||||||
else:
|
else:
|
||||||
if (offset < magic):
|
if (offset < magic):
|
||||||
iVar3 = 0
|
iVar3 = 0
|
||||||
else :
|
else :
|
||||||
@@ -1274,9 +1271,9 @@ def scramble3(st,magic):
|
|||||||
index=index-1
|
index=index-1
|
||||||
iVar5 = iVar5 + magic;
|
iVar5 = iVar5 + magic;
|
||||||
cntr += 1;
|
cntr += 1;
|
||||||
if iVar3>=divs: break
|
if iVar3>=divs: break
|
||||||
offset = offset + 1
|
offset = offset + 1
|
||||||
if offset >= ((magic - 1) + divs) :break
|
if offset >= ((magic - 1) + divs) :break
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
#not sure if the third variant is used anywhere, but it is in Kindle, so I tried to add it
|
#not sure if the third variant is used anywhere, but it is in Kindle, so I tried to add it
|
||||||
@@ -1342,14 +1339,14 @@ class DrmIonVoucher(object):
|
|||||||
_assert(False, "Unknown lock parameter: %s" % param)
|
_assert(False, "Unknown lock parameter: %s" % param)
|
||||||
|
|
||||||
|
|
||||||
# i know that version maps to scramble pretty much 1 to 1, but there was precendent where they changed it, so...
|
# 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),
|
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_V9708(shared), process_V1031(shared), process_V2069(shared), process_V9041(shared),
|
||||||
process_V3646(shared), process_V6052(shared), process_V9479(shared), process_V9888(shared),
|
process_V3646(shared), process_V6052(shared), process_V9479(shared), process_V9888(shared),
|
||||||
process_V4648(shared), process_V5683(shared)]
|
process_V4648(shared), process_V5683(shared)]
|
||||||
|
|
||||||
decrypted=False
|
decrypted=False
|
||||||
ex=None
|
lastexception: Exception | None = None
|
||||||
for sharedsecret in sharedsecrets:
|
for sharedsecret in sharedsecrets:
|
||||||
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
||||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||||
@@ -1361,14 +1358,15 @@ class DrmIonVoucher(object):
|
|||||||
|
|
||||||
_assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
|
_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())
|
"Expected KeySet, got %s" % self.drmkey.gettypename())
|
||||||
decrypted=True
|
decrypted=True
|
||||||
|
|
||||||
print("Decryption succeeded")
|
print("Decryption succeeded")
|
||||||
break
|
break
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
lastexception = ex
|
||||||
print("Decryption failed, trying next fallback ")
|
print("Decryption failed, trying next fallback ")
|
||||||
if not decrypted:
|
if not decrypted:
|
||||||
raise ex
|
raise lastexception
|
||||||
|
|
||||||
self.drmkey.stepin()
|
self.drmkey.stepin()
|
||||||
while self.drmkey.hasnext():
|
while self.drmkey.hasnext():
|
||||||
|
|||||||
@@ -374,7 +374,11 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
result['success'] = False
|
result['success'] = False
|
||||||
result['fileobj'] = None
|
result['fileobj'] = None
|
||||||
|
|
||||||
zin = zipfile.ZipFile(book.filename, 'r')
|
try:
|
||||||
|
zin = zipfile.ZipFile(book.filename, 'r')
|
||||||
|
except FileNotFoundError:
|
||||||
|
print (_('{0} - File "{1}" not found. Make sure the eBook has been properly downloaded in the Kobo app.').format(PLUGIN_NAME, book.filename))
|
||||||
|
return result
|
||||||
#print ('Kobo library filename: {0}'.format(book.filename))
|
#print ('Kobo library filename: {0}'.format(book.filename))
|
||||||
for userkey in self.userkeys:
|
for userkey in self.userkeys:
|
||||||
print (_('Trying key: '), codecs.encode(userkey, 'hex'))
|
print (_('Trying key: '), codecs.encode(userkey, 'hex'))
|
||||||
|
|||||||
@@ -327,36 +327,50 @@ class KoboLibrary(object):
|
|||||||
elif sys.platform.startswith('darwin'):
|
elif sys.platform.startswith('darwin'):
|
||||||
self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "Kobo Desktop Edition")
|
self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "Kobo Desktop Edition")
|
||||||
elif sys.platform.startswith('linux'):
|
elif sys.platform.startswith('linux'):
|
||||||
|
# Since on Linux, you have to run Kobo Desktop within Wine,
|
||||||
|
# there is no guarantee where Kobo.sqlite (and the rest of
|
||||||
|
# the kobo directory) might be.
|
||||||
|
#
|
||||||
|
# It should also be noted that Kobo Desktop will store all
|
||||||
|
# of it files in the current directory where you run it,
|
||||||
|
# meaning that a user might accidentally create several
|
||||||
|
# Kobo.sqlite files which are all separate and then be
|
||||||
|
# confused why Obok can't find any of the new books. Sadly
|
||||||
|
# there isn't a trivial way to deal with this.
|
||||||
|
|
||||||
#sets ~/.config/calibre as the location to store the kobodir location info file and creates this directory if necessary
|
# We cache the kobodir we find in ~/.config/calibre.
|
||||||
kobodir_cache_dir = os.path.join(os.environ['HOME'], ".config", "calibre")
|
kobodir_cache_dir = os.path.join(os.environ['HOME'], ".config", "calibre", "plugins", "obok")
|
||||||
if not os.path.isdir(kobodir_cache_dir):
|
if not os.path.isdir(kobodir_cache_dir):
|
||||||
os.mkdir(kobodir_cache_dir)
|
os.mkdir(kobodir_cache_dir)
|
||||||
|
kobodir_cache_file = os.path.join(kobodir_cache_dir, "kobo-location")
|
||||||
#appends the name of the file we're storing the kobodir location info to the above path
|
|
||||||
kobodir_cache_file = str(kobodir_cache_dir) + "/" + "kobo location"
|
|
||||||
|
|
||||||
"""if the above file does not exist, recursively searches from the root
|
|
||||||
of the filesystem until kobodir is found and stores the location of kobodir
|
|
||||||
in that file so this loop can be skipped in the future"""
|
|
||||||
original_stdout = sys.stdout
|
|
||||||
if not os.path.isfile(kobodir_cache_file):
|
|
||||||
for root, dirs, files in os.walk('/'):
|
|
||||||
for file in files:
|
|
||||||
if file == 'Kobo.sqlite':
|
|
||||||
kobo_linux_path = str(root)
|
|
||||||
with open(kobodir_cache_file, 'w') as f:
|
|
||||||
sys.stdout = f
|
|
||||||
print(kobo_linux_path, end='')
|
|
||||||
sys.stdout = original_stdout
|
|
||||||
|
|
||||||
f = open(kobodir_cache_file, 'r' )
|
try:
|
||||||
self.kobodir = f.read()
|
# If the cached version exists and the path does really
|
||||||
|
# contain Kobo.sqlite, use that.
|
||||||
|
with open(kobodir_cache_file, "r") as f:
|
||||||
|
cached_kobodir = f.read().strip()
|
||||||
|
assert os.path.isfile(os.path.join(cached_kobodir, "Kobo.sqlite"))
|
||||||
|
self.kobodir = cached_kobodir
|
||||||
|
except (AssertionError, FileNotFoundError):
|
||||||
|
# If there was no cached version, search the entire
|
||||||
|
# filesystem tree for a directory containing
|
||||||
|
# "Kobo.sqlite".
|
||||||
|
#
|
||||||
|
# We first search $HOME to avoid picking another user's
|
||||||
|
# Kobo.sqlite file, but then fallback to / if there was
|
||||||
|
# nothing in $HOME.
|
||||||
|
for candidate_root in (os.environ["HOME"], "/"):
|
||||||
|
for root, _, files in os.walk(candidate_root):
|
||||||
|
if "Kobo.sqlite" in files:
|
||||||
|
with open(kobodir_cache_file, "w") as f:
|
||||||
|
f.write("%s/\n" % (root,))
|
||||||
|
self.kobodir = root
|
||||||
|
break
|
||||||
|
|
||||||
# desktop versions use Kobo.sqlite
|
# Desktop versions use Kobo.sqlite.
|
||||||
kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
|
kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
|
||||||
# check for existence of file
|
# check for existence of file
|
||||||
if (not(os.path.isfile(kobodb))):
|
if not os.path.isfile(kobodb):
|
||||||
# give up here, we haven't found anything useful
|
# give up here, we haven't found anything useful
|
||||||
self.kobodir = u""
|
self.kobodir = u""
|
||||||
kobodb = u""
|
kobodb = u""
|
||||||
@@ -430,7 +444,7 @@ class KoboLibrary(object):
|
|||||||
macaddrs = []
|
macaddrs = []
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
c = re.compile('\s?(' + '[0-9a-f]{2}[:\-]' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
c = re.compile('\s?(' + '[0-9a-f]{2}[:\-]' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||||
try:
|
try:
|
||||||
output = subprocess.Popen('ipconfig /all', shell=True, stdout=subprocess.PIPE, text=True).stdout
|
output = subprocess.Popen('ipconfig /all', shell=True, stdout=subprocess.PIPE, text=True).stdout
|
||||||
for line in output:
|
for line in output:
|
||||||
m = c.search(line)
|
m = c.search(line)
|
||||||
@@ -449,9 +463,15 @@ class KoboLibrary(object):
|
|||||||
for m in matches:
|
for m in matches:
|
||||||
# print "m:{0}".format(m[0])
|
# print "m:{0}".format(m[0])
|
||||||
macaddrs.append(m[0].upper())
|
macaddrs.append(m[0].upper())
|
||||||
|
elif sys.platform.startswith('linux'):
|
||||||
|
for interface in os.listdir('/sys/class/net'):
|
||||||
|
with open('/sys/class/net/' + interface + '/address', 'r') as f:
|
||||||
|
mac = f.read().strip().upper()
|
||||||
|
# some interfaces, like Tailscale's VPN interface, do not have a MAC address
|
||||||
|
if mac != '':
|
||||||
|
macaddrs.append(mac)
|
||||||
else:
|
else:
|
||||||
# probably linux
|
# final fallback
|
||||||
|
|
||||||
# let's try ip
|
# let's try ip
|
||||||
c = re.compile('\s(' + '[0-9a-f]{2}:' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
c = re.compile('\s(' + '[0-9a-f]{2}:' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||||
for line in os.popen('ip -br link'):
|
for line in os.popen('ip -br link'):
|
||||||
|
|||||||
Reference in New Issue
Block a user