diff --git a/.gitignore b/.gitignore index 9d31326..c453997 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ work sshramdisk .irecovery *.log +nul +ramdisks/ +sshrd-kk.sh diff --git a/MINGW64_NT-10.0-22631/KPlooshFinder b/MINGW64_NT-10.0-22631/KPlooshFinder new file mode 100644 index 0000000..d3f5516 Binary files /dev/null and b/MINGW64_NT-10.0-22631/KPlooshFinder differ diff --git a/MINGW64_NT-10.0-22631/KPlooshFinder.exe b/MINGW64_NT-10.0-22631/KPlooshFinder.exe new file mode 100644 index 0000000..d3f5516 Binary files /dev/null and b/MINGW64_NT-10.0-22631/KPlooshFinder.exe differ diff --git a/MINGW64_NT-10.0-22631/gaster b/MINGW64_NT-10.0-22631/gaster new file mode 100644 index 0000000..df23022 Binary files /dev/null and b/MINGW64_NT-10.0-22631/gaster differ diff --git a/MINGW64_NT-10.0-22631/gaster.exe b/MINGW64_NT-10.0-22631/gaster.exe new file mode 100644 index 0000000..df23022 Binary files /dev/null and b/MINGW64_NT-10.0-22631/gaster.exe differ diff --git a/MINGW64_NT-10.0-22631/hfsplus b/MINGW64_NT-10.0-22631/hfsplus new file mode 100644 index 0000000..d5e5f9e Binary files /dev/null and b/MINGW64_NT-10.0-22631/hfsplus differ diff --git a/MINGW64_NT-10.0-22631/hfsplus.exe b/MINGW64_NT-10.0-22631/hfsplus.exe new file mode 100644 index 0000000..d5e5f9e Binary files /dev/null and b/MINGW64_NT-10.0-22631/hfsplus.exe differ diff --git a/MINGW64_NT-10.0-22631/iBoot64Patcher b/MINGW64_NT-10.0-22631/iBoot64Patcher new file mode 100644 index 0000000..3beeede Binary files /dev/null and b/MINGW64_NT-10.0-22631/iBoot64Patcher differ diff --git a/MINGW64_NT-10.0-22631/iBoot64Patcher.exe b/MINGW64_NT-10.0-22631/iBoot64Patcher.exe new file mode 100644 index 0000000..3beeede Binary files /dev/null and b/MINGW64_NT-10.0-22631/iBoot64Patcher.exe differ diff --git a/MINGW64_NT-10.0-22631/img4 b/MINGW64_NT-10.0-22631/img4 new file mode 100644 index 0000000..1937269 Binary files /dev/null and b/MINGW64_NT-10.0-22631/img4 differ diff --git a/MINGW64_NT-10.0-22631/img4.exe b/MINGW64_NT-10.0-22631/img4.exe new file mode 100644 index 0000000..1937269 Binary files /dev/null and b/MINGW64_NT-10.0-22631/img4.exe differ diff --git a/MINGW64_NT-10.0-22631/img4tool b/MINGW64_NT-10.0-22631/img4tool new file mode 100644 index 0000000..623af16 Binary files /dev/null and b/MINGW64_NT-10.0-22631/img4tool differ diff --git a/MINGW64_NT-10.0-22631/img4tool.exe b/MINGW64_NT-10.0-22631/img4tool.exe new file mode 100644 index 0000000..623af16 Binary files /dev/null and b/MINGW64_NT-10.0-22631/img4tool.exe differ diff --git a/MINGW64_NT-10.0-22631/iproxy b/MINGW64_NT-10.0-22631/iproxy new file mode 100644 index 0000000..bb93363 Binary files /dev/null and b/MINGW64_NT-10.0-22631/iproxy differ diff --git a/MINGW64_NT-10.0-22631/iproxy.exe b/MINGW64_NT-10.0-22631/iproxy.exe new file mode 100644 index 0000000..bb93363 Binary files /dev/null and b/MINGW64_NT-10.0-22631/iproxy.exe differ diff --git a/MINGW64_NT-10.0-22631/irecovery b/MINGW64_NT-10.0-22631/irecovery new file mode 100644 index 0000000..bdedbdc Binary files /dev/null and b/MINGW64_NT-10.0-22631/irecovery differ diff --git a/MINGW64_NT-10.0-22631/irecovery.exe b/MINGW64_NT-10.0-22631/irecovery.exe new file mode 100644 index 0000000..bdedbdc Binary files /dev/null and b/MINGW64_NT-10.0-22631/irecovery.exe differ diff --git a/MINGW64_NT-10.0-22631/jq b/MINGW64_NT-10.0-22631/jq new file mode 100644 index 0000000..fce8379 Binary files /dev/null and b/MINGW64_NT-10.0-22631/jq differ diff --git a/MINGW64_NT-10.0-22631/jq.exe b/MINGW64_NT-10.0-22631/jq.exe new file mode 100644 index 0000000..fce8379 Binary files /dev/null and b/MINGW64_NT-10.0-22631/jq.exe differ diff --git a/MINGW64_NT-10.0-22631/kairos b/MINGW64_NT-10.0-22631/kairos new file mode 100644 index 0000000..6d3da0e Binary files /dev/null and b/MINGW64_NT-10.0-22631/kairos differ diff --git a/MINGW64_NT-10.0-22631/kairos.exe b/MINGW64_NT-10.0-22631/kairos.exe new file mode 100644 index 0000000..6d3da0e Binary files /dev/null and b/MINGW64_NT-10.0-22631/kairos.exe differ diff --git a/MINGW64_NT-10.0-22631/kerneldiff b/MINGW64_NT-10.0-22631/kerneldiff new file mode 100644 index 0000000..4e90528 Binary files /dev/null and b/MINGW64_NT-10.0-22631/kerneldiff differ diff --git a/MINGW64_NT-10.0-22631/kerneldiff.exe b/MINGW64_NT-10.0-22631/kerneldiff.exe new file mode 100644 index 0000000..4e90528 Binary files /dev/null and b/MINGW64_NT-10.0-22631/kerneldiff.exe differ diff --git a/MINGW64_NT-10.0-22631/libcrypto-1_1-x64.dll b/MINGW64_NT-10.0-22631/libcrypto-1_1-x64.dll new file mode 100644 index 0000000..7dab416 Binary files /dev/null and b/MINGW64_NT-10.0-22631/libcrypto-1_1-x64.dll differ diff --git a/MINGW64_NT-10.0-22631/libcrypto-1_1.dll b/MINGW64_NT-10.0-22631/libcrypto-1_1.dll new file mode 100644 index 0000000..a56acac Binary files /dev/null and b/MINGW64_NT-10.0-22631/libcrypto-1_1.dll differ diff --git a/MINGW64_NT-10.0-22631/libimobiledevice-glue-1.0.dll b/MINGW64_NT-10.0-22631/libimobiledevice-glue-1.0.dll new file mode 100644 index 0000000..e83375a Binary files /dev/null and b/MINGW64_NT-10.0-22631/libimobiledevice-glue-1.0.dll differ diff --git a/MINGW64_NT-10.0-22631/libirecovery-1.0.dll b/MINGW64_NT-10.0-22631/libirecovery-1.0.dll new file mode 100644 index 0000000..049ef91 Binary files /dev/null and b/MINGW64_NT-10.0-22631/libirecovery-1.0.dll differ diff --git a/MINGW64_NT-10.0-22631/libplist-2.0.dll b/MINGW64_NT-10.0-22631/libplist-2.0.dll new file mode 100644 index 0000000..7a276e7 Binary files /dev/null and b/MINGW64_NT-10.0-22631/libplist-2.0.dll differ diff --git a/MINGW64_NT-10.0-22631/libreadline8.dll b/MINGW64_NT-10.0-22631/libreadline8.dll new file mode 100644 index 0000000..8853f62 Binary files /dev/null and b/MINGW64_NT-10.0-22631/libreadline8.dll differ diff --git a/MINGW64_NT-10.0-22631/libtermcap-0.dll b/MINGW64_NT-10.0-22631/libtermcap-0.dll new file mode 100644 index 0000000..1c1dd56 Binary files /dev/null and b/MINGW64_NT-10.0-22631/libtermcap-0.dll differ diff --git a/MINGW64_NT-10.0-22631/libusb-1.0-x64.dll b/MINGW64_NT-10.0-22631/libusb-1.0-x64.dll new file mode 100644 index 0000000..e5a0b84 Binary files /dev/null and b/MINGW64_NT-10.0-22631/libusb-1.0-x64.dll differ diff --git a/MINGW64_NT-10.0-22631/libusb-1.0.dll b/MINGW64_NT-10.0-22631/libusb-1.0.dll new file mode 100644 index 0000000..e9f95bb Binary files /dev/null and b/MINGW64_NT-10.0-22631/libusb-1.0.dll differ diff --git a/MINGW64_NT-10.0-22631/libusbmuxd-2.0.dll b/MINGW64_NT-10.0-22631/libusbmuxd-2.0.dll new file mode 100644 index 0000000..1eedb33 Binary files /dev/null and b/MINGW64_NT-10.0-22631/libusbmuxd-2.0.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-2.0.dll b/MINGW64_NT-10.0-22631/msys-2.0.dll new file mode 100644 index 0000000..b108b50 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-2.0.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-crypto-1.1.dll b/MINGW64_NT-10.0-22631/msys-crypto-1.1.dll new file mode 100644 index 0000000..ae51372 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-crypto-1.1.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-curl-4.dll b/MINGW64_NT-10.0-22631/msys-curl-4.dll new file mode 100644 index 0000000..9553f25 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-curl-4.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-fragmentzip-0.dll b/MINGW64_NT-10.0-22631/msys-fragmentzip-0.dll new file mode 100644 index 0000000..b754096 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-fragmentzip-0.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-gcc_s-seh-1.dll b/MINGW64_NT-10.0-22631/msys-gcc_s-seh-1.dll new file mode 100644 index 0000000..ba22a40 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-gcc_s-seh-1.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-general-0.dll b/MINGW64_NT-10.0-22631/msys-general-0.dll new file mode 100644 index 0000000..d98a2b7 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-general-0.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-iconv-2.dll b/MINGW64_NT-10.0-22631/msys-iconv-2.dll new file mode 100644 index 0000000..42f6106 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-iconv-2.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-idn2-0.dll b/MINGW64_NT-10.0-22631/msys-idn2-0.dll new file mode 100644 index 0000000..dd68957 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-idn2-0.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-img4tool-0.dll b/MINGW64_NT-10.0-22631/msys-img4tool-0.dll new file mode 100644 index 0000000..f241ca1 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-img4tool-0.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-intl-8.dll b/MINGW64_NT-10.0-22631/msys-intl-8.dll new file mode 100644 index 0000000..4279401 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-intl-8.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-lzfse.dll b/MINGW64_NT-10.0-22631/msys-lzfse.dll new file mode 100644 index 0000000..ca06277 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-lzfse.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-lzma-5.dll b/MINGW64_NT-10.0-22631/msys-lzma-5.dll new file mode 100644 index 0000000..c2efc50 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-lzma-5.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-plist-2.0-4.dll b/MINGW64_NT-10.0-22631/msys-plist-2.0-4.dll new file mode 100644 index 0000000..06560ad Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-plist-2.0-4.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-psl-5.dll b/MINGW64_NT-10.0-22631/msys-psl-5.dll new file mode 100644 index 0000000..4e61851 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-psl-5.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-ssh2-1.dll b/MINGW64_NT-10.0-22631/msys-ssh2-1.dll new file mode 100644 index 0000000..d15c4e4 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-ssh2-1.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-ssl-1.1.dll b/MINGW64_NT-10.0-22631/msys-ssl-1.1.dll new file mode 100644 index 0000000..fc4461c Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-ssl-1.1.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-stdc++-6.dll b/MINGW64_NT-10.0-22631/msys-stdc++-6.dll new file mode 100644 index 0000000..178286a Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-stdc++-6.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-unistring-2.dll b/MINGW64_NT-10.0-22631/msys-unistring-2.dll new file mode 100644 index 0000000..d465408 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-unistring-2.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-xml2-2.dll b/MINGW64_NT-10.0-22631/msys-xml2-2.dll new file mode 100644 index 0000000..c5ab0c6 Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-xml2-2.dll differ diff --git a/MINGW64_NT-10.0-22631/msys-z.dll b/MINGW64_NT-10.0-22631/msys-z.dll new file mode 100644 index 0000000..f85e3aa Binary files /dev/null and b/MINGW64_NT-10.0-22631/msys-z.dll differ diff --git a/MINGW64_NT-10.0-22631/plget b/MINGW64_NT-10.0-22631/plget new file mode 100644 index 0000000..9f13382 Binary files /dev/null and b/MINGW64_NT-10.0-22631/plget differ diff --git a/MINGW64_NT-10.0-22631/plget.exe b/MINGW64_NT-10.0-22631/plget.exe new file mode 100644 index 0000000..9f13382 Binary files /dev/null and b/MINGW64_NT-10.0-22631/plget.exe differ diff --git a/MINGW64_NT-10.0-22631/plistBuddy b/MINGW64_NT-10.0-22631/plistBuddy new file mode 100644 index 0000000..895d779 Binary files /dev/null and b/MINGW64_NT-10.0-22631/plistBuddy differ diff --git a/MINGW64_NT-10.0-22631/plistutil b/MINGW64_NT-10.0-22631/plistutil new file mode 100644 index 0000000..895d779 Binary files /dev/null and b/MINGW64_NT-10.0-22631/plistutil differ diff --git a/MINGW64_NT-10.0-22631/plistutil.exe b/MINGW64_NT-10.0-22631/plistutil.exe new file mode 100644 index 0000000..895d779 Binary files /dev/null and b/MINGW64_NT-10.0-22631/plistutil.exe differ diff --git a/MINGW64_NT-10.0-22631/pzb b/MINGW64_NT-10.0-22631/pzb new file mode 100644 index 0000000..aa9237a Binary files /dev/null and b/MINGW64_NT-10.0-22631/pzb differ diff --git a/MINGW64_NT-10.0-22631/pzb.exe b/MINGW64_NT-10.0-22631/pzb.exe new file mode 100644 index 0000000..aa9237a Binary files /dev/null and b/MINGW64_NT-10.0-22631/pzb.exe differ diff --git a/MINGW64_NT-10.0-22631/sshpass b/MINGW64_NT-10.0-22631/sshpass new file mode 100644 index 0000000..bbe8fd7 Binary files /dev/null and b/MINGW64_NT-10.0-22631/sshpass differ diff --git a/MINGW64_NT-10.0-22631/sshpass.exe b/MINGW64_NT-10.0-22631/sshpass.exe new file mode 100644 index 0000000..bbe8fd7 Binary files /dev/null and b/MINGW64_NT-10.0-22631/sshpass.exe differ diff --git a/MINGW64_NT-10.0-22631/zlib1.dll b/MINGW64_NT-10.0-22631/zlib1.dll new file mode 100644 index 0000000..31996cd Binary files /dev/null and b/MINGW64_NT-10.0-22631/zlib1.dll differ diff --git a/README.md b/README.md index 5ac6322..0467349 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,104 @@ -

SSH Ramdisk Script

+# SSH Ramdisk Script +

- - Contributors - - - Commits - + + + +

+

-Create and boot a SSH ramdisk on checkm8 devices + + Contributors + + + Commits +

+

Create and boot a SSH ramdisk on checkm8 devices

+ --- -# Prerequsites +# About this Fork +This fork differs from **verygenericname's** version in the following ways: +- **Supports offline IPSW files** for ramdisk creation. +- **Automatically saves a copy of generated ramdisk files** to: + ``` + ramdisk/_/ + ``` -1. A computer running macOS/linux -2. A checkm8 device (A7-A11) +--- + +# Prerequisites +1. A computer running macOS or Linux +2. A checkm8-compatible device (A7–A11) # Usage +1. Clone and enter the repository: + ```bash + git clone https://github.com/kagbontaen/SSHRD_Script --recursive && cd SSHRD_Script + ``` + If previously cloned: + ```bash + cd SSHRD_Script && git pull + ``` -1. Clone and cd into this repository: `git clone https://github.com/verygenericname/SSHRD_Script --recursive && cd SSHRD_Script` - - If you have cloned this before, run `cd SSHRD_Script && git pull` to pull new changes -2. Run `./sshrd.sh `, **without** the `<>`. - - The iOS version doesn't have to be the version you're currently on, but it should be close enough, and SEP has to be compatible - - If you're on Linux, you will not be able to make a ramdisk for 16.1+, please use something lower instead, like 16.0 - - This is due to ramdisks switching to APFS over HFS+, and another dmg library would have to be used -3. Place your device into DFU mode - - A11 users, go to recovery first, then DFU. -4. Run `./sshrd.sh boot` to boot the ramdisk -5. Run `./sshrd.sh ssh` to connect to SSH on your device -6. Finally, to mount the filesystems, run `mount_filesystems` - - /var is mounted to /mnt2 in the ssh session. - - /private/preboot is mounted to /mnt6. - - DO NOT RUN THIS IF THE DEVICE IS ON A REALLY OLD VERSION!!!!!!! -7. Have fun! - -# Linux notes - -On Linux, usbmuxd will have to be restarted. On most distros, it's as simple as these 2 commands in another terminal: -``` +2. Run the ramdisk creation command: + ```bash + ./sshrd.sh + ``` + - The iOS version does **not** need to match the device, but SEP must be compatible. + - **Linux users:** iOS 16.1+ ramdisks cannot be created due to APFS changes; use 16.0 or lower. + +3. Place your device into DFU mode. + - A11 devices: Recovery Mode → DFU. + +4. Boot the SSH ramdisk: + ```bash + ./sshrd.sh boot + ``` + +5. Connect via SSH: + ```bash + ./sshrd.sh ssh + ``` + +6. Mount filesystems: + ```bash + mount_filesystems + ``` + - `/var` mounts to `/mnt2` + - `/private/preboot` mounts to `/mnt6` + - **Do NOT run this on very old iOS versions.** + +# Linux Notes +On Linux, `usbmuxd` must be restarted. Run these in another terminal: +```bash sudo systemctl stop usbmuxd sudo usbmuxd -p -f ``` -# Other commands - -- Reboot your device: `./sshrd.sh reboot` -- Erase all data from your device: `./sshrd.sh reset` +# Other Commands +- Reboot device: `./sshrd.sh reboot` +- Erase all data: `./sshrd.sh reset` - Dump onboard SHSH blobs: `./sshrd.sh dump-blobs` -- Delete old SSH ramdisk: `./sshrd.sh clean` +- Delete old ramdisk: `./sshrd.sh clean` -# Other Stuff +--- +# Other Resources - [Reddit Post](https://www.reddit.com/r/jailbreak/comments/wgiye1/free_release_ssh_ramdisk_creator_for_iphones_ipad/) -# Credits +--- -- [tihmstar](https://github.com/tihmstar) for pzb/original iBoot64Patcher/img4tool -- [xerub](https://github.com/xerub) for img4lib and restored_external in the ramdisk -- [Cryptic](https://github.com/Cryptiiiic) for iBoot64Patcher fork -- [opa334](https://github.com/opa334) for TrollStore -- [Nebula](https://github.com/itsnebulalol) for a bunch of QOL fixes to this script -- [OpenAI](https://chat.openai.com/chat) for converting [kerneldiff](https://github.com/mcg29/kerneldiff) into [C](https://github.com/verygenericname/kerneldiff_C) -- [Ploosh](https://github.com/plooshi) for KPlooshFinder +# Credits +- **remote-zip-viewer.py** (from the [remote-zip-downloader](https://github.com/kagbontaen/remote-zip-downloader) project) +- [tihmstar](https://github.com/tihmstar) — pzb, original iBoot64Patcher, img4tool +- [xerub](https://github.com/xerub) — img4lib, restored_external +- [Cryptic](https://github.com/Cryptiiiic) — iBoot64Patcher fork +- [opa334](https://github.com/opa334) — TrollStore +- [Nebula](https://github.com/itsnebulalol) — QOL fixes +- [OpenAI](https://chat.openai.com/chat) — kerneldiff → C port +- [Ploosh](https://github.com/plooshi) — KPlooshFinder diff --git a/Windows/KPlooshFinder b/Windows/KPlooshFinder new file mode 100644 index 0000000..d3f5516 Binary files /dev/null and b/Windows/KPlooshFinder differ diff --git a/Windows/KPlooshFinder.exe b/Windows/KPlooshFinder.exe new file mode 100644 index 0000000..d3f5516 Binary files /dev/null and b/Windows/KPlooshFinder.exe differ diff --git a/Windows/gaster b/Windows/gaster new file mode 100644 index 0000000..df23022 Binary files /dev/null and b/Windows/gaster differ diff --git a/Windows/gaster.exe b/Windows/gaster.exe new file mode 100644 index 0000000..df23022 Binary files /dev/null and b/Windows/gaster.exe differ diff --git a/Windows/hfsplus b/Windows/hfsplus new file mode 100644 index 0000000..d5e5f9e Binary files /dev/null and b/Windows/hfsplus differ diff --git a/Windows/hfsplus.exe b/Windows/hfsplus.exe new file mode 100644 index 0000000..d5e5f9e Binary files /dev/null and b/Windows/hfsplus.exe differ diff --git a/Windows/iBoot64Patcher b/Windows/iBoot64Patcher new file mode 100644 index 0000000..3beeede Binary files /dev/null and b/Windows/iBoot64Patcher differ diff --git a/Windows/iBoot64Patcher.exe b/Windows/iBoot64Patcher.exe new file mode 100644 index 0000000..3beeede Binary files /dev/null and b/Windows/iBoot64Patcher.exe differ diff --git a/Windows/img4 b/Windows/img4 new file mode 100644 index 0000000..1937269 Binary files /dev/null and b/Windows/img4 differ diff --git a/Windows/img4.exe b/Windows/img4.exe new file mode 100644 index 0000000..1937269 Binary files /dev/null and b/Windows/img4.exe differ diff --git a/Windows/img4tool b/Windows/img4tool new file mode 100644 index 0000000..623af16 Binary files /dev/null and b/Windows/img4tool differ diff --git a/Windows/img4tool.exe b/Windows/img4tool.exe new file mode 100644 index 0000000..623af16 Binary files /dev/null and b/Windows/img4tool.exe differ diff --git a/Windows/iproxy b/Windows/iproxy new file mode 100644 index 0000000..bb93363 Binary files /dev/null and b/Windows/iproxy differ diff --git a/Windows/iproxy.exe b/Windows/iproxy.exe new file mode 100644 index 0000000..bb93363 Binary files /dev/null and b/Windows/iproxy.exe differ diff --git a/Windows/irecovery b/Windows/irecovery new file mode 100644 index 0000000..bdedbdc Binary files /dev/null and b/Windows/irecovery differ diff --git a/Windows/irecovery.exe b/Windows/irecovery.exe new file mode 100644 index 0000000..bdedbdc Binary files /dev/null and b/Windows/irecovery.exe differ diff --git a/Windows/jq b/Windows/jq new file mode 100644 index 0000000..fce8379 Binary files /dev/null and b/Windows/jq differ diff --git a/Windows/jq.exe b/Windows/jq.exe new file mode 100644 index 0000000..fce8379 Binary files /dev/null and b/Windows/jq.exe differ diff --git a/Windows/kairos b/Windows/kairos new file mode 100644 index 0000000..6d3da0e Binary files /dev/null and b/Windows/kairos differ diff --git a/Windows/kairos.exe b/Windows/kairos.exe new file mode 100644 index 0000000..6d3da0e Binary files /dev/null and b/Windows/kairos.exe differ diff --git a/Windows/kerneldiff b/Windows/kerneldiff new file mode 100644 index 0000000..4e90528 Binary files /dev/null and b/Windows/kerneldiff differ diff --git a/Windows/kerneldiff.exe b/Windows/kerneldiff.exe new file mode 100644 index 0000000..4e90528 Binary files /dev/null and b/Windows/kerneldiff.exe differ diff --git a/Windows/libcrypto-1_1-x64.dll b/Windows/libcrypto-1_1-x64.dll new file mode 100644 index 0000000..7dab416 Binary files /dev/null and b/Windows/libcrypto-1_1-x64.dll differ diff --git a/Windows/libcrypto-1_1.dll b/Windows/libcrypto-1_1.dll new file mode 100644 index 0000000..a56acac Binary files /dev/null and b/Windows/libcrypto-1_1.dll differ diff --git a/Windows/libimobiledevice-glue-1.0.dll b/Windows/libimobiledevice-glue-1.0.dll new file mode 100644 index 0000000..e83375a Binary files /dev/null and b/Windows/libimobiledevice-glue-1.0.dll differ diff --git a/Windows/libirecovery-1.0.dll b/Windows/libirecovery-1.0.dll new file mode 100644 index 0000000..049ef91 Binary files /dev/null and b/Windows/libirecovery-1.0.dll differ diff --git a/Windows/libplist-2.0.dll b/Windows/libplist-2.0.dll new file mode 100644 index 0000000..7a276e7 Binary files /dev/null and b/Windows/libplist-2.0.dll differ diff --git a/Windows/libreadline8.dll b/Windows/libreadline8.dll new file mode 100644 index 0000000..8853f62 Binary files /dev/null and b/Windows/libreadline8.dll differ diff --git a/Windows/libtermcap-0.dll b/Windows/libtermcap-0.dll new file mode 100644 index 0000000..1c1dd56 Binary files /dev/null and b/Windows/libtermcap-0.dll differ diff --git a/Windows/libusb-1.0-x64.dll b/Windows/libusb-1.0-x64.dll new file mode 100644 index 0000000..e5a0b84 Binary files /dev/null and b/Windows/libusb-1.0-x64.dll differ diff --git a/Windows/libusb-1.0.dll b/Windows/libusb-1.0.dll new file mode 100644 index 0000000..e9f95bb Binary files /dev/null and b/Windows/libusb-1.0.dll differ diff --git a/Windows/libusbmuxd-2.0.dll b/Windows/libusbmuxd-2.0.dll new file mode 100644 index 0000000..1eedb33 Binary files /dev/null and b/Windows/libusbmuxd-2.0.dll differ diff --git a/Windows/msys-2.0.dll b/Windows/msys-2.0.dll new file mode 100644 index 0000000..b108b50 Binary files /dev/null and b/Windows/msys-2.0.dll differ diff --git a/Windows/msys-crypto-1.1.dll b/Windows/msys-crypto-1.1.dll new file mode 100644 index 0000000..ae51372 Binary files /dev/null and b/Windows/msys-crypto-1.1.dll differ diff --git a/Windows/msys-curl-4.dll b/Windows/msys-curl-4.dll new file mode 100644 index 0000000..9553f25 Binary files /dev/null and b/Windows/msys-curl-4.dll differ diff --git a/Windows/msys-fragmentzip-0.dll b/Windows/msys-fragmentzip-0.dll new file mode 100644 index 0000000..b754096 Binary files /dev/null and b/Windows/msys-fragmentzip-0.dll differ diff --git a/Windows/msys-gcc_s-seh-1.dll b/Windows/msys-gcc_s-seh-1.dll new file mode 100644 index 0000000..ba22a40 Binary files /dev/null and b/Windows/msys-gcc_s-seh-1.dll differ diff --git a/Windows/msys-general-0.dll b/Windows/msys-general-0.dll new file mode 100644 index 0000000..d98a2b7 Binary files /dev/null and b/Windows/msys-general-0.dll differ diff --git a/Windows/msys-iconv-2.dll b/Windows/msys-iconv-2.dll new file mode 100644 index 0000000..42f6106 Binary files /dev/null and b/Windows/msys-iconv-2.dll differ diff --git a/Windows/msys-idn2-0.dll b/Windows/msys-idn2-0.dll new file mode 100644 index 0000000..dd68957 Binary files /dev/null and b/Windows/msys-idn2-0.dll differ diff --git a/Windows/msys-img4tool-0.dll b/Windows/msys-img4tool-0.dll new file mode 100644 index 0000000..f241ca1 Binary files /dev/null and b/Windows/msys-img4tool-0.dll differ diff --git a/Windows/msys-intl-8.dll b/Windows/msys-intl-8.dll new file mode 100644 index 0000000..4279401 Binary files /dev/null and b/Windows/msys-intl-8.dll differ diff --git a/Windows/msys-lzfse.dll b/Windows/msys-lzfse.dll new file mode 100644 index 0000000..ca06277 Binary files /dev/null and b/Windows/msys-lzfse.dll differ diff --git a/Windows/msys-lzma-5.dll b/Windows/msys-lzma-5.dll new file mode 100644 index 0000000..c2efc50 Binary files /dev/null and b/Windows/msys-lzma-5.dll differ diff --git a/Windows/msys-plist-2.0-4.dll b/Windows/msys-plist-2.0-4.dll new file mode 100644 index 0000000..06560ad Binary files /dev/null and b/Windows/msys-plist-2.0-4.dll differ diff --git a/Windows/msys-psl-5.dll b/Windows/msys-psl-5.dll new file mode 100644 index 0000000..4e61851 Binary files /dev/null and b/Windows/msys-psl-5.dll differ diff --git a/Windows/msys-ssh2-1.dll b/Windows/msys-ssh2-1.dll new file mode 100644 index 0000000..d15c4e4 Binary files /dev/null and b/Windows/msys-ssh2-1.dll differ diff --git a/Windows/msys-ssl-1.1.dll b/Windows/msys-ssl-1.1.dll new file mode 100644 index 0000000..fc4461c Binary files /dev/null and b/Windows/msys-ssl-1.1.dll differ diff --git a/Windows/msys-stdc++-6.dll b/Windows/msys-stdc++-6.dll new file mode 100644 index 0000000..178286a Binary files /dev/null and b/Windows/msys-stdc++-6.dll differ diff --git a/Windows/msys-unistring-2.dll b/Windows/msys-unistring-2.dll new file mode 100644 index 0000000..d465408 Binary files /dev/null and b/Windows/msys-unistring-2.dll differ diff --git a/Windows/msys-xml2-2.dll b/Windows/msys-xml2-2.dll new file mode 100644 index 0000000..c5ab0c6 Binary files /dev/null and b/Windows/msys-xml2-2.dll differ diff --git a/Windows/msys-z.dll b/Windows/msys-z.dll new file mode 100644 index 0000000..f85e3aa Binary files /dev/null and b/Windows/msys-z.dll differ diff --git a/Windows/plget b/Windows/plget new file mode 100644 index 0000000..9f13382 Binary files /dev/null and b/Windows/plget differ diff --git a/Windows/plget.exe b/Windows/plget.exe new file mode 100644 index 0000000..9f13382 Binary files /dev/null and b/Windows/plget.exe differ diff --git a/Windows/plistBuddy b/Windows/plistBuddy new file mode 100644 index 0000000..895d779 Binary files /dev/null and b/Windows/plistBuddy differ diff --git a/Windows/plistutil b/Windows/plistutil new file mode 100644 index 0000000..895d779 Binary files /dev/null and b/Windows/plistutil differ diff --git a/Windows/plistutil.exe b/Windows/plistutil.exe new file mode 100644 index 0000000..895d779 Binary files /dev/null and b/Windows/plistutil.exe differ diff --git a/Windows/pzb b/Windows/pzb new file mode 100644 index 0000000..aa9237a Binary files /dev/null and b/Windows/pzb differ diff --git a/Windows/pzb.exe b/Windows/pzb.exe new file mode 100644 index 0000000..aa9237a Binary files /dev/null and b/Windows/pzb.exe differ diff --git a/Windows/sshpass b/Windows/sshpass new file mode 100644 index 0000000..bbe8fd7 Binary files /dev/null and b/Windows/sshpass differ diff --git a/Windows/sshpass.exe b/Windows/sshpass.exe new file mode 100644 index 0000000..bbe8fd7 Binary files /dev/null and b/Windows/sshpass.exe differ diff --git a/Windows/zlib1.dll b/Windows/zlib1.dll new file mode 100644 index 0000000..31996cd Binary files /dev/null and b/Windows/zlib1.dll differ diff --git a/remote_zip_viewer.py b/remote_zip_viewer.py new file mode 100644 index 0000000..8f14690 --- /dev/null +++ b/remote_zip_viewer.py @@ -0,0 +1,834 @@ +def _ensure_dependencies(): + """ + Checks for required packages and prompts for installation if they are missing. + This makes the script more portable by handling its own dependencies. + """ + import sys + import subprocess + import os + import shutil + + # List of required packages and their corresponding import names + required_packages = { + 'Flask': 'flask', + 'remotezip': 'remotezip', + 'cachetools': 'cachetools', + 'waitress': 'waitress' + } + + missing_packages = [] + for package, import_name in required_packages.items(): + try: + __import__(import_name) + except ImportError: + missing_packages.append(package) + + if missing_packages: + print(f"The following required packages are missing: {', '.join(missing_packages)}") + print("Attempting to install them now...") + + # On Debian-based systems, try to use apt-get first as it's the standard. + if sys.platform.startswith('linux') and shutil.which('apt-get'): + apt_package_map = { + 'Flask': 'python3-flask', + 'remotezip': 'python3-remotezip', + 'cachetools': 'python3-cachetools', + 'waitress': 'python3-waitress' + } + apt_packages = [apt_package_map.get(p) for p in missing_packages] + + if all(apt_packages): + try: + print("Attempting to install using 'apt-get'. This may require sudo privileges.") + # It's good practice to update package lists before installing. + subprocess.check_call(['sudo', 'apt-get', 'update']) + subprocess.check_call(['sudo', 'apt-get', 'install', '-y', *apt_packages]) + + print("\nDependencies installed successfully. Restarting script...") + os.execv(sys.executable, [sys.executable] + sys.argv) + return # Exit after successful restart + except (subprocess.CalledProcessError, FileNotFoundError) as e: + print(f"\nInstallation with 'apt-get' failed: {e}") + print("This could be due to missing permissions or packages not being available.") + print("Falling back to 'pip' for installation.") + else: + print("Could not find all required packages in the apt repository. Falling back to 'pip'.") + + # Fallback to pip for non-Debian systems or if apt-get fails + try: + print("Attempting to install using 'pip'.") + python_executable = sys.executable + subprocess.check_call([python_executable, "-m", "pip", "install", *missing_packages]) + print("\nDependencies installed successfully. Restarting script...") + os.execv(sys.executable, [sys.executable] + sys.argv) + except (subprocess.CalledProcessError, FileNotFoundError) as e: + print(f"\nFATAL: Dependency installation failed using both apt-get and pip: {e}") + print("Please install the following packages manually and restart the script:") + print(f" {', '.join(missing_packages)}") + sys.exit(1) + +_ensure_dependencies() + +from flask import Flask, request, render_template_string, Response, redirect, url_for, abort, session +from remotezip import RemoteZip +from pathlib import Path +import mimetypes +import zipfile +from functools import wraps +from cachetools import cached, TTLCache +import os + +app = Flask(__name__) +__version__ = "0.8.8" +app.secret_key = os.urandom(24) # Needed for secure session management +app.jinja_env.globals['version'] = __version__ + +INDEX_HTML = """ + + + + + +Remote ZIP Viewer + + + + + +
+

