chore: python package

This commit is contained in:
zx
2024-09-03 19:46:22 -04:00
parent d7f38047be
commit 800bb8d5bf
11 changed files with 90 additions and 59 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
__pycache__/
build/
*.egg-info

3
MANIFEST.in Normal file
View File

@@ -0,0 +1,3 @@
graft extras
graft tools

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python3
# cyan, aka pyzule-rw; by zx, 2024
import os
import sys
import argparse
@@ -11,11 +10,6 @@ def main() -> None:
sys.exit("[!] please upgrade to python 3.12 or higher")
elif sys.platform == "win32":
sys.exit("[!] windows is not supported")
elif not (
os.path.isdir("/opt/cyan")
or os.path.isdir("/var/jb/opt/cyan")
):
sys.exit("[!] cyan must be installed in /opt/cyan, refer to the README")
parser = argparse.ArgumentParser(
description="cyan, an azule \"clone\" for modifying iOS apps"
@@ -40,8 +34,8 @@ def main() -> None:
help="skip main binary encryption check"
)
import cyan
cyan.main(parser)
from cyan import logic
logic.main(parser)
if __name__ == "__main__":

View File

@@ -5,8 +5,7 @@ from subprocess import run
from argparse import ArgumentParser
from tempfile import TemporaryDirectory
import tbhutils
import tbhtypes
from cyan import tbhutils, tbhtypes
def main(parser: ArgumentParser) -> None:
@@ -46,10 +45,15 @@ def main(parser: ArgumentParser) -> None:
# done!
if OUTPUT_IS_IPA:
print("[*] generating ipa..")
run(["bash", f"{tbhtypes.Executable.isd}/tools/compressIPA.sh"], env={
"TMPDIR": tmpdir,
"OUTPUT": args.o
})
run(
["bash", f"{tbhtypes.Executable.install_dir}/tools/compressIPA.sh"],
env={
"TMPDIR": tmpdir,
"OUTPUT": args.o
}
)
print(f"[*] generated ipa at {args.o}")
else:
# create subdirectories if necessary

10
cyan/tbhtypes/__init__.py Normal file
View File

@@ -0,0 +1,10 @@
from .app_bundle import AppBundle
from .executable import Executable
from .leaving_cm import LeavingCM
__all__ = [
"AppBundle",
"Executable",
"LeavingCM"
]

View File

@@ -0,0 +1,14 @@
from cyan.tbhutils import get_plist
from .executable import Executable
class AppBundle:
def __init__(self, path: str, plist_path: str):
self.path = path
self.plist = get_plist(plist_path)
self.executable = Executable(
f"{path}/{self.plist["CFBundleExecutable"]}",
path
)

0
cyan/tbhtypes/dylib.py Normal file
View File

View File

