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'<!DOCTYPE') : dec.index(b'</html>') + 7]
dec = b'<?xml version="1.0"?>\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('<dc:title>(.+)</dc:title>', 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))