9 Commits
v1.1 ... v1.1.3

Author SHA1 Message Date
zx
8f7db1438a chore: bump version to v1.1.3 2024-09-11 07:15:47 -04:00
zx
5e08afc0ac fix(cgen): folders ending in / 2024-09-11 07:14:50 -04:00
zx
0bd6393e78 chore: bundle cgen 2024-09-10 17:35:42 -04:00
zx
f5a5d7b5ed fix: sub-bundle detection 2024-09-10 17:23:30 -04:00
zx
c3a51e7e03 docs: update install instructions 2024-09-10 16:30:51 -04:00
zx
806db413e2 fix: crashing when jailbroken
i realized azule actually removes the signature (instead of fakesigning) before specific operations, maybe this is the fix?
2024-09-10 07:11:15 -04:00
zx
927df65b55 Merge branch 'main' of github.com:asdfzxcvbn/pyzule-rw 2024-09-09 19:37:24 -04:00
zx
851eedf629 fix: using wrong ldid 2024-09-09 19:36:38 -04:00
zx
126d17924b docs: feature requests 2024-09-09 10:32:34 -04:00
6 changed files with 175 additions and 36 deletions

View File

@@ -6,9 +6,9 @@ cyan supports **linux, macOS, WSL, and jailbroken iOS!** all either x86_64 or ar
## features ## features
more coming soon! i'm trying to copy pyzule in order to deprecate it in favor of cyan, which is 1000x better you can open an issue to request a feature :D !!
- generate and use [shareable .cyan files](https://github.com/asdfzxcvbn/cyan-gen) to configure IPAs! - generate and use shareable .cyan files to configure IPAs!
- inject deb, dylib, framework, bundle, and appex files/folders - inject deb, dylib, framework, bundle, and appex files/folders
- automatically fix dependencies on CydiaSubstrate **(cyan uses [ElleKit](https://github.com/evelyneee/ellekit/)!)**, Cephei*, and Orion - automatically fix dependencies on CydiaSubstrate **(cyan uses [ElleKit](https://github.com/evelyneee/ellekit/)!)**, Cephei*, and Orion
- copy any unknown file/folder types to app root - copy any unknown file/folder types to app root
@@ -26,34 +26,20 @@ first, make sure you have [ar](https://command-not-found.com/ar) and [tar](https
also obviously install python, version 3.9 or greater is required (the version available on the procursus repo for iOS) also obviously install python, version 3.9 or greater is required (the version available on the procursus repo for iOS)
**if you want to inject dylibs AND ARE NOT ON iOS,** make sure you install lief (you only have to do this once): `pip install -U lief` 1. install [pipx](https://github.com/pypa/pipx?tab=readme-ov-file#install-pipx)
2. install cyan: `pipx install --force https://github.com/asdfzxcvbn/pyzule-rw/archive/main.zip`
3. follow any instructions `pipx` may have given you, like `pipx ensurepath`
4. **if you want to inject dylibs and ARE NOT ON iOS**: `pipx inject cyan lief`
5. **if you want to change app icons (iOS NOT supported)**: `pipx inject cyan Pillow`
**if you want to change app icons (iOS NOT supported),** also make sure you install pillow: `pip install Pillow` to *update*, just run step 2 again !
then finally, to install or update cyan, just `pip install --force-reinstall git+https://github.com/asdfzxcvbn/pyzule-rw.git#egg=cyan` ## making cyan files
cyan comes bundled with the `cgen` command, which lets you generate `.cyan` files to pass to `-z`/`--cyan` !
## acknowledgements ## acknowledgements
- [Al4ise](https://github.com/Al4ise) for the original [Azule](https://github.com/Al4ise/Azule) - [Al4ise](https://github.com/Al4ise) for the original [Azule](https://github.com/Al4ise/Azule)
- [lief-project](https://github.com/lief-project) for [LIEF](https://github.com/lief-project/LIEF) - [lief-project](https://github.com/lief-project) for [LIEF](https://github.com/lief-project/LIEF)
### todo
[x] refactor: dont prepare, just copy and fix as you go
[x] feat: plist operations (-l and -r wont be implemented)
[x] feat: remove watch app
[x] feat: fakesign
[x] feat: thin binaries
[x] feat: plugin operations (-q, -e)
[x] feat: change app icon
[x] feat: .cyan files (lol rip .pyzule files)
:D

142
cgen/__main__.py Normal file
View File

@@ -0,0 +1,142 @@
import os
import sys
import json
import zipfile
import argparse
def main() -> None:
if sys.platform == "win32":
sys.exit("[!] windows is not supported")
parser = argparse.ArgumentParser(
description="a tool to generate .cyan files"
)
parser.add_argument(
"-o", "--output", metavar="output", required=True,
help="output of the .cyan file"
)
parser.add_argument(
"-f", metavar="file", nargs="+",
help="a tweak to inject/item to be added to the bundle"
)
parser.add_argument(
"-n", metavar="name",
help="modify the app's name"
)
parser.add_argument(
"-v", metavar="version",
help="modify the app's version"
)
parser.add_argument(
"-b", metavar="bundle id",
help="modify the app's bundle id"
)
parser.add_argument(
"-m", metavar="minimum",
help="modify the app's minimum OS version"
)
parser.add_argument(
"-k", metavar="icon",
help="modify the app's icon"
)
parser.add_argument(
"-u", "--remove-supported-devices", action="store_true",
help="remove UISupportedDevices"
)
parser.add_argument(
"-w", "--no-watch", action="store_true",
help="remove all watch apps"
)
parser.add_argument(
"-d", "--enable-documents", action="store_true",
help="enable documents support"
)
parser.add_argument(
"-s", "--fakesign", action="store_true",
help="fakesign all binaries for use with appsync/trollstore"
)
parser.add_argument(
"-q", "--thin", action="store_true",
help="thin all binaries to arm64, may largely reduce size"
)
parser.add_argument(
"-e", "--remove-extensions", action="store_true",
help="remove all app extensions"
)
parser.add_argument(
"-g", "--remove-encrypted", action="store_true",
help="only remove encrypted app extensions"
)
generate_cyan(parser)
def generate_cyan(parser: argparse.ArgumentParser) -> None:
args = parser.parse_args()
# input validation
if args.m is not None and any(c not in "0123456789." for c in args.m):
parser.error(f"invalid minimum OS version: {args.m}")
if args.k is not None and not os.path.isfile(args.k):
parser.error(f"{args.k} does not exist")
if args.f is not None:
fake = [f for f in args.f if not os.path.exists(f)]
# it would be great if everyone was using python 3.12 !!!
if len(fake) != 0:
parser.error(f"the following file(s) do not exist: {', '.join(fake)}")
if not args.output.endswith(".cyan"):
print("[*] appended cyan file extension to output")
args.output += ".cyan"
if os.path.isfile(args.output):
try:
overwrite = input(
f"[<] {args.output} already exists. overwrite? [Y/n] "
).lower().strip()
except KeyboardInterrupt:
sys.exit("\n[?] guess not")
if overwrite not in ("y", "yes", ""):
sys.exit("[>] quitting.")
real_args = {k: v for k, v in dict(vars(args)).items() if v}
del real_args["output"]
for key in "fk": # these need files
if key in real_args:
real_args[key] = True
print("[*] generating..")
with zipfile.ZipFile(
args.output, "w", zipfile.ZIP_DEFLATED, compresslevel=1
) as zf:
with zf.open("config.json", "w") as f:
f.write(json.dumps(real_args).encode())
if args.f is not None:
for f in args.f:
if os.path.isfile(f):
zf.write(f, f"inject/{os.path.basename(f)}")
else: # G YHUJMNFTGYHNFTGYHTGYHUT6Y7UJM8RFTYHNR564TY
if f.endswith("/"):
f = f[:-1] # yes this is needed to prevent a bug wtf
for dp, _, files in os.walk(f):
for f2 in files:
thing = f"{dp}/{f2}"
zf.write(
thing,
f"inject/{os.path.relpath(thing, os.path.dirname(f))}"
) # no, i don't know what this is doing.
if args.k is not None:
zf.write(args.k, "icon.idk")
if __name__ == "__main__":
main()

View File

@@ -95,7 +95,7 @@ def main() -> None:
) )
parser.add_argument( parser.add_argument(
"--version", action="version", version="cyan v1.0" "--version", action="version", version="cyan v1.1.3"
) )
from cyan import logic from cyan import logic

View File

@@ -76,7 +76,7 @@ class Executable:
has_entitlements = True has_entitlements = True
# iirc, injecting doesnt work (sometimes) if the file isn't signed # iirc, injecting doesnt work (sometimes) if the file isn't signed
self.fakesign(False) self.remove_signature()
if any(t.endswith(".appex") for t in tweaks): if any(t.endswith(".appex") for t in tweaks):
os.makedirs(PLUGINS_DIR, exist_ok=True) os.makedirs(PLUGINS_DIR, exist_ok=True)
@@ -151,9 +151,12 @@ class Executable:
self.inj.write(self.path) # type: ignore self.inj.write(self.path) # type: ignore
if has_entitlements: if has_entitlements:
subprocess.run(["ldid", f"-S{ENT_PATH}", self.path]) subprocess.run([self.ldid, f"-S{ENT_PATH}", self.path])
print("[*] restored entitlements") print("[*] restored entitlements")
def remove_signature(self) -> None:
subprocess.run([self.ldid, "-R", self.path], stderr=subprocess.DEVNULL)
def fakesign(self, keep_entitlements: bool = True) -> bool: def fakesign(self, keep_entitlements: bool = True) -> bool:
cmd = [self.ldid, "-S"] cmd = [self.ldid, "-S"]
if keep_entitlements: if keep_entitlements:
@@ -195,7 +198,7 @@ class Executable:
) )
def fix_dependencies(self, tweaks: dict[str, str], need: set[str]) -> None: def fix_dependencies(self, tweaks: dict[str, str], need: set[str]) -> None:
self.fakesign() # sometimes it doesnt work if we dont do this self.remove_signature()
for dep in self.get_dependencies(): for dep in self.get_dependencies():
for cname in (tweaks | self.common): for cname in (tweaks | self.common):

View File

@@ -153,12 +153,16 @@ def extract_deb(deb: str, tweaks: dict[str, str], tmpdir: str) -> None:
for hi in sum(( for hi in sum((
glob(f"{t2}/**/*.dylib", recursive=True), glob(f"{t2}/**/*.dylib", recursive=True),
glob(f"{t2}/**/*.bundle", recursive=True),
glob(f"{t2}/**/*.appex", recursive=True), glob(f"{t2}/**/*.appex", recursive=True),
glob(f"{t2}/**/*.bundle", recursive=True),
glob(f"{t2}/**/*.framework", recursive=True) glob(f"{t2}/**/*.framework", recursive=True)
), []): # type: ignore ), []): # type: ignore
if os.path.islink(hi): if (
continue # symlinks are broken iirc 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 tweaks[os.path.basename(hi)] = hi

View File

@@ -1,17 +1,21 @@
# type: ignore
from setuptools import setup from setuptools import setup
setup( setup(
name="cyan", name="cyan",
version="1.1", version="1.1.3",
description="finally, pyzule doesn't suck", description="finally, pyzule doesn't suck",
author="zx", author="zx",
author_email="zx@hrzn.email", author_email="zx@hrzn.email",
packages=["cyan", "cyan.tbhtypes"], packages=["cyan", "cyan.tbhtypes", "cgen"],
# install_requires=["lief"],
python_requires=">=3.9", python_requires=">=3.9",
include_package_data=True, include_package_data=True,
entry_points={ entry_points={
"console_scripts": ["cyan=cyan.__main__:main"], "console_scripts": [
"cyan=cyan.__main__:main",
"cgen=cgen.__main__:main"
],
} }
) )