mirror of
https://github.com/NohamR/pyzule-rw.git
synced 2026-10-10 18:39:41 +00:00
chore: making progress
This commit is contained in:
@@ -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..
|
||||||
|
|
||||||
|
|||||||
BIN
extras/CydiaSubstrate.framework/CydiaSubstrate
Normal file
BIN
extras/CydiaSubstrate.framework/CydiaSubstrate
Normal file
Binary file not shown.
25
extras/CydiaSubstrate.framework/Info.plist
Normal file
25
extras/CydiaSubstrate.framework/Info.plist
Normal 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>
|
||||||
27
extras/CydiaSubstrate.framework/LICENSE
Normal file
27
extras/CydiaSubstrate.framework/LICENSE
Normal 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.
|
||||||
@@ -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")
|
|
||||||
|
|||||||
17
src/cyan.py
17
src/cyan.py
@@ -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
31
src/tbhtypes.py
Normal 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
|
||||||
|
|
||||||
|
|
||||||
@@ -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}")
|
||||||
|
|
||||||
|
|||||||
BIN
tools/Darwin/arm64/install_name_tool
Normal file
BIN
tools/Darwin/arm64/install_name_tool
Normal file
Binary file not shown.
BIN
tools/Darwin/arm64/ldid
Normal file
BIN
tools/Darwin/arm64/ldid
Normal file
Binary file not shown.
BIN
tools/Darwin/arm64/otool
Normal file
BIN
tools/Darwin/arm64/otool
Normal file
Binary file not shown.
BIN
tools/Darwin/x86_64/install_name_tool
Normal file
BIN
tools/Darwin/x86_64/install_name_tool
Normal file
Binary file not shown.
BIN
tools/Darwin/x86_64/ldid
Normal file
BIN
tools/Darwin/x86_64/ldid
Normal file
Binary file not shown.
BIN
tools/Darwin/x86_64/otool
Normal file
BIN
tools/Darwin/x86_64/otool
Normal file
Binary file not shown.
BIN
tools/Linux/aarch64/install_name_tool
Executable file
BIN
tools/Linux/aarch64/install_name_tool
Executable file
Binary file not shown.
BIN
tools/Linux/aarch64/ldid
Executable file
BIN
tools/Linux/aarch64/ldid
Executable file
Binary file not shown.
BIN
tools/Linux/aarch64/otool
Executable file
BIN
tools/Linux/aarch64/otool
Executable file
Binary file not shown.
BIN
tools/Linux/x86_64/install_name_tool
Executable file
BIN
tools/Linux/x86_64/install_name_tool
Executable file
Binary file not shown.
BIN
tools/Linux/x86_64/ldid
Executable file
BIN
tools/Linux/x86_64/ldid
Executable file
Binary file not shown.
BIN
tools/Linux/x86_64/otool
Executable file
BIN
tools/Linux/x86_64/otool
Executable file
Binary file not shown.
Reference in New Issue
Block a user