Remote ZIP Viewer

+
+
+ + Browse... +
+ +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+
+
+ + {% if error %} +

Error: {{ error }}

+ {% endif %} + + {% if tree is defined %} +
+
Contents of {{ url }}
+
+ +
+
+ {% macro render_tree(subtree, prefix='') %} +
    + {% for name, node in subtree|dictsort %} + {% if node.type == 'dir' %} +
  • +
    + + + {{ name }} +
    + {{ render_tree(node.children, prefix + name + '/') }} +
  • + {% else %} +
  • +
    + + {{ name }} + {{ node.info.file_size|format_bytes }} +
    + {% if node.info.is_text or node.info.file_size < 102400 %} + Preview + {% endif %} + {% if node.info.is_image %} + Image + {% endif %} + Get File + +
    +
    +
  • + {% endif %} + {% endfor %} +
+ {% endmacro %} + {{ render_tree(tree) }} {# Initial call to the macro #} +
+
+ {% endif %} + + +
+ + +""" + +TEXT_EXTS = (".txt",".md",".py",".csv",".log",".json",".xml",".html",".htm",".cfg",".ini",".plist",".yaml",".yml") +IMAGE_EXTS = (".png",".jpg",".jpeg",".gif",".webp") + +@app.template_filter('format_bytes') +def format_bytes(size): + """Formats a file size in bytes into a human-readable string.""" + if size is None: + return "0 bytes" + power = 1024 + n = 0 + power_labels = {0: 'bytes', 1: 'KB', 2: 'MB', 3: 'GB', 4: 'TB'} + while size >= power and n < len(power_labels) - 1: + size /= power + n += 1 + if n == 0: + return f"{int(size)} {power_labels[n]}" + return f"{size:.2f} {power_labels[n]}" + +# Cache for storing the directory structure of remote ZIP files. +# It holds up to 100 different URLs and each entry expires after 300 seconds (5 minutes). +file_list_cache = TTLCache(maxsize=100, ttl=30000) + +def is_local_path(path): + """Checks if a given path is a local file.""" + # This is a simple but effective check. If the path points to an existing + # file on the disk, we'll treat it as a local path. + return Path(path).is_file() + +def _get_session_kwargs(insecure=False, auth=None): + """Returns the kwargs for the RemoteZip session.""" + kwargs = {'verify': not insecure} + if auth: + kwargs['auth'] = auth + return kwargs + +def get_zip_context(url, insecure=False, auth=None, is_retry=False): + """ + Returns a context manager for a local or remote zip file. + Automatically retries with SSL verification disabled on SSLCertVerificationError. + """ + from requests.exceptions import SSLError + + if is_local_path(url): + return zipfile.ZipFile(url, 'r') + + kwargs = _get_session_kwargs(insecure, auth) + try: + app.logger.info(f"Attempting to connect to {url} with verify={kwargs.get('verify')}") + return RemoteZip(url, **kwargs) + except SSLError as e: + # If it's a cert verification error and we haven't already retried, try again with verification off. + if not insecure and not is_retry and 'CERTIFICATE_VERIFY_FAILED' in str(e): + app.logger.warning("SSL certificate verification failed. Retrying automatically with verification disabled.") + return get_zip_context(url, insecure=True, auth=auth, is_retry=True) + raise # Re-raise the exception if it's not the one we're handling or if we've already retried. + +@cached(file_list_cache) +def list_entries(url, insecure=False, auth=None): + """Parses a remote or local ZIP file and returns its directory structure as a nested dict.""" + tree = {} + app.logger.info(f"Cache miss for {url}. Fetching and processing directory.") + with get_zip_context(url, insecure, auth) as zf: + for info in zf.infolist(): + # Skip directory entries, we build the structure from file paths + if info.is_dir(): + continue + + parts = info.filename.split('/') + current_level = tree + for part in parts[:-1]: + if part not in current_level: + current_level[part] = {"type": "dir", "children": {}} + current_level = current_level[part]["children"] + + filename = parts[-1] + if filename: + current_level[filename] = { + "type": "file", + "info": { + "filename": info.filename, + "file_size": info.file_size, + "compress_size": info.compress_size, + "is_text": info.filename.lower().endswith(TEXT_EXTS), + "is_image": info.filename.lower().endswith(IMAGE_EXTS), + } + } + return tree + +@app.route("/") +def index(): + return render_template_string(INDEX_HTML) + +@app.route("/view") +def view(): + url = request.args.get("url") + insecure = request.args.get("no_verify") == "on" + user = request.args.get("user") + password = request.args.get("password") + zip_password = request.args.get("zip_password") + + # Clear previous session data + session.clear() + if not url: + return redirect(url_for("index")) + + auth = None + if user: + auth = (user, password or '') + session['http_user'] = user + session['http_password'] = password or '' + if zip_password: + session['zip_password'] = zip_password + + try: + tree = list_entries(url, insecure=insecure, auth=auth) + return render_template_string(INDEX_HTML, tree=tree, url=url, no_verify=insecure, user=user, password=password, zip_password=zip_password) + except Exception as e: + return render_template_string(INDEX_HTML, error=str(e), url=url, no_verify=insecure, user=user, password=password, zip_password=zip_password) + +@app.route("/browse") +def browse_local_file(): + """Opens a native file dialog and redirects to the view page for the selected file.""" + import tkinter as tk + from tkinter import filedialog + + root = tk.Tk() + root.withdraw() # Hide the main tkinter window + file_path = filedialog.askopenfilename( + title="Select a ZIP file", + filetypes=[("ZIP Archives", "*.zip")] + ) + if file_path: + return redirect(url_for('view', url=file_path)) + return "" # Close the tab if no file is selected + +def with_remote_zip(f): + @wraps(f) + def decorated_function(*args, **kwargs): + url = request.args.get("url") + name = request.args.get("name") + insecure = request.args.get("no_verify") == "on" + # Retrieve credentials from session instead of URL + user = session.get('http_user') + password = session.get('http_password') + zip_password = session.get('zip_password') + + if not url or not name: + abort(400, "Missing 'url' or 'name' parameter.") + + auth = None + if user: + auth = (user, password) + + # Pass the parsed arguments to the decorated function + return f(url, name, insecure, auth, zip_password, *args, **kwargs) + return decorated_function + +def _stream_zip_file(url, name, insecure, auth=None, zip_password=None): + """ + A generator that creates a RemoteZip instance and streams a file from it. + This ensures the RemoteZip object remains open during the entire stream. + """ + pwd_bytes = None + if zip_password: + pwd_bytes = zip_password.encode('utf-8') + + try: + with get_zip_context(url, insecure, auth) as zf: + with zf.open(name, pwd=pwd_bytes) as f: + while True: + chunk = f.read(64 * 1024) + if not chunk: + break + yield chunk + except FileNotFoundError: + # This error won't be caught by Flask's regular error handlers + # because it happens inside a generator. We can't easily abort(404). + # The stream will just be empty, resulting in a 0-byte response. + app.logger.error(f"File '{name}' not found in zip at url {url}") + except RuntimeError as e: + if "password required" in str(e): + app.logger.error(f"Password required or incorrect for file '{name}' in zip at url {url}") + else: + app.logger.error(f"Error streaming zip file from url {url}: {e}") + except Exception as e: + app.logger.error(f"Error streaming zip file from url {url}: {e}") + +@app.route("/preview") +@with_remote_zip +def preview_file(url, name, insecure, auth, zip_password): + try: + pwd_bytes = zip_password.encode('utf-8') if zip_password else None + with get_zip_context(url, insecure, auth) as zf: + with zf.open(name, pwd=pwd_bytes) as f: + data = f.read(100*1024) # limit preview size + try: + text = data.decode("utf-8") + except UnicodeDecodeError: + text = data.decode("latin-1", errors="replace") + return f"

Preview of {name}

{text}
" + except RuntimeError as e: + if "password required" in str(e): + return "Error: This file is encrypted. Please provide a password in the main form and try again.", 401 + raise e + +@app.route("/image") +@with_remote_zip +def preview_image(url, name, insecure, auth, zip_password): + mime, _ = mimetypes.guess_type(name) + return Response(_stream_zip_file(url, name, insecure, auth, zip_password), mimetype=mime or "application/octet-stream") + +@app.route("/file") +@with_remote_zip +def download_file(url, name, insecure, auth, zip_password): + # We peek at the first chunk to see if a password error occurs before sending headers + stream_generator = _stream_zip_file(url, name, insecure, auth, zip_password) + try: + first_chunk = next(stream_generator) + except StopIteration: # Handles empty files + first_chunk = b'' + except RuntimeError as e: + if "password required" in str(e): + return "Error: This file is encrypted. Please provide a password in the main form and try again.", 401 + raise e + + def combined_stream(): + yield first_chunk + yield from stream_generator + + headers = {"Content-Disposition": f'attachment; filename="{Path(name).name}"'} + return Response(combined_stream(), headers=headers, mimetype="application/octet-stream") + +def _cli_download(file_in_zip, url, output_path, insecure, auth, create_dirs): + """Handles the command-line download operation with progress display.""" + import time + + if is_local_path: + print(f"Getting content of local file stored at {url}") + else: + print(f"Connecting to {url}...") + output = Path(output_path) + + if output.is_dir(): + output = output / Path(file_in_zip).name + + # If create_dirs is specified, create the full path + if create_dirs: + output = Path(output_path) / file_in_zip + + output.parent.mkdir(parents=True, exist_ok=True) + + try: + with get_zip_context(url, insecure, auth) as zf: + try: + print(f"Searching for '{file_in_zip}' in archive...") + info = zf.getinfo(file_in_zip) + total_size = info.file_size + except KeyError: + print(f"Error: File '{file_in_zip}' not found in the remote archive.") + return + + print(f"Downloading '{file_in_zip}' ({total_size} bytes) to '{output}'...") + + downloaded_bytes = 0 + start_time = time.time() + + with zf.open(file_in_zip) as source, open(output, "wb") as target: + while True: + chunk = source.read(8192) + if not chunk: + break + target.write(chunk) + downloaded_bytes += len(chunk) + + elapsed_time = time.time() - start_time + speed = downloaded_bytes / elapsed_time if elapsed_time > 0 else 0 + speed_mbps = (speed * 8) / (1024 * 1024) + + percent = (downloaded_bytes / total_size) * 100 if total_size > 0 else 100 + + progress_bar = f"[{'=' * int(percent / 2):<50}]" + + # Use \r to return to the beginning of the line + print(f"\r{progress_bar} {percent:.1f}% - {downloaded_bytes/1024/1024:.2f}MB - {speed_mbps:.2f} Mbps", end="") + + # Print a newline at the end + print("\nDownload complete.") + + except Exception as e: + print(f"An error occurred: {e}") + +def _cli_download_folder(folder_path, url, output_path, insecure, auth): + """Handles downloading all files within a specified folder in the ZIP.""" + import time + + print(f"Connecting to {url} to download folder '{folder_path}'...") + output_base = Path(output_path) + + # Ensure the base output path exists + output_base.mkdir(parents=True, exist_ok=True) + + try: + with get_zip_context(url, insecure, auth) as zf: + # Normalize folder path to end with a slash + if not folder_path.endswith('/'): + folder_path += '/' + + # Find all files that are inside the specified folder_path + files_to_download = [ + info for info in zf.infolist() + if info.filename.startswith(folder_path) and not info.is_dir() + ] + + if not files_to_download: + print(f"Error: No files found in folder '{folder_path}' or folder does not exist.") + return + + print(f"Found {len(files_to_download)} file(s) to download.") + + for i, info in enumerate(files_to_download): + # Determine the output path for the file + # This preserves the subdirectory structure relative to the output path + relative_path = info.filename + file_output_path = output_base / relative_path + + # Create parent directories for the file + file_output_path.parent.mkdir(parents=True, exist_ok=True) + + print(f"\n[{i+1}/{len(files_to_download)}] Downloading '{info.filename}' to '{file_output_path}'...") + + with zf.open(info.filename) as source, open(file_output_path, "wb") as target: + while True: + chunk = source.read(8192) + if not chunk: + break + target.write(chunk) + print("\nFolder download complete.") + + except Exception as e: + print(f"An error occurred: {e}") + +def _cli_stream_to_console(file_in_zip, url, insecure, auth): + """Handles streaming a file's content directly to standard output.""" + import sys + try: + # Write directly to the stdout buffer to handle binary data + for chunk in _stream_zip_file(url, file_in_zip, insecure, auth): + sys.stdout.buffer.write(chunk) + except Exception as e: + print(f"An error occurred: {e}", file=sys.stderr) + +def _cli_list_files(url, list_path, no_subdirs, insecure, auth): + """Lists files and directories from the remote ZIP.""" + from fnmatch import fnmatch + + print(f"Fetching file list from {url}...") + try: + with get_zip_context(url, insecure, auth) as zf: + all_files = [info.filename for info in zf.infolist() if not info.is_dir()] + + if list_path: + # Normalize list_path to ensure it's treated as a directory + if not list_path.endswith('/'): + list_path += '/' + + # Filter files that are directly within the list_path + if no_subdirs: + # Show only files directly in list_path, no deeper + files_to_show = [f for f in all_files if f.startswith(list_path) and '/' not in f[len(list_path):]] + else: + # Show all files and subdirs under list_path + files_to_show = [f for f in all_files if f.startswith(list_path)] + else: + # Listing from the root + if no_subdirs: + # Show only files in the root + files_to_show = [f for f in all_files if '/' not in f] + else: + # Show all files + files_to_show = all_files + + print("\nContents:") + for filename in sorted(files_to_show): + print(filename) + except Exception as e: + print(f"An error occurred: {e}") + +def main(): + """Main function to run the web server or handle CLI commands.""" + import socket + import webbrowser + import atexit + from threading import Timer + from waitress import serve + import sys + import argparse + + PORT_FILE = ".port" + port = None + + # Try to read the port from a file to maintain it across reloads + try: + with open(PORT_FILE, "r") as f: + port = int(f.read()) + except (FileNotFoundError, ValueError): + # If the file doesn't exist or is invalid, find a new port + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind(("127.0.0.1", 80)) + port = 80 + sock.close() + except (socket.error, OSError): + import random + port = random.randint(5000, 6000) + print(f"Port 80 not available. do you have IIS running on port 80?") + print(f"Using a random port between 5000 and 6000 instead.") + print(f" Using random port: {port}") + + # Save the chosen port for next time + with open(PORT_FILE, "w") as f: + f.write(str(port)) + # Register a function to clean up the file on exit + atexit.register(lambda: Path(PORT_FILE).unlink(missing_ok=True)) + + parser = argparse.ArgumentParser( + description="Remote ZIP Viewer and Downloader. Run without arguments to start the web UI.", + formatter_class=argparse.RawTextHelpFormatter + ) + + # --- CLI Mode Arguments --- + cli_group = parser.add_argument_group('CLI Mode Options') + cli_group.add_argument('url', nargs='?', help='The URL of the remote ZIP archive.') + + # Listing files + cli_group.add_argument('-l', '--list', nargs='?', const='', default=None, dest='list_path', + help="Shows contents of the zip. Optionally specify a path to list its contents.") + cli_group.add_argument('--nosubdirs', action='store_true', + help="Don't show subdirectories. Used with -l or --list.") + + # Downloading files/directories + cli_group.add_argument('-g', '--get', dest='get_path', + help='Path to a remote file or directory to download.') + cli_group.add_argument('-d', '--directory', action='store_true', + help='Treat the path from -g/--get as a directory and download recursively.') + cli_group.add_argument('-o', '--output', dest='output_path', default='.', + help='Specify destination path for downloads. Defaults to the current directory.') + cli_group.add_argument('-c', '--create-directories', action='store_true', + help="Create the full directory structure for a downloaded file.") + + # Authentication and Security + cli_group.add_argument('-u', '--user', dest='auth_user', + help='Authenticate to the web server. Format: user[:password]') + cli_group.add_argument('-k', '--insecure', action='store_true', + help='Disable SSL certificate verification.') + + args = parser.parse_args() + + # Determine if we are in CLI mode + is_cli_mode = args.url or args.list_path is not None or args.get_path + + if is_cli_mode: + if not args.url: + parser.error("The 'url' argument is required for CLI mode.") + + # --- Authentication --- + auth = None + if args.auth_user: + if ':' in args.auth_user: + user, pw = args.auth_user.split(':', 1) + auth = (user, pw) + else: + auth = (args.auth_user, '') + + # --- Action: List Files --- + if args.list_path is not None: + _cli_list_files(args.url, args.list_path, args.nosubdirs, args.insecure, auth) + + # --- Action: Get File/Directory --- + elif args.get_path: + if args.directory: + # Download a directory + _cli_download_folder(args.get_path, args.url, args.output_path, args.insecure, auth) + else: + # Download a single file + _cli_download(args.get_path, args.url, args.output_path, args.insecure, auth, args.create_directories) + else: + print("No action specified. Use -l/--list to list files or -g/--get to download.", file=sys.stderr) + + else: + # Otherwise, start the web server + url = f"http://127.0.0.1{f':{port}' if port != 80 else ''}" + print(f"Server starting at {url}") + Timer(1, lambda: webbrowser.open(url)).start() + serve(app, host="127.0.0.1", port=port) + +if __name__ == "__main__": + # This block is for local development and for running the compiled executable. + main() diff --git a/sshrd.py b/sshrd.py new file mode 100644 index 0000000..d553848 --- /dev/null +++ b/sshrd.py @@ -0,0 +1,590 @@ +#!/usr/bin/env python3 +import sys +import os +import subprocess +import platform +import shutil +import time +import glob +import datetime +import plistlib +import json +import urllib.request +import argparse +from pathlib import Path +import gzip +import zipfile + +# --- Configuration & Globals --- +LOG_DIR = Path("logs") +WORK_DIR = Path("work") +SSHRAMDISK_DIR = Path("sshramdisk") +SSHTARS_DIR = Path("sshtars") +REMOTE_ZIP_VIEWER = Path("remote_zip_viewer.py").resolve() + +# Determine OS Check string (mimic uname behavior) +try: + OS_CHECK = subprocess.check_output(['uname']).decode('utf-8').strip() +except (FileNotFoundError, subprocess.SubprocessError): + # Fallback for non-MSYS2/Unix environments + system = platform.system() + if system == 'Darwin': + OS_CHECK = 'Darwin' + elif system == 'Linux': + OS_CHECK = 'Linux' + else: + # Default fallback or specific handling for Windows if uname is missing + OS_CHECK = 'MINGW64_NT-10.0-22631' + +BIN_DIR = Path(OS_CHECK) + +# Setup Logging +LOG_DIR.mkdir(exist_ok=True) + +# Clean old logs (mimic $(rm logs/*.log 2> /dev/null)) +for log_file in LOG_DIR.glob("*.log"): + try: + log_file.unlink() + except OSError: + pass + +# Create a new log file for this session +current_time = datetime.datetime.now().strftime("%H:%M:%S-%Y-%m-%d") +kernel_release = platform.release() +log_filename = f"{current_time}-{OS_CHECK}-{kernel_release}.log".replace(":", ".") # Windows friendly +LOG_FILE = LOG_DIR / log_filename + +def log(message): + """Prints message to stdout and appends to log file.""" + timestamp = datetime.datetime.now().strftime("[%H:%M:%S]") + formatted_message = f"{timestamp} {message}" + print(formatted_message) + with open(LOG_FILE, "a", encoding="utf-8") as f: + f.write(formatted_message + "\n") + +def run_cmd(cmd, cwd=None, shell=False, ignore_errors=False, capture_output=False): + """Runs a command, streaming output to stdout and the log file.""" + + # Convert paths to strings + if isinstance(cmd, list): + cmd = [str(c) for c in cmd] + + cmd_str = cmd if isinstance(cmd, str) else " ".join(cmd) + with open(LOG_FILE, "a", encoding="utf-8") as f: + f.write(f"\n[CMD] {cmd_str}\n") + + if capture_output: + try: + result = subprocess.run(cmd, cwd=cwd, shell=shell, check=not ignore_errors, capture_output=True, text=True) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + if not ignore_errors: + raise e + return "" + + # Stream output + try: + process = subprocess.Popen( + cmd, + cwd=cwd, + shell=shell, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1 + ) + + with open(LOG_FILE, "a", encoding="utf-8") as f: + for line in process.stdout: + sys.stdout.write(line) + f.write(line) + + process.wait() + if process.returncode != 0 and not ignore_errors: + raise subprocess.CalledProcessError(process.returncode, cmd) + + except Exception as e: + if not ignore_errors: + raise e + +def kill_iproxy(): + if 'MINGW' in OS_CHECK or platform.system() == 'Windows': + run_cmd(["taskkill", "/F", "/IM", "iproxy.exe"], ignore_errors=True) + else: + run_cmd(["killall", "iproxy"], ignore_errors=True) + +def error_handler(): + """Cleanup function on error.""" + log("[-] An error occurred") + # rm -rf work 12rd | true + if WORK_DIR.exists(): + shutil.rmtree(WORK_DIR, ignore_errors=True) + if Path("12rd").exists(): + shutil.rmtree("12rd", ignore_errors=True) + + # killall iproxy + kill_iproxy() + +def check_dependencies(): + """Checks and installs dependencies.""" + if not (SSHTARS_DIR / "README.md").exists(): + log("[*] Updating git submodules...") + run_cmd(["git", "submodule", "update", "--init", "--recursive"]) + + if (SSHTARS_DIR / "ssh.tar.gz").exists() and OS_CHECK == 'Linux': + log("[*] Decompressing SSH tarballs for Linux...") + for tarball in ["ssh.tar.gz", "t2ssh.tar.gz", "atvssh.tar.gz"]: + gz_path = SSHTARS_DIR / tarball + if gz_path.exists(): + with gzip.open(gz_path, 'rb') as f_in: + with open(gz_path.with_suffix(''), 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + gz_path.unlink() + + gaster_path = BIN_DIR / "gaster" + if not gaster_path.exists() and not (BIN_DIR / "gaster.exe").exists(): + log("[*] Gaster not found, downloading...") + gaster_name = f"gaster-{OS_CHECK}" + if OS_CHECK == 'Linux': + gaster_name = f"gaster-{OS_CHECK}-x86_64" + + url = f"https://nightly.link/verygenericname/gaster/workflows/makefile/main/{gaster_name}.zip" + zip_path = Path(f"{gaster_name}.zip") + + log(f"Downloading {url}...") + urllib.request.urlretrieve(url, zip_path) + with zipfile.ZipFile(zip_path, 'r') as zf: + zf.extractall() + + if Path("gaster").exists(): + shutil.move("gaster", str(BIN_DIR)) + elif Path("gaster.exe").exists(): + shutil.move("gaster.exe", str(BIN_DIR)) + + if zip_path.exists(): + zip_path.unlink() + if Path("gaster").exists(): # Cleanup if unzip extracted to folder + shutil.rmtree("gaster", ignore_errors=True) + + # chmod +x "$oscheck"/* + for f in BIN_DIR.glob("*"): + if f.is_file(): + try: + f.chmod(f.stat().st_mode | 0o111) + except: + pass + +def wait_for_dfu(): + """Waits for a device in DFU mode.""" + log("[*] Waiting for device in DFU mode") + while True: + try: + if OS_CHECK == 'Darwin': + output = subprocess.check_output("system_profiler SPUSBDataType 2> /dev/null", shell=True).decode() + if ' Apple Mobile Device (DFU Mode)' in output: + break + elif OS_CHECK == 'MINGW64_NT-10.0-22631' or 'MINGW' in OS_CHECK: + # Windows/MSYS2 check + output = subprocess.check_output("pnputil -enum-devices -connected -class USB", shell=True).decode() + if 'VID_05AC&PID_1227' in output: + break + else: + # Linux + output = subprocess.check_output("lsusb 2> /dev/null", shell=True).decode() + if ' Apple, Inc. Mobile Device (DFU Mode)' in output: + break + except subprocess.CalledProcessError: + pass + time.sleep(1) + +def get_device_info(): + """Gets CPID, MODEL, and PRODUCT using irecovery.""" + log("[*] Getting device info and pwning... this may take a second") + irecovery = BIN_DIR / "irecovery" + + try: + output = run_cmd([irecovery, "-q"], capture_output=True) + except subprocess.CalledProcessError: + log("[-] Failed to query device info via irecovery.") + sys.exit(1) + + info = {} + for line in output.splitlines(): + if ": " in line: + key, val = line.split(": ", 1) + info[key] = val.strip() + + return info.get("CPID"), info.get("MODEL"), info.get("PRODUCT") + +def get_ipsw_url(device_id, version): + """Fetches IPSW URL from ipsw.me API.""" + try: + url = f"https://api.ipsw.me/v4/device/{device_id}?type=ipsw" + with urllib.request.urlopen(url) as response: + data = json.loads(response.read().decode()) + + for firmware in data.get('firmwares', []): + if firmware['version'] == version: + return firmware['url'] + except Exception as e: + log(f"[-] Error fetching IPSW URL: {e}") + sys.exit(1) + return None + +def download_file_from_zip(url, filename, output_path=None): + """Uses remote_zip_viewer.py to download a file.""" + cmd = [sys.executable, str(REMOTE_ZIP_VIEWER), "-g", filename, url] + # The bash script runs this inside 'work' usually. + # We will handle cwd in the caller. + run_cmd(cmd, cwd=output_path, capture_output=True) # Output to devnull in bash script + +def main(): + try: + check_dependencies() + + parser = argparse.ArgumentParser(description="SSHRD Script Python Port") + parser.add_argument("arg1", nargs='?', help="iOS version or command (clean, dump-blobs, reboot, ssh, boot)") + parser.add_argument("arg2", nargs='?', help="TrollStore option") + parser.add_argument("arg3", nargs='?', help="App for TrollStore") + args = parser.parse_args() + + cmd = args.arg1 + + if cmd == 'clean': + shutil.rmtree(SSHRAMDISK_DIR, ignore_errors=True) + shutil.rmtree(WORK_DIR, ignore_errors=True) + log("[*] Removed the current created SSH ramdisk") + return + + elif cmd == 'dump-blobs': + # Implementation of dump-blobs + run_cmd([BIN_DIR / "iproxy", "2222", "22"], ignore_errors=True, shell=False) # Background? + # In python, to run background, use Popen without wait. + iproxy_proc = subprocess.Popen([str(BIN_DIR / "iproxy"), "2222", "22"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + try: + sshpass = BIN_DIR / "sshpass" + ssh_cmd = [sshpass, "-p", "alpine", "ssh", "-o", "StrictHostKeyChecking=no", "-p2222", "root@localhost"] + + version_out = run_cmd(ssh_cmd + ["sw_vers -productVersion"], capture_output=True) + version_major = int(version_out.split('.')[0]) + + device = "rdisk2" if version_major >= 16 else "rdisk1" + + # Dump raw + # cat /dev/$device | dd of=dump.raw ... + # We can do this via ssh command directly + dump_cmd = f"cat /dev/{device}" + with open("dump.raw", "wb") as f: + # We need to pipe the output of ssh to the file, but sshpass makes it tricky. + # The bash script does: ssh ... "cat ..." | dd ... + # We'll just run the full command string with shell=True for simplicity here + full_cmd = f'"{sshpass}" -p alpine ssh -o StrictHostKeyChecking=no -p2222 root@localhost "cat /dev/{device}" | dd of=dump.raw bs=256 count=$((0x4000))' + run_cmd(full_cmd, shell=True) + + run_cmd([BIN_DIR / "img4tool", "--convert", "-s", "dumped.shsh2", "dump.raw"]) + log("[*] Onboard blobs should have dumped to the dumped.shsh2 file") + finally: + iproxy_proc.terminate() + kill_iproxy() + return + + elif cmd == 'reboot': + iproxy_proc = subprocess.Popen([str(BIN_DIR / "iproxy"), "2222", "22"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + try: + sshpass = BIN_DIR / "sshpass" + run_cmd([sshpass, "-p", "alpine", "ssh", "-o", "StrictHostKeyChecking=no", "-p2222", "root@localhost", "/sbin/reboot"]) + log("[*] Device should now reboot") + finally: + iproxy_proc.terminate() + return + + elif cmd == 'ssh': + kill_iproxy() + iproxy_proc = subprocess.Popen([str(BIN_DIR / "iproxy"), "2222", "22"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + try: + sshpass = BIN_DIR / "sshpass" + subprocess.run([str(sshpass), "-p", "alpine", "ssh", "-o", "StrictHostKeyChecking=no", "-p2222", "root@localhost"]) + finally: + iproxy_proc.terminate() + kill_iproxy() + return + + # --- Main Ramdisk Creation / Boot Logic --- + + # Check if arg1 is a file (IPSW) + ipsw_url = None + ios_version = None + + if cmd and os.path.isfile(cmd) and (cmd.lower().endswith('.ipsw')): + ipsw_url = cmd + # Extract BuildManifest to get version + run_cmd([sys.executable, str(REMOTE_ZIP_VIEWER), "-g", "BuildManifest.plist", ipsw_url], capture_output=True) + + with open("BuildManifest.plist", "rb") as f: + pl = plistlib.load(f) + ios_version = pl['ProductVersion'] + os.remove("BuildManifest.plist") + elif cmd: + ios_version = cmd + else: + print("1st argument: iOS version for the ramdisk\nExtra arguments:\nTrollStore: install trollstore to system app") + sys.exit(1) + + major = int(ios_version.split('.')[0]) + minor = int(ios_version.split('.')[1]) + patch = int(ios_version.split('.')[2]) if len(ios_version.split('.')) > 2 else 0 + + # Wait for DFU + wait_for_dfu() + + # Get Device Info + cpid, model, product = get_device_info() + check = cpid.replace("CPID: ", "") # Should be just the code e.g. 8010 + replace = model + deviceid = product + + # Resolve IPSW URL if not local + if not ipsw_url: + ipsw_url = get_ipsw_url(deviceid, ios_version) + if not ipsw_url: + log(f"[-] Could not find IPSW url for {deviceid} version {ios_version}") + sys.exit(1) + + # Prepare Directories + if WORK_DIR.exists(): shutil.rmtree(WORK_DIR) + if Path("12rd").exists(): shutil.rmtree("12rd") + if not SSHRAMDISK_DIR.exists(): SSHRAMDISK_DIR.mkdir() + + # --- Create Ramdisk --- + WORK_DIR.mkdir() + + run_cmd([BIN_DIR / "gaster", "pwn"], capture_output=True) + run_cmd([BIN_DIR / "img4tool", "-e", "-s", f"other/shsh/{check}.shsh", "-m", WORK_DIR / "IM4M"]) + + # Download files + # We need to parse BuildManifest to find paths. + # Download BuildManifest first + download_file_from_zip(ipsw_url, "BuildManifest.plist", output_path=WORK_DIR) + + with open(WORK_DIR / "BuildManifest.plist", "rb") as f: + manifest = plistlib.load(f) + + # Helper to find path in manifest + def find_path(component_name): + # Logic: find build identity where 'Info' -> 'DeviceClass' matches 'replace' (MODEL) + # Then get Manifest -> component -> Info -> Path + for identity in manifest['BuildIdentities']: + # The bash script uses awk on the file text, which is messy. + # It searches for the MODEL string, then looks for the component. + # In plistlib, we should check if this identity applies to our device. + # However, the bash script logic is: awk "/$replace/{x=1}x&&/iBSS[.]/{print;exit}" + # It relies on the text order. + # A safer way in Python is to look for the identity that has the correct DeviceClass. + # But 'replace' variable comes from 'irecovery -q | grep MODEL'. + # Let's assume the first identity that matches the device class is correct. + + # Actually, the bash script just greps the file. + # Let's try to find the component path by iterating identities. + # We need to match the device model. + + # 'replace' is like 'D10AP'. + # BuildIdentities -> [] -> Info -> DeviceClass + if identity['Info']['DeviceClass'].lower() == replace.lower(): + if component_name in identity['Manifest']: + return identity['Manifest'][component_name]['Info']['Path'] + return None + + # If we can't find by strict plist parsing, we might need to fallback to text search if the plist structure varies. + # But standard IPSW BuildManifests are consistent. + + ibss_path = find_path('iBSS') + ibec_path = find_path('iBEC') + devicetree_path = find_path('DeviceTree') + kernelcache_path = find_path('KernelCache') # Usually 'kernelcache.release' key in manifest? + # Bash: awk ... /kernelcache.release/ + if not kernelcache_path: + # Try to find key containing 'kernelcache.release' + for identity in manifest['BuildIdentities']: + if identity['Info']['DeviceClass'].lower() == replace.lower(): + for key in identity['Manifest']: + if 'kernelcache.release' in key: + kernelcache_path = identity['Manifest'][key]['Info']['Path'] + break + + # RestoreRamDisk + ramdisk_path = None + trustcache_path = None + + for identity in manifest['BuildIdentities']: + if identity['Info']['DeviceClass'].lower() == replace.lower(): + ramdisk_path = identity['Manifest']['RestoreRamDisk']['Info']['Path'] + if 'trustcache' in identity['Manifest'].get('RestoreRamDisk', {}).get('Info', {}).get('Path', ''): + # Wait, trustcache is usually a separate file in Firmware/ folder, + # or inside the ramdisk info? + # Bash: Firmware/"$(plutil ... RestoreRamDisk ... Path ...).trustcache" + # It appends .trustcache to the ramdisk path. + pass + break + + if not ibss_path or not ibec_path: + log("[-] Could not find iBSS/iBEC paths in Manifest") + sys.exit(1) + + download_file_from_zip(ipsw_url, ibss_path, output_path=WORK_DIR) + download_file_from_zip(ipsw_url, ibec_path, output_path=WORK_DIR) + download_file_from_zip(ipsw_url, devicetree_path, output_path=WORK_DIR) + + # Trustcache download logic + should_dl_trustcache = True + if major < 11: should_dl_trustcache = False + elif major == 11: + if minor < 4: should_dl_trustcache = False + elif minor == 4 and patch <= 1: should_dl_trustcache = False + elif check != '0x8012': should_dl_trustcache = False + + if should_dl_trustcache: + tc_path = f"Firmware/{Path(ramdisk_path).name}.trustcache" + # Note: Bash script constructs it: Firmware/$(... | head -1).trustcache + # The ramdisk path usually looks like "Firmware/018-....dmg" + # So we want "Firmware/018-....dmg.trustcache" + # But wait, the bash script takes the basename? + # "Firmware/" + basename + ".trustcache" + # Let's try to download it. + try: + download_file_from_zip(ipsw_url, tc_path, output_path=WORK_DIR) + except: + log(f"[-] Failed to download trustcache at {tc_path}") + + download_file_from_zip(ipsw_url, kernelcache_path, output_path=WORK_DIR) + download_file_from_zip(ipsw_url, ramdisk_path, output_path=WORK_DIR) + + # Decrypt and Patch + # iBSS + ibss_local = WORK_DIR / Path(ibss_path).name + ibec_local = WORK_DIR / Path(ibec_path).name + + if major >= 18: + run_cmd([BIN_DIR / "img4", "-i", ibss_local, "-o", WORK_DIR / "iBSS.dec"]) + run_cmd([BIN_DIR / "img4", "-i", ibec_local, "-o", WORK_DIR / "iBEC.dec"]) + else: + run_cmd([BIN_DIR / "gaster", "decrypt", ibss_local, WORK_DIR / "iBSS.dec"]) + run_cmd([BIN_DIR / "gaster", "decrypt", ibec_local, WORK_DIR / "iBEC.dec"]) + + run_cmd([BIN_DIR / "iBoot64Patcher", WORK_DIR / "iBSS.dec", WORK_DIR / "iBSS.patched"]) + run_cmd([BIN_DIR / "img4", "-i", WORK_DIR / "iBSS.patched", "-o", SSHRAMDISK_DIR / "iBSS.img4", "-M", WORK_DIR / "IM4M", "-A", "-T", "ibss"]) + + # iBEC args + boot_args = "rd=md0 debug=0x2014e -v wdt=-1" + if args.arg2: # TrollStore or other args + boot_args += f" {args.arg2}={args.arg3 if args.arg3 else ''}" + if check in ['0x8960', '0x7000', '0x7001']: + boot_args += " nand-enable-reformat=1 -restore" + + run_cmd([BIN_DIR / "iBoot64Patcher", WORK_DIR / "iBEC.dec", WORK_DIR / "iBEC.patched", "-b", boot_args, "-n"]) + run_cmd([BIN_DIR / "img4", "-i", WORK_DIR / "iBEC.patched", "-o", SSHRAMDISK_DIR / "iBEC.img4", "-M", WORK_DIR / "IM4M", "-A", "-T", "ibec"]) + + # Kernel + kcache_local = WORK_DIR / Path(kernelcache_path).name + run_cmd([BIN_DIR / "img4", "-i", kcache_local, "-o", WORK_DIR / "kcache.raw"]) + run_cmd([BIN_DIR / "KPlooshFinder", WORK_DIR / "kcache.raw", WORK_DIR / "kcache.patched"]) + run_cmd([BIN_DIR / "kerneldiff", WORK_DIR / "kcache.raw", WORK_DIR / "kcache.patched", WORK_DIR / "kc.bpatch"]) + + img4_kcache_cmd = [BIN_DIR / "img4", "-i", kcache_local, "-o", SSHRAMDISK_DIR / "kernelcache.img4", "-M", WORK_DIR / "IM4M", "-T", "rkrn", "-P", WORK_DIR / "kc.bpatch"] + if OS_CHECK == 'Linux': + img4_kcache_cmd.append("-J") + run_cmd(img4_kcache_cmd) + + # DeviceTree + dt_local = WORK_DIR / Path(devicetree_path).name + run_cmd([BIN_DIR / "img4", "-i", dt_local, "-o", SSHRAMDISK_DIR / "devicetree.img4", "-M", WORK_DIR / "IM4M", "-T", "rdtr"]) + + # TrustCache & Ramdisk + if should_dl_trustcache: + tc_local = WORK_DIR / Path(tc_path).name + run_cmd([BIN_DIR / "img4", "-i", tc_local, "-o", SSHRAMDISK_DIR / "trustcache.img4", "-M", WORK_DIR / "IM4M", "-T", "rtsc"]) + + rd_local = WORK_DIR / Path(ramdisk_path).name + run_cmd([BIN_DIR / "img4", "-i", rd_local, "-o", WORK_DIR / "ramdisk.dmg"]) + + # Ramdisk Modification (HFS/HDIUTIL) + if OS_CHECK == 'Darwin': + # macOS logic + if major > 16 or (major == 16 and minor >= 1): + pass # No resize? Bash says: if >16 ... : else resize + else: + run_cmd(["hdiutil", "resize", "-size", "210MB", WORK_DIR / "ramdisk.dmg"]) + + run_cmd(["hdiutil", "attach", "-mountpoint", "/tmp/SSHRD", WORK_DIR / "ramdisk.dmg", "-owners", "off"]) + + # 16.1+ logic for creating new image + if major > 16 or (major == 16 and minor >= 1): + run_cmd(["hdiutil", "create", "-size", "210m", "-imagekey", "diskimage-class=CRawDiskImage", "-format", "UDZO", "-fs", "HFS+", "-layout", "NONE", "-srcfolder", "/tmp/SSHRD", "-copyuid", "root", WORK_DIR / "ramdisk1.dmg"]) + run_cmd(["hdiutil", "detach", "-force", "/tmp/SSHRD"]) + run_cmd(["hdiutil", "attach", "-mountpoint", "/tmp/SSHRD", WORK_DIR / "ramdisk1.dmg", "-owners", "off"]) + + # Extract SSH + if replace == 'j42dap': + run_cmd([BIN_DIR / "gtar", "-x", "--no-overwrite-dir", "-f", SSHTARS_DIR / "atvssh.tar.gz", "-C", "/tmp/SSHRD/"]) + elif check == '0x8012': + run_cmd([BIN_DIR / "gtar", "-x", "--no-overwrite-dir", "-f", SSHTARS_DIR / "t2ssh.tar.gz", "-C", "/tmp/SSHRD/"]) + log("[!] WARNING: T2 MIGHT HANG AND DO NOTHING WHEN BOOTING THE RAMDISK!") + else: + # iOS 12 logic omitted for brevity, assuming standard flow or add if needed + run_cmd([BIN_DIR / "gtar", "-x", "--no-overwrite-dir", "-f", SSHTARS_DIR / "ssh.tar.gz", "-C", "/tmp/SSHRD/"]) + + run_cmd(["hdiutil", "detach", "-force", "/tmp/SSHRD"]) + + if major > 16 or (major == 16 and minor >= 1): + run_cmd(["hdiutil", "resize", "-sectors", "min", WORK_DIR / "ramdisk1.dmg"]) + else: + run_cmd(["hdiutil", "resize", "-sectors", "min", WORK_DIR / "ramdisk.dmg"]) + + else: + # Linux/Windows logic + if major > 16 or (major == 16 and minor >= 1): + log("Sorry, 16.1 and above doesn't work on Linux at the moment!") + sys.exit(1) + + run_cmd([BIN_DIR / "hfsplus", WORK_DIR / "ramdisk.dmg", "grow", "210000000"]) + + if replace == 'j42dap': + run_cmd([BIN_DIR / "hfsplus", WORK_DIR / "ramdisk.dmg", "untar", SSHTARS_DIR / "atvssh.tar"]) + elif check == '0x8012': + run_cmd([BIN_DIR / "hfsplus", WORK_DIR / "ramdisk.dmg", "untar", SSHTARS_DIR / "t2ssh.tar"]) + else: + run_cmd([BIN_DIR / "hfsplus", WORK_DIR / "ramdisk.dmg", "untar", SSHTARS_DIR / "ssh.tar"]) + + # Finalize Ramdisk + rd_input = WORK_DIR / "ramdisk.dmg" + if OS_CHECK == 'Darwin' and (major > 16 or (major == 16 and minor >= 1)): + rd_input = WORK_DIR / "ramdisk1.dmg" + + run_cmd([BIN_DIR / "img4", "-i", rd_input, "-o", SSHRAMDISK_DIR / "ramdisk.img4", "-M", WORK_DIR / "IM4M", "-A", "-T", "rdsk"]) + run_cmd([BIN_DIR / "img4", "-i", "other/bootlogo.im4p", "-o", SSHRAMDISK_DIR / "logo.img4", "-M", WORK_DIR / "IM4M", "-A", "-T", "rlgo"]) + + log("") + log("[*] Cleaning up work directory") + shutil.rmtree(WORK_DIR, ignore_errors=True) + if Path("12rd").exists(): shutil.rmtree("12rd") + + log("") + log("[*] Finished! Please use python3 sshrd.py boot to boot your device") + + with open(SSHRAMDISK_DIR / "version.txt", "w") as f: + f.write(ios_version) + + log("making ramdisk copy") + ramdisk_folder = Path("ramdisks") / f"{replace}_{ios_version}" + ramdisk_folder.mkdir(parents=True, exist_ok=True) + + for f in SSHRAMDISK_DIR.glob("*"): + shutil.copy2(f, ramdisk_folder) + + except Exception as e: + log(f"[-] Exception: {e}") + error_handler() + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/sshrd.sh b/sshrd.sh index bab0e41..8220224 100755 --- a/sshrd.sh +++ b/sshrd.sh @@ -7,11 +7,21 @@ $(rm logs/*.log 2> /dev/null) set -e oscheck=$(uname) -version="$1" +if [ -f "$1" ] && [[ "$1" == *.ipsw || "$1" == *.IPSW ]]; then + ipswurl=$1 + # Extract BuildManifest.plist to get the version + python3 ./remote_zip_viewer.py -g BuildManifest.plist "$ipswurl" >/dev/null + if [ "$oscheck" = 'Darwin' ]; then + version=$(plutil -p BuildManifest.plist | grep '"ProductVersion"' | awk -F ' => ' '{print $2}' | tr -d '"') + else + version=$(python3 -c "import plistlib; print(plistlib.load(open('BuildManifest.plist', 'rb'))['ProductVersion'])") + fi + rm BuildManifest.plist -major=$(echo "$version" | cut -d. -f1) -minor=$(echo "$version" | cut -d. -f2) -patch=$(echo "$version" | cut -d. -f3) + major=$(echo "$version" | cut -d. -f1) + minor=$(echo "$version" | cut -d. -f2) + patch=$(echo "$version" | cut -d. -f3) +fi ERR_HANDLER () { [ $? -eq 0 ] && exit @@ -91,6 +101,14 @@ elif [ "$oscheck" = 'Darwin' ]; then while ! (system_profiler SPUSBDataType 2> /dev/null | grep ' Apple Mobile Device (DFU Mode)' >> /dev/null); do sleep 1 done +elif [ "$oscheck" = 'MINGW64_NT-10.0-22631' ]; then + if ! pnputil -enum-devices -connected -class USB | grep -q ' Apple Mobile Device'; then + echo "[*] Waiting for device in DFU mode" + fi + + while ! pnputil -enum-devices -connected -class USB | grep ' Apple Mobile Device' >/dev/null; do + sleep 1 + done else if ! (lsusb 2> /dev/null | grep ' Apple, Inc. Mobile Device (DFU Mode)' >> /dev/null); then echo "[*] Waiting for device in DFU mode" @@ -105,7 +123,23 @@ echo "[*] Getting device info and pwning... this may take a second" check=$("$oscheck"/irecovery -q | grep CPID | sed 's/CPID: //') replace=$("$oscheck"/irecovery -q | grep MODEL | sed 's/MODEL: //') deviceid=$("$oscheck"/irecovery -q | grep PRODUCT | sed 's/PRODUCT: //') -ipswurl=$(curl -sL "https://api.ipsw.me/v4/device/$deviceid?type=ipsw" | "$oscheck"/jq '.firmwares | .[] | select(.version=="'$1'")' | "$oscheck"/jq -s '.[0] | .url' --raw-output) +if [ -f "$1" ]; then + ipswurl=$1 + # Extract BuildManifest.plist to get the version + python3 ./remote_zip_viewer.py -g BuildManifest.plist "$ipswurl" >/dev/null + if [ "$oscheck" = 'Darwin' ]; then + version=$(plutil -p BuildManifest.plist | grep '"ProductVersion"' | awk -F ' => ' '{print $2}' | tr -d '"') + else + version=$(python3 -c "import plistlib; print(plistlib.load(open('BuildManifest.plist', 'rb'))['ProductVersion'])") + fi + rm BuildManifest.plist + + major=$(echo "$version" | cut -d. -f1) + minor=$(echo "$version" | cut -d. -f2) + patch=$(echo "$version" | cut -d. -f3) +else + ipswurl=$(curl -sL "https://api.ipsw.me/v4/device/$deviceid?type=ipsw" | "$oscheck"/jq '.firmwares | .[] | select(.version=="'$1'")' | "$oscheck"/jq -s '.[0] | .url' --raw-output) +fi if [ -e work ]; then rm -rf work @@ -206,31 +240,31 @@ fi "$oscheck"/img4tool -e -s other/shsh/"${check}".shsh -m work/IM4M cd work -../"$oscheck"/pzb -g BuildManifest.plist "$ipswurl" -../"$oscheck"/pzb -g "$(awk "/""${replace}""/{x=1}x&&/iBSS[.]/{print;exit}" BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1)" "$ipswurl" -../"$oscheck"/pzb -g "$(awk "/""${replace}""/{x=1}x&&/iBEC[.]/{print;exit}" BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1)" "$ipswurl" -../"$oscheck"/pzb -g "$(awk "/""${replace}""/{x=1}x&&/DeviceTree[.]/{print;exit}" BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1)" "$ipswurl" +python3 ../remote_zip_viewer.py -g BuildManifest.plist "$ipswurl" +python3 ../remote_zip_viewer.py -g "$(awk "/""${replace}""/{x=1}x&&/iBSS[.]/{print;exit}" BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1)" "$ipswurl" +python3 ../remote_zip_viewer.py -g "$(awk "/""${replace}""/{x=1}x&&/iBEC[.]/{print;exit}" BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1)" "$ipswurl" +python3 ../remote_zip_viewer.py -g "$(awk "/""${replace}""/{x=1}x&&/DeviceTree[.]/{print;exit}" BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1)" "$ipswurl" if [ "$oscheck" = 'Darwin' ]; then if [ "$major" -lt 11 ] || ([ "$major" -eq 11 ] && ([ "$minor" -lt 4 ] || [ "$minor" -eq 4 ] && [ "$patch" -le 1 ] || [ "$check" != '0x8012' ])); then : else - ../"$oscheck"/pzb -g Firmware/"$(/usr/bin/plutil -extract "BuildIdentities".0."Manifest"."RestoreRamDisk"."Info"."Path" xml1 -o - BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1 | head -1)".trustcache "$ipswurl" + python3 ../remote_zip_viewer.py -g Firmware/"$(/usr/bin/plutil -extract "BuildIdentities".0."Manifest"."RestoreRamDisk"."Info"."Path" xml1 -o - BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1 | head -1)".trustcache "$ipswurl" fi else if [ "$major" -lt 11 ] || ([ "$major" -eq 11 ] && ([ "$minor" -lt 4 ] || [ "$minor" -eq 4 ] && [ "$patch" -le 1 ] || [ "$check" != '0x8012' ])); then : else - ../"$oscheck"/pzb -g Firmware/"$(../Linux/PlistBuddy BuildManifest.plist -c "Print BuildIdentities:0:Manifest:RestoreRamDisk:Info:Path" | sed 's/"//g')".trustcache "$ipswurl" + python3 ../remote_zip_viewer.py -g Firmware/"$(python3 -c "import plistlib; pl = plistlib.load(open('./BuildManifest.plist', 'rb')); print(pl['BuildIdentities'][0]['Manifest']['RestoreRamDisk']['Info']['Path'])" | sed 's/"//g')".trustcache "$ipswurl" fi fi -../"$oscheck"/pzb -g "$(awk "/""${replace}""/{x=1}x&&/kernelcache.release/{print;exit}" BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1)" "$ipswurl" +python3 ../remote_zip_viewer.py -g "$(awk "/""${replace}""/{x=1}x&&/kernelcache.release/{print;exit}" BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1)" "$ipswurl" if [ "$oscheck" = 'Darwin' ]; then - ../"$oscheck"/pzb -g "$(/usr/bin/plutil -extract "BuildIdentities".0."Manifest"."RestoreRamDisk"."Info"."Path" xml1 -o - BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1 | head -1)" "$ipswurl" + python3 ../remote_zip_viewer.py -g "$(/usr/bin/plutil -extract "BuildIdentities".0."Manifest"."RestoreRamDisk"."Info"."Path" xml1 -o - BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1 | head -1)" "$ipswurl" else - ../"$oscheck"/pzb -g "$(../Linux/PlistBuddy BuildManifest.plist -c "Print BuildIdentities:0:Manifest:RestoreRamDisk:Info:Path" | sed 's/"//g')" "$ipswurl" + python3 ../remote_zip_viewer.py -g "$(python3 -c "import plistlib; pl = plistlib.load(open('./BuildManifest.plist', 'rb')); print(pl['BuildIdentities'][0]['Manifest']['RestoreRamDisk']['Info']['Path'])" | sed 's/"//g')" "$ipswurl" fi cd .. @@ -263,9 +297,9 @@ else if [ "$major" -lt 11 ] || ([ "$major" -eq 11 ] && ([ "$minor" -lt 4 ] || [ "$minor" -eq 4 ] && [ "$patch" -le 1 ] || [ "$check" != '0x8012' ])); then : else - "$oscheck"/img4 -i work/"$(Linux/PlistBuddy work/BuildManifest.plist -c "Print BuildIdentities:0:Manifest:RestoreRamDisk:Info:Path" | sed 's/"//g')".trustcache -o sshramdisk/trustcache.img4 -M work/IM4M -T rtsc + "$oscheck"/img4 -i work/"$(python3 -c "import plistlib; pl = plistlib.load(open('work/BuildManifest.plist', 'rb')); print(pl['BuildIdentities'][0]['Manifest']['RestoreRamDisk']['Info']['Path'])" | sed 's/"//g')".trustcache -o sshramdisk/trustcache.img4 -M work/IM4M -T rtsc fi - "$oscheck"/img4 -i work/"$(Linux/PlistBuddy work/BuildManifest.plist -c "Print BuildIdentities:0:Manifest:RestoreRamDisk:Info:Path" | sed 's/"//g')" -o work/ramdisk.dmg + "$oscheck"/img4 -i work/"$(python3 -c "import plistlib; pl = plistlib.load(open('work/BuildManifest.plist', 'rb')); print(pl['BuildIdentities'][0]['Manifest']['RestoreRamDisk']['Info']['Path'])" | sed 's/"//g')" -o work/ramdisk.dmg fi if [ "$oscheck" = 'Darwin' ]; then @@ -294,8 +328,8 @@ if [ "$oscheck" = 'Darwin' ]; then mkdir 12rd ipswurl12=$(curl -sL "https://api.ipsw.me/v4/device/$deviceid?type=ipsw" | "$oscheck"/jq '.firmwares | .[] | select(.version=="'12.0'")' | "$oscheck"/jq -s '.[0] | .url' --raw-output) cd 12rd - ../"$oscheck"/pzb -g BuildManifest.plist "$ipswurl12" - ../"$oscheck"/pzb -g "$(/usr/bin/plutil -extract "BuildIdentities".0."Manifest"."RestoreRamDisk"."Info"."Path" xml1 -o - BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1 | head -1)" "$ipswurl12" + python3 ../remote_zip_viewer.py -g BuildManifest.plist "$ipswurl12" + python3 ../remote_zip_viewer.py -g "$(/usr/bin/plutil -extract "BuildIdentities".0."Manifest"."RestoreRamDisk"."Info"."Path" xml1 -o - BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1 | head -1)" "$ipswurl12" ../"$oscheck"/img4 -i "$(/usr/bin/plutil -extract "BuildIdentities".0."Manifest"."RestoreRamDisk"."Info"."Path" xml1 -o - BuildManifest.plist | grep '' |cut -d\> -f2 |cut -d\< -f1 | head -1)" -o ramdisk.dmg hdiutil attach -mountpoint /tmp/12rd ramdisk.dmg -owners off cp /tmp/12rd/usr/lib/libiconv.2.dylib /tmp/12rd/usr/lib/libcharset.1.dylib /tmp/SSHRD/usr/lib/ @@ -333,9 +367,11 @@ else mkdir 12rd ipswurl12=$(curl -sL "https://api.ipsw.me/v4/device/$deviceid?type=ipsw" | "$oscheck"/jq '.firmwares | .[] | select(.version=="'12.0'")' | "$oscheck"/jq -s '.[0] | .url' --raw-output) cd 12rd - ../"$oscheck"/pzb -g BuildManifest.plist "$ipswurl12" - ../"$oscheck"/pzb -g "$(../Linux/PlistBuddy BuildManifest.plist -c "Print BuildIdentities:0:Manifest:RestoreRamDisk:Info:Path" | sed 's/"//g')" "$ipswurl12" - ../"$oscheck"/img4 -i "$(../Linux/PlistBuddy BuildManifest.plist -c "Print BuildIdentities:0:Manifest:RestoreRamDisk:Info:Path" | sed 's/"//g')" -o ramdisk.dmg + python3 ../remote_zip_viewer.py -g BuildManifest.plist "$ipswurl12" + ramdiskname=python3 -c "import plistlib; pl = plistlib.load(open('./BuildManifest.plist', 'rb')); print(pl['BuildIdentities'][0]['Manifest']['RestoreRamDisk']['Info']['Path'])" | sed 's/"//g' + echo $ramdiskname + python3 ../remote_zip_viewer.py -g "$(python3 -c "import plistlib; pl = plistlib.load(open('./BuildManifest.plist', 'rb')); print(pl['BuildIdentities'][0]['Manifest']['RestoreRamDisk']['Info']['Path'])" | sed 's/"//g')" "$ipswurl12" + ../"$oscheck"/img4 -i "$(python3 -c "import plistlib; pl = plistlib.load(open('./BuildManifest.plist', 'rb')); print(pl['BuildIdentities'][0]['Manifest']['RestoreRamDisk']['Info']['Path'])" | sed 's/"//g')" -o ramdisk.dmg ../"$oscheck"/hfsplus ramdisk.dmg extract usr/lib/libcharset.1.dylib libcharset.1.dylib ../"$oscheck"/hfsplus ramdisk.dmg extract usr/lib/libiconv.2.dylib libiconv.2.dylib ../"$oscheck"/hfsplus ../work/ramdisk.dmg add libiconv.2.dylib usr/lib/libiconv.2.dylib @@ -373,6 +409,13 @@ rm -rf work 12rd echo "" echo "[*] Finished! Please use ./sshrd.sh boot to boot your device" -echo $1 > sshramdisk/version.txt - +echo $version > sshramdisk/version.txt + +echo "making ramdisk copy" +ramdiskfolder=ramdisks/"$replace"_"$version"/ +mkdir -p $ramdiskfolder | true +for file in sshramdisk/*; do + echo "copying $file to $ramdiskfolder" + cp -au "$file" "$ramdiskfolder" +done } | tee logs/"$(date +%T)"-"$(date +%F)"-"$(uname)"-"$(uname -r)".log