gkpln3 / evrit-to-kindle

Decrypts encrypted books from Evrit to allow sending them to Kindle devices, Please don't use this to pirate books. העברה של ספרים מאפליקציית עברית לקינדל.
GNU General Public License v3.0
23 stars 1 forks source link

New Simple method #7

Closed goldeneye5599 closed 1 year ago

goldeneye5599 commented 1 year ago

Evrit has web app similar to the Android app, It decryptes the book too, so this code extract the decrypted book. Instructions in the top of the file

By the way, in the webapp it uses aes in CBC mode, so I guess that it uses the same encryption in the Android app too.

Code Is Here ```python import asyncio import subprocess import json import base64 import zipfile import shutil import re from pathlib import Path from io import BytesIO from mitmproxy import options from mitmproxy.tools import dump from Crypto.Cipher import AES """ This program export encrypted books from read.e-vrit.co.il and decrypt them Please don't use this to pirate books You must install chrome browser on windows and it should located at C:/Program Files/Google/Chrome/Application/chrome.exe Install dependencies: pip install mitmproxy pycryptodome Then run the program python main.py navigate to the book you want to download, open it and it will be downloaded automatically then you can open and read it with https://calibre-ebook.com/ and also you can convert it to PDF using calibre """ CHROME_PATH = Path('C:/Program Files/Google/Chrome/Application/chrome.exe') APP_URL = 'https://read.e-vrit.co.il/main' def decrypt_book(book_b64, key): book = base64.b64decode(book_b64) zf = zipfile.ZipFile(BytesIO(book)) zf.extractall('book') # open each page recursively inside book/ folder which ends with .xhtml, read, decrypt, and rewrite for file in Path('book/').rglob('*.xhtml'): with file.open('rb') as f: page = f.read() try: cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC) dec = cipher.decrypt(page) # for some reason in the beggining and end of file it's corrupted try: dec = dec[dec.index(b'') + 7] dec = b'\n' + dec except Exception as e: pass with file.open('wb') as f: f.write(dec) except Exception as e: print(e) with file.open('wb') as f: f.write(page) # fix font size in css book_css = Path('book/OEBPS/css/idGeneratedStyles.css') if book_css.exists(): with book_css.open('r') as f: css = f.read() css += """\n p { font-size: 2em; padding: 12px; } """ with book_css.open('w') as f: f.write(css) # extract title, remove username title = 'book' content_file = Path('book/OEBPS/content.opf') if content_file.exists(): with content_file.open('r', encoding='utf-8') as f: content = f.read() title_re = re.search('(.+)', content) if title_re: title = title_re.group(1) content = re.sub('data-username="[^"]+"', '', content) with content_file.open('w', encoding='utf-8') as f: f.write(content) # Write decrypted EPUB with zipfile.ZipFile(f'{title}.epub', "w", zipfile.ZIP_DEFLATED) as zipf: for file_path in Path('book').rglob("*"): if file_path.is_file(): zipf.write(file_path, arcname=file_path.relative_to('book')) # Delete extracted files shutil.rmtree('book') print(f'Written {title}.epub') class JSInjector: def __init__(self) -> None: self.key = None self.book = None self.in_progress = False self.keys_decoded = [] def response(self, flow): if self.in_progress: return headers = flow.response.headers headers["Cache-Control"] = "no-cache, must-revalidate" # inject js code if 'main.d0465d0b.chunk.js' in flow.request.url: print('Editing JS') # clear indexedDB so the books will re fetched flow.response.content = b""" async function clearDatabases() { const r = await window.indexedDB.databases() for (var i = 0; i < r.length; i++) window.indexedDB.deleteDatabase(r[i].name); alert('Please Refresh the page one time, ignore if already') } clearDatabases() """ + flow.response.content # Inject js so it will send book key back to the proxy # This function decrypts pages of the book pattern = b"return _0x247dee['a']['utils'][_0x4fdc0c(0x43c)][_0x4fdc0c(0x301)](_0x150308);" replace = b"fetch(`127.0.0.1/sendKey?key=${_0x5dddc0}`);" + pattern flow.response.content = flow.response.content.replace(pattern, replace) if 'sendKey' in flow.request.url: # extract book key self.key = flow.request.url.split('=')[1] if 'GetBook' in flow.request.url: # get base64 encrypted book self.book = json.loads(flow.response.get_text())['ResponseData']['Book'] print('Got book') if self.key and self.book and self.key not in self.keys_decoded: self.in_progress = True decrypt_book(self.book, self.key) self.keys_decoded.append(self.key) self.in_progress = False async def start_proxy(host, port): opts = options.Options(listen_host=host, listen_port=port, ssl_insecure=True) master = dump.DumpMaster( opts, with_termlog=False, with_dumper=False, ) master.addons.add(JSInjector()) await master.run() return master if __name__ == '__main__': cmd = [ CHROME_PATH, '--proxy-server=http://localhost:8090', '--ignore-certificate-errors', '--disable-application-cache', '--new-window', APP_URL ] subprocess.Popen(cmd, shell=True) host = 'localhost' port = 8090 asyncio.run(start_proxy(host, port)) ```
gkpln3 commented 1 year ago

Cool! When I made this the web app was off due to security reasons 😅 Want to add this code to the repo? You can open a pull request

goldeneye5599 commented 1 year ago

https://github.com/gkpln3/evrit-to-kindle/pull/8

ron94 commented 5 months ago

@goldeneye5599 your code which was merged (and then was reverted by the repo owner) has been working till recently. I see that they have published a new version of the file named 5.e78ff86c.chunk.js so your injector code is now not up to date anymore. Is there a chance that you can guide how to make it working again?

omerfrydman commented 3 months ago

@ron94 did you manage to solve this issue?

ron94 commented 3 months ago

@omerfrydman no