mirror of
https://github.com/NohamR/pyzule-rw.git
synced 2026-10-10 18:39:41 +00:00
155 lines
4.2 KiB
Python
155 lines
4.2 KiB
Python
import os
|
|
import sys
|
|
import shutil
|
|
import zipfile
|
|
import platform
|
|
import subprocess
|
|
from typing import Optional
|
|
from glob import glob, iglob
|
|
from argparse import Namespace
|
|
from importlib import resources # type: ignore
|
|
from tempfile import TemporaryDirectory
|
|
|
|
|
|
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):
|
|
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.normpath(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)
|
|
|
|
|
|
def get_app(path: str, tmpdir: str, is_ipa: bool) -> tuple[str, 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]
|
|
plist = f"{app}/Info.plist"
|
|
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((plist := 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, plist
|
|
|
|
|
|
def get_tools_dir() -> tuple[str, str]:
|
|
mach = platform.machine()
|
|
system = platform.system()
|
|
|
|
if "iPhone" in mach or "iPad" in mach:
|
|
mach = "iOS"
|
|
|
|
with resources.files() as files: # type: ignore
|
|
return (
|
|
str(files), # type: ignore
|
|
str(files / "tools" / system / mach) # type: ignore
|
|
)
|
|
|
|
|
|
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
|
|
|
|
|
|
def extract_deb(deb: str, tweaks: dict[str, str], tmpdir: str) -> None:
|
|
with TemporaryDirectory(prefix=tmpdir + "/", delete=False) as t2:
|
|
if platform.system() == "Linux":
|
|
tool = ["ar", "-x", deb, f"--output={t2}"]
|
|
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}/**/*.bundle", recursive=True),
|
|
glob(f"{t2}/**/*.appex", recursive=True),
|
|
glob(f"{t2}/**/*.framework", recursive=True)
|
|
), []): # type: ignore
|
|
if os.path.islink(hi):
|
|
continue # symlinks are broken iirc
|
|
|
|
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)
|
|
|
|
with zipfile.ZipFile(
|
|
output, "w", zipfile.ZIP_DEFLATED, compresslevel=level
|
|
) as zf:
|
|
for f in iglob("Payload/**", recursive=True):
|
|
zf.write(f)
|
|
|