mirror of
https://github.com/NohamR/pyzule-rw.git
synced 2026-10-10 18:39:41 +00:00
chore: python package
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
__pycache__/
|
||||
|
||||
build/
|
||||
*.egg-info
|
||||
|
||||
|
||||
3
MANIFEST.in
Normal file
3
MANIFEST.in
Normal file
@@ -0,0 +1,3 @@
|
||||
graft extras
|
||||
graft tools
|
||||
|
||||
@@ -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__":
|
||||
@@ -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
10
cyan/tbhtypes/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from .app_bundle import AppBundle
|
||||
from .executable import Executable
|
||||
from .leaving_cm import LeavingCM
|
||||
|
||||
__all__ = [
|
||||
"AppBundle",
|
||||
"Executable",
|
||||
"LeavingCM"
|
||||
]
|
||||
|
||||
14
cyan/tbhtypes/app_bundle.py
Normal file
14
cyan/tbhtypes/app_bundle.py
Normal 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
0
cyan/tbhtypes/dylib.py
Normal 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..")
|
||||
|
||||
7
cyan/tbhtypes/leaving_cm.py
Normal file
7
cyan/tbhtypes/leaving_cm.py
Normal file
@@ -0,0 +1,7 @@
|
||||
class LeavingCM:
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, i, d, c): # type: ignore
|
||||
print("[*] deleting temporary directory..")
|
||||
|
||||
@@ -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
17
setup.py
Normal 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"],
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user