diff --git a/generate_json.py b/generate_json.py new file mode 100644 index 0000000..9d657b6 --- /dev/null +++ b/generate_json.py @@ -0,0 +1,105 @@ +from github import Github +import json +import argparse +import pandas as pd +from get_bundle_id import get_single_bundle_id +import os +import shutil + +REPO_NAME = "Realmzer/MySign-Repo" + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("-t", "--token", help="Github token") + args = parser.parse_args() + token = args.token + + out_file = "apps-repo.json" + + with open(out_file, "r") as f: + data = json.load(f) + + if os.path.isfile("bundleId.csv"): + df = pd.read_csv("bundleId.csv") + else: + df = pd.DataFrame(columns=["name", "bundleId", "genre"]) + + # clear apps + data["apps"] = [] + + g = Github(token) + repo = g.get_repo(REPO_NAME) + releases = repo.get_releases() + + for release in releases: + for asset in release.get_assets(): + if (spl := asset.name.split("."))[-1] not in ("ipa", "dylib", "deb"): + continue + IS_IPA = spl[-1] == "ipa" + name = ".".join(spl[:-1]) + date = asset.created_at.strftime("%Y-%m-%d") + full_date = asset.created_at.strftime("%Y%m%d%H%M%S") + try: + app_name, version, tweaks = name.split("_", 2) + tweaks, _ = tweaks.split("@", 1) + if tweaks: + tweaks = f"Injected with {tweaks[:-1].replace("_", " ")}" + except Exception: + app_name = name + version = "Unknown" + tweaks = None + + if IS_IPA: + if app_name in df.name.values: + info = {"bundle": df[df.name == app_name].bundleId.values[0], "genre": df[df.name == app_name].genre.values[0]} + else: + info: dict = get_single_bundle_id(asset.browser_download_url) + + if "error" in info: + print(f"[*] error detected in '{name}', deleting") + asset.delete_asset() + continue + + df = pd.concat([df, pd.DataFrame( + {"name": [app_name], "bundleId": [info["bundle"]], "genre": [info["genre"]]})], ignore_index=True) + + data["apps"].append({ + "name": app_name, + "type": int(info["genre"]), + "bundleID": str(info["bundle"]), + "bundleIdentifier": str(info["bundle"]), + "version": version, + "versionDate": date, + "fullDate": full_date, + "size": int(asset.size), + "down": asset.browser_download_url, + "downloadURL": asset.browser_download_url, + "developerName": "", + "localizedDescription": tweaks, + "icon": f"https://raw.githubusercontent.com/{REPO_NAME}/main/icons/{info["bundle"]}.png", + "iconURL": f"https://raw.githubusercontent.com/{REPO_NAME}/main/icons/{info["bundle"]}.png" + }) + else: + data["apps"].append({ + "name": app_name, + "type": 5, # type: dylib + "bundleId": f"com.mysign.{app_name.lower()}", + "bundleIdentifier": f"com.mysign.{app_name.lower()}", + "version": version, + "versionDate": date, + "fullDate": full_date, + "size": int(asset.size), + "down": asset.browser_download_url, + "downloadURL": asset.browser_download_url, + "developerName": "", + "localizedDescription": app_name, + "icon": "https://cdn1.realmzer.xyz//IMG_3830-tF.png", + "iconURL": "https://cdn1.realmzer.xyz//IMG_3830-tF.png" + }) + + data["apps"].sort(key=lambda x: x["fullDate"], reverse=True) + df.to_csv("bundleId.csv", index=False) + + with open(out_file, 'w') as json_file: + json.dump(data, json_file, indent=4) \ No newline at end of file diff --git a/get_bundle_id.py b/get_bundle_id.py new file mode 100644 index 0000000..379b023 --- /dev/null +++ b/get_bundle_id.py @@ -0,0 +1,96 @@ +import os +import zipfile +import plistlib +from tempfile import NamedTemporaryFile as NTF + +import requests +from PIL import Image + + +# returns genre id +def save_appstore_icon(bundle: str) -> dict: + x = requests.get(f"https://itunes.apple.com/lookup?bundleId={bundle}&limit=1&country=US").json() + try: + icon_url = x["results"][0]["artworkUrl512"] + genres = x["results"][0]["genreIds"] + except (KeyError, IndexError): + # type 1 = app + return {"genre": 1, "err": True} # invalid appstore app, will have to extract from ipa + + with NTF() as tmp: + tmp.write(requests.get(icon_url).content) + with Image.open(tmp.name) as img: + img.save(f"icons/{bundle}.png", "PNG") # usually jpg, so we save as png instead + + if "6014" in genres or any(genre.startswith("70") for genre in genres): + return {"genre": 2, "err": False} # type 2 = game + return {"genre": 1, "err": False} + + +# this is shit so gotta seperate into its own func lol +# TIL: the namelist doesnt always have the .app name?? +def get_app_name(nl: list[str]) -> str: + for name in nl: + if ".app/" in name and len(name.split("/")) >= 2: + return "/".join(name.split("/")[:2]) + return "" + + +# uses same method as seashell cli: +# https://github.com/EntySec/SeaShell/blob/8ae1ecba722ba303c961c537633b663717fcfbe7/seashell/core/ipa.py#L189 +def no_seashell(path: str) -> dict: + with zipfile.ZipFile(path) as zf: + app: str = get_app_name((nl := zf.namelist())) + + if f"{app}/mussel" in nl: + return {"unsafe": 1} + + # note: `CFBundleSignature` is now appearing in the real world? + # why is this even becoming an official key? whatever + + with zf.open((pl_name := f"{app}/Info.plist")) as pl: + plist = plistlib.load(pl) + + # if "CFBundleSignature" in plist: + # return {"unsafe": 1} + + return {"pl": plist, "nl": nl, "pl_name": pl_name} + + +# if called, guaranteed that icon is not yet saved +def get_single_bundle_id(url, name = "temp.ipa") -> dict: + with open(name, "wb") as f: + f.write(requests.get(url).content) + + os.makedirs("icons", exist_ok=True) + + try: + assert(zipfile.is_zipfile(name)) + except AssertionError: + print(f"[!] bad zipfile: {os.path.basename(url)} ({url})") + return {"error": 1} + + try: + assert("unsafe" not in (sscheck := no_seashell(name))) + except AssertionError: + print(f"[!] seashell detected in: {os.path.basename(url)} ({url})") + return {"error": 1} + + with zipfile.ZipFile(name) as archive: + bundleId = sscheck["pl"]["CFBundleIdentifier"] + + if (res := save_appstore_icon(bundleId))["err"]: + try: + icon_path = sscheck["pl"]["CFBundleIcons"]["CFBundlePrimaryIcon"]["CFBundleIconFiles"][0] + for name in sscheck["nl"]: + if icon_path in name: + icon_path = name # im so tired + break + except (KeyError, IndexError): + # is this doing what i think it's doing..? + icon_path = f"{os.path.dirname(sscheck["pl_name"])}/{sscheck["pl"]["CFBundleIconFiles"][0]}" + + with archive.open(icon_path) as orig, open(f"icons/{bundleId}.png", "wb") as new: + new.write(orig.read()) + + return {"bundle": bundleId, "genre": res["genre"]} \ No newline at end of file diff --git a/app-repo-altstore.json b/unused/app-repo-altstore.json similarity index 100% rename from app-repo-altstore.json rename to unused/app-repo-altstore.json diff --git a/app-repo-esign.json b/unused/app-repo-esign.json similarity index 100% rename from app-repo-esign.json rename to unused/app-repo-esign.json diff --git a/app-repo-feather.json b/unused/app-repo-feather.json similarity index 100% rename from app-repo-feather.json rename to unused/app-repo-feather.json diff --git a/app-repo-gbox.json b/unused/app-repo-gbox.json similarity index 100% rename from app-repo-gbox.json rename to unused/app-repo-gbox.json diff --git a/app-repo-scarlet.json b/unused/app-repo-scarlet.json similarity index 100% rename from app-repo-scarlet.json rename to unused/app-repo-scarlet.json