Skip to content

Instantly share code, notes, and snippets.

@Geczy
Created June 13, 2023 00:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Geczy/cc1f3f1b5d7d7a44fbaa0c204b5b7567 to your computer and use it in GitHub Desktop.
Save Geczy/cc1f3f1b5d7d7a44fbaa0c204b5b7567 to your computer and use it in GitHub Desktop.
#!/bin/bash
set -e
# check os compatibility
system=$(uname)
if [ "$system" = "Windows" ]; then
echo "Windows is not currently supported."
exit 1
fi
# set/get all args
while getopts "i:o:c:f:uw" opt; do
case $opt in
i)
ipa=$OPTARG
;;
o)
output=$OPTARG
;;
c)
compression_level=$OPTARG
;;
f)
files+=("$OPTARG")
;;
u)
remove_UISupportedDevices=true
;;
w)
remove_watch_app=true
;;
\?)
exit 1
;;
esac
done
# checking received args
if [ "${ipa: -4}" != ".ipa" ] || [ "${output: -4}" != ".ipa" ]; then
echo "The input and output file must be an ipa."
exit 1
fi
if [ ! -f "$ipa" ]; then
echo "$ipa does not exist."
exit 1
fi
if [ ${#files[@]} -eq 0 ] && [ -z "$remove_UISupportedDevices" ] && [ -z "$remove_watch_app" ]; then
echo "At least one option to modify the ipa must be present."
exit 1
fi
cleanup() {
rm -rf "$extract_dir"
if [ "$1" = true ]; then
exit 0
else
exit 1
fi
}
# extracting ipa
echo "[*] extracting ipa.."
extract_dir=".pyzule-$(date +%s)"
mkdir "$extract_dir"
unzip "$ipa" -d "$extract_dir" > /dev/null
echo "[*] extracted ipa successfully"
# checking if everything exists (to see if it's a valid ipa)
app_path=$(find "$extract_dir"/Payload -type d -name "*.app" | head -n 1)
plist_path=$(find "$app_path" -type f -name "Info.plist" | head -n 1)
if [ -z "$app_path" ] || [ -z "$plist_path" ]; then
echo "[!] Couldn't find Payload folder and/or Info.plist file, invalid ipa specified"
cleanup false
fi
# injecting stuff
if [ ${#files[@]} -gt 0 ]; then
binary=$(plutil -convert json -o - "$plist_path" | grep -o 'CFBundleExecutable[^,]*' | cut -d '"' -f 4)
if [[ " ${files[@]} " =~ \.appex ]]; then
mkdir -p "$app_path/PlugIns"
fi
if [[ " ${files[@]} " =~ \.(deb|dylib|framework) ]]; then
mkdir -p "$app_path/Frameworks"
deb_counter=0
fi
dylibs=()
id=("${dylibs[@]}" $(for file in "${files[@]}"; do if [[ "$file" == *.dylib ]]; then echo "$file"; fi; done))
remove=()
substrate_injected=0
# extracting all debs
for deb in "${files[@]}"; do
if [ "${deb: -4}" != ".deb" ]; then
continue
fi
bn=$(basename "$deb")
echo "[*] extracting $bn.."
output="$extract_dir/$deb_counter"
mkdir -p "$output" "$output/e"
if [ "$system" = "Linux" ]; then
ar -x "$deb" --output="$output" > /dev/null
else
tar -xf "$deb" -C "$output" > /dev/null
fi
data_tar=$(find "$output" -name "data.*")
tar -xf "$data_tar" -C "$output/e" > /dev/null
find "$output/e" -type f -name "*.dylib" -exec cp {} "$WORKING_DIR" \;
find "$output/e" \( -type d -name "*.bundle" -o -name "*.framework" \) -exec cp -r {} "$WORKING_DIR" \;
echo "[*] extracted $bn successfully"
deb_counter=$((deb_counter + 1))
done
# remove codesign + fix all dependencies
for dylib in "${dylibs[@]}"; do
ldid -S "$dylib" > /dev/null
deps_temp=$(otool -L "$dylib" | tail -n +3 | awk '{print $1}' | grep -E '^/Library/|^/usr/lib')
deps=()
while IFS= read -r dep; do
deps+=("$dep")
done <<< "$deps_temp"
if [[ " ${deps_temp[@]} " =~ substrate ]]; then
install_name_tool -change "/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate" "@rpath/CydiaSubstrate.framework/CydiaSubstrate" "$dylib" > /dev/null
install_name_tool -change "@executable_path/libsubstrate.dylib" "@rpath/CydiaSubstrate.framework/CydiaSubstrate" "$dylib" > /dev/null
if [ "$substrate_injected" -eq 0 ]; then
if [ ! -d "$app_path/Frameworks/CydiaSubstrate.framework" ]; then
cp -R "$USER_DIR/CydiaSubstrate.framework" "$app_path/Frameworks/CydiaSubstrate.framework"
fi
echo "[*] injected CydiaSubstrate.framework and fixed dependencies"
substrate_injected=1
fi
fi
for dep in "${deps[@]}"; do
for known in "${id[@]}"; do
if [[ "$dep" == *"$known"* ]]; then
bn=$(basename "$dep")
if [[ "$dep" == *.dylib ]]; then
fni=$(echo "$dep" | awk -v bn="$bn" '{print index($0, bn)}')
install_name_tool -change "${dep:0:fni}$bn" "@rpath/$bn" "$dylib" > /dev/null
echo "[*] fixed dependency in $dylib: ${dep:0:fni}$bn -> @rpath/$bn"
elif [[ "$dep" == *".framework" ]]; then
fni=$(echo "$dep" | awk -v bn="$bn" '{print index($0, bn ".framework/" bn)}')
install_name_tool -change "${dep:0:fni}$bn.framework/$bn" "@rpath/$bn.framework/$bn" "$dylib" > /dev/null
echo "[*] fixed dependency in $dylib: ${dep:0:fni}$bn.framework/$bn -> @rpath/$bn.framework/$bn"
fi
fi
done
done
done
echo "[*] injecting.."
for d in "${dylibs[@]}"; do
bn=$(basename "$d")
insert_dylib --inplace --no-strip-codesig "@rpath/$bn" "$app_path/$BINARY" > /dev/null
cp "$d" "$app_path/Frameworks/$bn"
echo "[*] successfully injected $bn"
done
for tweak in "${args_f[@]}"; do
bn=$(basename "$tweak")
if [[ "$tweak" == *".framework" ]]; then
cp -R "$tweak" "$app_path/Frameworks/$bn"
echo "[*] successfully injected $bn"
elif [[ "$tweak" == *".appex" ]]; then
cp -R "$tweak" "$app_path/PlugIns/$bn"
echo "[*] successfully copied $bn to PlugIns"
elif [[ ! " ${dylibs[*]} " =~ " $tweak " && ! "$tweak" == *".deb" ]]; then
if [ -d "$tweak" ]; then
cp -R "$tweak" "$app_path/$bn"
else
cp "$tweak" "$app_path/$bn"
fi
echo "[*] successfully copied $bn to app root"
fi
done
changed=1
for r in "${remove[@]}"; do
if [ -f "$r" ]; then
rm "$r"
else
rm -r "$r"
fi
done
fi
# removing UISupportedDevices (if specified)
if [ "$args_u" -eq 1 ]; then
echo "[*] removing UISupportedDevices.."
plist_tmp=$(mktemp /tmp/plist.XXXXXX)
plutil -convert xml1 -o "$plist_tmp" "$plist_path"
if [ $(/usr/libexec/PlistBuddy -c 'Print :UISupportedDevices' "$plist_tmp" 2> /dev/null | wc -l) -ne 0 ]; then
/usr/libexec/PlistBuddy -c 'Delete :UISupportedDevices' "$plist_tmp" > /dev/null
echo "[*] removed UISupportedDevices"
changed=1
plutil -convert binary1 -o "$plist_path" "$plist_tmp"
else
echo "[?] UISupportedDevices not present"
fi
rm "$plist_tmp"
fi
# removing watch app (if specified)
if [ "$args_w" -eq 1 ]; then
echo "[*] removing watch app.."
if [ -d "$app_path/Watch" ]; then
rm -r "$app_path/Watch"
echo "[*] removed watch app"
changed=1
else
echo "[?] watch app not present"
fi
fi
# checking if anything was actually changed
if [ "$changed" -eq 0 ]; then
echo "[!] nothing was changed, output file will not be created"
cleanup "$EXTRACT_DIR" true
fi
# zipping everything back into an ipa
cd "$EXTRACT_DIR" || exit 1
echo "[*] generating ipa using compression level $args_c.."
zip "-$args_c" -r "$(basename "$args_o")" Payload > /dev/null
# cleanup when everything is done
cd "$WORKING_DIR" || exit 1
if [[ "$args_o" == */* ]]; then
o2=$(dirname "$args_o")
mkdir -p "$o2"
fi
mv "$EXTRACT_DIR/$(basename "$args_o")" "$args_o"
echo "[*] generated ipa at $args_o"
echo "[*] deleting temporary files.."
cleanup "$EXTRACT_DIR" true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment