Files
pyzule-rw/cyan/tbhutils.py

217 lines
5.9 KiB
Python

import os
import sys
import json
import shutil
import zipfile
import platform
import subprocess
from uuid import uuid4
from glob import glob, iglob
from argparse import Namespace
from typing import Optional, Any
def validate_inputs(args: Namespace) -> Optional[str]:
if not (
args.i.endswith(".ipa")
or args.i.endswith(".app")
):
return "the input file must be an ipa/app"
if not os.path.exists(args.i):
return f"{args.i} does not exist"
if os.path.exists(args.o):
if args.overwrite:
print(f"[*] {args.o} already exists; overwriting")
else:
try:
overwrite = input(
f"[<] {args.o} already exists, overwrite it? [Y/n] "
if args.output is not None
else "[<] no output was specified. overwrite the input? [Y/n] "
).strip().lower()
except KeyboardInterrupt:
sys.exit("[>] bye!")
if overwrite not in ("y", "yes", ""):
print("[>] quitting.")
sys.exit(0)
if args.f is not None:
# dictionary ensures unique names
args.f = {os.path.basename(f): os.path.realpath(f) for f in args.f}
nonexistent = [f for f in args.f.values() if not os.path.exists(f)]
if len(nonexistent) != 0:
print("[!] please ensure the following file(s) exist:")
for ne in nonexistent:
print(f"[?] - {ne}")
sys.exit(1)
if (
args.m is not None
and any(char not in "0123456789." for char in args.m)
):
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")
if args.cyan is not None and not os.path.isfile(args.cyan):
sys.exit(f"[!] {args.cyan} does not exist")
def get_app(path: str, tmpdir: str, is_ipa: bool) -> str:
payload = f"{tmpdir}/Payload"
if is_ipa:
print("[*] extracting ipa..")
try:
with zipfile.ZipFile(path) as ipa:
names = ipa.namelist()
if not any(name.startswith("Payload/") for name in names):
raise KeyError
elif not any(name.endswith(".app/Info.plist") for name in names):
sys.exit("[!] no Info.plist, invalid app")
ipa.extractall(tmpdir)
app = glob(f"{payload}/*.app")[0]
except (KeyError, IndexError):
sys.exit("[!] couldn't find either Payload or app folder, invalid ipa")
except zipfile.BadZipFile:
sys.exit(f"[!] {path} is not a zipfile (ipa)")
print("[*] extracted ipa")
else:
if not os.path.isfile(f"{path}/Info.plist"):
sys.exit("[!] no Info.plist, invalid app")
print("[*] copying app..")
shutil.copytree(path, (app := f"{payload}/{os.path.basename(path)}"))
print("[*] copied app")
return app
def get_tools_dir() -> tuple[str, str]:
mach = platform.machine()
system = platform.system()
if "iPhone" in mach or "iPad" in mach:
mach = "iOS"
# thank god i dont have to use importlib,
# it's the WORST standard library package prior to 3.12
install_dir = os.path.dirname(__file__)
specific_dir = f"{install_dir}/tools/{system}/{mach}"
if not os.path.isdir(specific_dir):
sys.exit(f"[!] cyan is not supported on: {system} {mach}")
return (install_dir, specific_dir)
def delete_if_exists(path: str, bn: str) -> bool:
is_file = os.path.isfile(path)
try:
if is_file:
os.remove(path)
else:
shutil.rmtree(path)
print(f"[?] {bn} already existed, replacing")
return True
except FileNotFoundError:
return False
# damn it, literally EVERY FUCKING python version before 3.12 FUCKING SUCKS
# no `delete` in `TemporaryDirectory` ?! GREAT !!!
def extract_deb(deb: str, tweaks: dict[str, str], tmpdir: str) -> None:
t2 = f"{tmpdir}/{uuid4()}"
os.mkdir(t2)
if platform.system() == "Linux":
tool = ["ar", "-x", deb, f"--output={t2}"]
elif "iPhone" in platform.machine() or "iPad" in platform.machine():
os.chdir(t2) # BAHAHAHAHHAHA.
tool = ["ar", "-x", deb]
else:
tool = ["tar", "-xf", deb, f"--directory={t2}"]
try:
subprocess.run(tool, check=True)
except Exception:
sys.exit(f"[!] couldn't extract {os.path.basename(deb)}")
# it's not always "data.tar.gz"
data_tar = glob(f"{t2}/data.*")[0]
subprocess.run(["tar", "-xf", data_tar, f"--directory={t2}"])
for hi in sum((
glob(f"{t2}/**/*.dylib", recursive=True),
glob(f"{t2}/**/*.appex", recursive=True),
glob(f"{t2}/**/*.bundle", recursive=True),
glob(f"{t2}/**/*.framework", recursive=True)
), []): # type: ignore
if (
os.path.islink(hi) # symlinks are broken iirc
or hi.count(".bundle") > 1 # prevent sub-bundle detection (rip)
or hi.count(".framework") > 1
):
continue
tweaks[os.path.basename(hi)] = hi
print(f"[*] extracted {os.path.basename(deb)}")
del tweaks[os.path.basename(deb)]
def make_ipa(tmpdir: str, output: str, level: int) -> None:
# ensure names are written as Payload/...
os.chdir(tmpdir)
weird = 0
with zipfile.ZipFile(
output, "w", zipfile.ZIP_DEFLATED, compresslevel=level
) as zf:
for f in iglob("Payload/**", recursive=True):
try:
zf.write(f)
except ValueError:
weird += 1
if weird != 0:
print(f"[?] was unable to zip {weird} file(s) due to timestamps")
def parse_cyan(args: dict[str, Any], tmpdir: str) -> None:
print("[*] parsing .cyan file..")
with zipfile.ZipFile(args["cyan"]) as zf:
DOT_PATH = f"{tmpdir}/cyan"
os.mkdir(DOT_PATH)
with zf.open("config.json") as f:
config = json.load(f)
if "f" in config:
NAMES = [n for n in zf.namelist() if n.startswith("inject/")]
zf.extractall(DOT_PATH, NAMES)
# ensure not None
args["f"] = args["f"] if args["f"] is not None else {}
for e in os.scandir(f"{DOT_PATH}/inject"):
args["f"][e.name] = e.path
del config["f"]
if "k" in config:
args["k"] = zf.extract("icon.idk", DOT_PATH)
del config["k"]
for k, v in config.items():
args[k] = v