@@ -6,31 +6,23 @@ from typing import Optional
import lief
import tbhutils
class AppBundle:
def __init__(self, path: str, plist_path: str):
self.path = path
self.plist = tbhutils.get_plist(plist_path)
self.executable = Executable(
f"{path}/{self.plist["CFBundleExecutable"]}",
self
)
from cyan import tbhutils
class Executable:
isd, td = tbhutils.get_tools_dir()
nt = f"{td}/install_name_tool"
ldid = f"{td}/ldid"
otool = f"{td}/otool"
install_dir, specific = tbhutils.get_tools_dir()
nt = f"{specific}/install_name_tool"
ldid = f"{specific}/ldid"
otool = f"{specific}/otool"
def __init__(self, path: str, bundle: Optional[AppBundle] = None):
starters = ("\t/Library/", "\t@rpath", "\t@executable_path")
def __init__(self, path: str, bundle_path: Optional[str] = None):
if not os.path.isfile(path):
sys.exit(f"[!] {path} does not exist (executable)")
self.path = path
self.bundle = bundle
self.bundle_path = bundle_path
self.bn = os.path.basename(path)
self.inj: Optional[lief.MachO.Binary] = None
@@ -46,12 +38,12 @@ class Executable:
def inject(self, tweaks: dict[str, str], tmpdir: str) -> None:
# we only inject into the main executable
assert self.bundle is not None
assert self.bundle_path is not None
has_entitlements = False
ENT_PATH = f"{self.bundle.path}/cyan.entitlements"
PLUGINS_DIR = f"{self.bundle.path}/PlugIns"
FRAMEWORKS_DIR = f"{self.bundle.path}/Frameworks"
ENT_PATH = f"{self.bundle_path}/cyan.entitlements"
PLUGINS_DIR = f"{self.bundle_path}/PlugIns"
FRAMEWORKS_DIR = f"{self.bundle_path}/Frameworks"
with open(ENT_PATH, "wb") as entf:
proc = subprocess.run(
@@ -115,11 +107,11 @@ class Executable:
if not dbn.endswith(".dylib"):
continue
dylib = Dylib(path)
dylib = Executable(path)
dylib.fakesign()
# fix dependencies
for dep in dylib.dependencies:
for dep in dylib.get_dependencies():
for cname in (common | tweaks):
if cname in dep:
if cname.endswith(".framework"):
@@ -149,7 +141,7 @@ class Executable:
for missing in needed:
ip = f"{FRAMEWORKS_DIR}/{missing}"
existed = tbhutils.delete_if_exists(ip, missing)
shutil.copytree(f"{self.isd}/extras/{missing}", ip)
shutil.copytree(f"{self.install_dir}/extras/{missing}", ip)
if not existed:
print(f"[*] auto-injected {missing}")
@@ -171,12 +163,12 @@ class Executable:
self.insert_cmd(f"@rpath/{bn}/{bn[:-10]}")
shutil.copytree(path, fpath)
else:
fpath = f"{self.bundle.path}/{bn}"
fpath = f"{self.bundle_path}/{bn}"
existed = tbhutils.delete_if_exists(fpath, bn)
try:
shutil.copytree(path, fpath)
except NotADirectoryError:
shutil.copy2(path, self.bundle.path)
shutil.copy2(path, self.bundle_path)
if not existed:
print(f"[*] injected {bn}")
@@ -205,14 +197,6 @@ class Executable:
self.inj.add(lief.MachO.DylibCommand.weak_lib(cmd)) # type: ignore
class Dylib(Executable):
starters = ("\t/Library/", "\t@rpath", "\t@executable_path")
def __init__(self, path: str):
super().__init__(path)
self.dependencies = self.get_dependencies()
def get_dependencies(self) -> list[str]:
proc = subprocess.run(
[self.otool, "-L", self.path],
@@ -232,11 +216,3 @@ class Dylib(Executable):
return deps
class LeavingCM:
def __enter__(self):
pass
def __exit__(self, i, d, c): # type: ignore
print("[*] deleting temporary directory..")

View File

@@ -0,0 +1,7 @@
class LeavingCM:
def __enter__(self):
pass
def __exit__(self, i, d, c): # type: ignore
print("[*] deleting temporary directory..")

View File

@@ -7,6 +7,7 @@ import plistlib
import subprocess
from glob import glob
from argparse import Namespace
from importlib import resources # type: ignore
from typing import Optional, Any
from tempfile import TemporaryDirectory
@@ -85,13 +86,15 @@ def get_app(path: str, tmpdir: str, is_ipa: bool) -> tuple[str, str]:
def get_tools_dir() -> tuple[str, str]:
mach = platform.machine()
system = platform.system()
prefix = ""
if "iPhone" in mach or "iPad" in mach:
mach = "arm64"
prefix = "/var/jb" # sorry, rootless only !!
return (f"{prefix}/opt/cyan", f"{prefix}/opt/cyan/tools/{system}/{mach}")
with resources.files() as files: # type: ignore
return (
str(files), # type: ignore
str(files / "tools" / system / mach) # type: ignore
)
def get_plist(path: str) -> dict[str, Any]:

17
setup.py Normal file
View File

@@ -0,0 +1,17 @@
from setuptools import setup
setup(
name="cyan",
version="1.0",
description="finally, pyzule doesn't suck",
author="zx",
author_email="hi@zxcvbn.fyi",
packages=["cyan", "cyan.tbhtypes"],
install_requires=["lief"],
python_requires=">=3.12",
include_package_data=True,
entry_points={
"console_scripts": ["cyan=cyan.__main__:main"],
}
)