chore: making progress

This commit is contained in:
zx
2024-09-02 18:29:26 -04:00
parent 104964ac56
commit 8c4d35f568
20 changed files with 197 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
# pyzule-rw / cyan # pyzule-rw / cyan
rewriting [pyzule](https://github.com/asdfzxcvbn/pyzule), but actually good this time! rewriting [pyzule](https://github.com/asdfzxcvbn/pyzule), but actually good this time! btw.. cyan/pyzule looks best with a monospace font !! :D
this is pretty much just a hobby project that i'll work on when i'm bored. pyzule in its current state is kinda ugly, but it works and i haven't really had the motivation to work on it. there really isn't any need for improvements, anyway.. this is pretty much just a hobby project that i'll work on when i'm bored. pyzule in its current state is kinda ugly, but it works and i haven't really had the motivation to work on it. there really isn't any need for improvements, anyway..

Binary file not shown.

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>CydiaSubstrate</string>
<key>CFBundleIdentifier</key>
<string>ellekit</string>
<key>CFBundleName</key>
<string>ElleKit</string>
<key>CFBundleShortVersionString</key>
<string>1.1.3</string>
<key>CFBundleVersion</key>
<string>1.1.3</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,27 @@
Copyright (c) 2022 Évelyne Bélanger All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither ElleKit nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,12 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cyan, aka pyzule-rw; by zx, 2024 # cyan, aka pyzule-rw; by zx, 2024
import sys
import argparse import argparse
import cyan import cyan
def main() -> None: def main() -> None:
if sys.version_info < (3, 12):
sys.exit("[!] please upgrade to python 3.12 or higher")
elif sys.platform == "win32":
sys.exit("[!] windows is not supported")
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="cyan, an azule \"clone\" for modifying iOS apps" description="cyan, an azule \"clone\" for modifying iOS apps"
) )
@@ -29,21 +35,14 @@ def main() -> None:
"--overwrite", action="store_true", "--overwrite", action="store_true",
help="skip overwrite confirmation" help="skip overwrite confirmation"
) )
parser.add_argument(
"--ignore-encrypted", action="store_true",
help="skip main binary encryption check"
)
cyan.main(parser) cyan.main(parser)
if __name__ == "__main__": if __name__ == "__main__":
main() main()
# INPUT = os.path.realpath(args.input)
# WORKING_DIR = os.getcwd()
# PZ_DIR = os.path.expanduser("~/.config/cyan")
# changed = False
# if not (INPUT.endswith(".ipa") or INPUT.endswith(".app")):
# sys.exit("[!] input must be a .ipa/.app")
# elif not os.path.exists(INPUT):
# sys.exit("[!] input does not exist")
# elif not args.o and (input("[<] override your input file? [y/N] ").strip()
# .lower() not in ("y", "yes")):
# sys.exit("[!] not overriding input, missing -o")

View File

@@ -1,7 +1,10 @@
import os import os
import sys
from argparse import ArgumentParser from argparse import ArgumentParser
from tempfile import TemporaryDirectory
import tbhutils import tbhutils
import tbhtypes
def main(parser: ArgumentParser) -> None: def main(parser: ArgumentParser) -> None:
@@ -20,6 +23,16 @@ def main(parser: ArgumentParser) -> None:
if arg_err is not None: if arg_err is not None:
parser.error(arg_err) parser.error(arg_err)
# verbose notices INPUT_IS_IPA = True if args.i.endswith(".ipa") else False
if args.o # OUTPUT_IS_IPA = True if args.o.endswith(".ipa") else False
with TemporaryDirectory() as tmpdir:
app_path, plist_path = tbhutils.get_app(args.i, tmpdir, INPUT_IS_IPA)
app = tbhtypes.AppBundle(app_path, plist_path)
if app.executable.is_encrypted():
if args.ignore_encrypted:
print("[?] main binary is encrypted, ignoring")
else:
sys.exit("[!] main binary is encrypted; exiting")

31
src/tbhtypes.py Normal file
View File

@@ -0,0 +1,31 @@
import os
import sys
from subprocess import run
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"]}"
)
class Executable:
td = tbhutils.get_tools_dir()
otool = f"{td}/otool"
def __init__(self, path: str):
if not os.path.isfile(path):
sys.exit(f"[!] {path} does not exist (executable)")
self.path = path
def is_encrypted(self) -> bool:
proc = run([self.otool, "-l", self.path])
return b"cryptid 1" in proc.stdout

View File

@@ -1,6 +1,12 @@
import os import os
from typing import Optional import sys
import shutil
import zipfile
import platform
import plistlib
from glob import glob
from argparse import Namespace from argparse import Namespace
from typing import Optional, Any
def validate_inputs(args: Namespace) -> Optional[str]: def validate_inputs(args: Namespace) -> Optional[str]:
@@ -13,5 +19,85 @@ def validate_inputs(args: Namespace) -> Optional[str]:
if not os.path.exists(args.i): if not os.path.exists(args.i):
return f"{args.i} does not exist" return f"{args.i} does not exist"
if os.path.exists(args.o):
if args.overwrite:
return print("[*] output already exists; will overwrite !")
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:
args.f = {os.path.normpath(f) for f in args.f}
nonexistent = [f for f in args.f 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() -> 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/tools/{system}/{mach}"
def get_plist(path: str) -> dict[str, Any]:
try:
with open(path, "rb") as f:
return plistlib.load(f)
except Exception:
sys.exit(f"[!] couldn't read {path}")

Binary file not shown.

BIN
tools/Darwin/arm64/ldid Normal file

Binary file not shown.

BIN
tools/Darwin/arm64/otool Normal file

Binary file not shown.

Binary file not shown.

BIN
tools/Darwin/x86_64/ldid Normal file

Binary file not shown.

BIN
tools/Darwin/x86_64/otool Normal file

Binary file not shown.

Binary file not shown.

BIN
tools/Linux/aarch64/ldid Executable file

Binary file not shown.

BIN
tools/Linux/aarch64/otool Executable file

Binary file not shown.

Binary file not shown.

BIN
tools/Linux/x86_64/ldid Executable file

Binary file not shown.

BIN
tools/Linux/x86_64/otool Executable file

Binary file not shown.