From 1e9607d9e19c4fd4410170418a9ee90691a08edd Mon Sep 17 00:00:00 2001 From: zx Date: Sun, 8 Sep 2024 19:29:59 -0400 Subject: [PATCH] feat: change app icons --- README.md | 5 ++++- cyan/__main__.py | 4 ++++ cyan/logic.py | 2 ++ cyan/tbhtypes/app_bundle.py | 43 +++++++++++++++++++++++++++++++++++++ cyan/tbhtypes/plist.py | 3 +++ cyan/tbhutils.py | 3 +++ 6 files changed, 59 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4daccda..431970d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ more coming soon! i'm trying to copy pyzule in order to deprecate it in favor of - change app name, version, bundle id, and minimum os version - remove UISupportedDevices - remove watch app +- change the app icon - fakesign the output ipa/app - thin all binaries to arm64, it can LARGELY reduce app size sometimes! - remove all app extensions (or just encrypted ones!) @@ -30,6 +31,8 @@ also obviously install python, version 3.9 or greater is required (the version a **if you want to inject dylibs AND ARE NOT ON iOS,** make sure you install lief (you only have to do this once): `pip install -U lief` +**if you want to change app icons (iOS NOT supported),** also make sure you install pillow: `pip install Pillow` + then finally, to install or update cyan, just `pip install --force-reinstall git+https://github.com/asdfzxcvbn/pyzule-rw.git#egg=cyan` ## todo @@ -46,7 +49,7 @@ then finally, to install or update cyan, just `pip install --force-reinstall git [x] feat: plugin operations (-q, -e) -[] feat: change app icon +[x] feat: change app icon [] feat: .cyan files (lol rip .pyzule files) diff --git a/cyan/__main__.py b/cyan/__main__.py index fbd3f19..dd4e1e9 100755 --- a/cyan/__main__.py +++ b/cyan/__main__.py @@ -42,6 +42,10 @@ def main() -> None: "-m", metavar="minimum", help="modify the app's minimum OS version" ) + parser.add_argument( + "-k", metavar="icon", + help="modify the app's icon" + ) parser.add_argument( "-u", "--remove-supported-devices", action="store_true", diff --git a/cyan/logic.py b/cyan/logic.py index 8d9b870..f104e98 100644 --- a/cyan/logic.py +++ b/cyan/logic.py @@ -55,6 +55,8 @@ def main(parser: ArgumentParser) -> None: app.plist.change_bundle_id(args.b) if args.m is not None: app.plist.change_minimum_version(args.m) + if args.k is not None: + app.change_icon(args.k, tmpdir) if args.remove_supported_devices: app.plist.remove_uisd() diff --git a/cyan/tbhtypes/app_bundle.py b/cyan/tbhtypes/app_bundle.py index cfa00f6..9066ed6 100644 --- a/cyan/tbhtypes/app_bundle.py +++ b/cyan/tbhtypes/app_bundle.py @@ -1,6 +1,7 @@ import os import shutil from glob import glob +from uuid import uuid4 from typing import Optional, Literal from .executable import Executable @@ -101,3 +102,45 @@ class AppBundle: else: print("[*] removed encrypted plugins:", ", ".join(removed)) + def change_icon(self, path: str, tmpdir: str) -> None: + try: + from PIL import Image + except Exception: + return print("[?] pillow is not installed, -k is not available") + + tmpath = f"{tmpdir}/icon.png" + if not path.endswith(".png"): + with Image.open(path) as img: + img.save(tmpath, "PNG") + else: + shutil.copyfile(path, tmpath) + + uid = f"cyan_{uuid4().hex[:7]}a" # can't have it end with a num + i60 = f"{uid}60x60" + i76 = f"{uid}76x76" + + with Image.open(tmpath) as img: + img.resize((120, 120)).save(f"{self.path}/{i60}@2x.png", "PNG") + img.resize((152, 152)).save(f"{self.path}/{i76}@2x~ipad.png", "PNG") + + if "CFBundleIcons" not in self.plist: + self.plist["CFBundleIcons"] = {} + if "CFBundleIcons~ipad" not in self.plist: + self.plist["CFBundleIcons~ipad"] = {} + + self.plist["CFBundleIcons"] = self.plist["CFBundleIcons"] | { + "CFBundlePrimaryIcon": { + "CFBundleIconFiles": [i60], + "CFBundleIconName": uid + } + } + self.plist["CFBundleIcons~ipad"] = self.plist["CFBundleIcons~ipad"] | { + "CFBundlePrimaryIcon": { + "CFBundleIconFiles": [i60, i76], + "CFBundleIconName": uid + } + } + + self.plist.save() + print("[*] updated app icon") + diff --git a/cyan/tbhtypes/plist.py b/cyan/tbhtypes/plist.py index 1d5c023..5cb045b 100644 --- a/cyan/tbhtypes/plist.py +++ b/cyan/tbhtypes/plist.py @@ -25,6 +25,9 @@ class Plist: def __setitem__(self, key: str, val: Any) -> None: self.data[key] = val + def __contains__(self, key: str) -> bool: + return key in self.data + def save(self) -> None: with open(self.path, "wb") as f: plistlib.dump(self.data, f) diff --git a/cyan/tbhutils.py b/cyan/tbhutils.py index ef9e15d..334d533 100644 --- a/cyan/tbhutils.py +++ b/cyan/tbhutils.py @@ -54,6 +54,9 @@ def validate_inputs(args: Namespace) -> Optional[str]: ): sys.exit(f"[!] invalid OS version: {args.m}") + if args.k is not None and not os.path.isfile(args.k): + sys.exit(f"[!] {args.k} does not exist") + def get_app(path: str, tmpdir: str, is_ipa: bool) -> str: payload = f"{tmpdir}/Payload"