Created
January 29, 2025 20:51
-
-
Save notmarek/f41548dc5ac1950182a2f0cee566e496 to your computer and use it in GitHub Desktop.
Simple script to grab latest direct ota url for your kindle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from Crypto.Hash import SHA256 | |
| from Crypto.PublicKey import ECC | |
| from Crypto.Signature import DSS | |
| from urllib.parse import urlparse | |
| import re | |
| import json | |
| import requests | |
| import datetime | |
| import base64 | |
| import sys | |
| class Kindle: | |
| def __init__(self, token_file="token.json"): | |
| # you can grab your token from /var/local/token/amzn.acountid, it should work as is | |
| tokens = json.load(open(token_file, "r")) | |
| self.ADP_SIGNING_KEY = ECC.import_key(tokens["device_private_key"]) | |
| self.ECDSA_SIGNER = DSS.new( | |
| self.ADP_SIGNING_KEY, "deterministic-rfc6979", "der" | |
| ) | |
| self.ADP_TOKEN = tokens["device_token"] | |
| self.HEADERS = { | |
| "User-Agent": "Java/1.8.0_341-ea", | |
| "Content-Type": "application/json", | |
| } | |
| self.SESSION = requests.session() | |
| def get_latest_ota(self, codename): | |
| request = { | |
| "buildDimensions": { | |
| "platform": "juno", | |
| "ro.build.lab126.sign.type": "release", | |
| }, | |
| "inventory": [ | |
| { | |
| "softwareComponentId": f"com.lab126.eink.{codename}.os", | |
| "softwareComponentVersionCode": 1, | |
| }, | |
| { | |
| "softwareComponentId": f"com.lab126.eink.{codename}.app.nh", | |
| "softwareComponentVersionCode": 0, | |
| }, | |
| ], | |
| "softwareComponentTypes": ["OS", "APPLICATION"], | |
| "demoModeStatus": "NOT_ENABLED", | |
| } | |
| res = self.SESSION.send( | |
| self.signed_request( | |
| "POST", | |
| "https://softwareupdates.amazon.com/software/inventory2", | |
| headers={"Content-type": "application/x-www-form-urlencoded"}, | |
| body=request, | |
| ) | |
| ) | |
| return res.json() | |
| def signed_request(self, method, url, headers=None, body=None): | |
| # adapted from amazon_api.py signature type is different for the kindle ECDSA vs RSA | |
| if not headers: | |
| headers = {} | |
| if isinstance(body, object): | |
| body = json.dumps(body) | |
| elif not body: | |
| body = "" | |
| date = datetime.datetime.now(datetime.timezone.utc).isoformat("T")[:-7] + "Z" | |
| u = urlparse(url) | |
| path = f"{u.path}" | |
| if u.query != "": | |
| path += f"{u.params}?{u.query}" | |
| data = f"{method}\n{path}\n{date}\n{body}\n{self.ADP_TOKEN}" | |
| signed_encoded = base64.b64encode( | |
| self.ECDSA_SIGNER.sign(SHA256.new(data.encode())) | |
| ) | |
| signature = f"{signed_encoded.decode()}:{date}" | |
| headers.update(self.HEADERS) | |
| headers.update( | |
| { | |
| "x-adp-token": self.ADP_TOKEN, | |
| "x-adp-alg": "SHA256withECDSA:1.0", | |
| "x-adp-signature": signature, | |
| } | |
| ) | |
| return requests.Request(method, url, headers, data=body).prepare() | |
| def yoink_model_info(): | |
| res = requests.get("https://kindlemodding.org/models.json") | |
| matches = re.findall('"board":\\s*?"(.*?)",', res.text) | |
| for i in range(len(matches) - 1, -1, -1): | |
| match = matches[i] | |
| if "/" in match: | |
| matches.remove(match) | |
| matches.extend(match.split("/")) | |
| return [m.lower() for m in matches] | |
| if __name__ == "__main__": | |
| token_path = sys.argv[1] if len(sys.argv) > 1 else "" | |
| codename = sys.argv[2] if len(sys.argv) > 2 else "" | |
| if token_path == "": | |
| print("You can grab a token file for your own kindle by doing \"cat /var/local/token/$(cat /var/local/token/activeprofile.txt)\" on your jailbroken kindle.") | |
| k = Kindle(input("Path to tokens.json: ") if token_path == "" else token_path) | |
| if codename == "": | |
| models = yoink_model_info() | |
| print("Grabbed model names :)") | |
| for model in models: | |
| res = k.get_latest_ota(model) | |
| if res["availableUpdates"] != []: | |
| print(f"Latest OTA info for {model}: ") | |
| print(json.dumps(res, indent=4)) | |
| break | |
| else: | |
| res = k.get_latest_ota(codename) | |
| if res["availableUpdates"] != []: | |
| print(f"Latest OTA info for {codename}: ") | |
| print(json.dumps(res, indent=4)) | |
| else: | |
| print(json.dumps(res, indent=4)) | |
| print(f"Codename ({codename}) doesn't match provided key, make sure its lowercase or try the normal bruteforce method.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment