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__/
|
__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
|
#!/usr/bin/env python3
|
||||||
# cyan, aka pyzule-rw; by zx, 2024
|
# cyan, aka pyzule-rw; by zx, 2024
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
@@ -11,11 +10,6 @@ def main() -> None:
|
|||||||
sys.exit("[!] please upgrade to python 3.12 or higher")
|
sys.exit("[!] please upgrade to python 3.12 or higher")
|
||||||
elif sys.platform == "win32":
|
elif sys.platform == "win32":
|
||||||
sys.exit("[!] windows is not supported")
|
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(
|
parser = argparse.ArgumentParser(
|
||||||
description="cyan, an azule \"clone\" for modifying iOS apps"
|
description="cyan, an azule \"clone\" for modifying iOS apps"
|
||||||
@@ -40,8 +34,8 @@ def main() -> None:
|
|||||||
help="skip main binary encryption check"
|
help="skip main binary encryption check"
|
||||||
)
|
)
|
||||||
|
|
||||||
import cyan
|
from cyan import logic
|
||||||
cyan.main(parser)
|
logic.main(parser)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -5,8 +5,7 @@ from subprocess import run
|
|||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
import tbhutils
|
from cyan import tbhutils, tbhtypes
|
||||||
import tbhtypes
|
|
||||||
|
|
||||||
|
|
||||||
def main(parser: ArgumentParser) -> None:
|
def main(parser: ArgumentParser) -> None:
|
||||||
@@ -46,10 +45,15 @@ def main(parser: ArgumentParser) -> None:
|
|||||||
# done!
|
# done!
|
||||||
if OUTPUT_IS_IPA:
|
if OUTPUT_IS_IPA:
|
||||||
print("[*] generating ipa..")
|
print("[*] generating ipa..")
|
||||||
run(["bash", f"{tbhtypes.Executable.isd}/tools/compressIPA.sh"], env={
|
|
||||||
"TMPDIR": tmpdir,
|
run(
|
||||||
"OUTPUT": args.o
|
["bash", f"{tbhtypes.Executable.install_dir}/tools/compressIPA.sh"],
|
||||||
})
|
env={
|
||||||
|
"TMPDIR": tmpdir,
|
||||||
|
"OUTPUT": args.o
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
print(f"[*] generated ipa at {args.o}")
|
print(f"[*] generated ipa at {args.o}")
|
||||||
else:
|
else:
|
||||||
# create subdirectories if necessary
|
# 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 lief
|
||||||
|
|
||||||
import tbhutils
|
from cyan 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
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Executable:
|
class Executable:
|
||||||
isd, td = tbhutils.get_tools_dir()
|
install_dir, specific = tbhutils.get_tools_dir()
|
||||||
nt = f"{td}/install_name_tool"
|
nt = f"{specific}/install_name_tool"
|
||||||
ldid = f"{td}/ldid"
|
ldid = f"{specific}/ldid"
|
||||||
otool = f"{td}/otool"
|
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):
|
if not os.path.isfile(path):
|
||||||
sys.exit(f"[!] {path} does not exist (executable)")
|
sys.exit(f"[!] {path} does not exist (executable)")
|
||||||
|
|
||||||
self.path = path
|
self.path = path
|
||||||
self.bundle = bundle
|
self.bundle_path = bundle_path
|
||||||
|
|
||||||
self.bn = os.path.basename(path)
|
self.bn = os.path.basename(path)
|
||||||
self.inj: Optional[lief.MachO.Binary] = None
|
self.inj: Optional[lief.MachO.Binary] = None
|
||||||
@@ -46,12 +38,12 @@ class Executable:
|
|||||||
|
|
||||||
def inject(self, tweaks: dict[str, str], tmpdir: str) -> None:
|
def inject(self, tweaks: dict[str, str], tmpdir: str) -> None:
|
||||||
# we only inject into the main executable
|
# we only inject into the main executable
|
||||||
assert self.bundle is not None
|
assert self.bundle_path is not None
|
||||||
|
|
||||||
has_entitlements = False
|
has_entitlements = False
|
||||||
ENT_PATH = f"{self.bundle.path}/cyan.entitlements"
|
ENT_PATH = f"{self.bundle_path}/cyan.entitlements"
|
||||||
PLUGINS_DIR = f"{self.bundle.path}/PlugIns"
|
PLUGINS_DIR = f"{self.bundle_path}/PlugIns"
|
||||||
FRAMEWORKS_DIR = f"{self.bundle.path}/Frameworks"
|
FRAMEWORKS_DIR = f"{self.bundle_path}/Frameworks"
|
||||||
|
|
||||||
with open(ENT_PATH, "wb") as entf:
|
with open(ENT_PATH, "wb") as entf:
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
@@ -115,11 +107,11 @@ class Executable:
|
|||||||
if not dbn.endswith(".dylib"):
|
if not dbn.endswith(".dylib"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
dylib = Dylib(path)
|
dylib = Executable(path)
|
||||||
dylib.fakesign()
|
dylib.fakesign()
|
||||||
|
|
||||||
# fix dependencies
|
# fix dependencies
|
||||||
for dep in dylib.dependencies:
|
for dep in dylib.get_dependencies():
|
||||||
for cname in (common | tweaks):
|
for cname in (common | tweaks):
|
||||||
if cname in dep:
|
if cname in dep:
|
||||||
if cname.endswith(".framework"):
|
if cname.endswith(".framework"):
|
||||||
@@ -149,7 +141,7 @@ class Executable:
|
|||||||
for missing in needed:
|
for missing in needed:
|
||||||
ip = f"{FRAMEWORKS_DIR}/{missing}"
|
ip = f"{FRAMEWORKS_DIR}/{missing}"
|
||||||
existed = tbhutils.delete_if_exists(ip, 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:
|
if not existed:
|
||||||
print(f"[*] auto-injected {missing}")
|
print(f"[*] auto-injected {missing}")
|
||||||
@@ -171,12 +163,12 @@ class Executable:
|
|||||||
self.insert_cmd(f"@rpath/{bn}/{bn[:-10]}")
|
self.insert_cmd(f"@rpath/{bn}/{bn[:-10]}")
|
||||||
shutil.copytree(path, fpath)
|
shutil.copytree(path, fpath)
|
||||||
else:
|
else:
|
||||||
fpath = f"{self.bundle.path}/{bn}"
|
fpath = f"{self.bundle_path}/{bn}"
|
||||||
existed = tbhutils.delete_if_exists(fpath, bn)
|
existed = tbhutils.delete_if_exists(fpath, bn)
|
||||||
try:
|
try:
|
||||||
shutil.copytree(path, fpath)
|
shutil.copytree(path, fpath)
|
||||||
except NotADirectoryError:
|
except NotADirectoryError:
|
||||||
shutil.copy2(path, self.bundle.path)
|
shutil.copy2(path, self.bundle_path)
|
||||||
|
|
||||||
if not existed:
|
if not existed:
|
||||||
print(f"[*] injected {bn}")
|
print(f"[*] injected {bn}")
|
||||||
@@ -205,14 +197,6 @@ class Executable:
|
|||||||
|
|
||||||
self.inj.add(lief.MachO.DylibCommand.weak_lib(cmd)) # type: ignore
|
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]:
|
def get_dependencies(self) -> list[str]:
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
[self.otool, "-L", self.path],
|
[self.otool, "-L", self.path],
|
||||||
@@ -232,11 +216,3 @@ class Dylib(Executable):
|
|||||||
|
|
||||||
return deps
|
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
|
import subprocess
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
|
from importlib import resources # type: ignore
|
||||||
from typing import Optional, Any
|
from typing import Optional, Any
|
||||||
from tempfile import TemporaryDirectory
|
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]:
|
def get_tools_dir() -> tuple[str, str]:
|
||||||
mach = platform.machine()
|
mach = platform.machine()
|
||||||
system = platform.system()
|
system = platform.system()
|
||||||
prefix = ""
|
|
||||||
|
|
||||||
if "iPhone" in mach or "iPad" in mach:
|
if "iPhone" in mach or "iPad" in mach:
|
||||||
mach = "arm64"
|
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]:
|
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