Repository: opa334/TrollStore Branch: main Commit: d11c04666a77 Files: 83 Total size: 302.2 KB Directory structure: gitextract_wvjx7p6u/ ├── .github/ │ └── ISSUE_TEMPLATE/ │ └── bug-report-issue-template.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── Pwnify/ │ ├── Makefile │ └── main.m ├── README.md ├── RootHelper/ │ ├── Makefile │ ├── control │ ├── devmode.h │ ├── devmode.m │ ├── entitlements.plist │ ├── jit.h │ ├── jit.m │ ├── main.m │ ├── uicache.h │ ├── uicache.m │ ├── unarchive.h │ └── unarchive.m ├── Shared/ │ ├── CoreServices.h │ ├── TSListControllerShared.h │ ├── TSListControllerShared.m │ ├── TSPresentationDelegate.h │ ├── TSPresentationDelegate.m │ ├── TSUtil.h │ └── TSUtil.m ├── TrollHelper/ │ ├── Makefile │ ├── Resources/ │ │ └── Info.plist │ ├── TSHAppDelegateNoScene.h │ ├── TSHAppDelegateNoScene.m │ ├── TSHAppDelegateWithScene.h │ ├── TSHAppDelegateWithScene.m │ ├── TSHRootViewController.h │ ├── TSHRootViewController.m │ ├── TSHSceneDelegate.h │ ├── TSHSceneDelegate.m │ ├── control │ ├── entitlements.plist │ └── main.m ├── TrollStore/ │ ├── Makefile │ ├── Resources/ │ │ ├── Base.lproj/ │ │ │ └── LaunchScreen.storyboardc/ │ │ │ ├── Info.plist │ │ │ ├── Kx4-55-vNS-view-9BB-B5-Vbi.nib │ │ │ ├── UITabBarController-9el-pn-lH0.nib │ │ │ └── X3T-Aa-nEE-view-vAu-RC-m7d.nib │ │ └── Info.plist │ ├── TSAppDelegate.h │ ├── TSAppDelegate.m │ ├── TSAppInfo.h │ ├── TSAppInfo.m │ ├── TSAppTableViewController.h │ ├── TSAppTableViewController.m │ ├── TSApplicationsManager.h │ ├── TSApplicationsManager.m │ ├── TSCommonTCCServiceNames.h │ ├── TSDonateListController.h │ ├── TSDonateListController.m │ ├── TSInstallationController.h │ ├── TSInstallationController.m │ ├── TSRootViewController.h │ ├── TSRootViewController.m │ ├── TSSceneDelegate.h │ ├── TSSceneDelegate.m │ ├── TSSettingsAdvancedListController.h │ ├── TSSettingsAdvancedListController.m │ ├── TSSettingsListController.h │ ├── TSSettingsListController.m │ ├── control │ ├── entitlements.plist │ └── main.m ├── TrollStoreLite/ │ ├── .gitignore │ ├── Makefile │ ├── Resources/ │ │ ├── Base.lproj/ │ │ │ └── LaunchScreen.storyboardc/ │ │ │ ├── Info.plist │ │ │ ├── Kx4-55-vNS-view-9BB-B5-Vbi.nib │ │ │ ├── UITabBarController-9el-pn-lH0.nib │ │ │ └── X3T-Aa-nEE-view-vAu-RC-m7d.nib │ │ └── Info.plist │ ├── control │ └── entitlements.plist ├── Victim/ │ ├── README.md │ ├── make_cert.sh │ └── victim.p12 └── legacy.p12 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report-issue-template.yml ================================================ name: Bug Report description: Bug Report (No Duplicates Issue Here!) body: - type: markdown attributes: value: | # Before post issue please read me Your issue may already be reported! Please search on the [issue tracker](https://github.com/opa334/TrollStore/issues) before creating one. - type: checkboxes id: no-duplicates-issue attributes: label: No Duplicates Issue options: - label: I'm sure I've searched on the issue tracker before creating one. required: true - type: textarea id: expected-behavior attributes: label: Expected Behavior? description: | Describing a bug, tell us what should happen placeholder: Tell us what you **Expected**! validations: required: true - type: textarea id: current-behavior attributes: label: Current Behavior? description: | Describing a bug, tell us what happens instead of the expected behavior placeholder: Tell us what you **Happened**! validations: required: true - type: textarea id: possible-solution attributes: label: Possible Solution? description: | Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change placeholder: If not sure leave blank. value: "-" - type: textarea id: steps-to-reproduce attributes: label: Steps to Reproduce description: | Provide a link to a live example, or an unambiguous set of steps to reproduce this bug. Include code to reproduce, if relevant placeholder: | 1. 2. 3. value: "-" - type: markdown attributes: value: | # Your Environment - type: input id: trollstore-version attributes: label: TrollStore Version description: like TrollInstaller2 validations: required: true - type: input id: ios-version attributes: label: iOS/iPadOS version description: like iOS 15.1 validations: required: true - type: input id: idevice-model attributes: label: iDevice Model description: like iPhone 11 validations: required: true - type: input id: other-env attributes: label: Other info of your environment description: like macOS 12.6, the newest THEOS... ================================================ FILE: .gitignore ================================================ out/ .DS_Store .theos/ packages/ xcuserdata .vscode Pwnify/pwnify InstallerVictim.ipa _build ================================================ FILE: .gitmodules ================================================ [submodule "ChOma"] path = ChOma url = https://github.com/opa334/ChOma ================================================ FILE: LICENSE ================================================ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: TrollStore Upstream-Contact: opa334 Source: https://github.com/opa334/TrollStore Files: * Copyright: 2022-2024 Lars Fröder License: MIT Files: RootHelper/uicache.m Copyright: Copyright (c) 2019 CoolStar, Modified work Copyright (c) 2020-2022 Procursus Team Modified work Copyright (c) 2022-2024 Lars Fröder License: BSD-4-Clause License: BSD-4-Clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by CoolStar. 4. Neither the name of the copyright holder 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 HOLDERS 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. License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ TOPTARGETS := all clean update $(TOPTARGETS): pre_build make_fastPathSign make_roothelper make_trollstore make_trollhelper_embedded make_trollhelper_package assemble_trollstore build_installer15 build_installer64e make_trollstore_lite pre_build: @rm -rf ./_build 2>/dev/null || true @mkdir -p ./_build make_fastPathSign: @$(MAKE) -C ./Exploits/fastPathSign $(MAKECMDGOALS) make_roothelper: @$(MAKE) -C ./RootHelper DEBUG=0 $(MAKECMDGOALS) make_trollstore: @$(MAKE) -C ./TrollStore FINALPACKAGE=1 $(MAKECMDGOALS) ifneq ($(MAKECMDGOALS),clean) make_trollhelper_package: @$(MAKE) clean -C ./TrollHelper @cp ./RootHelper/.theos/obj/trollstorehelper ./TrollHelper/Resources/trollstorehelper @$(MAKE) -C ./TrollHelper FINALPACKAGE=1 package $(MAKECMDGOALS) @$(MAKE) clean -C ./TrollHelper @$(MAKE) -C ./TrollHelper THEOS_PACKAGE_SCHEME=rootless FINALPACKAGE=1 package $(MAKECMDGOALS) @rm ./TrollHelper/Resources/trollstorehelper make_trollhelper_embedded: @$(MAKE) clean -C ./TrollHelper @$(MAKE) -C ./TrollHelper FINALPACKAGE=1 EMBEDDED_ROOT_HELPER=1 $(MAKECMDGOALS) @cp ./TrollHelper/.theos/obj/TrollStorePersistenceHelper.app/TrollStorePersistenceHelper ./_build/PersistenceHelper_Embedded @$(MAKE) clean -C ./TrollHelper @$(MAKE) -C ./TrollHelper FINALPACKAGE=1 EMBEDDED_ROOT_HELPER=1 LEGACY_CT_BUG=1 $(MAKECMDGOALS) @cp ./TrollHelper/.theos/obj/TrollStorePersistenceHelper.app/TrollStorePersistenceHelper ./_build/PersistenceHelper_Embedded_Legacy_arm64 @$(MAKE) clean -C ./TrollHelper @$(MAKE) -C ./TrollHelper FINALPACKAGE=1 EMBEDDED_ROOT_HELPER=1 CUSTOM_ARCHS=arm64e $(MAKECMDGOALS) @cp ./TrollHelper/.theos/obj/TrollStorePersistenceHelper.app/TrollStorePersistenceHelper ./_build/PersistenceHelper_Embedded_Legacy_arm64e @$(MAKE) clean -C ./TrollHelper assemble_trollstore: @cp ./RootHelper/.theos/obj/trollstorehelper ./TrollStore/.theos/obj/TrollStore.app/trollstorehelper @cp ./TrollHelper/.theos/obj/TrollStorePersistenceHelper.app/TrollStorePersistenceHelper ./TrollStore/.theos/obj/TrollStore.app/PersistenceHelper @export COPYFILE_DISABLE=1 @tar -czvf ./_build/TrollStore.tar -C ./TrollStore/.theos/obj TrollStore.app build_installer15: @mkdir -p ./_build/tmp15 @unzip ./Victim/InstallerVictim.ipa -d ./_build/tmp15 @cp ./_build/PersistenceHelper_Embedded_Legacy_arm64 ./_build/TrollStorePersistenceHelperToInject @pwnify set-cpusubtype ./_build/TrollStorePersistenceHelperToInject 1 @ldid -s -K./Victim/victim.p12 ./_build/TrollStorePersistenceHelperToInject APP_PATH=$$(find ./_build/tmp15/Payload -name "*" -depth 1) ; \ APP_NAME=$$(basename $$APP_PATH) ; \ BINARY_NAME=$$(echo "$$APP_NAME" | cut -f 1 -d '.') ; \ echo $$BINARY_NAME ; \ pwnify pwn ./_build/tmp15/Payload/$$APP_NAME/$$BINARY_NAME ./_build/TrollStorePersistenceHelperToInject @pushd ./_build/tmp15 ; \ zip -vrD ../../_build/TrollHelper_iOS15.ipa * ; \ popd @rm ./_build/TrollStorePersistenceHelperToInject @rm -rf ./_build/tmp15 build_installer64e: @mkdir -p ./_build/tmp64e @unzip ./Victim/InstallerVictim.ipa -d ./_build/tmp64e APP_PATH=$$(find ./_build/tmp64e/Payload -name "*" -depth 1) ; \ APP_NAME=$$(basename $$APP_PATH) ; \ BINARY_NAME=$$(echo "$$APP_NAME" | cut -f 1 -d '.') ; \ echo $$BINARY_NAME ; \ pwnify pwn64e ./_build/tmp64e/Payload/$$APP_NAME/$$BINARY_NAME ./_build/PersistenceHelper_Embedded_Legacy_arm64e @pushd ./_build/tmp64e ; \ zip -vrD ../../_build/TrollHelper_arm64e.ipa * ; \ popd @rm -rf ./_build/tmp64e make_trollstore_lite: @$(MAKE) -C ./RootHelper DEBUG=0 TROLLSTORE_LITE=1 @rm -rf ./TrollStoreLite/Resources/trollstorehelper @cp ./RootHelper/.theos/obj/trollstorehelper_lite ./TrollStoreLite/Resources/trollstorehelper @$(MAKE) -C ./TrollStoreLite package FINALPACKAGE=1 @$(MAKE) -C ./RootHelper TROLLSTORE_LITE=1 clean @$(MAKE) -C ./TrollStoreLite clean @$(MAKE) -C ./RootHelper DEBUG=0 TROLLSTORE_LITE=1 THEOS_PACKAGE_SCHEME=rootless @rm -rf ./TrollStoreLite/Resources/trollstorehelper @cp ./RootHelper/.theos/obj/trollstorehelper_lite ./TrollStoreLite/Resources/trollstorehelper @$(MAKE) -C ./TrollStoreLite package FINALPACKAGE=1 THEOS_PACKAGE_SCHEME=rootless else make_trollstore_lite: @$(MAKE) -C ./TrollStoreLite $(MAKECMDGOALS) endif .PHONY: $(TOPTARGETS) pre_build assemble_trollstore make_trollhelper_package make_trollhelper_embedded build_installer15 build_installer64e ================================================ FILE: Pwnify/Makefile ================================================ pwnify: @clang main.m -fobjc-arc -fmodules -mmacosx-version-min=11.0 -o pwnify install: pwnify -@sudo rm /usr/local/bin/pwnify 2>/dev/null || true @sudo cp ./pwnify /usr/local/bin/pwnify clean: @rm ./pwnify 2>/dev/null || true ================================================ FILE: Pwnify/main.m ================================================ // // main.m // pwnify-universal // // Created by Lars Fröder on 08.10.22. // #import #import #import #import #define ALIGN_DEFAULT 0xE uint32_t roundUp(int numToRound, int multiple) { if (multiple == 0) return numToRound; int remainder = numToRound % multiple; if (remainder == 0) return numToRound; return numToRound + multiple - remainder; } void expandFile(FILE* file, uint32_t size) { fseek(file, 0, SEEK_END); if(ftell(file) >= size) return; while(ftell(file) != size) { char c = 0; fwrite(&c, 1, 1, file); } } void copyData(FILE* sourceFile, FILE* targetFile, size_t size) { for(size_t i = 0; i < size; i++) { char b; fread(&b, 1, 1, sourceFile); fwrite(&b, 1, 1, targetFile); } } void enumerateArchs(NSString* binaryPath, void (^archEnumBlock)(struct fat_arch* arch, uint32_t archFileOffset, struct mach_header* machHeader, uint32_t sliceFileOffset, FILE* file, BOOL* stop)) { FILE* machoFile = fopen(binaryPath.fileSystemRepresentation, "rb"); if(!machoFile) return; struct mach_header header; fread(&header,sizeof(header),1,machoFile); if(header.magic == FAT_MAGIC || header.magic == FAT_CIGAM) { fseek(machoFile,0,SEEK_SET); struct fat_header fatHeader; fread(&fatHeader,sizeof(fatHeader),1,machoFile); for(int i = 0; i < OSSwapBigToHostInt32(fatHeader.nfat_arch); i++) { uint32_t archFileOffset = sizeof(fatHeader) + sizeof(struct fat_arch) * i; struct fat_arch fatArch; fseek(machoFile, archFileOffset,SEEK_SET); fread(&fatArch,sizeof(fatArch),1,machoFile); uint32_t sliceFileOffset = OSSwapBigToHostInt32(fatArch.offset); struct mach_header archHeader; fseek(machoFile, sliceFileOffset, SEEK_SET); fread(&archHeader,sizeof(archHeader),1,machoFile); BOOL stop = NO; archEnumBlock(&fatArch, archFileOffset, &archHeader, sliceFileOffset, machoFile, &stop); if(stop) break; } } else if(header.magic == MH_MAGIC_64 || header.magic == MH_CIGAM_64) { BOOL stop; archEnumBlock(NULL, 0, &header, 0, machoFile, &stop); } fclose(machoFile); } void printArchs(NSString* binaryPath) { __block int i = 0; enumerateArchs(binaryPath, ^(struct fat_arch* arch, uint32_t archFileOffset, struct mach_header* machHeader, uint32_t sliceFileOffset, FILE* file, BOOL* stop) { if(arch) { printf("%d. fatArch type: 0x%X, subtype: 0x%X, align:0x%X, size:0x%X, offset:0x%X\n| ", i, OSSwapBigToHostInt32(arch->cputype), OSSwapBigToHostInt32(arch->cpusubtype), OSSwapBigToHostInt32(arch->align), OSSwapBigToHostInt32(arch->size), OSSwapBigToHostInt32(arch->offset)); } printf("machHeader type: 0x%X, subtype: 0x%X\n", OSSwapLittleToHostInt32(machHeader->cputype), OSSwapLittleToHostInt32(machHeader->cpusubtype)); i++; }); } void pwnify(NSString* appStoreBinary, NSString* binaryToInject, BOOL preferArm64e) { NSString* tmpFilePath = [NSTemporaryDirectory() stringByAppendingString:[[NSUUID UUID] UUIDString]]; // Determine amount of slices in output __block int slicesCount = 1; enumerateArchs(appStoreBinary, ^(struct fat_arch* arch, uint32_t archFileOffset, struct mach_header* machHeader, uint32_t sliceFileOffset, FILE* file, BOOL* stop) { slicesCount++; }); // Allocate FAT data uint32_t fatDataSize = sizeof(struct fat_header) + slicesCount * sizeof(struct fat_arch); char* fatData = malloc(fatDataSize); // Construct new fat header struct fat_header fatHeader; fatHeader.magic = OSSwapHostToBigInt32(0xCAFEBABE); fatHeader.nfat_arch = OSSwapHostToBigInt32(slicesCount); memcpy(&fatData[0], &fatHeader, sizeof(fatHeader)); uint32_t align = pow(2, ALIGN_DEFAULT); __block uint32_t curOffset = align; __block uint32_t curArchIndex = 0; // Construct new fat arch data enumerateArchs(appStoreBinary, ^(struct fat_arch* arch, uint32_t archFileOffset, struct mach_header* machHeader, uint32_t sliceFileOffset, FILE* file, BOOL* stop) { struct fat_arch newArch; if(arch) { newArch.cputype = arch->cputype; if(OSSwapBigToHostInt32(arch->cputype) == 0x100000C) { newArch.cpusubtype = OSSwapHostToBigInt32(2); // SET app store binary in FAT header to 2, fixes arm64e } else { newArch.cpusubtype = arch->cpusubtype; } newArch.size = arch->size; } else { newArch.cputype = OSSwapHostToBigInt32(OSSwapLittleToHostInt32(machHeader->cputype)); if(OSSwapLittleToHostInt32(machHeader->cputype) == 0x100000C) { newArch.cpusubtype = OSSwapHostToBigInt32(2); // SET app store binary in FAT header to 2, fixes arm64e } else { newArch.cpusubtype = OSSwapHostToBigInt32(OSSwapLittleToHostInt32(machHeader->cpusubtype)); } newArch.size = OSSwapHostToBigInt32((uint32_t)[[[NSFileManager defaultManager] attributesOfItemAtPath:appStoreBinary error:nil] fileSize]); } newArch.align = OSSwapHostToBigInt32(ALIGN_DEFAULT); newArch.offset = OSSwapHostToBigInt32(curOffset); curOffset += roundUp(OSSwapBigToHostInt32(newArch.size), align); memcpy(&fatData[sizeof(fatHeader) + sizeof(struct fat_arch)*curArchIndex], &newArch, sizeof(newArch)); curArchIndex++; }); // Determine what slices our injection binary contains __block BOOL toInjectHasArm64e = NO; __block BOOL toInjectHasArm64 = NO; enumerateArchs(binaryToInject, ^(struct fat_arch* arch, uint32_t archFileOffset, struct mach_header* machHeader, uint32_t sliceFileOffset, FILE* file, BOOL* stop) { if(arch) { if(OSSwapBigToHostInt32(arch->cputype) == 0x100000C) { if (!((OSSwapBigToHostInt32(arch->cpusubtype) ^ 0x2) & 0xFFFFFF)) { toInjectHasArm64e = YES; } else if(!((OSSwapBigToHostInt32(arch->cpusubtype) ^ 0x1) & 0xFFFFFF)) { toInjectHasArm64 = YES; } } } else { if(OSSwapLittleToHostInt32(machHeader->cputype) == 0x100000C) { if (!((OSSwapLittleToHostInt32(machHeader->cpusubtype) ^ 0x2) & 0xFFFFFF)) { toInjectHasArm64e = YES; } else if(!((OSSwapLittleToHostInt32(machHeader->cpusubtype) ^ 0x1) & 0xFFFFFF)) { toInjectHasArm64 = YES; } } } }); if(!toInjectHasArm64 && !preferArm64e) { printf("ERROR: can't proceed injection because binary to inject has no arm64 slice\n"); return; } uint32_t subtypeToUse = 0x1; if(preferArm64e && toInjectHasArm64e) { subtypeToUse = 0x2; } enumerateArchs(binaryToInject, ^(struct fat_arch* arch, uint32_t archFileOffset, struct mach_header* machHeader, uint32_t sliceFileOffset, FILE* file, BOOL* stop) { struct fat_arch currentArch; if(arch) { currentArch.cputype = arch->cputype; currentArch.cpusubtype = arch->cpusubtype; currentArch.size = arch->size; } else { currentArch.cputype = OSSwapHostToBigInt(OSSwapLittleToHostInt32(machHeader->cputype)); currentArch.cpusubtype = OSSwapHostToBigInt(OSSwapLittleToHostInt32(machHeader->cpusubtype)); currentArch.size = OSSwapHostToBigInt((uint32_t)[[[NSFileManager defaultManager] attributesOfItemAtPath:binaryToInject error:nil] fileSize]); } if(OSSwapBigToHostInt32(currentArch.cputype) == 0x100000C) { if (!((OSSwapBigToHostInt32(currentArch.cpusubtype) ^ subtypeToUse) & 0xFFFFFF)) { currentArch.align = OSSwapHostToBigInt32(ALIGN_DEFAULT); currentArch.offset = OSSwapHostToBigInt32(curOffset); curOffset += roundUp(OSSwapBigToHostInt32(currentArch.size), align); memcpy(&fatData[sizeof(fatHeader) + sizeof(struct fat_arch)*curArchIndex], ¤tArch, sizeof(currentArch)); curArchIndex++; *stop = YES; } } }); // FAT Header constructed, now write to file and then write the slices themselves FILE* tmpFile = fopen(tmpFilePath.fileSystemRepresentation, "wb"); fwrite(&fatData[0], fatDataSize, 1, tmpFile); curArchIndex = 0; enumerateArchs(appStoreBinary, ^(struct fat_arch* arch, uint32_t archFileOffset, struct mach_header* machHeader, uint32_t sliceFileOffset, FILE* file, BOOL* stop) { struct fat_arch* toWriteArch = (struct fat_arch*)&fatData[sizeof(fatHeader) + sizeof(struct fat_arch)*curArchIndex]; expandFile(tmpFile, OSSwapBigToHostInt32(toWriteArch->offset)); uint32_t offset = 0; uint32_t size = 0; if(arch) { offset = OSSwapBigToHostInt32(arch->offset); size = OSSwapBigToHostInt32(arch->size); } else { size = OSSwapBigToHostInt32(toWriteArch->size); } FILE* appStoreBinaryFile = fopen(appStoreBinary.fileSystemRepresentation, "rb"); fseek(appStoreBinaryFile, offset, SEEK_SET); copyData(appStoreBinaryFile, tmpFile, size); fclose(appStoreBinaryFile); curArchIndex++; }); struct fat_arch* toWriteArch = (struct fat_arch*)&fatData[sizeof(fatHeader) + sizeof(struct fat_arch)*curArchIndex]; enumerateArchs(binaryToInject, ^(struct fat_arch* arch, uint32_t archFileOffset, struct mach_header* machHeader, uint32_t sliceFileOffset, FILE* file, BOOL* stop) { struct fat_arch currentArch; if(arch) { currentArch.cputype = arch->cputype; currentArch.cpusubtype = arch->cpusubtype; currentArch.size = arch->size; } else { currentArch.cputype = OSSwapHostToBigInt32(OSSwapLittleToHostInt32(machHeader->cputype)); currentArch.cpusubtype = OSSwapHostToBigInt32(OSSwapLittleToHostInt32(machHeader->cpusubtype)); currentArch.size = OSSwapHostToBigInt32((uint32_t)[[[NSFileManager defaultManager] attributesOfItemAtPath:binaryToInject error:nil] fileSize]); } if(OSSwapBigToHostInt32(currentArch.cputype) == 0x100000C) { if (!((OSSwapBigToHostInt32(currentArch.cpusubtype) ^ subtypeToUse) & 0xFFFFFF)) { expandFile(tmpFile, OSSwapBigToHostInt32(toWriteArch->offset)); uint32_t offset = 0; uint32_t size = 0; if(arch) { offset = OSSwapBigToHostInt32(arch->offset); size = OSSwapBigToHostInt32(arch->size); } else { size = OSSwapBigToHostInt32(toWriteArch->size); } FILE* binaryToInjectFile = fopen(binaryToInject.fileSystemRepresentation, "rb"); fseek(binaryToInjectFile, offset, SEEK_SET); copyData(binaryToInjectFile, tmpFile, size); fclose(binaryToInjectFile); *stop = YES; } } }); fclose(tmpFile); chmod(tmpFilePath.fileSystemRepresentation, 0755); [[NSFileManager defaultManager] removeItemAtPath:appStoreBinary error:nil]; [[NSFileManager defaultManager] moveItemAtPath:tmpFilePath toPath:appStoreBinary error:nil]; } void setCPUSubtype(NSString* binaryPath, uint32_t subtype) { FILE* binaryFile = fopen(binaryPath.fileSystemRepresentation, "rb+"); if(!binaryFile) { printf("ERROR: File not found\n"); return; } enumerateArchs(binaryPath, ^(struct fat_arch *arch, uint32_t archFileOffset, struct mach_header *machHeader, uint32_t sliceFileOffset, FILE *file, BOOL *stop) { if(arch) { if(OSSwapBigToHostInt(arch->cputype) == 0x100000C) { if(OSSwapBigToHostInt(arch->cpusubtype) == 0x0) { arch->cpusubtype = OSSwapHostToBigInt32(subtype); fseek(binaryFile, archFileOffset, SEEK_SET); fwrite(arch, sizeof(struct fat_arch), 1, binaryFile); } } } if(OSSwapLittleToHostInt32(machHeader->cputype) == 0x100000C) { if(OSSwapLittleToHostInt32(machHeader->cpusubtype) == 0x0) { machHeader->cpusubtype = OSSwapHostToLittleInt32(subtype); fseek(binaryFile, sliceFileOffset, SEEK_SET); fwrite(machHeader, sizeof(struct mach_header), 1, binaryFile); } } }); fclose(binaryFile); } void printUsageAndExit(void) { printf("Usage:\n\nPrint architectures of a binary:\npwnify print \n\nInject target slice into victim binary:\npwnify pwn(64e) \n\nModify cpusubtype of a non FAT binary:\npwnify set-cpusubtype \n"); exit(0); } int main(int argc, const char * argv[]) { @autoreleasepool { if(argc < 3) { printUsageAndExit(); } NSString* operation = [NSString stringWithUTF8String:argv[1]]; if([operation isEqualToString:@"print"]) { NSString* binaryToPrint = [NSString stringWithUTF8String:argv[2]]; printArchs(binaryToPrint); } else if([operation isEqualToString:@"pwn"]) { if(argc < 4) printUsageAndExit(); NSString* victimBinary = [NSString stringWithUTF8String:argv[2]]; NSString* targetBinary = [NSString stringWithUTF8String:argv[3]]; pwnify(victimBinary, targetBinary, NO); } else if([operation isEqualToString:@"pwn64e"]) { if(argc < 4) printUsageAndExit(); NSString* victimBinary = [NSString stringWithUTF8String:argv[2]]; NSString* targetBinary = [NSString stringWithUTF8String:argv[3]]; pwnify(victimBinary, targetBinary, YES); } else if([operation isEqualToString:@"set-cpusubtype"]) { if(argc < 4) printUsageAndExit(); NSString* binaryToModify = [NSString stringWithUTF8String:argv[2]]; NSString* subtypeToSet = [NSString stringWithUTF8String:argv[3]]; NSNumberFormatter* f = [[NSNumberFormatter alloc] init]; f.numberStyle = NSNumberFormatterDecimalStyle; NSNumber* subtypeToSetNum = [f numberFromString:subtypeToSet]; setCPUSubtype(binaryToModify, [subtypeToSetNum unsignedIntValue]); } else { printUsageAndExit(); } } return 0; } ================================================ FILE: README.md ================================================ # TrollStore TrollStore is a permasigned jailed app that can permanently install any IPA you open in it. It works because of an AMFI/CoreTrust bug where iOS does not correctly verify code signatures of binaries in which there are multiple signers. Supported versions: 14.0 beta 2 - 16.6.1, 16.7 RC (20H18), 17.0 ## Installing TrollStore For installing TrollStore, refer to the guides at [ios.cfw.guide](https://ios.cfw.guide/installing-trollstore) 16.7.x (excluding 16.7 RC) and 17.0.1+ will NEVER be supported (unless a third CoreTrust bug is discovered, which is unlikely). ## Updating TrollStore When a new TrollStore update is available, a button to install it will appear at the top in the TrollStore settings. After tapping the button, TrollStore will automatically download the update, install it, and respring. Alternatively (if anything goes wrong), you can download the TrollStore.tar file under Releases and open it in TrollStore, TrollStore will install the update and respring. ## Uninstalling an app Apps installed from TrollStore can only be uninstalled from TrollStore itself, tap an app or swipe it to the left in the 'Apps' tab to delete it. ## Persistence Helper The CoreTrust bug used in TrollStore is only enough to install "System" apps, this is because FrontBoard has an additional security check (it calls libmis) every time before a user app is launched. Unfortunately it is not possible to install new "System" apps that stay through an icon cache reload. Therefore, when iOS reloads the icon cache, all TrollStore installed apps including TrollStore itself will revert back to "User" state and will no longer launch. The only way to work around this is to install a persistence helper into a system app, this helper can then be used to reregister TrollStore and its installed apps as "System" so that they become launchable again, an option for this is available in TrollStore settings. On jailbroken iOS 14 when TrollHelper is used for installation, it is located in /Applications and will persist as a "System" app through icon cache reloads, therefore TrollHelper is used as the persistence helper on iOS 14. ## URL Scheme As of version 1.3, TrollStore replaces the system URL scheme "apple-magnifier" (this is done so "jailbreak" detections can't detect TrollStore like they could if TrollStore had a unique URL scheme). This URL scheme can be used to install applications right from the browser, or to enable JIT from the app itself (only 2.0.12 and above), the format goes as follows: - `apple-magnifier://install?url=` - `apple-magnifier://enable-jit?bundle-id=` On devices that don't have TrollStore (1.3+) installed, this will just open the magnifier app. ## Features The binaries inside an IPA can have arbitrary entitlements, fakesign them with ldid and the entitlements you want (`ldid -S `) and TrollStore will preserve the entitlements when resigning them with the fake root certificate on installation. This gives you a lot of possibilities, some of which are explained below. ### Banned entitlements iOS 15 on A12+ has banned the following three entitlements related to running unsigned code, these are impossible to get without a PPL bypass, apps signed with them will crash on launch. `com.apple.private.cs.debugger` `dynamic-codesigning` `com.apple.private.skip-library-validation` ### Unsandboxing Your app can run unsandboxed using one of the following entitlements: ```xml com.apple.private.security.container-required ``` ```xml com.apple.private.security.no-container ``` ```xml com.apple.private.security.no-sandbox ``` The third one is recommended if you still want a sandbox container for your application. You might also need the platform-application entitlement in order for these to work properly: ```xml platform-application ``` Please note that the platform-application entitlement causes side effects such as some parts of the sandbox becoming tighter, so you may need additional private entitlements to circumvent that. (For example afterwards you need an exception entitlement for every single IOKit user client class you want to access). In order for an app with `com.apple.private.security.no-sandbox` and `platform-application` to be able to access it's own data container, you might need the additional entitlement: ```xml com.apple.private.security.storage.AppDataContainers ``` ### Root Helpers When your app is not sandboxed, you can spawn other binaries using posix_spawn, you can also spawn binaries as root with the following entitlement: ```xml com.apple.private.persona-mgmt ``` You can also add your own binaries into your app bundle. Afterwards you can use the [spawnRoot function in TSUtil.m](./Shared/TSUtil.m#L79) to spawn the binary as root. ### Things that are not possible using TrollStore - Getting proper platformization (`TF_PLATFORM` / `CS_PLATFORMIZED`) - Spawning a launch daemon (Would need `CS_PLATFORMIZED`) - Injecting a tweak into a system process (Would need `TF_PLATFORM`, a userland PAC bypass and a PMAP trust level bypass) ### Compilation To compile TrollStore, ensure [theos](https://theos.dev/docs/installation) is installed. Additionaly ensure [brew](https://brew.sh/) is installed and install [libarchive](https://formulae.brew.sh/formula/libarchive) from brew. ## Credits and Further Reading [@alfiecg_dev](https://twitter.com/alfiecg_dev/) - Found the CoreTrust bug that allows TrollStore to work through patchdiffing and worked on automating the bypass. Google Threat Analysis Group - Found the CoreTrust bug as part of an in-the-wild spyware chain and reported it to Apple. [@LinusHenze](https://twitter.com/LinusHenze) - Found the installd bypass used to install TrollStore on iOS 14-15.6.1 via TrollHelperOTA, as well as the original CoreTrust bug used in TrollStore 1.0. [Fugu15 Presentation](https://youtu.be/rPTifU1lG7Q) [Write-Up on the first CoreTrust bug with more information](https://worthdoingbadly.com/coretrust/). ================================================ FILE: RootHelper/Makefile ================================================ TARGET := iphone:clang:16.5:14.0 ARCHS = arm64 ifdef TROLLSTORE_LITE HELPER_NAME = trollstorehelper_lite else HELPER_NAME = trollstorehelper TARGET_CODESIGN = ../Exploits/fastPathSign/fastPathSign endif include $(THEOS)/makefiles/common.mk TOOL_NAME = $(HELPER_NAME) $(HELPER_NAME)_FILES = $(wildcard *.m) $(wildcard ../Shared/*.m) $(wildcard ../ChOma/src/*.c) ifndef TROLLSTORE_LITE $(HELPER_NAME)_FILES += ../Exploits/fastPathSign/src/coretrust_bug.c ../Exploits/fastPathSign/src/codesign.m $(HELPER_NAME)_CODESIGN_FLAGS = --entitlements entitlements.plist $(HELPER_NAME)_LDFLAGS = -L../ChOma/external/ios -lcrypto else $(HELPER_NAME)_CODESIGN_FLAGS = -Sentitlements.plist endif $(HELPER_NAME)_CFLAGS = -fobjc-arc -I../Shared $(shell pkg-config --cflags libcrypto) -I../ChOma/src -I../Exploits/fastPathSign/src -I$(shell brew --prefix)/opt/libarchive/include $(HELPER_NAME)_INSTALL_PATH = /usr/local/bin $(HELPER_NAME)_LIBRARIES = archive $(HELPER_NAME)_FRAMEWORKS = CoreTelephony $(HELPER_NAME)_PRIVATE_FRAMEWORKS = SpringBoardServices BackBoardServices MobileContainerManager FrontBoardServices RunningBoardServices ifdef TROLLSTORE_LITE $(HELPER_NAME)_CFLAGS += -DTROLLSTORE_LITE -DDISABLE_SIGNING=1 endif include $(THEOS_MAKE_PATH)/tool.mk ================================================ FILE: RootHelper/control ================================================ Package: com.opa334.trollstoreroothelper Name: trollstoreroothelper Version: 2.1 Architecture: iphoneos-arm Description: An awesome tool of some sort!! Maintainer: opa334 Author: opa334 Section: System Tag: role::hacker ================================================ FILE: RootHelper/devmode.h ================================================ #import BOOL checkDeveloperMode(void); BOOL armDeveloperMode(BOOL* alreadyEnabled); ================================================ FILE: RootHelper/devmode.m ================================================ @import Foundation; #ifndef __XPC_H__ // Types typedef NSObject* xpc_object_t; typedef xpc_object_t xpc_connection_t; typedef void (^xpc_handler_t)(xpc_object_t object); // Communication extern xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq, uint64_t flags); extern void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler); extern void xpc_connection_resume(xpc_connection_t connection); extern void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message, dispatch_queue_t replyq, xpc_handler_t handler); extern xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection, xpc_object_t message); extern xpc_object_t xpc_dictionary_get_value(xpc_object_t xdict, const char *key); #endif // Serialization extern CFTypeRef _CFXPCCreateCFObjectFromXPCObject(xpc_object_t xpcattrs); extern xpc_object_t _CFXPCCreateXPCObjectFromCFObject(CFTypeRef attrs); extern xpc_object_t _CFXPCCreateXPCMessageWithCFObject(CFTypeRef obj); extern CFTypeRef _CFXPCCreateCFObjectFromXPCMessage(xpc_object_t obj); typedef enum { kAMFIActionArm = 0, // Trigger a prompt asking the user to enable developer mode on the next reboot // (regardless of current state) kAMFIActionDisable = 1, // Disable developer mode if it's currently enabled. Takes effect immediately. kAMFIActionStatus = 2, // Returns a dict: {success: bool, status: bool, armed: bool} } AMFIXPCAction; xpc_connection_t startConnection(void) { xpc_connection_t connection = xpc_connection_create_mach_service("com.apple.amfi.xpc", NULL, 0); if (!connection) { NSLog(@"[startXPCConnection] Failed to create XPC connection to amfid"); return nil; } xpc_connection_set_event_handler(connection, ^(xpc_object_t event) { }); xpc_connection_resume(connection); return connection; } NSDictionary* sendXPCRequest(xpc_connection_t connection, AMFIXPCAction action) { xpc_object_t message = _CFXPCCreateXPCMessageWithCFObject((__bridge CFDictionaryRef) @{@"action": @(action)}); xpc_object_t replyMsg = xpc_connection_send_message_with_reply_sync(connection, message); if (!replyMsg) { NSLog(@"[sendXPCRequest] got no reply from amfid"); return nil; } xpc_object_t replyObj = xpc_dictionary_get_value(replyMsg, "cfreply"); if (!replyObj) { NSLog(@"[sendXPCRequest] got reply but no cfreply"); return nil; } NSDictionary* asCF = (__bridge NSDictionary*)_CFXPCCreateCFObjectFromXPCMessage(replyObj); return asCF; } BOOL getDeveloperModeState(xpc_connection_t connection) { NSDictionary* reply = sendXPCRequest(connection, kAMFIActionStatus); if (!reply) { NSLog(@"[getDeveloperModeState] failed to get reply"); return NO; } NSLog(@"[getDeveloperModeState] got reply %@", reply); NSObject* success = reply[@"success"]; if (!success || ![success isKindOfClass:[NSNumber class]] || ![(NSNumber*)success boolValue]) { NSLog(@"[getDeveloperModeState] request failed with error %@", reply[@"error"]); return NO; } NSObject* status = reply[@"status"]; if (!status || ![status isKindOfClass:[NSNumber class]]) { NSLog(@"[getDeveloperModeState] request succeeded but no status"); return NO; } return [(NSNumber*)status boolValue]; } BOOL setDeveloperModeState(xpc_connection_t connection, BOOL enable) { NSDictionary* reply = sendXPCRequest(connection, enable ? kAMFIActionArm : kAMFIActionDisable); if (!reply) { NSLog(@"[setDeveloperModeState] failed to get reply"); return NO; } NSObject* success = reply[@"success"]; if (!success || ![success isKindOfClass:[NSNumber class]] || ![(NSNumber*)success boolValue]) { NSLog(@"[setDeveloperModeState] request failed with error %@", reply[@"error"]); return NO; } return YES; } BOOL checkDeveloperMode(void) { // Developer mode does not exist before iOS 16 if (@available(iOS 16, *)) { xpc_connection_t connection = startConnection(); if (!connection) { NSLog(@"[checkDeveloperMode] failed to start connection"); // Assume it's disabled return NO; } return getDeveloperModeState(connection); } else { return YES; } } BOOL armDeveloperMode(BOOL* alreadyEnabled) { // Developer mode does not exist before iOS 16 if (@available(iOS 16, *)) { xpc_connection_t connection = startConnection(); if (!connection) { NSLog(@"[armDeveloperMode] failed to start connection"); return NO; } BOOL enabled = getDeveloperModeState(connection); if (alreadyEnabled) { *alreadyEnabled = enabled; } if (enabled) { // NSLog(@"[armDeveloperMode] already enabled"); return YES; } BOOL success = setDeveloperModeState(connection, YES); if (!success) { NSLog(@"[armDeveloperMode] failed to arm"); return NO; } } return YES; } ================================================ FILE: RootHelper/entitlements.plist ================================================ platform-application com.apple.private.security.container-required com.apple.private.security.no-sandbox com.apple.private.security.container-manager com.apple.private.MobileContainerManager.allowed com.apple.private.coreservices.canmaplsdatabase com.apple.lsapplicationworkspace.rebuildappdatabases com.apple.private.security.storage.AppBundles com.apple.private.security.storage.MobileDocuments com.apple.private.security.storage-exempt.heritable com.apple.private.MobileInstallationHelperService.InstallDaemonOpsEnabled com.apple.private.MobileInstallationHelperService.allowed com.apple.private.uninstall.deletion com.apple.springboard.launchapplications com.apple.backboardd.launchapplications com.apple.frontboard.launchapplications com.apple.multitasking.termination com.apple.private.mobileinstall.allowedSPI InstallForLaunchServices Install UninstallForLaunchServices Uninstall UpdatePlaceholderMetadata com.apple.private.amfi.developer-mode-control com.apple.frontboard.shutdown com.apple.runningboard.process-state ================================================ FILE: RootHelper/jit.h ================================================ #import int enableJIT(NSString *bundleID); ================================================ FILE: RootHelper/jit.m ================================================ @import Foundation; @import Darwin; @interface RBSProcessPredicate + (instancetype)predicateMatchingBundleIdentifier:(NSString *)bundleID; @end @interface RBSProcessHandle + (instancetype)handleForPredicate:(RBSProcessPredicate *)predicate error:(NSError **)error; - (int)rbs_pid; @end #define PT_DETACH 11 #define PT_ATTACHEXC 14 int ptrace(int request, pid_t pid, caddr_t addr, int data); int enableJIT(NSString *bundleID) { #ifdef EMBEDDED_ROOT_HELPER return -1; #else RBSProcessPredicate *predicate = [RBSProcessPredicate predicateMatchingBundleIdentifier:bundleID]; RBSProcessHandle* process = [RBSProcessHandle handleForPredicate:predicate error:nil]; int pid = process.rbs_pid; if (!pid) { return ESRCH; } int ret = ptrace(PT_ATTACHEXC, pid, 0, 0); if (ret == -1) { return errno; } usleep(100000); ret = ptrace(PT_DETACH, pid, 0, 0); if (ret == -1) { return errno; } return 0; #endif } ================================================ FILE: RootHelper/main.m ================================================ #import #import "unarchive.h" @import Foundation; #import "uicache.h" #import #import #import #import #import #import #import #import #import "devmode.h" #import "jit.h" #ifndef EMBEDDED_ROOT_HELPER #import "codesign.h" #import "coretrust_bug.h" #import "FAT.h" #import "MachO.h" #import "FileStream.h" #import "Host.h" #endif #import #import #import #import #ifdef EMBEDDED_ROOT_HELPER #define MAIN_NAME rootHelperMain #else #define MAIN_NAME main #endif void cleanRestrictions(void); extern mach_msg_return_t SBReloadIconForIdentifier(mach_port_t machport, const char* identifier); @interface SBSHomeScreenService : NSObject - (void)reloadIcons; @end extern NSString* BKSActivateForEventOptionTypeBackgroundContentFetching; extern NSString* BKSOpenApplicationOptionKeyActivateForEvent; extern void BKSTerminateApplicationForReasonAndReportWithDescription(NSString *bundleID, int reasonID, bool report, NSString *description); #define kCFPreferencesNoContainer CFSTR("kCFPreferencesNoContainer") typedef CFPropertyListRef (*_CFPreferencesCopyValueWithContainerType)(CFStringRef key, CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath); typedef void (*_CFPreferencesSetValueWithContainerType)(CFStringRef key, CFPropertyListRef value, CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath); typedef Boolean (*_CFPreferencesSynchronizeWithContainerType)(CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath); typedef CFArrayRef (*_CFPreferencesCopyKeyListWithContainerType)(CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath); typedef CFDictionaryRef (*_CFPreferencesCopyMultipleWithContainerType)(CFArrayRef keysToFetch, CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath); BOOL _installPersistenceHelper(LSApplicationProxy* appProxy, NSString* sourcePersistenceHelper, NSString* sourceRootHelper); NSArray* applicationsWithGroupId(NSString* groupId) { LSEnumerator* enumerator = [LSEnumerator enumeratorForApplicationProxiesWithOptions:0]; enumerator.predicate = [NSPredicate predicateWithFormat:@"groupContainerURLs[%@] != nil", groupId]; return enumerator.allObjects; } NSSet* systemURLSchemes(void) { LSEnumerator* enumerator = [LSEnumerator enumeratorForApplicationProxiesWithOptions:0]; NSMutableSet* systemURLSchemesSet = [NSMutableSet new]; LSApplicationProxy* proxy; while(proxy = [enumerator nextObject]) { if(isRemovableSystemApp(proxy.bundleIdentifier) || ![proxy.bundleURL.path hasPrefix:@"/private/var/containers"]) { for(NSString* claimedURLScheme in proxy.claimedURLSchemes) { if([claimedURLScheme isKindOfClass:NSString.class]) { [systemURLSchemesSet addObject:claimedURLScheme.lowercaseString]; } } } } return systemURLSchemesSet.copy; } NSSet* immutableAppBundleIdentifiers(void) { NSMutableSet* systemAppIdentifiers = [NSMutableSet new]; LSEnumerator* enumerator = [LSEnumerator enumeratorForApplicationProxiesWithOptions:0]; LSApplicationProxy* appProxy; while(appProxy = [enumerator nextObject]) { if(appProxy.installed) { if(![appProxy.bundleURL.path hasPrefix:@"/private/var/containers"]) { [systemAppIdentifiers addObject:appProxy.bundleIdentifier.lowercaseString]; } } } return systemAppIdentifiers.copy; } NSDictionary* infoDictionaryForAppPath(NSString* appPath) { if(!appPath) return nil; NSString* infoPlistPath = [appPath stringByAppendingPathComponent:@"Info.plist"]; return [NSDictionary dictionaryWithContentsOfFile:infoPlistPath]; } NSString* appIdForAppPath(NSString* appPath) { if(!appPath) return nil; return infoDictionaryForAppPath(appPath)[@"CFBundleIdentifier"]; } NSString* appMainExecutablePathForAppPath(NSString* appPath) { if(!appPath) return nil; return [appPath stringByAppendingPathComponent:infoDictionaryForAppPath(appPath)[@"CFBundleExecutable"]]; } NSString* appPathForAppId(NSString* appId) { if(!appId) return nil; for(NSString* appPath in trollStoreInstalledAppBundlePaths()) { if([appIdForAppPath(appPath) isEqualToString:appId]) { return appPath; } } return nil; } NSString* findAppNameInBundlePath(NSString* bundlePath) { NSArray* bundleItems = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:bundlePath error:nil]; for(NSString* bundleItem in bundleItems) { if([bundleItem.pathExtension isEqualToString:@"app"]) { return bundleItem; } } return nil; } NSString* findAppPathInBundlePath(NSString* bundlePath) { NSString* appName = findAppNameInBundlePath(bundlePath); if(!appName) return nil; return [bundlePath stringByAppendingPathComponent:appName]; } NSURL* findAppURLInBundleURL(NSURL* bundleURL) { NSString* appName = findAppNameInBundlePath(bundleURL.path); if(!appName) return nil; return [bundleURL URLByAppendingPathComponent:appName]; } BOOL isMachoFile(NSString* filePath) { FILE* file = fopen(filePath.fileSystemRepresentation, "r"); if(!file) return NO; fseek(file, 0, SEEK_SET); uint32_t magic; fread(&magic, sizeof(uint32_t), 1, file); fclose(file); return magic == FAT_MAGIC || magic == FAT_CIGAM || magic == MH_MAGIC_64 || magic == MH_CIGAM_64; } void fixPermissionsOfAppBundle(NSString* appBundlePath) { // Apply correct permissions (First run, set everything to 644, owner 33) NSURL* fileURL; NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:appBundlePath] includingPropertiesForKeys:nil options:0 errorHandler:nil]; while(fileURL = [enumerator nextObject]) { NSString* filePath = fileURL.path; chown(filePath.fileSystemRepresentation, 33, 33); chmod(filePath.fileSystemRepresentation, 0644); } // Apply correct permissions (Second run, set executables and directories to 0755) enumerator = [[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:appBundlePath] includingPropertiesForKeys:nil options:0 errorHandler:nil]; while(fileURL = [enumerator nextObject]) { NSString* filePath = fileURL.path; BOOL isDir; [[NSFileManager defaultManager] fileExistsAtPath:fileURL.path isDirectory:&isDir]; if(isDir || isMachoFile(filePath)) { chmod(filePath.fileSystemRepresentation, 0755); } } } NSArray* TSURLScheme(void) { return @[ @{ @"CFBundleURLName" : @"com.apple.Magnifier", @"CFBundleURLSchemes" : @[ @"apple-magnifier" ] } ]; } BOOL getTSURLSchemeState(NSString* customAppPath) { NSString* pathToUse = customAppPath ?: trollStoreAppPath(); NSDictionary* trollStoreInfoDict = infoDictionaryForAppPath(pathToUse); return (BOOL)trollStoreInfoDict[@"CFBundleURLTypes"]; } void setTSURLSchemeState(BOOL newState, NSString* customAppPath) { NSString* tsAppPath = trollStoreAppPath(); NSString* pathToUse = customAppPath ?: tsAppPath; if(newState != getTSURLSchemeState(pathToUse)) { NSDictionary* trollStoreInfoDict = infoDictionaryForAppPath(pathToUse); NSMutableDictionary* trollStoreInfoDictM = trollStoreInfoDict.mutableCopy; if(newState) { trollStoreInfoDictM[@"CFBundleURLTypes"] = TSURLScheme(); } else { [trollStoreInfoDictM removeObjectForKey:@"CFBundleURLTypes"]; } NSString* outPath = [pathToUse stringByAppendingPathComponent:@"Info.plist"]; [trollStoreInfoDictM.copy writeToURL:[NSURL fileURLWithPath:outPath] error:nil]; } } #ifdef TROLLSTORE_LITE BOOL isLdidInstalled(void) { // Since TrollStore Lite depends on ldid, we assume it exists return YES; } NSString *getLdidPath(void) { return JBROOT_PATH(@"/usr/bin/ldid"); } #else void installLdid(NSString* ldidToCopyPath, NSString* ldidVersion) { if(![[NSFileManager defaultManager] fileExistsAtPath:ldidToCopyPath]) return; NSString* ldidPath = [trollStoreAppPath() stringByAppendingPathComponent:@"ldid"]; NSString* ldidVersionPath = [trollStoreAppPath() stringByAppendingPathComponent:@"ldid.version"]; if([[NSFileManager defaultManager] fileExistsAtPath:ldidPath]) { [[NSFileManager defaultManager] removeItemAtPath:ldidPath error:nil]; } [[NSFileManager defaultManager] copyItemAtPath:ldidToCopyPath toPath:ldidPath error:nil]; NSData* ldidVersionData = [ldidVersion dataUsingEncoding:NSUTF8StringEncoding]; [ldidVersionData writeToFile:ldidVersionPath atomically:YES]; chmod(ldidPath.fileSystemRepresentation, 0755); chmod(ldidVersionPath.fileSystemRepresentation, 0644); } BOOL isLdidInstalled(void) { NSString* ldidPath = [trollStoreAppPath() stringByAppendingPathComponent:@"ldid"]; return [[NSFileManager defaultManager] fileExistsAtPath:ldidPath]; } NSString *getLdidPath(void) { return [trollStoreAppPath() stringByAppendingPathComponent:@"ldid"]; } #endif int runLdid(NSArray* args, NSString** output, NSString** errorOutput) { NSString* ldidPath = getLdidPath(); NSMutableArray* argsM = args.mutableCopy ?: [NSMutableArray new]; [argsM insertObject:ldidPath.lastPathComponent atIndex:0]; NSUInteger argCount = [argsM count]; char **argsC = (char **)malloc((argCount + 1) * sizeof(char*)); for (NSUInteger i = 0; i < argCount; i++) { argsC[i] = strdup([[argsM objectAtIndex:i] UTF8String]); } argsC[argCount] = NULL; posix_spawn_file_actions_t action; posix_spawn_file_actions_init(&action); int outErr[2]; pipe(outErr); posix_spawn_file_actions_adddup2(&action, outErr[1], STDERR_FILENO); posix_spawn_file_actions_addclose(&action, outErr[0]); int out[2]; pipe(out); posix_spawn_file_actions_adddup2(&action, out[1], STDOUT_FILENO); posix_spawn_file_actions_addclose(&action, out[0]); pid_t task_pid; int status = -200; NSLog(@"About to spawn ldid (%@) with args %@", ldidPath, args); int spawnError = posix_spawn(&task_pid, [ldidPath fileSystemRepresentation], &action, NULL, (char* const*)argsC, NULL); for (NSUInteger i = 0; i < argCount; i++) { free(argsC[i]); } free(argsC); if(spawnError != 0) { NSLog(@"ldid failed to spawn with error %d (%s)\n", spawnError, strerror(spawnError)); return spawnError; } do { if (waitpid(task_pid, &status, 0) != -1) { //printf("Child status %dn", WEXITSTATUS(status)); } else { perror("waitpid"); return -222; } } while (!WIFEXITED(status) && !WIFSIGNALED(status)); close(outErr[1]); close(out[1]); NSString* ldidOutput = getNSStringFromFile(out[0]); if(output) { *output = ldidOutput; } NSString* ldidErrorOutput = getNSStringFromFile(outErr[0]); if(errorOutput) { *errorOutput = ldidErrorOutput; } return WEXITSTATUS(status); } BOOL certificateHasDataForExtensionOID(SecCertificateRef certificate, CFStringRef oidString) { if(certificate == NULL || oidString == NULL) { NSLog(@"[certificateHasDataForExtensionOID] attempted to check null certificate or OID"); return NO; } CFDataRef extensionData = SecCertificateCopyExtensionValue(certificate, oidString, NULL); if(extensionData != NULL) { CFRelease(extensionData); return YES; } return NO; } BOOL codeCertChainContainsFakeAppStoreExtensions(SecStaticCodeRef codeRef) { if(codeRef == NULL) { NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] attempted to check cert chain of null static code object"); return NO; } CFDictionaryRef signingInfo = NULL; OSStatus result; result = SecCodeCopySigningInformation(codeRef, kSecCSSigningInformation, &signingInfo); if(result != errSecSuccess) { NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] failed to copy signing info from static code"); return NO; } CFArrayRef certificates = CFDictionaryGetValue(signingInfo, kSecCodeInfoCertificates); if(certificates == NULL || CFArrayGetCount(certificates) == 0) { return NO; } // If we match the standard Apple policy, we are signed properly, but we haven't been deliberately signed with a custom root SecPolicyRef appleAppStorePolicy = SecPolicyCreateWithProperties(kSecPolicyAppleiPhoneApplicationSigning, NULL); SecTrustRef trust = NULL; SecTrustCreateWithCertificates(certificates, appleAppStorePolicy, &trust); if(SecTrustEvaluateWithError(trust, nil)) { CFRelease(trust); CFRelease(appleAppStorePolicy); CFRelease(signingInfo); NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] found certificate extension, but was issued by Apple (App Store)"); return NO; } // We haven't matched Apple, so keep going. Is the app profile signed? CFRelease(appleAppStorePolicy); SecPolicyRef appleProfileSignedPolicy = SecPolicyCreateWithProperties(kSecPolicyAppleiPhoneProfileApplicationSigning, NULL); if(SecTrustSetPolicies(trust, appleProfileSignedPolicy) != errSecSuccess) { NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] error replacing trust policy to check for profile-signed app"); CFRelease(trust); CFRelease(signingInfo); return NO; } if(SecTrustEvaluateWithError(trust, nil)) { CFRelease(trust); CFRelease(appleProfileSignedPolicy); CFRelease(signingInfo); NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] found certificate extension, but was issued by Apple (profile-signed)"); return NO; } // Still haven't matched Apple. Are we using a custom root that would take the App Store fastpath? CFRelease(appleProfileSignedPolicy); // Cert chain should be of length 3 if(CFArrayGetCount(certificates) != 3) { CFRelease(signingInfo); NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] certificate chain length != 3"); return NO; } // AppleCodeSigning only checks for the codeSigning EKU by default SecPolicyRef customRootPolicy = SecPolicyCreateWithProperties(kSecPolicyAppleCodeSigning, NULL); SecPolicySetOptionsValue(customRootPolicy, CFSTR("LeafMarkerOid"), CFSTR("1.2.840.113635.100.6.1.3")); if(SecTrustSetPolicies(trust, customRootPolicy) != errSecSuccess) { NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] error replacing trust policy to check for custom root"); CFRelease(trust); CFRelease(signingInfo); return NO; } // Need to add our certificate chain to the anchor as it is expected to be a self-signed root SecTrustSetAnchorCertificates(trust, certificates); BOOL evaluatesToCustomAnchor = SecTrustEvaluateWithError(trust, nil); NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] app signed with non-Apple certificate %@ using valid custom certificates", evaluatesToCustomAnchor ? @"IS" : @"is NOT"); CFRelease(trust); CFRelease(customRootPolicy); CFRelease(signingInfo); return evaluatesToCustomAnchor; } BOOL isSameFile(NSString *path1, NSString *path2) { struct stat sb1; struct stat sb2; stat(path1.fileSystemRepresentation, &sb1); stat(path2.fileSystemRepresentation, &sb2); return sb1.st_ino == sb2.st_ino; } #ifdef EMBEDDED_ROOT_HELPER // The embedded root helper is not able to sign apps // But it does not need that functionality anyways int signApp(NSString* appPath) { return -1; } #else int signAdhoc(NSString *filePath, NSDictionary *entitlements) { //if (@available(iOS 16, *)) { // return codesign_sign_adhoc(filePath.fileSystemRepresentation, true, entitlements); //} // If iOS 14 is so great, how come there is no iOS 14 2????? //else { if(!isLdidInstalled()) return 173; NSString *entitlementsPath = nil; NSString *signArg = @"-s"; NSString* errorOutput; if(entitlements) { NSData *entitlementsXML = [NSPropertyListSerialization dataWithPropertyList:entitlements format:NSPropertyListXMLFormat_v1_0 options:0 error:nil]; if (entitlementsXML) { entitlementsPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString] stringByAppendingPathExtension:@"plist"]; [entitlementsXML writeToFile:entitlementsPath atomically:NO]; signArg = [@"-S" stringByAppendingString:entitlementsPath]; } } int ldidRet = runLdid(@[signArg, filePath], nil, &errorOutput); if (entitlementsPath) { [[NSFileManager defaultManager] removeItemAtPath:entitlementsPath error:nil]; } NSLog(@"ldid exited with status %d", ldidRet); NSLog(@"- ldid error output start -"); printMultilineNSString(errorOutput); NSLog(@"- ldid error output end -"); if(ldidRet == 0) { return 0; } else { return 175; } //} } int signApp(NSString* appPath) { NSDictionary* appInfoDict = infoDictionaryForAppPath(appPath); if(!appInfoDict) return 172; NSString* mainExecutablePath = appMainExecutablePathForAppPath(appPath); if(!mainExecutablePath) return 176; if(![[NSFileManager defaultManager] fileExistsAtPath:mainExecutablePath]) return 174; #ifndef TROLLSTORE_LITE // Check if the bundle has had a supported exploit pre-applied EXPLOIT_TYPE declaredPreAppliedExploitType = getDeclaredExploitTypeFromInfoDictionary(appInfoDict); if(isPlatformVulnerableToExploitType(declaredPreAppliedExploitType)) { NSLog(@"[signApp] taking fast path for app which declares use of a supported pre-applied exploit (%@)", mainExecutablePath); return 0; } else if (declaredPreAppliedExploitType != 0) { NSLog(@"[signApp] app (%@) declares use of a pre-applied exploit that is not supported on this device. Proceeding to re-sign...", mainExecutablePath); } // If the app doesn't declare a pre-applied exploit, and the host supports fake custom root certs, // we can also skip doing any work here when that app is signed with fake roots // If not, with the new bypass, a previously modified binary should failed to be adhoc signed, and // reapplying the bypass should produce an identical binary if(isPlatformVulnerableToExploitType(EXPLOIT_TYPE_CUSTOM_ROOT_CERTIFICATE_V1)) { SecStaticCodeRef codeRef = getStaticCodeRef(mainExecutablePath); if(codeRef != NULL) { if(codeCertChainContainsFakeAppStoreExtensions(codeRef)) { NSLog(@"[signApp] taking fast path for app signed using a custom root certificate (%@)", mainExecutablePath); CFRelease(codeRef); return 0; } CFRelease(codeRef); } } // On iOS 16+, binaries with certain entitlements requires developer mode to be enabled, so we'll check // while we're fixing entitlements BOOL requiresDevMode = NO; #endif // The majority of IPA decryption utilities only decrypt the main executable of the app bundle // As a result, we cannot bail on the entire app if an additional binary is encrypted (e.g. app extensions) // Instead, we will display a warning to the user, and warn them that the app may not work properly BOOL hasAdditionalEncryptedBinaries = NO; NSURL* fileURL; NSDirectoryEnumerator *enumerator; // Due to how the new CT bug works, in order for data containers to work properly we need to add the // com.apple.private.security.container-required= entitlement to every binary inside a bundle // For this we will want to first collect info about all the bundles in the app by seeking for Info.plist files and adding the ent to the main binary enumerator = [[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:appPath] includingPropertiesForKeys:nil options:0 errorHandler:nil]; while(fileURL = [enumerator nextObject]) { NSString *filePath = fileURL.path; if ([filePath.lastPathComponent isEqualToString:@"Info.plist"]) { NSDictionary *infoDict = [NSDictionary dictionaryWithContentsOfFile:filePath]; if (!infoDict) continue; NSString *bundleId = infoDict[@"CFBundleIdentifier"]; NSString *bundleExecutable = infoDict[@"CFBundleExecutable"]; if (!bundleId || !bundleExecutable) continue; if ([bundleId isEqualToString:@""] || [bundleExecutable isEqualToString:@""]) continue; NSString *bundleMainExecutablePath = [[filePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:bundleExecutable]; if (![[NSFileManager defaultManager] fileExistsAtPath:bundleMainExecutablePath]) continue; NSString *packageType = infoDict[@"CFBundlePackageType"]; // We don't care about frameworks (yet) if ([packageType isEqualToString:@"FMWK"]) continue; NSMutableDictionary *entitlementsToUse = dumpEntitlementsFromBinaryAtPath(bundleMainExecutablePath).mutableCopy; if (isSameFile(bundleMainExecutablePath, mainExecutablePath)) { // In the case where the main executable of the app currently has no entitlements at all // We want to ensure it gets signed with fallback entitlements // These mimic the entitlements that Xcodes gives every app it signs if (!entitlementsToUse) { entitlementsToUse = @{ @"application-identifier" : @"TROLLTROLL.*", @"com.apple.developer.team-identifier" : @"TROLLTROLL", @"get-task-allow" : (__bridge id)kCFBooleanTrue, @"keychain-access-groups" : @[ @"TROLLTROLL.*", @"com.apple.token" ], }.mutableCopy; } } if (!entitlementsToUse) entitlementsToUse = [NSMutableDictionary new]; #ifndef TROLLSTORE_LITE // Developer mode does not exist before iOS 16 if (@available(iOS 16, *)){ if (!requiresDevMode) { for (NSString* restrictedEntitlementKey in @[ @"get-task-allow", @"task_for_pid-allow", @"com.apple.system-task-ports", @"com.apple.system-task-ports.control", @"com.apple.system-task-ports.token.control", @"com.apple.private.cs.debugger" ]) { NSObject *restrictedEntitlement = entitlementsToUse[restrictedEntitlementKey]; if (restrictedEntitlement && [restrictedEntitlement isKindOfClass:[NSNumber class]] && [(NSNumber *)restrictedEntitlement boolValue]) { requiresDevMode = YES; } } } } NSObject *containerRequiredO = entitlementsToUse[@"com.apple.private.security.container-required"]; BOOL containerRequired = YES; if (containerRequiredO && [containerRequiredO isKindOfClass:[NSNumber class]]) { containerRequired = [(NSNumber *)containerRequiredO boolValue]; } else if (containerRequiredO && [containerRequiredO isKindOfClass:[NSString class]]) { // Keep whatever is in it if it's a string... containerRequired = NO; } if (containerRequired) { NSObject *noContainerO = entitlementsToUse[@"com.apple.private.security.no-container"]; BOOL noContainer = NO; if (noContainerO && [noContainerO isKindOfClass:[NSNumber class]]) { noContainer = [(NSNumber *)noContainerO boolValue]; } NSObject *noSandboxO = entitlementsToUse[@"com.apple.private.security.no-sandbox"]; BOOL noSandbox = NO; if (noSandboxO && [noSandboxO isKindOfClass:[NSNumber class]]) { noSandbox = [(NSNumber *)noSandboxO boolValue]; } if (!noContainer && !noSandbox) { entitlementsToUse[@"com.apple.private.security.container-required"] = bundleId; } } #else // Since TrollStore Lite adhoc signs stuff, this means that on PMAP_CS devices, it will run with "PMAP_CS_IN_LOADED_TRUST_CACHE" trust level // We need to overwrite it so that the app runs as expected (Dopamine 2.1.5+ feature) entitlementsToUse[@"jb.pmap_cs_custom_trust"] = @"PMAP_CS_APP_STORE"; #endif int r = signAdhoc(bundleMainExecutablePath, entitlementsToUse); if (r != 0) return r; } } // All entitlement related issues should be fixed at this point, so all we need to do is sign the entire bundle // And then apply the CoreTrust bypass to all executables // XXX: This only works because we're using ldid at the moment and that recursively signs everything int r = signAdhoc(appPath, nil); if (r != 0) return r; #ifndef TROLLSTORE_LITE // Apply CoreTrust bypass enumerator = [[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:appPath] includingPropertiesForKeys:nil options:0 errorHandler:nil]; while(fileURL = [enumerator nextObject]) { NSString *filePath = fileURL.path; FAT *fat = fat_init_from_path(filePath.fileSystemRepresentation); if (fat) { NSLog(@"%@ is binary", filePath); MachO *machoForExtraction = fat_find_preferred_slice(fat); if (machoForExtraction) { // Extract best slice NSString *tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString]; MemoryStream *sliceStream = macho_get_stream(machoForExtraction); MemoryStream *sliceOutStream = file_stream_init_from_path(tmpPath.fileSystemRepresentation, 0, 0, FILE_STREAM_FLAG_WRITABLE | FILE_STREAM_FLAG_AUTO_EXPAND); if (sliceOutStream) { memory_stream_copy_data(sliceStream, 0, sliceOutStream, 0, memory_stream_get_size(sliceStream)); memory_stream_free(sliceOutStream); // Now we have the best slice at tmpPath, which we will apply the bypass to, then copy it over the original file // We loose all other slices doing that but they aren't a loss as they wouldn't run either way NSLog(@"[%@] Applying CoreTrust bypass...", filePath); int r = apply_coretrust_bypass(tmpPath.fileSystemRepresentation); if (r == 0) { NSLog(@"[%@] Applied CoreTrust bypass!", filePath); } else if (r == 2) { NSLog(@"[%@] Cannot apply CoreTrust bypass on an encrypted binary!", filePath); if (isSameFile(filePath, mainExecutablePath)) { // If this is the main binary, this error is fatal NSLog(@"[%@] Main binary is encrypted, cannot continue!", filePath); fat_free(fat); return 180; } else { // If not, we can continue but want to show a warning after the app is installed hasAdditionalEncryptedBinaries = YES; } } else if (r == 3) { // Non-fatal - unsupported MachO type NSLog(@"[%@] Cannot apply CoreTrust bypass on an unsupported MachO type!", filePath); } else { NSLog(@"[%@] CoreTrust bypass failed!!! :(", filePath); fat_free(fat); return 185; } // tempFile is now signed, overwrite original file at filePath with it [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; [[NSFileManager defaultManager] moveItemAtPath:tmpPath toPath:filePath error:nil]; } } fat_free(fat); } } if (requiresDevMode) { // Postpone trying to enable dev mode until after the app is (successfully) installed return 182; } #else // TROLLSTORE_LITE // Just check for whether anything is fairplay encrypted enumerator = [[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:appPath] includingPropertiesForKeys:nil options:0 errorHandler:nil]; while(fileURL = [enumerator nextObject]) { NSString *filePath = fileURL.path; FAT *fat = fat_init_from_path(filePath.fileSystemRepresentation); if (fat) { NSLog(@"%@ is binary", filePath); MachO *macho = fat_find_preferred_slice(fat); if (macho) { if (macho_is_encrypted(macho)) { NSLog(@"[%@] Cannot apply CoreTrust bypass on an encrypted binary!", filePath); if (isSameFile(filePath, mainExecutablePath)) { // If this is the main binary, this error is fatal NSLog(@"[%@] Main binary is encrypted, cannot continue!", filePath); fat_free(fat); return 180; } else { // If not, we can continue but want to show a warning after the app is installed hasAdditionalEncryptedBinaries = YES; } } } fat_free(fat); } } #endif if (hasAdditionalEncryptedBinaries) { return 184; } return 0; } #endif void applyPatchesToInfoDictionary(NSString* appPath) { NSURL* appURL = [NSURL fileURLWithPath:appPath]; NSURL* infoPlistURL = [appURL URLByAppendingPathComponent:@"Info.plist"]; NSMutableDictionary* infoDictM = [[NSDictionary dictionaryWithContentsOfURL:infoPlistURL error:nil] mutableCopy]; if(!infoDictM) return; // Enable Notifications infoDictM[@"SBAppUsesLocalNotifications"] = @1; // Remove system claimed URL schemes if existant NSSet* appleSchemes = systemURLSchemes(); NSArray* CFBundleURLTypes = infoDictM[@"CFBundleURLTypes"]; if([CFBundleURLTypes isKindOfClass:[NSArray class]]) { NSMutableArray* CFBundleURLTypesM = [NSMutableArray new]; for(NSDictionary* URLType in CFBundleURLTypes) { if(![URLType isKindOfClass:[NSDictionary class]]) continue; NSMutableDictionary* modifiedURLType = URLType.mutableCopy; NSArray* URLSchemes = URLType[@"CFBundleURLSchemes"]; if(URLSchemes) { NSMutableSet* URLSchemesSet = [NSMutableSet setWithArray:URLSchemes]; for(NSString* existingURLScheme in [URLSchemesSet copy]) { if(![existingURLScheme isKindOfClass:[NSString class]]) { [URLSchemesSet removeObject:existingURLScheme]; continue; } if([appleSchemes containsObject:existingURLScheme.lowercaseString]) { [URLSchemesSet removeObject:existingURLScheme]; } } modifiedURLType[@"CFBundleURLSchemes"] = [URLSchemesSet allObjects]; } [CFBundleURLTypesM addObject:modifiedURLType.copy]; } infoDictM[@"CFBundleURLTypes"] = CFBundleURLTypesM.copy; } [infoDictM writeToURL:infoPlistURL error:nil]; } // 170: failed to create container for app bundle // 171: a non trollstore app with the same identifier is already installled // 172: no info.plist found in app // 173: app is not signed and cannot be signed because ldid not installed or didn't work // 174: // 180: tried to sign app where the main binary is encrypted // 184: tried to sign app where an additional binary is encrypted int installApp(NSString* appPackagePath, BOOL sign, BOOL force, BOOL isTSUpdate, BOOL useInstalldMethod, BOOL skipUICache) { NSLog(@"[installApp force = %d]", force); NSString* appPayloadPath = [appPackagePath stringByAppendingPathComponent:@"Payload"]; NSString* appBundleToInstallPath = findAppPathInBundlePath(appPayloadPath); if(!appBundleToInstallPath) return 167; NSString* appId = appIdForAppPath(appBundleToInstallPath); if(!appId) return 176; if(([appId.lowercaseString isEqualToString:@"com.opa334.trollstore"] && !isTSUpdate) || [immutableAppBundleIdentifiers() containsObject:appId.lowercaseString]) { return 179; } if(!infoDictionaryForAppPath(appBundleToInstallPath)) return 172; if(!isTSUpdate) { applyPatchesToInfoDictionary(appBundleToInstallPath); } BOOL requiresDevMode = NO; BOOL hasAdditionalEncryptedBinaries = NO; if(sign) { int signRet = signApp(appBundleToInstallPath); // 182: app requires developer mode; non-fatal // 184: app has additional encrypted binaries; non-fatal if(signRet != 0) { if (signRet == 182) { requiresDevMode = YES; } else if (signRet == 184) { hasAdditionalEncryptedBinaries = YES; } else { return signRet; } }; } MCMAppContainer* appContainer = [MCMAppContainer containerWithIdentifier:appId createIfNecessary:NO existed:nil error:nil]; if(appContainer) { // App update // Replace existing bundle with new version // Check if the existing app bundle is empty NSURL* bundleContainerURL = appContainer.url; NSURL* appBundleURL = findAppURLInBundleURL(bundleContainerURL); // Make sure the installed app is a TrollStore app or the container is empty (or the force flag is set) NSURL* trollStoreMarkURL = [bundleContainerURL URLByAppendingPathComponent:TS_ACTIVE_MARKER]; if(appBundleURL && ![trollStoreMarkURL checkResourceIsReachableAndReturnError:nil] && !force) { NSLog(@"[installApp] already installed and not a TrollStore app... bailing out"); return 171; } else if (appBundleURL) { // When overwriting an app that has been installed with a different TrollStore flavor, make sure to remove the marker of said flavor NSURL *otherMarkerURL = [bundleContainerURL URLByAppendingPathComponent:TS_INACTIVE_MARKER]; if ([otherMarkerURL checkResourceIsReachableAndReturnError:nil]) { [[NSFileManager defaultManager] removeItemAtURL:otherMarkerURL error:nil]; } } // Terminate app if it's still running if(!isTSUpdate) { BKSTerminateApplicationForReasonAndReportWithDescription(appId, 5, false, @"TrollStore - App updated"); } NSLog(@"[installApp] replacing existing app with new version"); // Delete existing .app directory if it exists if(appBundleURL) { [[NSFileManager defaultManager] removeItemAtURL:appBundleURL error:nil]; } NSString* newAppBundlePath = [bundleContainerURL.path stringByAppendingPathComponent:appBundleToInstallPath.lastPathComponent]; NSLog(@"[installApp] new app path: %@", newAppBundlePath); // Install new version into existing app bundle NSError* copyError; BOOL suc = [[NSFileManager defaultManager] copyItemAtPath:appBundleToInstallPath toPath:newAppBundlePath error:©Error]; if(!suc) { NSLog(@"[installApp] Error copying new version during update: %@", copyError); return 178; } } else { // Initial app install BOOL systemMethodSuccessful = NO; if(useInstalldMethod) { // System method // Do initial placeholder installation using LSApplicationWorkspace NSLog(@"[installApp] doing placeholder installation using LSApplicationWorkspace"); // The installApplication API (re)moves the app bundle, so in order to be able to later // fall back to the custom method, we need to make a temporary copy just for using it on this API once // Yeah this sucks, but there is no better solution unfortunately NSError* tmpCopyError; NSString* lsAppPackageTmpCopy = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString]; if(![[NSFileManager defaultManager] copyItemAtPath:appPackagePath toPath:lsAppPackageTmpCopy error:&tmpCopyError]) { NSLog(@"failed to make temporary copy of app packge: %@", tmpCopyError); return 170; } NSError* installError; @try { systemMethodSuccessful = [[LSApplicationWorkspace defaultWorkspace] installApplication:[NSURL fileURLWithPath:lsAppPackageTmpCopy] withOptions:@{ LSInstallTypeKey : @1, @"PackageType" : @"Placeholder" } error:&installError]; } @catch(NSException* e) { NSLog(@"[installApp] encountered expection %@ while trying to do placeholder install", e); systemMethodSuccessful = NO; } if(!systemMethodSuccessful) { NSLog(@"[installApp] encountered error %@ while trying to do placeholder install", installError); } [[NSFileManager defaultManager] removeItemAtPath:lsAppPackageTmpCopy error:nil]; } if(!systemMethodSuccessful) { // Custom method // Manually create app bundle via MCM apis and move app there NSLog(@"[installApp] doing custom installation using MCMAppContainer"); NSError* mcmError; appContainer = [MCMAppContainer containerWithIdentifier:appId createIfNecessary:YES existed:nil error:&mcmError]; if(!appContainer || mcmError) { NSLog(@"[installApp] failed to create app container for %@: %@", appId, mcmError); return 170; } else { NSLog(@"[installApp] created app container: %@", appContainer); } NSString* newAppBundlePath = [appContainer.url.path stringByAppendingPathComponent:appBundleToInstallPath.lastPathComponent]; NSLog(@"[installApp] new app path: %@", newAppBundlePath); NSError* copyError; BOOL suc = [[NSFileManager defaultManager] copyItemAtPath:appBundleToInstallPath toPath:newAppBundlePath error:©Error]; if(!suc) { NSLog(@"[installApp] Failed to copy app bundle for app %@, error: %@", appId, copyError); return 178; } } } appContainer = [MCMAppContainer containerWithIdentifier:appId createIfNecessary:NO existed:nil error:nil]; // Mark app as TrollStore app NSURL* trollStoreMarkURL = [appContainer.url URLByAppendingPathComponent:TS_ACTIVE_MARKER]; if(![[NSFileManager defaultManager] fileExistsAtPath:trollStoreMarkURL.path]) { NSError* creationError; NSData* emptyData = [NSData data]; BOOL marked = [emptyData writeToURL:trollStoreMarkURL options:0 error:&creationError]; if(!marked) { NSLog(@"[installApp] failed to mark %@ as TrollStore app by creating %@, error: %@", appId, trollStoreMarkURL.path, creationError); return 177; } } // At this point the (new version of the) app is installed but still needs to be registered // Also permissions need to be fixed NSURL* updatedAppURL = findAppURLInBundleURL(appContainer.url); fixPermissionsOfAppBundle(updatedAppURL.path); if (!skipUICache) { if (!registerPath(updatedAppURL.path, 0, !shouldRegisterAsUserByDefault())) { [[NSFileManager defaultManager] removeItemAtURL:appContainer.url error:nil]; return 181; } } // Handle developer mode after installing and registering the app, to ensure that we // don't arm developer mode but then fail to install the app if (requiresDevMode) { BOOL alreadyEnabled = NO; if (armDeveloperMode(&alreadyEnabled)) { if (!alreadyEnabled) { NSLog(@"[installApp] app requires developer mode and we have successfully armed it"); // non-fatal return 182; } } else { NSLog(@"[installApp] failed to arm developer mode"); // fatal return 183; } } if (hasAdditionalEncryptedBinaries) { NSLog(@"[installApp] app has additional encrypted binaries"); // non-fatal return 184; } return 0; } int uninstallApp(NSString* appPath, NSString* appId, BOOL useCustomMethod) { BOOL deleteSuc = NO; if(!appId && appPath) { // Special case, something is wrong about this app // Most likely the Info.plist is missing // (Hopefully this never happens) deleteSuc = [[NSFileManager defaultManager] removeItemAtPath:[appPath stringByDeletingLastPathComponent] error:nil]; registerPath(appPath, YES, YES); return 0; } if(appId) { LSApplicationProxy* appProxy = [LSApplicationProxy applicationProxyForIdentifier:appId]; // delete data container if (appProxy.dataContainerURL) { [[NSFileManager defaultManager] removeItemAtURL:appProxy.dataContainerURL error:nil]; } // delete group container paths [[appProxy groupContainerURLs] enumerateKeysAndObjectsUsingBlock:^(NSString* groupId, NSURL* groupURL, BOOL* stop) { // If another app still has this group, don't delete it NSArray* appsWithGroup = applicationsWithGroupId(groupId); if(appsWithGroup.count > 1) { NSLog(@"[uninstallApp] not deleting %@, appsWithGroup.count:%lu", groupURL, appsWithGroup.count); return; } NSLog(@"[uninstallApp] deleting %@", groupURL); [[NSFileManager defaultManager] removeItemAtURL:groupURL error:nil]; }]; // delete app plugin paths for(LSPlugInKitProxy* pluginProxy in appProxy.plugInKitPlugins) { NSURL* pluginURL = pluginProxy.dataContainerURL; if(pluginURL) { NSLog(@"[uninstallApp] deleting %@", pluginURL); [[NSFileManager defaultManager] removeItemAtURL:pluginURL error:nil]; } } BOOL systemMethodSuccessful = NO; if(!useCustomMethod) { systemMethodSuccessful = [[LSApplicationWorkspace defaultWorkspace] uninstallApplication:appId withOptions:nil]; } if(!systemMethodSuccessful) { deleteSuc = [[NSFileManager defaultManager] removeItemAtPath:[appPath stringByDeletingLastPathComponent] error:nil]; registerPath(appPath, YES, YES); } else { deleteSuc = systemMethodSuccessful; } } if(deleteSuc) { cleanRestrictions(); return 0; } else { return 1; } } int uninstallAppByPath(NSString* appPath, BOOL useCustomMethod) { if(!appPath) return 1; NSString* standardizedAppPath = appPath.stringByStandardizingPath; if(![standardizedAppPath hasPrefix:@"/var/containers/Bundle/Application/"] && standardizedAppPath.pathComponents.count == 5) { return 1; } NSString* appId = appIdForAppPath(standardizedAppPath); return uninstallApp(appPath, appId, useCustomMethod); } int uninstallAppById(NSString* appId, BOOL useCustomMethod) { if(!appId) return 1; NSString* appPath = appPathForAppId(appId); if(!appPath) return 1; return uninstallApp(appPath, appId, useCustomMethod); } // 166: IPA does not exist or is not accessible // 167: IPA does not appear to contain an app // 180: IPA's main binary is encrypted // 184: IPA contains additional encrypted binaries int installIpa(NSString* ipaPath, BOOL force, BOOL useInstalldMethod, BOOL skipUICache) { cleanRestrictions(); if(![[NSFileManager defaultManager] fileExistsAtPath:ipaPath]) return 166; BOOL suc = NO; NSString* tmpPackagePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString]; suc = [[NSFileManager defaultManager] createDirectoryAtPath:tmpPackagePath withIntermediateDirectories:NO attributes:nil error:nil]; if(!suc) return 1; int extractRet = extract(ipaPath, tmpPackagePath); if(extractRet != 0) { [[NSFileManager defaultManager] removeItemAtPath:tmpPackagePath error:nil]; return 168; } int ret = installApp(tmpPackagePath, YES, force, NO, useInstalldMethod, skipUICache); [[NSFileManager defaultManager] removeItemAtPath:tmpPackagePath error:nil]; return ret; } void uninstallAllApps(BOOL useCustomMethod) { for(NSString* appPath in trollStoreInstalledAppBundlePaths()) { uninstallAppById(appIdForAppPath(appPath), useCustomMethod); } } int uninstallTrollStore(BOOL unregister) { NSString* trollStore = trollStorePath(); if(![[NSFileManager defaultManager] fileExistsAtPath:trollStore]) return NO; if(unregister) { registerPath(trollStoreAppPath(), YES, YES); } return [[NSFileManager defaultManager] removeItemAtPath:trollStore error:nil]; } int installTrollStore(NSString* pathToTar) { _CFPreferencesSetValueWithContainerType _CFPreferencesSetValueWithContainer = (_CFPreferencesSetValueWithContainerType)dlsym(RTLD_DEFAULT, "_CFPreferencesSetValueWithContainer"); _CFPreferencesSynchronizeWithContainerType _CFPreferencesSynchronizeWithContainer = (_CFPreferencesSynchronizeWithContainerType)dlsym(RTLD_DEFAULT, "_CFPreferencesSynchronizeWithContainer"); _CFPreferencesSetValueWithContainer(CFSTR("SBShowNonDefaultSystemApps"), kCFBooleanTrue, CFSTR("com.apple.springboard"), CFSTR("mobile"), kCFPreferencesAnyHost, kCFPreferencesNoContainer); _CFPreferencesSynchronizeWithContainer(CFSTR("com.apple.springboard"), CFSTR("mobile"), kCFPreferencesAnyHost, kCFPreferencesNoContainer); if(![[NSFileManager defaultManager] fileExistsAtPath:pathToTar]) return 1; if(![pathToTar.pathExtension isEqualToString:@"tar"]) return 1; NSString* tmpPackagePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString]; NSString* tmpPayloadPath = [tmpPackagePath stringByAppendingPathComponent:@"Payload"]; BOOL suc = [[NSFileManager defaultManager] createDirectoryAtPath:tmpPayloadPath withIntermediateDirectories:YES attributes:nil error:nil]; if(!suc) return 1; int extractRet = extract(pathToTar, tmpPayloadPath); if(extractRet != 0) { [[NSFileManager defaultManager] removeItemAtPath:tmpPackagePath error:nil]; return 169; } NSString* tmpTrollStorePath = [tmpPayloadPath stringByAppendingPathComponent:@"TrollStore.app"]; if(![[NSFileManager defaultManager] fileExistsAtPath:tmpTrollStorePath]) return 1; //if (@available(iOS 16, *)) {} else { // Transfer existing ldid installation if it exists // But only if the to-be-installed version of TrollStore is 1.5.0 or above // This is to make it possible to downgrade to older versions still NSString* toInstallInfoPlistPath = [tmpTrollStorePath stringByAppendingPathComponent:@"Info.plist"]; if(![[NSFileManager defaultManager] fileExistsAtPath:toInstallInfoPlistPath]) return 1; NSDictionary* toInstallInfoDict = [NSDictionary dictionaryWithContentsOfFile:toInstallInfoPlistPath]; NSString* toInstallVersion = toInstallInfoDict[@"CFBundleVersion"]; NSComparisonResult result = [@"1.5.0" compare:toInstallVersion options:NSNumericSearch]; if(result != NSOrderedDescending) { NSString* existingLdidPath = [trollStoreAppPath() stringByAppendingPathComponent:@"ldid"]; NSString* existingLdidVersionPath = [trollStoreAppPath() stringByAppendingPathComponent:@"ldid.version"]; if([[NSFileManager defaultManager] fileExistsAtPath:existingLdidPath]) { NSString* tmpLdidPath = [tmpTrollStorePath stringByAppendingPathComponent:@"ldid"]; if(![[NSFileManager defaultManager] fileExistsAtPath:tmpLdidPath]) { [[NSFileManager defaultManager] copyItemAtPath:existingLdidPath toPath:tmpLdidPath error:nil]; } } if([[NSFileManager defaultManager] fileExistsAtPath:existingLdidVersionPath]) { NSString* tmpLdidVersionPath = [tmpTrollStorePath stringByAppendingPathComponent:@"ldid.version"]; if(![[NSFileManager defaultManager] fileExistsAtPath:tmpLdidVersionPath]) { [[NSFileManager defaultManager] copyItemAtPath:existingLdidVersionPath toPath:tmpLdidVersionPath error:nil]; } } } //} // Merge existing URL scheme settings value if(!getTSURLSchemeState(nil)) { setTSURLSchemeState(NO, tmpTrollStorePath); } // Update system app persistence helper if used LSApplicationProxy* persistenceHelperApp = findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE_SYSTEM); if(persistenceHelperApp) { NSString* trollStorePersistenceHelper = [tmpTrollStorePath stringByAppendingPathComponent:@"PersistenceHelper"]; NSString* trollStoreRootHelper = [tmpTrollStorePath stringByAppendingPathComponent:@"trollstorehelper"]; _installPersistenceHelper(persistenceHelperApp, trollStorePersistenceHelper, trollStoreRootHelper); } int ret = installApp(tmpPackagePath, NO, YES, YES, YES, NO); NSLog(@"[installTrollStore] installApp => %d", ret); [[NSFileManager defaultManager] removeItemAtPath:tmpPackagePath error:nil]; return ret; } void refreshAppRegistrations(BOOL system) { registerPath(trollStoreAppPath(), NO, system); // the reason why there is even an option to register everything as user // is because it fixes an issue where app permissions would reset during an icon cache reload for(NSString* appPath in trollStoreInstalledAppBundlePaths()) { registerPath(appPath, NO, system); } } BOOL _installPersistenceHelper(LSApplicationProxy* appProxy, NSString* sourcePersistenceHelper, NSString* sourceRootHelper) { NSLog(@"_installPersistenceHelper(%@, %@, %@)", appProxy, sourcePersistenceHelper, sourceRootHelper); NSString* executablePath = appProxy.canonicalExecutablePath; NSString* bundlePath = appProxy.bundleURL.path; if(!executablePath) { NSBundle* appBundle = [NSBundle bundleWithPath:bundlePath]; executablePath = [bundlePath stringByAppendingPathComponent:[appBundle objectForInfoDictionaryKey:@"CFBundleExecutable"]]; } NSString* markPath = [bundlePath stringByAppendingPathComponent:@".TrollStorePersistenceHelper"]; NSString* rootHelperPath = [bundlePath stringByAppendingPathComponent:@"trollstorehelper"]; // remove existing persistence helper binary if exists if([[NSFileManager defaultManager] fileExistsAtPath:markPath] && [[NSFileManager defaultManager] fileExistsAtPath:executablePath]) { [[NSFileManager defaultManager] removeItemAtPath:executablePath error:nil]; } // remove existing root helper binary if exists if([[NSFileManager defaultManager] fileExistsAtPath:rootHelperPath]) { [[NSFileManager defaultManager] removeItemAtPath:rootHelperPath error:nil]; } // install new persistence helper binary if(![[NSFileManager defaultManager] copyItemAtPath:sourcePersistenceHelper toPath:executablePath error:nil]) { return NO; } chmod(executablePath.fileSystemRepresentation, 0755); chown(executablePath.fileSystemRepresentation, 33, 33); NSError* error; if(![[NSFileManager defaultManager] copyItemAtPath:sourceRootHelper toPath:rootHelperPath error:&error]) { NSLog(@"error copying root helper: %@", error); } chmod(rootHelperPath.fileSystemRepresentation, 0755); chown(rootHelperPath.fileSystemRepresentation, 0, 0); // mark system app as persistence helper if(![[NSFileManager defaultManager] fileExistsAtPath:markPath]) { [[NSFileManager defaultManager] createFileAtPath:markPath contents:[NSData data] attributes:nil]; } return YES; } void installPersistenceHelper(NSString* systemAppId, NSString *persistenceHelperBinary, NSString *rootHelperBinary) { if(findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE_ALL)) return; if (persistenceHelperBinary == nil) { persistenceHelperBinary = [trollStoreAppPath() stringByAppendingPathComponent:@"PersistenceHelper"]; } if (rootHelperBinary == nil) { rootHelperBinary = [trollStoreAppPath() stringByAppendingPathComponent:@"trollstorehelper"]; } LSApplicationProxy* appProxy = [LSApplicationProxy applicationProxyForIdentifier:systemAppId]; if(!appProxy || ![appProxy.bundleType isEqualToString:@"System"]) return; NSString* executablePath = appProxy.canonicalExecutablePath; NSString* bundlePath = appProxy.bundleURL.path; NSString* backupPath = [bundlePath stringByAppendingPathComponent:[[executablePath lastPathComponent] stringByAppendingString:@"_TROLLSTORE_BACKUP"]]; if([[NSFileManager defaultManager] fileExistsAtPath:backupPath]) return; if(![[NSFileManager defaultManager] moveItemAtPath:executablePath toPath:backupPath error:nil]) return; if(!_installPersistenceHelper(appProxy, persistenceHelperBinary, rootHelperBinary)) { [[NSFileManager defaultManager] moveItemAtPath:backupPath toPath:executablePath error:nil]; return; } BKSTerminateApplicationForReasonAndReportWithDescription(systemAppId, 5, false, @"TrollStore - Reload persistence helper"); } void unregisterUserPersistenceHelper() { LSApplicationProxy* userAppProxy = findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE_USER); if(userAppProxy) { NSString* markPath = [userAppProxy.bundleURL.path stringByAppendingPathComponent:@".TrollStorePersistenceHelper"]; [[NSFileManager defaultManager] removeItemAtPath:markPath error:nil]; } } void uninstallPersistenceHelper(void) { LSApplicationProxy* systemAppProxy = findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE_SYSTEM); if(systemAppProxy) { NSString* executablePath = systemAppProxy.canonicalExecutablePath; NSString* bundlePath = systemAppProxy.bundleURL.path; NSString* backupPath = [bundlePath stringByAppendingPathComponent:[[executablePath lastPathComponent] stringByAppendingString:@"_TROLLSTORE_BACKUP"]]; if(![[NSFileManager defaultManager] fileExistsAtPath:backupPath]) return; NSString* helperPath = [bundlePath stringByAppendingPathComponent:@"trollstorehelper"]; NSString* markPath = [bundlePath stringByAppendingPathComponent:@".TrollStorePersistenceHelper"]; [[NSFileManager defaultManager] removeItemAtPath:executablePath error:nil]; [[NSFileManager defaultManager] removeItemAtPath:markPath error:nil]; [[NSFileManager defaultManager] removeItemAtPath:helperPath error:nil]; [[NSFileManager defaultManager] moveItemAtPath:backupPath toPath:executablePath error:nil]; BKSTerminateApplicationForReasonAndReportWithDescription(systemAppProxy.bundleIdentifier, 5, false, @"TrollStore - Reload persistence helper"); } LSApplicationProxy* userAppProxy = findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE_USER); if(userAppProxy) { unregisterUserPersistenceHelper(); } } void registerUserPersistenceHelper(NSString* userAppId) { if(findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE_ALL)) return; LSApplicationProxy* appProxy = [LSApplicationProxy applicationProxyForIdentifier:userAppId]; if(!appProxy || ![appProxy.bundleType isEqualToString:@"User"]) return; NSString* markPath = [appProxy.bundleURL.path stringByAppendingPathComponent:@".TrollStorePersistenceHelper"]; [[NSFileManager defaultManager] createFileAtPath:markPath contents:[NSData data] attributes:nil]; } // Apparently there is some odd behaviour where TrollStore installed apps sometimes get restricted // This works around that issue at least and is triggered when rebuilding icon cache void cleanRestrictions(void) { NSString* clientTruthPath = @"/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/ClientTruth.plist"; NSURL* clientTruthURL = [NSURL fileURLWithPath:clientTruthPath]; NSDictionary* clientTruthDictionary = [NSDictionary dictionaryWithContentsOfURL:clientTruthURL]; if(!clientTruthDictionary) return; NSArray* valuesArr; NSDictionary* lsdAppRemoval = clientTruthDictionary[@"com.apple.lsd.appremoval"]; if(lsdAppRemoval && [lsdAppRemoval isKindOfClass:NSDictionary.class]) { NSDictionary* clientRestrictions = lsdAppRemoval[@"clientRestrictions"]; if(clientRestrictions && [clientRestrictions isKindOfClass:NSDictionary.class]) { NSDictionary* unionDict = clientRestrictions[@"union"]; if(unionDict && [unionDict isKindOfClass:NSDictionary.class]) { NSDictionary* removedSystemAppBundleIDs = unionDict[@"removedSystemAppBundleIDs"]; if(removedSystemAppBundleIDs && [removedSystemAppBundleIDs isKindOfClass:NSDictionary.class]) { valuesArr = removedSystemAppBundleIDs[@"values"]; } } } } if(!valuesArr || !valuesArr.count) return; NSMutableArray* valuesArrM = valuesArr.mutableCopy; __block BOOL changed = NO; [valuesArrM enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSString* value, NSUInteger idx, BOOL *stop) { if(!isRemovableSystemApp(value)) { [valuesArrM removeObjectAtIndex:idx]; changed = YES; } }]; if(!changed) return; NSMutableDictionary* clientTruthDictionaryM = (__bridge_transfer NSMutableDictionary*)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)clientTruthDictionary, kCFPropertyListMutableContainersAndLeaves); clientTruthDictionaryM[@"com.apple.lsd.appremoval"][@"clientRestrictions"][@"union"][@"removedSystemAppBundleIDs"][@"values"] = valuesArrM; [clientTruthDictionaryM writeToURL:clientTruthURL error:nil]; killall(@"profiled", NO); // profiled needs to restart for the changes to apply } int MAIN_NAME(int argc, char *argv[], char *envp[]) { @autoreleasepool { if(argc <= 1) return -1; if(getuid() != 0) { NSLog(@"ERROR: trollstorehelper has to be run as root."); return -1; } NSMutableArray* args = [NSMutableArray new]; for (int i = 1; i < argc; i++) { [args addObject:[NSString stringWithUTF8String:argv[i]]]; } NSLog(@"trollstorehelper invoked with arguments: %@", args); int ret = 0; NSString* cmd = args.firstObject; if([cmd isEqualToString:@"install"]) { if(args.count < 2) return -3; // use system method when specified, otherwise use custom method BOOL useInstalldMethod = [args containsObject:@"installd"]; BOOL force = [args containsObject:@"force"]; BOOL skipUICache = [args containsObject:@"skip-uicache"]; NSString* ipaPath = args.lastObject; ret = installIpa(ipaPath, force, useInstalldMethod, skipUICache); } else if([cmd isEqualToString:@"uninstall"]) { if(args.count < 2) return -3; // use custom method when specified, otherwise use system method BOOL useCustomMethod = [args containsObject:@"custom"]; NSString* appId = args.lastObject; ret = uninstallAppById(appId, useCustomMethod); } else if([cmd isEqualToString:@"uninstall-path"]) { if(args.count < 2) return -3; // use custom method when specified, otherwise use system method BOOL useCustomMethod = [args containsObject:@"custom"]; NSString* appPath = args.lastObject; ret = uninstallAppByPath(appPath, useCustomMethod); } else if([cmd isEqualToString:@"refresh"]) { refreshAppRegistrations(!shouldRegisterAsUserByDefault()); } else if([cmd isEqualToString:@"refresh-all"]) { cleanRestrictions(); //refreshAppRegistrations(NO); // <- fixes app permissions resetting, causes apps to move around on home screen, so I had to disable it [[NSFileManager defaultManager] removeItemAtPath:@"/var/containers/Shared/SystemGroup/systemgroup.com.apple.lsd.iconscache/Library/Caches/com.apple.IconsCache" error:nil]; [[LSApplicationWorkspace defaultWorkspace] _LSPrivateRebuildApplicationDatabasesForSystemApps:YES internal:YES user:YES]; if (!shouldRegisterAsUserByDefault()) refreshAppRegistrations(YES); killall(@"backboardd", YES); } else if([cmd isEqualToString:@"url-scheme"]) { if(args.count < 2) return -3; NSString* modifyArg = args.lastObject; BOOL newState = [modifyArg isEqualToString:@"enable"]; if(newState == YES || [modifyArg isEqualToString:@"disable"]) { setTSURLSchemeState(newState, nil); } } else if([cmd isEqualToString:@"reboot"]) { [[FBSSystemService sharedService] reboot]; // Give the system some time to reboot sleep(1); } else if([cmd isEqualToString:@"enable-jit"]) { if(args.count < 2) return -3; NSString* userAppId = args.lastObject; ret = enableJIT(userAppId); } else if([cmd isEqualToString:@"modify-registration"]) { if(args.count < 3) return -3; NSString* appPath = args[1]; NSString* newRegistration = args[2]; NSString* trollStoreMark = [[appPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:TS_ACTIVE_MARKER]; if([[NSFileManager defaultManager] fileExistsAtPath:trollStoreMark]) { registerPath(appPath, NO, [newRegistration isEqualToString:@"System"]); } } else if ([cmd isEqualToString:@"transfer-apps"]) { bool oneFailed = false; for (NSString *appBundlePath in trollStoreInactiveInstalledAppBundlePaths()) { NSLog(@"Transfering %@...", appBundlePath); // Ldid lacks the entitlement to sign in place // So copy to /tmp, resign, then replace >.< NSString *tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString]; if (![[NSFileManager defaultManager] createDirectoryAtPath:tmpPath withIntermediateDirectories:YES attributes:nil error:nil]) return -3; NSString *tmpAppPath = [tmpPath stringByAppendingPathComponent:appBundlePath.lastPathComponent]; if (![[NSFileManager defaultManager] copyItemAtPath:appBundlePath toPath:tmpAppPath error:nil]) { [[NSFileManager defaultManager] removeItemAtPath:tmpPath error:nil]; oneFailed = true; continue; } NSLog(@"Copied %@ to %@", appBundlePath, tmpAppPath); int signRet = signApp(tmpAppPath); NSLog(@"Signing %@ returned %d", tmpAppPath, signRet); if (signRet == 0 || signRet == 182 || signRet == 184) { // Either 0 or non fatal error codes are fine [[NSFileManager defaultManager] removeItemAtPath:appBundlePath error:nil]; [[NSFileManager defaultManager] moveItemAtPath:tmpAppPath toPath:appBundlePath error:nil]; [[NSFileManager defaultManager] removeItemAtPath:tmpPath error:nil]; } else { [[NSFileManager defaultManager] removeItemAtPath:tmpPath error:nil]; oneFailed = true; continue; } fixPermissionsOfAppBundle(appBundlePath); NSString *containerPath = [appBundlePath stringByDeletingLastPathComponent]; NSString *activeMarkerPath = [containerPath stringByAppendingPathComponent:TS_ACTIVE_MARKER]; NSString *inactiveMarkerPath = [containerPath stringByAppendingPathComponent:TS_INACTIVE_MARKER]; NSData* emptyData = [NSData data]; [emptyData writeToFile:activeMarkerPath options:0 error:nil]; [[NSFileManager defaultManager] removeItemAtPath:inactiveMarkerPath error:nil]; registerPath(appBundlePath, 0, !shouldRegisterAsUserByDefault()); NSLog(@"Transfered %@!", appBundlePath); } if (oneFailed) ret = -1; } #ifndef TROLLSTORE_LITE else if([cmd isEqualToString:@"install-trollstore"]) { if(args.count < 2) return -3; NSString* tsTar = args.lastObject; ret = installTrollStore(tsTar); NSLog(@"installed troll store? %d", ret==0); } else if([cmd isEqualToString:@"uninstall-trollstore"]) { if(![args containsObject:@"preserve-apps"]) { uninstallAllApps([args containsObject:@"custom"]); } uninstallTrollStore(YES); } else if([cmd isEqualToString:@"install-ldid"]) { //if (@available(iOS 16, *)) {} else { if(args.count < 3) return -3; NSString* ldidPath = args[1]; NSString* ldidVersion = args[2]; installLdid(ldidPath, ldidVersion); //} } else if([cmd isEqualToString:@"install-persistence-helper"]) { if(args.count < 2) return -3; NSString* systemAppId = args[1]; NSString* persistenceHelperBinary; NSString* rootHelperBinary; if (args.count == 4) { persistenceHelperBinary = args[2]; rootHelperBinary = args[3]; } installPersistenceHelper(systemAppId, persistenceHelperBinary, rootHelperBinary); } else if([cmd isEqualToString:@"uninstall-persistence-helper"]) { uninstallPersistenceHelper(); } else if([cmd isEqualToString:@"register-user-persistence-helper"]) { if(args.count < 2) return -3; NSString* userAppId = args.lastObject; registerUserPersistenceHelper(userAppId); } else if([cmd isEqualToString:@"check-dev-mode"]) { // switch the result, so 0 is enabled, and 1 is disabled/error ret = !checkDeveloperMode(); } else if([cmd isEqualToString:@"arm-dev-mode"]) { // assumes that checkDeveloperMode() has already been called ret = !armDeveloperMode(NULL); } #endif NSLog(@"trollstorehelper returning %d", ret); return ret; } } ================================================ FILE: RootHelper/uicache.h ================================================ extern bool registerPath(NSString *path, BOOL unregister, BOOL forceSystem); ================================================ FILE: RootHelper/uicache.m ================================================ @import Foundation; @import CoreServices; #import "CoreServices.h" #import #import "dlfcn.h" #import #import // uicache on steroids extern NSSet* immutableAppBundleIdentifiers(void); extern NSDictionary* dumpEntitlementsFromBinaryAtPath(NSString* binaryPath); NSDictionary *constructGroupsContainersForEntitlements(NSDictionary *entitlements, BOOL systemGroups) { if (!entitlements) return nil; NSString *entitlementForGroups; Class mcmClass; if (systemGroups) { entitlementForGroups = @"com.apple.security.system-groups"; mcmClass = [MCMSystemDataContainer class]; } else { entitlementForGroups = @"com.apple.security.application-groups"; mcmClass = [MCMSharedDataContainer class]; } NSArray *groupIDs = entitlements[entitlementForGroups]; if (groupIDs && [groupIDs isKindOfClass:[NSArray class]]) { NSMutableDictionary *groupContainers = [NSMutableDictionary new]; for (NSString *groupID in groupIDs) { MCMContainer *container = [mcmClass containerWithIdentifier:groupID createIfNecessary:YES existed:nil error:nil]; if (container.url) { groupContainers[groupID] = container.url.path; } } return groupContainers.copy; } return nil; } BOOL constructContainerizationForEntitlements(NSDictionary *entitlements, NSString **customContainerOut) { NSNumber *noContainer = entitlements[@"com.apple.private.security.no-container"]; if (noContainer && [noContainer isKindOfClass:[NSNumber class]]) { if (noContainer.boolValue) { return NO; } } NSObject *containerRequired = entitlements[@"com.apple.private.security.container-required"]; if (containerRequired && [containerRequired isKindOfClass:[NSNumber class]]) { if (!((NSNumber *)containerRequired).boolValue) { return NO; } } else if (containerRequired && [containerRequired isKindOfClass:[NSString class]]) { *customContainerOut = (NSString *)containerRequired; } return YES; } NSString *constructTeamIdentifierForEntitlements(NSDictionary *entitlements) { NSString *teamIdentifier = entitlements[@"com.apple.developer.team-identifier"]; if (teamIdentifier && [teamIdentifier isKindOfClass:[NSString class]]) { return teamIdentifier; } return nil; } NSDictionary *constructEnvironmentVariablesForContainerPath(NSString *containerPath, BOOL isContainerized) { NSString *homeDir = isContainerized ? containerPath : @"/var/mobile"; NSString *tmpDir = isContainerized ? [containerPath stringByAppendingPathComponent:@"tmp"] : @"/var/tmp"; return @{ @"CFFIXED_USER_HOME" : homeDir, @"HOME" : homeDir, @"TMPDIR" : tmpDir }; } bool registerPath(NSString *path, BOOL unregister, BOOL forceSystem) { if (!path) return false; LSApplicationWorkspace *workspace = [LSApplicationWorkspace defaultWorkspace]; if (unregister && ![[NSFileManager defaultManager] fileExistsAtPath:path]) { LSApplicationProxy *app = [LSApplicationProxy applicationProxyForIdentifier:path]; if (app.bundleURL) { path = [app bundleURL].path; } } path = path.stringByResolvingSymlinksInPath.stringByStandardizingPath; NSDictionary *appInfoPlist = [NSDictionary dictionaryWithContentsOfFile:[path stringByAppendingPathComponent:@"Info.plist"]]; NSString *appBundleID = [appInfoPlist objectForKey:@"CFBundleIdentifier"]; if([immutableAppBundleIdentifiers() containsObject:appBundleID.lowercaseString]) return false; if (appBundleID && !unregister) { NSString *appExecutablePath = [path stringByAppendingPathComponent:appInfoPlist[@"CFBundleExecutable"]]; NSDictionary *entitlements = dumpEntitlementsFromBinaryAtPath(appExecutablePath); NSString *appDataContainerID = appBundleID; BOOL appContainerized = constructContainerizationForEntitlements(entitlements, &appDataContainerID); MCMContainer *appDataContainer = [NSClassFromString(@"MCMAppDataContainer") containerWithIdentifier:appDataContainerID createIfNecessary:YES existed:nil error:nil]; NSString *containerPath = [appDataContainer url].path; BOOL isRemovableSystemApp = [[NSFileManager defaultManager] fileExistsAtPath:[@"/System/Library/AppSignatures" stringByAppendingPathComponent:appBundleID]]; BOOL registerAsUser = [path hasPrefix:@"/var/containers"] && !isRemovableSystemApp && !forceSystem; NSMutableDictionary *dictToRegister = [NSMutableDictionary dictionary]; // Add entitlements if (entitlements) { dictToRegister[@"Entitlements"] = entitlements; } // Misc dictToRegister[@"ApplicationType"] = registerAsUser ? @"User" : @"System"; dictToRegister[@"CFBundleIdentifier"] = appBundleID; dictToRegister[@"CodeInfoIdentifier"] = appBundleID; dictToRegister[@"CompatibilityState"] = @0; dictToRegister[@"IsContainerized"] = @(appContainerized); if (containerPath) { dictToRegister[@"Container"] = containerPath; dictToRegister[@"EnvironmentVariables"] = constructEnvironmentVariablesForContainerPath(containerPath, appContainerized); } dictToRegister[@"IsDeletable"] = @(![appBundleID isEqualToString:@"com.opa334.TrollStore"] && kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_15_0); dictToRegister[@"Path"] = path; dictToRegister[@"SignerOrganization"] = @"Apple Inc."; dictToRegister[@"SignatureVersion"] = @132352; dictToRegister[@"SignerIdentity"] = @"Apple iPhone OS Application Signing"; dictToRegister[@"IsAdHocSigned"] = @YES; dictToRegister[@"LSInstallType"] = @1; dictToRegister[@"HasMIDBasedSINF"] = @0; dictToRegister[@"MissingSINF"] = @0; dictToRegister[@"FamilyID"] = @0; dictToRegister[@"IsOnDemandInstallCapable"] = @0; NSString *teamIdentifier = constructTeamIdentifierForEntitlements(entitlements); if (teamIdentifier) dictToRegister[@"TeamIdentifier"] = teamIdentifier; // Add group containers NSDictionary *appGroupContainers = constructGroupsContainersForEntitlements(entitlements, NO); NSDictionary *systemGroupContainers = constructGroupsContainersForEntitlements(entitlements, YES); NSMutableDictionary *groupContainers = [NSMutableDictionary new]; [groupContainers addEntriesFromDictionary:appGroupContainers]; [groupContainers addEntriesFromDictionary:systemGroupContainers]; if (groupContainers.count) { if (appGroupContainers.count) { dictToRegister[@"HasAppGroupContainers"] = @YES; } if (systemGroupContainers.count) { dictToRegister[@"HasSystemGroupContainers"] = @YES; } dictToRegister[@"GroupContainers"] = groupContainers.copy; } // Add plugins NSString *pluginsPath = [path stringByAppendingPathComponent:@"PlugIns"]; NSArray *plugins = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:pluginsPath error:nil]; NSMutableDictionary *bundlePlugins = [NSMutableDictionary dictionary]; for (NSString *pluginName in plugins) { NSString *pluginPath = [pluginsPath stringByAppendingPathComponent:pluginName]; NSDictionary *pluginInfoPlist = [NSDictionary dictionaryWithContentsOfFile:[pluginPath stringByAppendingPathComponent:@"Info.plist"]]; NSString *pluginBundleID = [pluginInfoPlist objectForKey:@"CFBundleIdentifier"]; if (!pluginBundleID) continue; NSString *pluginExecutablePath = [pluginPath stringByAppendingPathComponent:pluginInfoPlist[@"CFBundleExecutable"]]; NSDictionary *pluginEntitlements = dumpEntitlementsFromBinaryAtPath(pluginExecutablePath); NSString *pluginDataContainerID = pluginBundleID; BOOL pluginContainerized = constructContainerizationForEntitlements(pluginEntitlements, &pluginDataContainerID); MCMContainer *pluginContainer = [NSClassFromString(@"MCMPluginKitPluginDataContainer") containerWithIdentifier:pluginDataContainerID createIfNecessary:YES existed:nil error:nil]; NSString *pluginContainerPath = [pluginContainer url].path; NSMutableDictionary *pluginDict = [NSMutableDictionary dictionary]; // Add entitlements if (pluginEntitlements) { pluginDict[@"Entitlements"] = pluginEntitlements; } // Misc pluginDict[@"ApplicationType"] = @"PluginKitPlugin"; pluginDict[@"CFBundleIdentifier"] = pluginBundleID; pluginDict[@"CodeInfoIdentifier"] = pluginBundleID; pluginDict[@"CompatibilityState"] = @0; pluginDict[@"IsContainerized"] = @(pluginContainerized); if (pluginContainerPath) { pluginDict[@"Container"] = pluginContainerPath; pluginDict[@"EnvironmentVariables"] = constructEnvironmentVariablesForContainerPath(pluginContainerPath, pluginContainerized); } pluginDict[@"Path"] = pluginPath; pluginDict[@"PluginOwnerBundleID"] = appBundleID; pluginDict[@"SignerOrganization"] = @"Apple Inc."; pluginDict[@"SignatureVersion"] = @132352; pluginDict[@"SignerIdentity"] = @"Apple iPhone OS Application Signing"; NSString *pluginTeamIdentifier = constructTeamIdentifierForEntitlements(pluginEntitlements); if (pluginTeamIdentifier) pluginDict[@"TeamIdentifier"] = pluginTeamIdentifier; // Add plugin group containers NSDictionary *pluginAppGroupContainers = constructGroupsContainersForEntitlements(pluginEntitlements, NO); NSDictionary *pluginSystemGroupContainers = constructGroupsContainersForEntitlements(pluginEntitlements, YES); NSMutableDictionary *pluginGroupContainers = [NSMutableDictionary new]; [pluginGroupContainers addEntriesFromDictionary:pluginAppGroupContainers]; [pluginGroupContainers addEntriesFromDictionary:pluginSystemGroupContainers]; if (pluginGroupContainers.count) { if (pluginAppGroupContainers.count) { pluginDict[@"HasAppGroupContainers"] = @YES; } if (pluginSystemGroupContainers.count) { pluginDict[@"HasSystemGroupContainers"] = @YES; } pluginDict[@"GroupContainers"] = pluginGroupContainers.copy; } [bundlePlugins setObject:pluginDict forKey:pluginBundleID]; } [dictToRegister setObject:bundlePlugins forKey:@"_LSBundlePlugins"]; if (![workspace registerApplicationDictionary:dictToRegister]) { NSLog(@"Error: Unable to register %@", path); NSLog(@"Used dictionary: {"); [dictToRegister enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSObject *obj, BOOL *stop) { NSLog(@"%@ = %@", key, obj); }]; NSLog(@"}"); return false; } } else { NSURL *url = [NSURL fileURLWithPath:path]; if (![workspace unregisterApplication:url]) { NSLog(@"Error: Unable to register %@", path); return false; } } return true; } ================================================ FILE: RootHelper/unarchive.h ================================================ @import Foundation; extern int extract(NSString* fileToExtract, NSString* extractionPath); ================================================ FILE: RootHelper/unarchive.m ================================================ #import "unarchive.h" #include #include static int copy_data(struct archive *ar, struct archive *aw) { int r; const void *buff; size_t size; la_int64_t offset; for (;;) { r = archive_read_data_block(ar, &buff, &size, &offset); if (r == ARCHIVE_EOF) return (ARCHIVE_OK); if (r < ARCHIVE_OK) return (r); r = archive_write_data_block(aw, buff, size, offset); if (r < ARCHIVE_OK) { fprintf(stderr, "%s\n", archive_error_string(aw)); return (r); } } } int extract(NSString* fileToExtract, NSString* extractionPath) { struct archive *a; struct archive *ext; struct archive_entry *entry; int flags; int r; /* Select which attributes we want to restore. */ flags = ARCHIVE_EXTRACT_TIME; flags |= ARCHIVE_EXTRACT_PERM; flags |= ARCHIVE_EXTRACT_ACL; flags |= ARCHIVE_EXTRACT_FFLAGS; a = archive_read_new(); archive_read_support_format_all(a); archive_read_support_filter_all(a); ext = archive_write_disk_new(); archive_write_disk_set_options(ext, flags); archive_write_disk_set_standard_lookup(ext); if ((r = archive_read_open_filename(a, fileToExtract.fileSystemRepresentation, 10240))) return 1; for (;;) { r = archive_read_next_header(a, &entry); if (r == ARCHIVE_EOF) break; if (r < ARCHIVE_OK) fprintf(stderr, "%s\n", archive_error_string(a)); if (r < ARCHIVE_WARN) return 1; NSString* currentFile = [NSString stringWithUTF8String:archive_entry_pathname(entry)]; NSString* fullOutputPath = [extractionPath stringByAppendingPathComponent:currentFile]; //printf("extracting %@ to %@\n", currentFile, fullOutputPath); archive_entry_set_pathname(entry, fullOutputPath.fileSystemRepresentation); r = archive_write_header(ext, entry); if (r < ARCHIVE_OK) fprintf(stderr, "%s\n", archive_error_string(ext)); else if (archive_entry_size(entry) > 0) { r = copy_data(a, ext); if (r < ARCHIVE_OK) fprintf(stderr, "%s\n", archive_error_string(ext)); if (r < ARCHIVE_WARN) return 1; } r = archive_write_finish_entry(ext); if (r < ARCHIVE_OK) fprintf(stderr, "%s\n", archive_error_string(ext)); if (r < ARCHIVE_WARN) return 1; } archive_read_close(a); archive_read_free(a); archive_write_close(ext); archive_write_free(ext); return 0; } ================================================ FILE: Shared/CoreServices.h ================================================ extern NSString *LSInstallTypeKey; @interface LSBundleProxy @property (nonatomic,readonly) NSString * bundleIdentifier; @property (nonatomic) NSURL* dataContainerURL; @property (nonatomic,readonly) NSURL* bundleContainerURL; -(NSString*)localizedName; @end @interface LSApplicationProxy : LSBundleProxy + (instancetype)applicationProxyForIdentifier:(NSString*)identifier; + (instancetype)applicationProxyForBundleURL:(NSURL*)bundleURL; @property NSURL* bundleURL; @property NSString* bundleType; @property NSString* canonicalExecutablePath; @property (nonatomic,readonly) NSDictionary* groupContainerURLs; @property (nonatomic,readonly) NSArray* plugInKitPlugins; @property (getter=isInstalled,nonatomic,readonly) BOOL installed; @property (getter=isPlaceholder,nonatomic,readonly) BOOL placeholder; @property (getter=isRestricted,nonatomic,readonly) BOOL restricted; @property (nonatomic,readonly) NSSet* claimedURLSchemes; @property (nonatomic,readonly) NSString* applicationType; @end @interface LSApplicationWorkspace : NSObject + (instancetype)defaultWorkspace; - (BOOL)registerApplicationDictionary:(NSDictionary*)dict; - (BOOL)unregisterApplication:(id)arg1; - (BOOL)_LSPrivateRebuildApplicationDatabasesForSystemApps:(BOOL)arg1 internal:(BOOL)arg2 user:(BOOL)arg3; - (BOOL)openApplicationWithBundleID:(NSString *)arg1 ; - (void)enumerateApplicationsOfType:(NSUInteger)type block:(void (^)(LSApplicationProxy*))block; - (BOOL)installApplication:(NSURL*)appPackageURL withOptions:(NSDictionary*)options error:(NSError**)error; - (BOOL)uninstallApplication:(NSString*)appId withOptions:(NSDictionary*)options; - (void)addObserver:(id)arg1; - (void)removeObserver:(id)arg1; @end @protocol LSApplicationWorkspaceObserverProtocol @optional - (void)applicationsDidInstall:(NSArray *)apps; - (void)applicationsDidUninstall:(NSArray *)apps; @end @interface LSEnumerator : NSEnumerator @property (nonatomic,copy) NSPredicate * predicate; + (instancetype)enumeratorForApplicationProxiesWithOptions:(NSUInteger)options; @end @interface LSPlugInKitProxy : LSBundleProxy @property (nonatomic,readonly) NSString* pluginIdentifier; @property (nonatomic,readonly) NSDictionary * pluginKitDictionary; + (instancetype)pluginKitProxyForIdentifier:(NSString*)arg1; @end @interface MCMContainer : NSObject + (id)containerWithIdentifier:(id)arg1 createIfNecessary:(BOOL)arg2 existed:(BOOL*)arg3 error:(id*)arg4; @property (nonatomic,readonly) NSURL * url; @end @interface MCMDataContainer : MCMContainer @end @interface MCMAppDataContainer : MCMDataContainer @end @interface MCMAppContainer : MCMContainer @end @interface MCMPluginKitPluginDataContainer : MCMDataContainer @end @interface MCMSystemDataContainer : MCMContainer @end @interface MCMSharedDataContainer : MCMContainer @end ================================================ FILE: Shared/TSListControllerShared.h ================================================ #import #import #import @interface TSListControllerShared : PSListController - (BOOL)isTrollStore; - (NSString*)getTrollStoreVersion; - (void)downloadTrollStoreAndRun:(void (^)(NSString* localTrollStoreTarPath))doHandler; - (void)installTrollStorePressed; - (void)updateTrollStorePressed; - (void)rebuildIconCachePressed; - (void)refreshAppRegistrationsPressed; - (void)uninstallPersistenceHelperPressed; - (void)handleUninstallation; - (NSMutableArray*)argsForUninstallingTrollStore; - (void)uninstallTrollStorePressed; @end ================================================ FILE: Shared/TSListControllerShared.m ================================================ #import "TSListControllerShared.h" #import "TSUtil.h" #import "TSPresentationDelegate.h" @implementation TSListControllerShared - (BOOL)isTrollStore { return YES; } - (NSString*)getTrollStoreVersion { if([self isTrollStore]) { return [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; } else { NSString* trollStorePath = trollStoreAppPath(); if(!trollStorePath) return nil; NSBundle* trollStoreBundle = [NSBundle bundleWithPath:trollStorePath]; return [trollStoreBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; } } - (void)downloadTrollStoreAndRun:(void (^)(NSString* localTrollStoreTarPath))doHandler { NSURL* trollStoreURL = [NSURL URLWithString:@"https://github.com/opa334/TrollStore/releases/latest/download/TrollStore.tar"]; NSURLRequest* trollStoreRequest = [NSURLRequest requestWithURL:trollStoreURL]; NSURLSessionDownloadTask* downloadTask = [NSURLSession.sharedSession downloadTaskWithRequest:trollStoreRequest completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { if(error) { UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"Error downloading TrollStore: %@", error] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [errorAlert addAction:closeAction]; dispatch_async(dispatch_get_main_queue(), ^ { [TSPresentationDelegate stopActivityWithCompletion:^ { [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; }]; }); } else { NSString* tarTmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"TrollStore.tar"]; [[NSFileManager defaultManager] removeItemAtPath:tarTmpPath error:nil]; [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:tarTmpPath error:nil]; doHandler(tarTmpPath); } }]; [downloadTask resume]; } - (void)_installTrollStoreComingFromUpdateFlow:(BOOL)update { if(update) { [TSPresentationDelegate startActivity:@"Updating TrollStore"]; } else { [TSPresentationDelegate startActivity:@"Installing TrollStore"]; } [self downloadTrollStoreAndRun:^(NSString* tmpTarPath) { int ret = spawnRoot(rootHelperPath(), @[@"install-trollstore", tmpTarPath], nil, nil); [[NSFileManager defaultManager] removeItemAtPath:tmpTarPath error:nil]; if(ret == 0) { respring(); if([self isTrollStore]) { exit(0); } else { dispatch_async(dispatch_get_main_queue(), ^ { [TSPresentationDelegate stopActivityWithCompletion:^ { [self reloadSpecifiers]; }]; }); } } else { dispatch_async(dispatch_get_main_queue(), ^ { [TSPresentationDelegate stopActivityWithCompletion:^ { UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"Error installing TrollStore: trollstorehelper returned %d", ret] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [errorAlert addAction:closeAction]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; }]; }); } }]; } - (void)installTrollStorePressed { [self _installTrollStoreComingFromUpdateFlow:NO]; } - (void)updateTrollStorePressed { [self _installTrollStoreComingFromUpdateFlow:YES]; } - (void)rebuildIconCachePressed { [TSPresentationDelegate startActivity:@"Rebuilding Icon Cache"]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ { spawnRoot(rootHelperPath(), @[@"refresh-all"], nil, nil); dispatch_async(dispatch_get_main_queue(), ^ { [TSPresentationDelegate stopActivityWithCompletion:nil]; }); }); } - (void)refreshAppRegistrationsPressed { [TSPresentationDelegate startActivity:@"Refreshing"]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ { spawnRoot(rootHelperPath(), @[@"refresh"], nil, nil); respring(); dispatch_async(dispatch_get_main_queue(), ^ { [TSPresentationDelegate stopActivityWithCompletion:nil]; }); }); } - (void)uninstallPersistenceHelperPressed { if([self isTrollStore]) { spawnRoot(rootHelperPath(), @[@"uninstall-persistence-helper"], nil, nil); [self reloadSpecifiers]; } else { UIAlertController* uninstallWarningAlert = [UIAlertController alertControllerWithTitle:@"Warning" message:@"Uninstalling the persistence helper will revert this app back to it's original state, you will however no longer be able to persistently refresh the TrollStore app registrations. Continue?" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; [uninstallWarningAlert addAction:cancelAction]; UIAlertAction* continueAction = [UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) { spawnRoot(rootHelperPath(), @[@"uninstall-persistence-helper"], nil, nil); exit(0); }]; [uninstallWarningAlert addAction:continueAction]; [TSPresentationDelegate presentViewController:uninstallWarningAlert animated:YES completion:nil]; } } - (void)handleUninstallation { if([self isTrollStore]) { exit(0); } else { [self reloadSpecifiers]; } } - (NSMutableArray*)argsForUninstallingTrollStore { return @[@"uninstall-trollstore"].mutableCopy; } - (void)uninstallTrollStorePressed { UIAlertController* uninstallAlert = [UIAlertController alertControllerWithTitle:@"Uninstall" message:@"You are about to uninstall TrollStore, do you want to preserve the apps installed by it?" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* uninstallAllAction = [UIAlertAction actionWithTitle:@"Uninstall TrollStore, Uninstall Apps" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) { NSMutableArray* args = [self argsForUninstallingTrollStore]; spawnRoot(rootHelperPath(), args, nil, nil); [self handleUninstallation]; }]; [uninstallAlert addAction:uninstallAllAction]; UIAlertAction* preserveAppsAction = [UIAlertAction actionWithTitle:@"Uninstall TrollStore, Preserve Apps" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) { NSMutableArray* args = [self argsForUninstallingTrollStore]; [args addObject:@"preserve-apps"]; spawnRoot(rootHelperPath(), args, nil, nil); [self handleUninstallation]; }]; [uninstallAlert addAction:preserveAppsAction]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; [uninstallAlert addAction:cancelAction]; [TSPresentationDelegate presentViewController:uninstallAlert animated:YES completion:nil]; } @end ================================================ FILE: Shared/TSPresentationDelegate.h ================================================ #import @interface TSPresentationDelegate : NSObject @property (class) UIViewController* presentationViewController; @property (class) UIAlertController* activityController; + (void)startActivity:(NSString*)activity withCancelHandler:(void (^)(void))cancelHandler; + (void)startActivity:(NSString*)activity; + (void)stopActivityWithCompletion:(void (^)(void))completion; + (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion; @end ================================================ FILE: Shared/TSPresentationDelegate.m ================================================ #import "TSPresentationDelegate.h" @implementation TSPresentationDelegate static UIViewController* g_presentationViewController; static UIAlertController* g_activityController; + (UIViewController*)presentationViewController { return g_presentationViewController; } + (void)setPresentationViewController:(UIViewController*)vc { g_presentationViewController = vc; } + (UIAlertController*)activityController { return g_activityController; } + (void)setActivityController:(UIAlertController*)ac { g_activityController = ac; } + (void)startActivity:(NSString*)activity withCancelHandler:(void (^)(void))cancelHandler { if(self.activityController) { self.activityController.title = activity; } else { self.activityController = [UIAlertController alertControllerWithTitle:activity message:@"" preferredStyle:UIAlertControllerStyleAlert]; UIActivityIndicatorView* activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(5,5,50,50)]; activityIndicator.hidesWhenStopped = YES; activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium; [activityIndicator startAnimating]; [self.activityController.view addSubview:activityIndicator]; if(cancelHandler) { UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action) { self.activityController = nil; cancelHandler(); }]; [self.activityController addAction:cancelAction]; } [self presentViewController:self.activityController animated:YES completion:nil]; } } + (void)startActivity:(NSString*)activity { [self startActivity:activity withCancelHandler:nil]; } + (void)stopActivityWithCompletion:(void (^)(void))completionBlock { if(!self.activityController) return; [self.activityController dismissViewControllerAnimated:YES completion:^ { self.activityController = nil; if(completionBlock) completionBlock(); }]; } + (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completionBlock { [self.presentationViewController presentViewController:viewControllerToPresent animated:flag completion:completionBlock]; } @end ================================================ FILE: Shared/TSUtil.h ================================================ @import Foundation; #import "CoreServices.h" #define TrollStoreErrorDomain @"TrollStoreErrorDomain" #define TS_MARKER @"_TrollStore" #define TS_LITE_MARKER @"_TrollStoreLite" #define TS_NAME @"TrollStore" #define TS_LITE_NAME @"Trollstore Lite" #ifdef TROLLSTORE_LITE #define TS_ACTIVE_MARKER TS_LITE_MARKER #define TS_INACTIVE_MARKER TS_MARKER #define APP_ID @"com.opa334.TrollStoreLite" #define APP_NAME TS_LITE_NAME #define OTHER_APP_NAME TS_NAME #else #define TS_ACTIVE_MARKER TS_MARKER #define TS_INACTIVE_MARKER TS_LITE_MARKER #define APP_ID @"com.opa334.TrollStore" #define APP_NAME TS_NAME #define OTHER_APP_NAME TS_LITE_NAME #endif extern void chineseWifiFixup(void); extern NSString *getExecutablePath(void); extern BOOL shouldRegisterAsUserByDefault(void); extern NSString* rootHelperPath(void); extern NSString* getNSStringFromFile(int fd); extern void printMultilineNSString(NSString* stringToPrint); extern int spawnRoot(NSString* path, NSArray* args, NSString** stdOut, NSString** stdErr); extern void killall(NSString* processName, BOOL softly); extern void respring(void); extern void fetchLatestTrollStoreVersion(void (^completionHandler)(NSString* latestVersion)); extern void fetchLatestLdidVersion(void (^completionHandler)(NSString* latestVersion)); extern NSArray* trollStoreInstalledAppBundlePaths(void); extern NSArray* trollStoreInactiveInstalledAppBundlePaths(void); extern NSArray* trollStoreInstalledAppContainerPaths(void); extern NSString* trollStorePath(void); extern NSString* trollStoreAppPath(void); extern BOOL isRemovableSystemApp(NSString* appId); #import @interface UIAlertController (Private) @property (setter=_setAttributedTitle:,getter=_attributedTitle,nonatomic,copy) NSAttributedString* attributedTitle; @property (setter=_setAttributedMessage:,getter=_attributedMessage,nonatomic,copy) NSAttributedString* attributedMessage; @property (nonatomic,retain) UIImage* image; @end typedef enum { PERSISTENCE_HELPER_TYPE_USER = 1 << 0, PERSISTENCE_HELPER_TYPE_SYSTEM = 1 << 1, PERSISTENCE_HELPER_TYPE_ALL = PERSISTENCE_HELPER_TYPE_USER | PERSISTENCE_HELPER_TYPE_SYSTEM } PERSISTENCE_HELPER_TYPE; // EXPLOIT_TYPE is defined as a bitmask as some devices are vulnerable to multiple exploits // // An app that has had one of these exploits applied ahead of time can declare which exploit // was used via the TSPreAppliedExploitType Info.plist key. The corresponding value should be // (number of bits to left-shift + 1). typedef enum { // CVE-2022-26766 // TSPreAppliedExploitType = 1 EXPLOIT_TYPE_CUSTOM_ROOT_CERTIFICATE_V1 = 1 << 0, // CVE-2023-41991 // TSPreAppliedExploitType = 2 EXPLOIT_TYPE_CMS_SIGNERINFO_V1 = 1 << 1 } EXPLOIT_TYPE; extern LSApplicationProxy* findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE allowedTypes); typedef struct __SecCode const *SecStaticCodeRef; typedef CF_OPTIONS(uint32_t, SecCSFlags) { kSecCSDefaultFlags = 0 }; #define kSecCSRequirementInformation 1 << 2 #define kSecCSSigningInformation 1 << 1 OSStatus SecStaticCodeCreateWithPathAndAttributes(CFURLRef path, SecCSFlags flags, CFDictionaryRef attributes, SecStaticCodeRef *staticCode); OSStatus SecCodeCopySigningInformation(SecStaticCodeRef code, SecCSFlags flags, CFDictionaryRef *information); CFDataRef SecCertificateCopyExtensionValue(SecCertificateRef certificate, CFTypeRef extensionOID, bool *isCritical); void SecPolicySetOptionsValue(SecPolicyRef policy, CFStringRef key, CFTypeRef value); extern CFStringRef kSecCodeInfoEntitlementsDict; extern CFStringRef kSecCodeInfoCertificates; extern CFStringRef kSecPolicyAppleiPhoneApplicationSigning; extern CFStringRef kSecPolicyAppleiPhoneProfileApplicationSigning; extern CFStringRef kSecPolicyLeafMarkerOid; extern SecStaticCodeRef getStaticCodeRef(NSString *binaryPath); extern NSDictionary* dumpEntitlements(SecStaticCodeRef codeRef); extern NSDictionary* dumpEntitlementsFromBinaryAtPath(NSString *binaryPath); extern NSDictionary* dumpEntitlementsFromBinaryData(NSData* binaryData); extern EXPLOIT_TYPE getDeclaredExploitTypeFromInfoDictionary(NSDictionary *infoDict); extern bool isPlatformVulnerableToExploitType(EXPLOIT_TYPE exploitType); ================================================ FILE: Shared/TSUtil.m ================================================ #import "TSUtil.h" #import #import #import #import #import static EXPLOIT_TYPE gPlatformVulnerabilities; void* _CTServerConnectionCreate(CFAllocatorRef, void *, void *); int64_t _CTServerConnectionSetCellularUsagePolicy(CFTypeRef* ct, NSString* identifier, NSDictionary* policies); #define POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE 1 extern int posix_spawnattr_set_persona_np(const posix_spawnattr_t* __restrict, uid_t, uint32_t); extern int posix_spawnattr_set_persona_uid_np(const posix_spawnattr_t* __restrict, uid_t); extern int posix_spawnattr_set_persona_gid_np(const posix_spawnattr_t* __restrict, uid_t); void chineseWifiFixup(void) { _CTServerConnectionSetCellularUsagePolicy( _CTServerConnectionCreate(kCFAllocatorDefault, NULL, NULL), NSBundle.mainBundle.bundleIdentifier, @{ @"kCTCellularDataUsagePolicy" : @"kCTCellularDataUsagePolicyAlwaysAllow", @"kCTWiFiDataUsagePolicy" : @"kCTCellularDataUsagePolicyAlwaysAllow" } ); } NSString *getExecutablePath(void) { uint32_t len = PATH_MAX; char selfPath[len]; _NSGetExecutablePath(selfPath, &len); return [NSString stringWithUTF8String:selfPath]; } #ifdef TROLLSTORE_LITE BOOL shouldRegisterAsUserByDefault(void) { if ([[NSFileManager defaultManager] fileExistsAtPath:JBROOT_PATH(@"/Library/MobileSubstrate/DynamicLibraries/AppSyncUnified-FrontBoard.dylib")]) { return YES; } return NO; } #else BOOL shouldRegisterAsUserByDefault(void) { return NO; } #endif #ifdef EMBEDDED_ROOT_HELPER NSString* rootHelperPath(void) { return getExecutablePath(); } #else NSString* rootHelperPath(void) { return [[NSBundle mainBundle].bundlePath stringByAppendingPathComponent:@"trollstorehelper"]; } #endif int fd_is_valid(int fd) { return fcntl(fd, F_GETFD) != -1 || errno != EBADF; } NSString* getNSStringFromFile(int fd) { NSMutableString* ms = [NSMutableString new]; ssize_t num_read; char c; if(!fd_is_valid(fd)) return @""; while((num_read = read(fd, &c, sizeof(c)))) { [ms appendString:[NSString stringWithFormat:@"%c", c]]; if(c == '\n') break; } return ms.copy; } void printMultilineNSString(NSString* stringToPrint) { NSCharacterSet *separator = [NSCharacterSet newlineCharacterSet]; NSArray* lines = [stringToPrint componentsSeparatedByCharactersInSet:separator]; for(NSString* line in lines) { NSLog(@"%@", line); } } int spawnRoot(NSString* path, NSArray* args, NSString** stdOut, NSString** stdErr) { NSMutableArray* argsM = args.mutableCopy ?: [NSMutableArray new]; [argsM insertObject:path atIndex:0]; NSUInteger argCount = [argsM count]; char **argsC = (char **)malloc((argCount + 1) * sizeof(char*)); for (NSUInteger i = 0; i < argCount; i++) { argsC[i] = strdup([[argsM objectAtIndex:i] UTF8String]); } argsC[argCount] = NULL; posix_spawnattr_t attr; posix_spawnattr_init(&attr); posix_spawnattr_set_persona_np(&attr, 99, POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE); posix_spawnattr_set_persona_uid_np(&attr, 0); posix_spawnattr_set_persona_gid_np(&attr, 0); posix_spawn_file_actions_t action; posix_spawn_file_actions_init(&action); int outErr[2]; if(stdErr) { pipe(outErr); posix_spawn_file_actions_adddup2(&action, outErr[1], STDERR_FILENO); posix_spawn_file_actions_addclose(&action, outErr[0]); } int out[2]; if(stdOut) { pipe(out); posix_spawn_file_actions_adddup2(&action, out[1], STDOUT_FILENO); posix_spawn_file_actions_addclose(&action, out[0]); } pid_t task_pid; int status = -200; int spawnError = posix_spawn(&task_pid, [path UTF8String], &action, &attr, (char* const*)argsC, NULL); posix_spawnattr_destroy(&attr); for (NSUInteger i = 0; i < argCount; i++) { free(argsC[i]); } free(argsC); if(spawnError != 0) { NSLog(@"posix_spawn error %d\n", spawnError); return spawnError; } __block volatile BOOL _isRunning = YES; NSMutableString* outString = [NSMutableString new]; NSMutableString* errString = [NSMutableString new]; dispatch_semaphore_t sema = 0; dispatch_queue_t logQueue; if(stdOut || stdErr) { logQueue = dispatch_queue_create("com.opa334.TrollStore.LogCollector", NULL); sema = dispatch_semaphore_create(0); int outPipe = out[0]; int outErrPipe = outErr[0]; __block BOOL outEnabled = (BOOL)stdOut; __block BOOL errEnabled = (BOOL)stdErr; dispatch_async(logQueue, ^ { while(_isRunning) { @autoreleasepool { if(outEnabled) { [outString appendString:getNSStringFromFile(outPipe)]; } if(errEnabled) { [errString appendString:getNSStringFromFile(outErrPipe)]; } } } dispatch_semaphore_signal(sema); }); } do { if (waitpid(task_pid, &status, 0) != -1) { NSLog(@"Child status %d", WEXITSTATUS(status)); } else { perror("waitpid"); _isRunning = NO; return -222; } } while (!WIFEXITED(status) && !WIFSIGNALED(status)); _isRunning = NO; if(stdOut || stdErr) { if(stdOut) { close(out[1]); } if(stdErr) { close(outErr[1]); } // wait for logging queue to finish dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); if(stdOut) { *stdOut = outString.copy; } if(stdErr) { *stdErr = errString.copy; } } return WEXITSTATUS(status); } void enumerateProcessesUsingBlock(void (^enumerator)(pid_t pid, NSString* executablePath, BOOL* stop)) { static int maxArgumentSize = 0; if (maxArgumentSize == 0) { size_t size = sizeof(maxArgumentSize); if (sysctl((int[]){ CTL_KERN, KERN_ARGMAX }, 2, &maxArgumentSize, &size, NULL, 0) == -1) { perror("sysctl argument size"); maxArgumentSize = 4096; // Default } } int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL}; struct kinfo_proc *info; size_t length; int count; if (sysctl(mib, 3, NULL, &length, NULL, 0) < 0) return; if (!(info = malloc(length))) return; if (sysctl(mib, 3, info, &length, NULL, 0) < 0) { free(info); return; } count = length / sizeof(struct kinfo_proc); for (int i = 0; i < count; i++) { @autoreleasepool { pid_t pid = info[i].kp_proc.p_pid; if (pid == 0) { continue; } size_t size = maxArgumentSize; char* buffer = (char *)malloc(length); if (sysctl((int[]){ CTL_KERN, KERN_PROCARGS2, pid }, 3, buffer, &size, NULL, 0) == 0) { NSString* executablePath = [NSString stringWithCString:(buffer+sizeof(int)) encoding:NSUTF8StringEncoding]; BOOL stop = NO; enumerator(pid, executablePath, &stop); if(stop) { free(buffer); break; } } free(buffer); } } free(info); } void killall(NSString* processName, BOOL softly) { enumerateProcessesUsingBlock(^(pid_t pid, NSString* executablePath, BOOL* stop) { if([executablePath.lastPathComponent isEqualToString:processName]) { if(softly) { kill(pid, SIGTERM); } else { kill(pid, SIGKILL); } } }); } void respring(void) { killall(@"SpringBoard", YES); exit(0); } void github_fetchLatestVersion(NSString* repo, void (^completionHandler)(NSString* latestVersion)) { NSString* urlString = [NSString stringWithFormat:@"https://api.github.com/repos/%@/releases/latest", repo]; NSURL* githubLatestAPIURL = [NSURL URLWithString:urlString]; NSURLSessionDataTask* task = [NSURLSession.sharedSession dataTaskWithURL:githubLatestAPIURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if(!error) { if ([response isKindOfClass:[NSHTTPURLResponse class]]) { NSError *jsonError; NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; if (!jsonError) { completionHandler(jsonResponse[@"tag_name"]); } } } }]; [task resume]; } void fetchLatestTrollStoreVersion(void (^completionHandler)(NSString* latestVersion)) { github_fetchLatestVersion(@"opa334/TrollStore", completionHandler); } void fetchLatestLdidVersion(void (^completionHandler)(NSString* latestVersion)) { github_fetchLatestVersion(@"opa334/ldid", completionHandler); } NSArray* trollStoreInstalledAppContainerPathsInternal(NSString *marker) { NSMutableArray* appContainerPaths = [NSMutableArray new]; NSString* appContainersPath = @"/var/containers/Bundle/Application"; NSError* error; NSArray* containers = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:appContainersPath error:&error]; if(error) { NSLog(@"error getting app bundles paths %@", error); } if(!containers) return nil; for(NSString* container in containers) { NSString* containerPath = [appContainersPath stringByAppendingPathComponent:container]; BOOL isDirectory = NO; BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:containerPath isDirectory:&isDirectory]; if(exists && isDirectory) { NSString* trollStoreMark = [containerPath stringByAppendingPathComponent:marker]; if([[NSFileManager defaultManager] fileExistsAtPath:trollStoreMark]) { NSString* trollStoreApp = [containerPath stringByAppendingPathComponent:@"TrollStore.app"]; NSString* trollStoreLiteApp = [containerPath stringByAppendingPathComponent:@"TrollStoreLite.app"]; if(![[NSFileManager defaultManager] fileExistsAtPath:trollStoreApp] && ![[NSFileManager defaultManager] fileExistsAtPath:trollStoreLiteApp]) { [appContainerPaths addObject:containerPath]; } } } } return appContainerPaths.copy; } NSArray *trollStoreInstalledAppContainerPaths(void) { return trollStoreInstalledAppContainerPathsInternal(TS_ACTIVE_MARKER); } NSArray* trollStoreInstalledAppBundlePathsInternal(NSString *marker) { NSMutableArray* appPaths = [NSMutableArray new]; for(NSString* containerPath in trollStoreInstalledAppContainerPathsInternal(marker)) { NSArray* items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:containerPath error:nil]; if(!items) return nil; for(NSString* item in items) { if([item.pathExtension isEqualToString:@"app"]) { [appPaths addObject:[containerPath stringByAppendingPathComponent:item]]; } } } return appPaths.copy; } NSArray *trollStoreInstalledAppBundlePaths(void) { return trollStoreInstalledAppBundlePathsInternal(TS_ACTIVE_MARKER); } NSArray *trollStoreInactiveInstalledAppBundlePaths(void) { return trollStoreInstalledAppBundlePathsInternal(TS_INACTIVE_MARKER); } NSString* trollStorePath() { NSError* mcmError; MCMAppContainer* appContainer = [MCMAppContainer containerWithIdentifier:APP_ID createIfNecessary:NO existed:NULL error:&mcmError]; if(!appContainer) return nil; return appContainer.url.path; } NSString* trollStoreAppPath() { return [trollStorePath() stringByAppendingPathComponent:@"TrollStore.app"]; } BOOL isRemovableSystemApp(NSString* appId) { return [[NSFileManager defaultManager] fileExistsAtPath:[@"/System/Library/AppSignatures" stringByAppendingPathComponent:appId]]; } LSApplicationProxy* findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE allowedTypes) { __block LSApplicationProxy* outProxy; void (^searchBlock)(LSApplicationProxy* appProxy) = ^(LSApplicationProxy* appProxy) { if(appProxy.installed && !appProxy.restricted) { if([appProxy.bundleURL.path hasPrefix:@"/private/var/containers"]) { NSURL* trollStorePersistenceMarkURL = [appProxy.bundleURL URLByAppendingPathComponent:@".TrollStorePersistenceHelper"]; if([trollStorePersistenceMarkURL checkResourceIsReachableAndReturnError:nil]) { outProxy = appProxy; } } } }; if(allowedTypes & PERSISTENCE_HELPER_TYPE_USER) { [[LSApplicationWorkspace defaultWorkspace] enumerateApplicationsOfType:0 block:searchBlock]; } if(allowedTypes & PERSISTENCE_HELPER_TYPE_SYSTEM) { [[LSApplicationWorkspace defaultWorkspace] enumerateApplicationsOfType:1 block:searchBlock]; } return outProxy; } SecStaticCodeRef getStaticCodeRef(NSString *binaryPath) { if(binaryPath == nil) { return NULL; } CFURLRef binaryURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (__bridge CFStringRef)binaryPath, kCFURLPOSIXPathStyle, false); if(binaryURL == NULL) { NSLog(@"[getStaticCodeRef] failed to get URL to binary %@", binaryPath); return NULL; } SecStaticCodeRef codeRef = NULL; OSStatus result; result = SecStaticCodeCreateWithPathAndAttributes(binaryURL, kSecCSDefaultFlags, NULL, &codeRef); CFRelease(binaryURL); if(result != errSecSuccess) { NSLog(@"[getStaticCodeRef] failed to create static code for binary %@", binaryPath); return NULL; } return codeRef; } NSDictionary* dumpEntitlements(SecStaticCodeRef codeRef) { if(codeRef == NULL) { NSLog(@"[dumpEntitlements] attempting to dump entitlements without a StaticCodeRef"); return nil; } CFDictionaryRef signingInfo = NULL; OSStatus result; result = SecCodeCopySigningInformation(codeRef, kSecCSRequirementInformation, &signingInfo); if(result != errSecSuccess) { NSLog(@"[dumpEntitlements] failed to copy signing info from static code"); return nil; } NSDictionary *entitlementsNSDict = nil; CFDictionaryRef entitlements = CFDictionaryGetValue(signingInfo, kSecCodeInfoEntitlementsDict); if(entitlements == NULL) { NSLog(@"[dumpEntitlements] no entitlements specified"); } else if(CFGetTypeID(entitlements) != CFDictionaryGetTypeID()) { NSLog(@"[dumpEntitlements] invalid entitlements"); } else { entitlementsNSDict = (__bridge NSDictionary *)(entitlements); NSLog(@"[dumpEntitlements] dumped %@", entitlementsNSDict); } CFRelease(signingInfo); return entitlementsNSDict; } NSDictionary* dumpEntitlementsFromBinaryAtPath(NSString *binaryPath) { // This function is intended for one-shot checks. Main-event functions should retain/release their own SecStaticCodeRefs if(binaryPath == nil) { return nil; } SecStaticCodeRef codeRef = getStaticCodeRef(binaryPath); if(codeRef == NULL) { return nil; } NSDictionary *entitlements = dumpEntitlements(codeRef); CFRelease(codeRef); return entitlements; } NSDictionary* dumpEntitlementsFromBinaryData(NSData* binaryData) { NSDictionary* entitlements; NSString* tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString]; NSURL* tmpURL = [NSURL fileURLWithPath:tmpPath]; if([binaryData writeToURL:tmpURL options:NSDataWritingAtomic error:nil]) { entitlements = dumpEntitlementsFromBinaryAtPath(tmpPath); [[NSFileManager defaultManager] removeItemAtURL:tmpURL error:nil]; } return entitlements; } EXPLOIT_TYPE getDeclaredExploitTypeFromInfoDictionary(NSDictionary *infoDict) { NSObject *tsPreAppliedExploitType = infoDict[@"TSPreAppliedExploitType"]; if([tsPreAppliedExploitType isKindOfClass:[NSNumber class]]) { NSNumber *tsPreAppliedExploitTypeNum = (NSNumber *)tsPreAppliedExploitType; int exploitTypeInt = [tsPreAppliedExploitTypeNum intValue]; if(exploitTypeInt > 0) { // Convert versions 1, 2, etc... for use with bitmasking return (1 << (exploitTypeInt - 1)); } else { NSLog(@"[getDeclaredExploitTypeFromInfoDictionary] rejecting TSPreAppliedExploitType Info.plist value (%i) which is out of range", exploitTypeInt); } } // Legacy Info.plist flag - now deprecated, but we treat it as a custom root cert if present NSObject *tsBundleIsPreSigned = infoDict[@"TSBundlePreSigned"]; if([tsBundleIsPreSigned isKindOfClass:[NSNumber class]]) { NSNumber *tsBundleIsPreSignedNum = (NSNumber *)tsBundleIsPreSigned; if([tsBundleIsPreSignedNum boolValue] == YES) { return EXPLOIT_TYPE_CUSTOM_ROOT_CERTIFICATE_V1; } } // No declarations return 0; } void determinePlatformVulnerableExploitTypes(void *context) { size_t size = 0; // Get the current build number int mib[2] = {CTL_KERN, KERN_OSVERSION}; // Get size of buffer sysctl(mib, 2, NULL, &size, NULL, 0); // Get the actual value char *os_build = malloc(size); if(!os_build) { // malloc failed perror("malloc buffer for KERN_OSVERSION"); return; } if (sysctl(mib, 2, os_build, &size, NULL, 0) != 0) { // sysctl failed perror("sysctl KERN_OSVERSION"); free(os_build); return; } if(strncmp(os_build, "18A5319i", 8) < 0) { // Below iOS 14.0 beta 2 gPlatformVulnerabilities = 0; } else if(strncmp(os_build, "21A326", 6) >= 0 && strncmp(os_build, "21A331", 6) <= 0) { // iOS 17.0 final gPlatformVulnerabilities = EXPLOIT_TYPE_CMS_SIGNERINFO_V1; } else if(strncmp(os_build, "21A5248v", 8) >= 0 && strncmp(os_build, "21A5326a", 8) <= 0) { // iOS 17.0 beta 1 - 8 gPlatformVulnerabilities = EXPLOIT_TYPE_CMS_SIGNERINFO_V1; } else if(strncmp(os_build, "19G5027e", 8) >= 0 && strncmp(os_build, "19G5063a", 8) <= 0) { // iOS 15.6 beta 1 - 5 gPlatformVulnerabilities = (EXPLOIT_TYPE_CUSTOM_ROOT_CERTIFICATE_V1 | EXPLOIT_TYPE_CMS_SIGNERINFO_V1); } else if(strncmp(os_build, "19F5070b", 8) <= 0) { // iOS 14.0 beta 2 - 15.5 beta 4 gPlatformVulnerabilities = (EXPLOIT_TYPE_CUSTOM_ROOT_CERTIFICATE_V1 | EXPLOIT_TYPE_CMS_SIGNERINFO_V1); } else if(strncmp(os_build, "20H18", 5) <= 0) { // iOS 14.0 - 16.6.1, 16.7 RC (if CUSTOM_ROOT_CERTIFICATE_V1 not supported) gPlatformVulnerabilities = EXPLOIT_TYPE_CMS_SIGNERINFO_V1; } free(os_build); } bool isPlatformVulnerableToExploitType(EXPLOIT_TYPE exploitType) { // Find out what we are vulnerable to static dispatch_once_t once; dispatch_once_f(&once, NULL, determinePlatformVulnerableExploitTypes); return (exploitType & gPlatformVulnerabilities) != 0; } ================================================ FILE: TrollHelper/Makefile ================================================ export EMBEDDED_ROOT_HELPER ?= 0 export LEGACY_CT_BUG ?= 0 TARGET := iphone:clang:16.5:14.0 INSTALL_TARGET_PROCESSES = TrollStorePersistenceHelper ifdef CUSTOM_ARCHS ARCHS = $(CUSTOM_ARCHS) else ARCHS = arm64 endif ifneq ($(LEGACY_CT_BUG),1) TARGET_CODESIGN = ../Exploits/fastPathSign/fastPathSign endif include $(THEOS)/makefiles/common.mk APPLICATION_NAME = TrollStorePersistenceHelper TrollStorePersistenceHelper_FILES = $(wildcard *.m) $(wildcard ../Shared/*.m) TrollStorePersistenceHelper_FRAMEWORKS = UIKit CoreGraphics CoreServices CoreTelephony TrollStorePersistenceHelper_PRIVATE_FRAMEWORKS = Preferences MobileContainerManager TrollStorePersistenceHelper_CFLAGS = -fobjc-arc -I../Shared -I$(shell brew --prefix)/opt/libarchive/include ifeq ($(LEGACY_CT_BUG),1) TrollStorePersistenceHelper_CODESIGN_FLAGS = -Sentitlements.plist -K../legacy.p12 TrollStorePersistenceHelper_CFLAGS += -DLEGACY_CT_BUG=1 else TrollStorePersistenceHelper_CODESIGN_FLAGS = --entitlements entitlements.plist endif ifeq ($(EMBEDDED_ROOT_HELPER),1) TrollStorePersistenceHelper_CFLAGS += -DEMBEDDED_ROOT_HELPER=1 TrollStorePersistenceHelper_FILES += $(wildcard ../RootHelper/*.m) TrollStorePersistenceHelper_LIBRARIES += archive TrollStorePersistenceHelper_PRIVATE_FRAMEWORKS += SpringBoardServices BackBoardServices FrontBoardServices endif include $(THEOS_MAKE_PATH)/application.mk ================================================ FILE: TrollHelper/Resources/Info.plist ================================================ CFBundleExecutable TrollStorePersistenceHelper CFBundleDisplayName TrollHelper CFBundleIcons CFBundlePrimaryIcon CFBundleIconFiles AppIcon29x29 AppIcon40x40 AppIcon57x57 AppIcon60x60 UIPrerenderedIcon CFBundleIcons~ipad CFBundlePrimaryIcon CFBundleIconFiles AppIcon29x29 AppIcon40x40 AppIcon57x57 AppIcon60x60 AppIcon50x50 AppIcon72x72 AppIcon76x76 UIPrerenderedIcon CFBundleIdentifier com.opa334.trollstorepersistencehelper CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType APPL CFBundleSignature ???? CFBundleSupportedPlatforms iPhoneOS CFBundleVersion 2.1 LSRequiresIPhoneOS UIDeviceFamily 1 2 UIRequiredDeviceCapabilities arm64 UILaunchStoryboardName LaunchScreen UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: TrollHelper/TSHAppDelegateNoScene.h ================================================ #import @interface TSHAppDelegateNoScene : UIResponder @property (nonatomic, strong) UIWindow *window; @property (nonatomic, strong) UINavigationController *rootViewController; @end ================================================ FILE: TrollHelper/TSHAppDelegateNoScene.m ================================================ #import "TSHAppDelegateNoScene.h" #import "TSHRootViewController.h" @implementation TSHAppDelegateNoScene - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; _rootViewController = [[UINavigationController alloc] initWithRootViewController:[[TSHRootViewController alloc] init]]; _window.rootViewController = _rootViewController; [_window makeKeyAndVisible]; return YES; } @end ================================================ FILE: TrollHelper/TSHAppDelegateWithScene.h ================================================ #import @interface TSHAppDelegateWithScene : UIResponder @end ================================================ FILE: TrollHelper/TSHAppDelegateWithScene.m ================================================ #import "TSHAppDelegateWithScene.h" @implementation TSHAppDelegateWithScene - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return YES; } - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; } - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } @end ================================================ FILE: TrollHelper/TSHRootViewController.h ================================================ #import @interface TSHRootViewController : TSListControllerShared { NSString* _newerVersion; } @end ================================================ FILE: TrollHelper/TSHRootViewController.m ================================================ #import "TSHRootViewController.h" #import #import @implementation TSHRootViewController - (BOOL)isTrollStore { return NO; } - (void)viewDidLoad { [super viewDidLoad]; TSPresentationDelegate.presentationViewController = self; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadSpecifiers) name:UIApplicationWillEnterForegroundNotification object:nil]; fetchLatestTrollStoreVersion(^(NSString* latestVersion) { NSString* currentVersion = [self getTrollStoreVersion]; NSComparisonResult result = [currentVersion compare:latestVersion options:NSNumericSearch]; if(result == NSOrderedAscending) { _newerVersion = latestVersion; dispatch_async(dispatch_get_main_queue(), ^ { [self reloadSpecifiers]; }); } }); } - (NSMutableArray*)specifiers { if(!_specifiers) { _specifiers = [NSMutableArray new]; #ifdef LEGACY_CT_BUG NSString* credits = @"Powered by Fugu15 CoreTrust & installd bugs, thanks to @LinusHenze\n\n© 2022-2024 Lars Fröder (opa334)"; #else NSString* credits = @"Powered by CVE-2023-41991, originally discovered by Google TAG, rediscovered via patchdiffing by @alfiecg_dev\n\n© 2022-2024 Lars Fröder (opa334)"; #endif PSSpecifier* infoGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; infoGroupSpecifier.name = @"Info"; [_specifiers addObject:infoGroupSpecifier]; PSSpecifier* infoSpecifier = [PSSpecifier preferenceSpecifierNamed:@"TrollStore" target:self set:nil get:@selector(getTrollStoreInfoString) detail:nil cell:PSTitleValueCell edit:nil]; infoSpecifier.identifier = @"info"; [infoSpecifier setProperty:@YES forKey:@"enabled"]; [_specifiers addObject:infoSpecifier]; BOOL isInstalled = trollStoreAppPath(); if(_newerVersion && isInstalled) { // Update TrollStore PSSpecifier* updateTrollStoreSpecifier = [PSSpecifier preferenceSpecifierNamed:[NSString stringWithFormat:@"Update TrollStore to %@", _newerVersion] target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; updateTrollStoreSpecifier.identifier = @"updateTrollStore"; [updateTrollStoreSpecifier setProperty:@YES forKey:@"enabled"]; updateTrollStoreSpecifier.buttonAction = @selector(updateTrollStorePressed); [_specifiers addObject:updateTrollStoreSpecifier]; } PSSpecifier* lastGroupSpecifier; PSSpecifier* utilitiesGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; [_specifiers addObject:utilitiesGroupSpecifier]; lastGroupSpecifier = utilitiesGroupSpecifier; if(isInstalled || trollStoreInstalledAppContainerPaths().count) { PSSpecifier* refreshAppRegistrationsSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Refresh App Registrations" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; refreshAppRegistrationsSpecifier.identifier = @"refreshAppRegistrations"; [refreshAppRegistrationsSpecifier setProperty:@YES forKey:@"enabled"]; refreshAppRegistrationsSpecifier.buttonAction = @selector(refreshAppRegistrationsPressed); [_specifiers addObject:refreshAppRegistrationsSpecifier]; } if(isInstalled) { PSSpecifier* uninstallTrollStoreSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Uninstall TrollStore" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; uninstallTrollStoreSpecifier.identifier = @"uninstallTrollStore"; [uninstallTrollStoreSpecifier setProperty:@YES forKey:@"enabled"]; [uninstallTrollStoreSpecifier setProperty:NSClassFromString(@"PSDeleteButtonCell") forKey:@"cellClass"]; uninstallTrollStoreSpecifier.buttonAction = @selector(uninstallTrollStorePressed); [_specifiers addObject:uninstallTrollStoreSpecifier]; } else { PSSpecifier* installTrollStoreSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Install TrollStore" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; installTrollStoreSpecifier.identifier = @"installTrollStore"; [installTrollStoreSpecifier setProperty:@YES forKey:@"enabled"]; installTrollStoreSpecifier.buttonAction = @selector(installTrollStorePressed); [_specifiers addObject:installTrollStoreSpecifier]; } NSString* backupPath = [getExecutablePath() stringByAppendingString:@"_TROLLSTORE_BACKUP"]; if([[NSFileManager defaultManager] fileExistsAtPath:backupPath]) { PSSpecifier* uninstallHelperGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; [_specifiers addObject:uninstallHelperGroupSpecifier]; lastGroupSpecifier = uninstallHelperGroupSpecifier; PSSpecifier* uninstallPersistenceHelperSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Uninstall Persistence Helper" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; uninstallPersistenceHelperSpecifier.identifier = @"uninstallPersistenceHelper"; [uninstallPersistenceHelperSpecifier setProperty:@YES forKey:@"enabled"]; [uninstallPersistenceHelperSpecifier setProperty:NSClassFromString(@"PSDeleteButtonCell") forKey:@"cellClass"]; uninstallPersistenceHelperSpecifier.buttonAction = @selector(uninstallPersistenceHelperPressed); [_specifiers addObject:uninstallPersistenceHelperSpecifier]; } #ifdef EMBEDDED_ROOT_HELPER LSApplicationProxy* persistenceHelperProxy = findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE_ALL); BOOL isRegistered = [persistenceHelperProxy.bundleIdentifier isEqualToString:NSBundle.mainBundle.bundleIdentifier]; if((isRegistered || !persistenceHelperProxy) && ![[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/TrollStorePersistenceHelper.app"]) { PSSpecifier* registerUnregisterGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; lastGroupSpecifier = nil; NSString* bottomText; PSSpecifier* registerUnregisterSpecifier; if(isRegistered) { bottomText = @"This app is registered as the TrollStore persistence helper and can be used to fix TrollStore app registrations in case they revert back to \"User\" state and the apps say they're unavailable."; registerUnregisterSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Unregister Persistence Helper" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; registerUnregisterSpecifier.identifier = @"registerUnregisterSpecifier"; [registerUnregisterSpecifier setProperty:@YES forKey:@"enabled"]; [registerUnregisterSpecifier setProperty:NSClassFromString(@"PSDeleteButtonCell") forKey:@"cellClass"]; registerUnregisterSpecifier.buttonAction = @selector(unregisterPersistenceHelperPressed); } else if(!persistenceHelperProxy) { bottomText = @"If you want to use this app as the TrollStore persistence helper, you can register it here."; registerUnregisterSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Register Persistence Helper" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; registerUnregisterSpecifier.identifier = @"registerUnregisterSpecifier"; [registerUnregisterSpecifier setProperty:@YES forKey:@"enabled"]; registerUnregisterSpecifier.buttonAction = @selector(registerPersistenceHelperPressed); } [registerUnregisterGroupSpecifier setProperty:[NSString stringWithFormat:@"%@\n\n%@", bottomText, credits] forKey:@"footerText"]; lastGroupSpecifier = nil; [_specifiers addObject:registerUnregisterGroupSpecifier]; [_specifiers addObject:registerUnregisterSpecifier]; } #endif if(lastGroupSpecifier) { [lastGroupSpecifier setProperty:credits forKey:@"footerText"]; } } [(UINavigationItem *)self.navigationItem setTitle:@"TrollStore Helper"]; return _specifiers; } - (NSString*)getTrollStoreInfoString { NSString* version = [self getTrollStoreVersion]; if(!version) { return @"Not Installed"; } else { return [NSString stringWithFormat:@"Installed, %@", version]; } } - (void)handleUninstallation { _newerVersion = nil; [super handleUninstallation]; } - (void)registerPersistenceHelperPressed { int ret = spawnRoot(rootHelperPath(), @[@"register-user-persistence-helper", NSBundle.mainBundle.bundleIdentifier], nil, nil); NSLog(@"registerPersistenceHelperPressed -> %d", ret); if(ret == 0) { [self reloadSpecifiers]; } } - (void)unregisterPersistenceHelperPressed { int ret = spawnRoot(rootHelperPath(), @[@"uninstall-persistence-helper"], nil, nil); if(ret == 0) { [self reloadSpecifiers]; } } @end ================================================ FILE: TrollHelper/TSHSceneDelegate.h ================================================ #import @interface TSHSceneDelegate : UIResponder @property (strong, nonatomic) UIWindow * window; @property (nonatomic, strong) UINavigationController *rootViewController; @end ================================================ FILE: TrollHelper/TSHSceneDelegate.m ================================================ #import "TSHSceneDelegate.h" #import "TSHRootViewController.h" @implementation TSHSceneDelegate - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). UIWindowScene* windowScene = (UIWindowScene*)scene; _window = [[UIWindow alloc] initWithWindowScene:windowScene]; _rootViewController = [[UINavigationController alloc] initWithRootViewController:[[TSHRootViewController alloc] init]]; _window.rootViewController = _rootViewController; [_window makeKeyAndVisible]; } - (void)sceneDidDisconnect:(UIScene *)scene { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). } - (void)sceneDidBecomeActive:(UIScene *)scene { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } - (void)sceneWillResignActive:(UIScene *)scene { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } - (void)sceneWillEnterForeground:(UIScene *)scene { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } - (void)sceneDidEnterBackground:(UIScene *)scene { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } - (void)scene:(UIScene *)scene openURLContexts:(NSSet *)URLContexts { } @end ================================================ FILE: TrollHelper/control ================================================ Package: com.opa334.trollstorehelper Name: TrollStore Helper Version: 2.1 Architecture: iphoneos-arm Description: Helper utility to install and manage TrollStore! Maintainer: opa334 Author: opa334 Section: Applications ================================================ FILE: TrollHelper/entitlements.plist ================================================ application-identifier com.opa334.trollstorepersistencehelper com.apple.CommCenter.fine-grained data-allowed-write com.apple.private.persona-mgmt platform-application com.apple.private.security.no-sandbox com.apple.private.security.container-manager com.apple.private.MobileContainerManager.allowed com.apple.private.coreservices.canmaplsdatabase com.apple.lsapplicationworkspace.rebuildappdatabases com.apple.private.security.storage.AppBundles com.apple.private.security.storage.MobileDocuments com.apple.private.security.storage-exempt.heritable com.apple.private.MobileInstallationHelperService.InstallDaemonOpsEnabled com.apple.private.MobileInstallationHelperService.allowed com.apple.private.uninstall.deletion com.apple.springboard.launchapplications com.apple.backboardd.launchapplications com.apple.frontboard.launchapplications com.apple.multitasking.termination com.apple.private.mobileinstall.allowedSPI InstallForLaunchServices Install UninstallForLaunchServices Uninstall UpdatePlaceholderMetadata ================================================ FILE: TrollHelper/main.m ================================================ #import #import "TSHAppDelegateNoScene.h" #import "TSHAppDelegateWithScene.h" #import "TSHSceneDelegate.h" #import #import BOOL sceneDelegateFix(void) { NSString* sceneDelegateClassName = nil; NSDictionary* UIApplicationSceneManifest = [NSBundle.mainBundle objectForInfoDictionaryKey:@"UIApplicationSceneManifest"]; if(UIApplicationSceneManifest && [UIApplicationSceneManifest isKindOfClass:NSDictionary.class]) { NSDictionary* UISceneConfiguration = UIApplicationSceneManifest[@"UISceneConfigurations"]; if(UISceneConfiguration && [UISceneConfiguration isKindOfClass:NSDictionary.class]) { NSArray* UIWindowSceneSessionRoleApplication = UISceneConfiguration[@"UIWindowSceneSessionRoleApplication"]; if(UIWindowSceneSessionRoleApplication && [UIWindowSceneSessionRoleApplication isKindOfClass:NSArray.class]) { NSDictionary* sceneToUse = nil; if(UIWindowSceneSessionRoleApplication.count > 1) { for(NSDictionary* scene in UIWindowSceneSessionRoleApplication) { if([scene isKindOfClass:NSDictionary.class]) { NSString* UISceneConfigurationName = scene[@"UISceneConfigurationName"]; if([UISceneConfigurationName isKindOfClass:NSString.class]) { if([UISceneConfigurationName isEqualToString:@"Default Configuration"]) { sceneToUse = scene; break; } } } } if(!sceneToUse) { sceneToUse = UIWindowSceneSessionRoleApplication.firstObject; } } else { sceneToUse = UIWindowSceneSessionRoleApplication.firstObject; } if(sceneToUse && [sceneToUse isKindOfClass:NSDictionary.class]) { sceneDelegateClassName = sceneToUse[@"UISceneDelegateClassName"]; } } } } if(sceneDelegateClassName && [sceneDelegateClassName isKindOfClass:NSString.class]) { Class newClass = objc_allocateClassPair([TSHSceneDelegate class], sceneDelegateClassName.UTF8String, 0); objc_registerClassPair(newClass); return YES; } return NO; } int main(int argc, char *argv[], char *envp[]) { @autoreleasepool { #ifdef EMBEDDED_ROOT_HELPER extern int rootHelperMain(int argc, char *argv[], char *envp[]); if(getuid() == 0) { // I got this idea while taking a dump // Don't judge return rootHelperMain(argc, argv, envp); } #endif chineseWifiFixup(); if(sceneDelegateFix()) { return UIApplicationMain(argc, argv, nil, NSStringFromClass(TSHAppDelegateWithScene.class)); } else { return UIApplicationMain(argc, argv, nil, NSStringFromClass(TSHAppDelegateNoScene.class)); } } } ================================================ FILE: TrollStore/Makefile ================================================ TARGET := iphone:clang:16.5:14.0 INSTALL_TARGET_PROCESSES = TrollStore ARCHS = arm64 TARGET_CODESIGN = ../Exploits/fastPathSign/fastPathSign include $(THEOS)/makefiles/common.mk APPLICATION_NAME = TrollStore TrollStore_FILES = $(wildcard *.m) $(wildcard ../Shared/*.m) TrollStore_FRAMEWORKS = UIKit CoreGraphics CoreServices CoreTelephony TrollStore_PRIVATE_FRAMEWORKS = Preferences MobileIcons MobileContainerManager TrollStore_LIBRARIES = archive TrollStore_CFLAGS = -fobjc-arc -I../Shared -I$(shell brew --prefix)/opt/libarchive/include TrollStore_CODESIGN_FLAGS = --entitlements entitlements.plist include $(THEOS_MAKE_PATH)/application.mk ================================================ FILE: TrollStore/Resources/Info.plist ================================================ CFBundleExecutable TrollStore CFBundleIcons CFBundlePrimaryIcon CFBundleIconFiles AppIcon29x29 AppIcon40x40 AppIcon57x57 AppIcon60x60 UIPrerenderedIcon CFBundleIcons~ipad CFBundlePrimaryIcon CFBundleIconFiles AppIcon29x29 AppIcon40x40 AppIcon57x57 AppIcon60x60 AppIcon50x50 AppIcon72x72 AppIcon76x76 UIPrerenderedIcon CFBundleIdentifier com.opa334.TrollStore CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType APPL CFBundleSignature ???? CFBundleSupportedPlatforms iPhoneOS CFBundleVersion 2.1 LSRequiresIPhoneOS UIDeviceFamily 1 2 UIRequiredDeviceCapabilities armv7 UILaunchStoryboardName LaunchScreen UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneConfigurationName Default Configuration UISceneDelegateClassName TSSceneDelegate UTImportedTypeDeclarations UTTypeConformsTo public.data UTTypeDescription iOS App UTTypeIconFiles UTTypeIdentifier com.apple.itunes.ipa UTTypeTagSpecification public.filename-extension ipa public.mime-type CFBundleDocumentTypes CFBundleTypeName iOS App LSHandlerRank Default LSItemContentTypes com.apple.itunes.ipa CFBundleTypeName TrollStore Update LSHandlerRank Default LSItemContentTypes public.tar-archive CFBundleTypeName AirDrop friendly iOS app CFBundleTypeRole Viewer LSHandlerRank Owner LSItemContentTypes com.opa334.trollstore.tipa UTExportedTypeDeclarations UTTypeIdentifier com.opa334.trollstore.tipa UTTypeDescription AirDrop friendly iOS app UTTypeConformsTo public.data UTTypeTagSpecification public.filename-extension tipa public.mime-type application/trollstore-ipa CFBundleURLTypes CFBundleURLName com.apple.Magnifier CFBundleURLSchemes apple-magnifier LSSupportsOpeningDocumentsInPlace TSRootBinaries trollstorehelper ldid ================================================ FILE: TrollStore/TSAppDelegate.h ================================================ #import @interface TSAppDelegate : UIResponder @end ================================================ FILE: TrollStore/TSAppDelegate.m ================================================ #import "TSAppDelegate.h" #import "TSRootViewController.h" @implementation TSAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return YES; } - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; } - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } @end ================================================ FILE: TrollStore/TSAppInfo.h ================================================ // // TSIPAInfo.h // IPAInfo // // Created by Lars Fröder on 22.10.22. // #import #import #import @import UIKit; @interface TSAppInfo : NSObject { NSString* _path; BOOL _isArchive; struct archive* _archive; NSString* _cachedAppBundleName; NSString* _cachedRegistrationState; NSDictionary* _cachedInfoDictionary; NSDictionary* _cachedInfoDictionariesByPluginSubpaths; NSDictionary* _cachedEntitlementsByBinarySubpaths; UIImage* _cachedPreviewIcon; int64_t _cachedSize; } - (instancetype)initWithIPAPath:(NSString*)ipaPath; - (instancetype)initWithAppBundlePath:(NSString*)bundlePath; - (NSError*)determineAppBundleName; - (NSError*)loadInfoDictionary; - (NSError*)loadEntitlements; - (NSError*)loadPreviewIcon; - (NSError*)sync_loadBasicInfo; - (NSError*)sync_loadInfo; - (void)loadBasicInfoWithCompletion:(void (^)(NSError*))completionHandler; - (void)loadInfoWithCompletion:(void (^)(NSError*))completionHandler; - (NSString*)displayName; - (NSString*)bundleIdentifier; - (NSString*)versionString; - (NSString*)sizeString; - (NSString*)bundlePath; - (NSString*)registrationState; - (UIImage*)iconForSize:(CGSize)size; - (NSAttributedString*)detailedInfoTitle; - (NSAttributedString*)detailedInfoDescription; //- (UIImage*)image; - (BOOL)isDebuggable; - (void)log; @end ================================================ FILE: TrollStore/TSAppInfo.m ================================================ #import "TSAppInfo.h" #import "TSCommonTCCServiceNames.h" #import extern CGImageRef LICreateIconForImage(CGImageRef image, int variant, int precomposed); extern UIImage* imageWithSize(UIImage* image, CGSize size); @implementation TSAppInfo - (instancetype)initWithIPAPath:(NSString*)ipaPath { self = [super init]; if(self) { _path = ipaPath; _isArchive = YES; _archive = nil; } return self; } - (instancetype)initWithAppBundlePath:(NSString*)bundlePath { self = [super init]; if(self) { _path = bundlePath; _isArchive = NO; _archive = nil; } return self; } - (void)dealloc { [self closeArchive]; } - (void)enumerateArchive:(void (^)(struct archive_entry* entry, BOOL* stop))enumerateBlock { [self openArchive]; struct archive_entry *entry; int r; for (;;) { r = archive_read_next_header(_archive, &entry); if (r == ARCHIVE_EOF) break; if (r < ARCHIVE_OK) fprintf(stderr, "%s\n", archive_error_string(_archive)); if (r < ARCHIVE_WARN) return; BOOL stop = NO; enumerateBlock(entry, &stop); if(stop) break; } } - (struct archive_entry*)archiveEntryForSubpath:(NSString*)subpath { __block struct archive_entry* outEntry = nil; [self enumerateArchive:^(struct archive_entry *entry, BOOL *stop) { NSString* currentSubpath = [NSString stringWithUTF8String:archive_entry_pathname(entry)]; if([currentSubpath isEqualToString:subpath]) { outEntry = entry; *stop = YES; } }]; return outEntry; } - (NSError*)determineAppBundleName { NSError* outError; if(!_cachedAppBundleName) { if(_isArchive) { [self enumerateArchive:^(struct archive_entry *entry, BOOL *stop) { NSString* currentSubpath = [NSString stringWithUTF8String:archive_entry_pathname(entry)]; if(currentSubpath.pathComponents.count == 3) { if([currentSubpath.pathComponents[0] isEqualToString:@"Payload"] && [currentSubpath.pathComponents[1].pathExtension isEqualToString:@"app"]) { self->_cachedAppBundleName = currentSubpath.pathComponents[1]; *stop = YES; } } }]; if(!_cachedAppBundleName) { NSString* errorDescription = @"Unable to locate app bundle inside the .IPA archive."; outError = [NSError errorWithDomain:TrollStoreErrorDomain code:301 userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; } } } return outError; } - (NSError*)loadInfoDictionary { if(_isArchive && _cachedAppBundleName) { NSString* mainInfoPlistPath = [NSString stringWithFormat:@"Payload/%@/Info.plist", _cachedAppBundleName]; struct archive_entry* infoDictEntry = [self archiveEntryForSubpath:mainInfoPlistPath]; if(infoDictEntry) { size_t size = archive_entry_size(infoDictEntry); void* buf = malloc(size); size_t read = archive_read_data(_archive, buf, size); if(read == size) { NSData* infoPlistData = [NSData dataWithBytes:buf length:size]; _cachedInfoDictionary = [NSPropertyListSerialization propertyListWithData:infoPlistData options:NSPropertyListImmutable format:nil error:nil]; } free(buf); } __block NSMutableDictionary* pluginInfoDictionaries = [NSMutableDictionary new]; [self enumerateArchive:^(struct archive_entry *entry, BOOL *stop) { NSString* currentSubpath = [NSString stringWithUTF8String:archive_entry_pathname(entry)]; if([currentSubpath isEqualToString:mainInfoPlistPath]) return; if([currentSubpath.lastPathComponent isEqualToString:@"Info.plist"] && currentSubpath.pathComponents.count == 5) { if([currentSubpath.pathComponents[2] isEqualToString:@"PlugIns"]) { size_t size = archive_entry_size(entry); void* buf = malloc(size); size_t read = archive_read_data(self->_archive, buf, size); if(read == size) { NSData* infoPlistData = [NSData dataWithBytes:buf length:size]; NSDictionary* pluginPlist = [NSPropertyListSerialization propertyListWithData:infoPlistData options:NSPropertyListImmutable format:nil error:nil]; pluginInfoDictionaries[currentSubpath.stringByDeletingLastPathComponent] = pluginPlist; } free(buf); } } }]; _cachedInfoDictionariesByPluginSubpaths = pluginInfoDictionaries.copy; } else { NSString* mainInfoPlistPath = [_path stringByAppendingPathComponent:@"Info.plist"]; if([[NSFileManager defaultManager] fileExistsAtPath:mainInfoPlistPath]) { _cachedInfoDictionary = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:mainInfoPlistPath] error:nil]; } __block NSMutableDictionary* pluginInfoDictionaries = [NSMutableDictionary new]; NSArray* plugIns = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[_path stringByAppendingPathComponent:@"PlugIns"] error:nil]; for(NSString* plugIn in plugIns) { NSString* pluginSubpath = [NSString stringWithFormat:@"PlugIns/%@", plugIn]; NSString* pluginInfoDictionaryPath = [[_path stringByAppendingPathComponent:pluginSubpath] stringByAppendingPathComponent:@"Info.plist"]; NSDictionary* pluginInfoDictionary = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:pluginInfoDictionaryPath] error:nil]; if(pluginInfoDictionary) { pluginInfoDictionaries[pluginSubpath] = pluginInfoDictionary; } } _cachedInfoDictionariesByPluginSubpaths = pluginInfoDictionaries.copy; } if(!_cachedInfoDictionary) { NSString* errorDescription = @"Unable to locate Info.plist inside app bundle."; return [NSError errorWithDomain:TrollStoreErrorDomain code:302 userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; } return nil; } - (NSError*)loadInstalledState { if(!_isArchive) { NSURL* bundleURL = [NSURL fileURLWithPath:_path]; LSApplicationProxy* appProxy = [LSApplicationProxy applicationProxyForBundleURL:bundleURL]; if(appProxy) { if(appProxy && appProxy.isInstalled) { _cachedRegistrationState = appProxy.applicationType; } } } return nil; } - (NSError*)loadEntitlements { if(!_cachedEntitlementsByBinarySubpaths) { NSMutableDictionary* entitlementsByBinarySubpaths = [NSMutableDictionary new]; if(_isArchive) { if(_cachedInfoDictionary) { NSString* bundleExecutable = _cachedInfoDictionary[@"CFBundleExecutable"]; NSString* bundleExecutableSubpath = [NSString stringWithFormat:@"Payload/%@/%@", _cachedAppBundleName, bundleExecutable]; struct archive_entry* mainBinaryEntry = [self archiveEntryForSubpath:bundleExecutableSubpath]; if(!mainBinaryEntry) { NSString* errorDescription = @"Unable to locate main binary inside app bundle."; return [NSError errorWithDomain:TrollStoreErrorDomain code:303 userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; } size_t size = archive_entry_size(mainBinaryEntry); void* buf = malloc(size); size_t read = archive_read_data(_archive, buf, size); if(read == size) { NSData* binaryData = [NSData dataWithBytes:buf length:size]; entitlementsByBinarySubpaths[bundleExecutableSubpath] = dumpEntitlementsFromBinaryData(binaryData); } free(buf); } [_cachedInfoDictionariesByPluginSubpaths enumerateKeysAndObjectsUsingBlock:^(NSString* pluginSubpath, NSDictionary* infoDictionary, BOOL * _Nonnull stop) { NSString* pluginExecutable = infoDictionary[@"CFBundleExecutable"]; NSString* pluginExecutableSubpath = [NSString stringWithFormat:@"%@/%@", pluginSubpath, pluginExecutable]; struct archive_entry* pluginBinaryEntry = [self archiveEntryForSubpath:pluginExecutableSubpath]; if(!pluginBinaryEntry) return; size_t size = archive_entry_size(pluginBinaryEntry); void* buf = malloc(size); size_t read = archive_read_data(_archive, buf, size); if(read == size) { NSData* binaryData = [NSData dataWithBytes:buf length:size]; entitlementsByBinarySubpaths[pluginExecutableSubpath] = dumpEntitlementsFromBinaryData(binaryData); } free(buf); }]; } else { if(_cachedInfoDictionary) { NSString* bundleExecutable = _cachedInfoDictionary[@"CFBundleExecutable"]; NSString* bundleExecutablePath = [_path stringByAppendingPathComponent:bundleExecutable]; if(![[NSFileManager defaultManager] fileExistsAtPath:bundleExecutablePath]) { NSString* errorDescription = @"Unable to locate main binary inside app bundle."; return [NSError errorWithDomain:TrollStoreErrorDomain code:303 userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; } entitlementsByBinarySubpaths[bundleExecutable] = dumpEntitlementsFromBinaryAtPath(bundleExecutablePath); } [_cachedInfoDictionariesByPluginSubpaths enumerateKeysAndObjectsUsingBlock:^(NSString* pluginSubpath, NSDictionary* infoDictionary, BOOL * _Nonnull stop) { NSString* pluginExecutable = infoDictionary[@"CFBundleExecutable"]; NSString* pluginExecutableSubpath = [NSString stringWithFormat:@"%@/%@", pluginSubpath, pluginExecutable]; NSString* pluginExecutablePath = [_path stringByAppendingPathComponent:pluginExecutableSubpath]; entitlementsByBinarySubpaths[pluginExecutableSubpath] = dumpEntitlementsFromBinaryAtPath(pluginExecutablePath); }]; } _cachedEntitlementsByBinarySubpaths = entitlementsByBinarySubpaths.copy; } return 0; } - (NSError*)loadSize { _cachedSize = 0; if(_isArchive) { [self enumerateArchive:^(struct archive_entry* entry, BOOL* stop) { int64_t size = archive_entry_size(entry); _cachedSize += size; }]; } else { NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:_path] includingPropertiesForKeys:@[NSURLIsRegularFileKey,NSURLFileAllocatedSizeKey,NSURLTotalFileAllocatedSizeKey] options:0 errorHandler:nil]; for(NSURL* itemURL in enumerator) { NSNumber* isRegularFile; NSError* error; [itemURL getResourceValue:&isRegularFile forKey:NSURLIsRegularFileKey error:&error]; if(isRegularFile.boolValue) { NSNumber* totalFileAllocatedSize; [itemURL getResourceValue:&totalFileAllocatedSize forKey:NSURLTotalFileAllocatedSizeKey error:nil]; if(totalFileAllocatedSize) { _cachedSize += totalFileAllocatedSize.integerValue; } else { NSNumber* fileAllocatedSize; [itemURL getResourceValue:&fileAllocatedSize forKey:NSURLFileAllocatedSizeKey error:nil]; if(fileAllocatedSize) { _cachedSize += fileAllocatedSize.integerValue; } } } } } return nil; } - (NSError*)loadPreviewIcon { int imageVariant; CGFloat screenScale = UIScreen.mainScreen.scale; if(screenScale >= 3.0) { imageVariant = 34; } else if(screenScale >= 2.0) { imageVariant = 17; } else { imageVariant = 4; } CGImageRef liIcon = LICreateIconForImage([[self iconForSize:CGSizeMake(29,29)] CGImage], imageVariant, 0); _cachedPreviewIcon = [[UIImage alloc] initWithCGImage:liIcon scale:screenScale orientation:0];; return nil; } - (int)openArchive { if(_archive) { [self closeArchive]; } _archive = archive_read_new(); archive_read_support_format_all(_archive); archive_read_support_filter_all(_archive); int r = archive_read_open_filename(_archive, _path.fileSystemRepresentation, 10240); return r ? r : 0; } - (void)closeArchive { if(_archive) { archive_read_close(_archive); archive_read_free(_archive); _archive = nil; } } - (NSError*)sync_loadBasicInfo { NSError* e; e = [self determineAppBundleName]; if(e) return e; e = [self loadInfoDictionary]; if(e) return e; e = [self loadInstalledState]; if(e) return e; return nil; } - (NSError*)sync_loadInfo { NSError* e; e = [self sync_loadBasicInfo]; if(e) return e; e = [self loadEntitlements]; if(e) return e; e = [self loadSize]; if(e) return e; e = [self loadPreviewIcon]; if(e) return e; return nil; } - (void)loadBasicInfoWithCompletion:(void (^)(NSError*))completionBlock { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if(completionBlock) completionBlock([self sync_loadBasicInfo]); }); } - (void)loadInfoWithCompletion:(void (^)(NSError*))completionBlock { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if(completionBlock) completionBlock([self sync_loadInfo]); }); } - (void)enumerateAllInfoDictionaries:(void (^)(NSString* key, NSObject* value, BOOL* stop))enumerateBlock { if(!enumerateBlock) return; __block BOOL b_stop = NO; [_cachedInfoDictionary enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSObject* value, BOOL* stop) { enumerateBlock(key, value, &b_stop); if(b_stop) *stop = YES; }]; if(b_stop) return; [_cachedInfoDictionariesByPluginSubpaths enumerateKeysAndObjectsUsingBlock:^(NSString* pluginSubpath, NSDictionary* pluginInfoDictionary, BOOL* stop_1) { if([pluginInfoDictionary isKindOfClass:NSDictionary.class]) { [pluginInfoDictionary enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSObject* value, BOOL * _Nonnull stop_2) { enumerateBlock(key, value, &b_stop); if(b_stop) { *stop_1 = YES; *stop_2 = YES; } }]; } }]; } - (void)enumerateAllEntitlements:(void (^)(NSString* key, NSObject* value, BOOL* stop))enumerateBlock { if(!enumerateBlock) return; __block BOOL b_stop = NO; [_cachedEntitlementsByBinarySubpaths enumerateKeysAndObjectsUsingBlock:^(NSString* binarySubpath, NSDictionary* binaryInfoDictionary, BOOL* stop_1) { if([binaryInfoDictionary isKindOfClass:NSDictionary.class]) { [binaryInfoDictionary enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSObject* value, BOOL * _Nonnull stop_2) { enumerateBlock(key, value, &b_stop); if(b_stop) { *stop_1 = YES; *stop_2 = YES; } }]; } }]; } - (void)enumerateAvailableIcons:(void (^)(CGSize iconSize, NSUInteger iconScale, NSString* iconPath, BOOL* stop))enumerateBlock { if(!enumerateBlock) return; if(_cachedInfoDictionary) { NSString* iconName = nil; NSDictionary* cfBundleIcons = _cachedInfoDictionary[@"CFBundleIcons"]; if(!cfBundleIcons) { cfBundleIcons = _cachedInfoDictionary[@"CFBundleIcons~ipad"]; } if(cfBundleIcons && [cfBundleIcons isKindOfClass:NSDictionary.class]) { NSDictionary* cfBundlePrimaryIcon = cfBundleIcons[@"CFBundlePrimaryIcon"]; if(cfBundlePrimaryIcon && [cfBundlePrimaryIcon isKindOfClass:NSDictionary.class]) { NSString* potentialIconName = cfBundlePrimaryIcon[@"CFBundleIconName"]; if(potentialIconName && [potentialIconName isKindOfClass:NSString.class]) { iconName = potentialIconName; } else { NSArray* cfBundleIconFiles = cfBundlePrimaryIcon[@"CFBundleIconFiles"]; if(cfBundleIconFiles && [cfBundleIconFiles isKindOfClass:NSArray.class]) { NSString* oneIconFile = cfBundleIconFiles.firstObject; NSString* otherIconFile = cfBundleIconFiles.lastObject; iconName = [oneIconFile commonPrefixWithString:otherIconFile options:NSLiteralSearch]; } } } } if(!iconName) return; void (^wrapperBlock)(NSString* iconPath, BOOL* stop) = ^(NSString* iconPath, BOOL* stop) { NSString* currentIconName = iconPath.lastPathComponent; NSString* iconSuffix = [currentIconName substringFromIndex:[iconName length]]; NSArray* seperatedIconSuffix = [iconSuffix componentsSeparatedByString:@"@"]; NSString* currentIconResolution = seperatedIconSuffix.firstObject; NSString* currentIconScale; if(seperatedIconSuffix.count > 1) { currentIconScale = seperatedIconSuffix.lastObject; } NSNumberFormatter* f = [[NSNumberFormatter alloc] init]; f.numberStyle = NSNumberFormatterDecimalStyle; NSArray* separatedIconSize = [currentIconResolution componentsSeparatedByString:@"x"]; NSNumber* widthNum = [f numberFromString:separatedIconSize.firstObject]; NSNumber* heightNum = [f numberFromString:separatedIconSize.lastObject]; CGSize iconSize = CGSizeMake(widthNum.unsignedIntegerValue, heightNum.unsignedIntegerValue); NSUInteger scale = 1; if(currentIconScale) { NSNumber* scaleNum = [f numberFromString:currentIconScale]; scale = scaleNum.unsignedIntegerValue; } enumerateBlock(iconSize, scale, iconPath, stop); }; if(_isArchive) { NSString* iconPrefix = [NSString stringWithFormat:@"Payload/%@/%@", _cachedAppBundleName, iconName]; [self enumerateArchive:^(struct archive_entry* entry, BOOL* stop) { NSString* currentSubpath = [NSString stringWithUTF8String:archive_entry_pathname(entry)]; if([currentSubpath hasPrefix:iconPrefix]) { wrapperBlock(currentSubpath, stop); } }]; } else { NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:_path error:nil]; for(NSString* fileName in files) { if([fileName hasPrefix:iconName]) { NSString* iconPath = [_path stringByAppendingPathComponent:fileName]; BOOL stop = NO; wrapperBlock(iconPath, &stop); if(stop) return; } } } } } - (UIImage*)iconForSize:(CGSize)size { if(size.width != size.height) { //not supported return nil; } // Flow: Check if icon with the exact size exists // If not, take the next best one and scale it down //UIImage* imageToReturn; __block NSString* foundIconPath; // Attempt 1: Check for icon with exact size [self enumerateAvailableIcons:^(CGSize iconSize, NSUInteger iconScale, NSString* iconPath, BOOL* stop) { if(CGSizeEqualToSize(iconSize, size) && UIScreen.mainScreen.scale == iconScale) { foundIconPath = iconPath; //imageToReturn = imageWithSize([UIImage imageWithContentsOfFile:iconPath], size); *stop = YES; } }]; if(!foundIconPath) { // Attempt 2: Check for icon with bigger size __block CGSize closestIconSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); [self enumerateAvailableIcons:^(CGSize iconSize, NSUInteger iconScale, NSString* iconPath, BOOL* stop) { if(iconSize.width > size.width && iconSize.width < closestIconSize.width) { closestIconSize = iconSize; } }]; if(closestIconSize.width == CGFLOAT_MAX) { // Attempt 3: Take biggest icon and scale it up closestIconSize = CGSizeMake(0,0); [self enumerateAvailableIcons:^(CGSize iconSize, NSUInteger iconScale, NSString* iconPath, BOOL* stop) { if(iconSize.width > closestIconSize.width) { closestIconSize = iconSize; } }]; } if(closestIconSize.width == 0) return nil; [self enumerateAvailableIcons:^(CGSize iconSize, NSUInteger iconScale, NSString* iconPath, BOOL* stop) { if(CGSizeEqualToSize(iconSize, closestIconSize)) { closestIconSize = iconSize; foundIconPath = iconPath; *stop = YES; } }]; } if(!foundIconPath) return nil; if(_isArchive) { __block NSData* iconData; struct archive_entry* iconEntry = [self archiveEntryForSubpath:foundIconPath]; if(iconEntry) { size_t size = archive_entry_size(iconEntry); void* buf = malloc(size); size_t read = archive_read_data(_archive, buf, size); if(read == size) { iconData = [NSData dataWithBytes:buf length:size]; } free(buf); } if(iconData) { return imageWithSize([UIImage imageWithData:iconData], size); } } else { return imageWithSize([UIImage imageWithContentsOfFile:foundIconPath], size); } return nil; } - (NSString*)displayName { NSString* displayName = _cachedInfoDictionary[@"CFBundleDisplayName"]; if(!displayName || ![displayName isKindOfClass:NSString.class]) { displayName = _cachedInfoDictionary[@"CFBundleName"]; if(!displayName || ![displayName isKindOfClass:NSString.class]) { displayName = _cachedInfoDictionary[@"CFBundleExecutable"]; if(!displayName || ![displayName isKindOfClass:NSString.class]) { if(_isArchive) { displayName = [_cachedAppBundleName stringByDeletingPathExtension]; } else { displayName = [[_path lastPathComponent] stringByDeletingPathExtension]; } } } } return displayName; } - (NSString*)bundleIdentifier { return _cachedInfoDictionary[@"CFBundleIdentifier"]; } - (NSString*)versionString { NSString* version = _cachedInfoDictionary[@"CFBundleShortVersionString"]; if(!version) { version = _cachedInfoDictionary[@"CFBundleVersion"]; } return version; } - (NSString*)sizeString { return [NSByteCountFormatter stringFromByteCount:_cachedSize countStyle:NSByteCountFormatterCountStyleFile]; } - (NSString*)bundlePath { if(!_isArchive) { return _path; } return nil; } - (NSString*)registrationState { return _cachedRegistrationState; } - (NSAttributedString*)detailedInfoTitle { NSString* displayName = [self displayName]; NSMutableDictionary* titleAttributes = @{ NSFontAttributeName : [UIFont boldSystemFontOfSize:16] }.mutableCopy; NSMutableAttributedString* description = [NSMutableAttributedString new]; if(_cachedPreviewIcon) { titleAttributes[NSBaselineOffsetAttributeName] = @9.0; NSTextAttachment* previewAttachment = [[NSTextAttachment alloc] init]; previewAttachment.image = _cachedPreviewIcon; [description appendAttributedString:[NSAttributedString attributedStringWithAttachment:previewAttachment]]; [description appendAttributedString:[[NSAttributedString alloc] initWithString:@" " attributes:titleAttributes]]; } [description appendAttributedString:[[NSAttributedString alloc] initWithString:displayName attributes:titleAttributes]]; return description.copy; } - (NSAttributedString*)detailedInfoDescription { NSString* bundleId = [self bundleIdentifier]; NSString* version = [self versionString]; NSString* sizeString = [self sizeString]; // Check if any bundles main binary runs unsandboxed __block BOOL isUnsandboxed = NO; [self enumerateAllEntitlements:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"com.apple.private.security.container-required"]) { NSNumber* valueNum = (NSNumber*)value; if(valueNum && [valueNum isKindOfClass:NSNumber.class]) { isUnsandboxed = !valueNum.boolValue; if(isUnsandboxed) *stop = YES; } } else if([key isEqualToString:@"com.apple.private.security.no-container"]) { NSNumber* valueNum = (NSNumber*)value; if(valueNum && [valueNum isKindOfClass:NSNumber.class]) { isUnsandboxed = valueNum.boolValue; if(isUnsandboxed) *stop = YES; } } else if([key isEqualToString:@"com.apple.private.security.no-sandbox"]) { NSNumber* valueNum = (NSNumber*)value; if(valueNum && [valueNum isKindOfClass:NSNumber.class]) { isUnsandboxed = valueNum.boolValue; if(isUnsandboxed) *stop = YES; } } }]; // Check if any bundles main binary can spawn an external binary __block BOOL isPlatformApplication = NO; [self enumerateAllEntitlements:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"platform-application"]) { NSNumber* valueNum = (NSNumber*)value; if(valueNum && [valueNum isKindOfClass:NSNumber.class]) { isPlatformApplication = valueNum.boolValue; if(isPlatformApplication) *stop = YES; } } }]; // Check if any bundles main binary can spawn an external binary as root __block BOOL hasPersonaMngmt = NO; [self enumerateAllEntitlements:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"com.apple.private.persona-mgmt"]) { NSNumber* valueNum = (NSNumber*)value; if(valueNum && [valueNum isKindOfClass:NSNumber.class]) { hasPersonaMngmt = valueNum.boolValue; if(hasPersonaMngmt) *stop = YES; } } }]; // Accessible containers // com.apple.developer.icloud-container-identifiers // com.apple.security.application-groups // Unrestricted if special entitlement __block BOOL unrestrictedContainerAccess = NO; [self enumerateAllEntitlements:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"com.apple.private.security.storage.AppDataContainers"]) { NSNumber* valueNum = (NSNumber*)value; if(valueNum && [valueNum isKindOfClass:NSNumber.class]) { unrestrictedContainerAccess = valueNum.boolValue; if(hasPersonaMngmt) *stop = YES; } } }]; __block NSMutableArray* accessibleContainers = [NSMutableArray new]; //array by design, should be ordered if(!unrestrictedContainerAccess) { __block NSString *dataContainer = nil; // If com.apple.private.security.container-required Entitlement is a string, prefer it to CFBundleIdentifier [self enumerateAllEntitlements:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"com.apple.private.security.container-required"]) { NSString* valueString = (NSString*)value; if(valueString && [valueString isKindOfClass:NSString.class]) { dataContainer = valueString; } } }]; // Else take CFBundleIdentifier if (!dataContainer) { [self enumerateAllInfoDictionaries:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"CFBundleIdentifier"]) { NSString* valueStr = (NSString*)value; if([valueStr isKindOfClass:NSString.class]) { dataContainer = valueStr; } } }]; } if (dataContainer) { [accessibleContainers addObject:dataContainer]; } [self enumerateAllEntitlements:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"com.apple.developer.icloud-container-identifiers"] || [key isEqualToString:@"com.apple.security.application-groups"] || [key isEqualToString:@"com.apple.security.system-groups"]) { NSArray* valueArr = (NSArray*)value; if([valueArr isKindOfClass:NSArray.class]) { for(NSString* containerID in valueArr) { if([containerID isKindOfClass:NSString.class]) { if(![accessibleContainers containsObject:containerID]) { [accessibleContainers addObject:containerID]; } } } } } }]; } // Accessible Keychain Groups // keychain-access-groups // Unrestricted if single * (maybe?) __block BOOL unrestrictedKeychainAccess = NO; __block NSMutableSet* accessibleKeychainGroups = [NSMutableSet new]; [self enumerateAllEntitlements:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"keychain-access-groups"]) { NSArray* valueArr = (NSArray*)value; if([valueArr isKindOfClass:NSArray.class]) { for(NSString* keychainID in valueArr) { if([keychainID isKindOfClass:NSString.class]) { if([keychainID isEqualToString:@"*"]) { unrestrictedKeychainAccess = YES; } else { [accessibleKeychainGroups addObject:keychainID]; } } } } } }]; __block NSMutableSet* URLSchemes = [NSMutableSet new]; [self enumerateAllInfoDictionaries:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"CFBundleURLTypes"]) { NSArray* valueArr = (NSArray*)value; if([valueArr isKindOfClass:NSArray.class]) { for(NSDictionary* URLTypeDict in valueArr) { if([URLTypeDict isKindOfClass:NSDictionary.class]) { NSArray* cURLSchemes = URLTypeDict[@"CFBundleURLSchemes"]; if(cURLSchemes && [cURLSchemes isKindOfClass:NSArray.class]) { for(NSString* URLScheme in cURLSchemes) { [URLSchemes addObject:URLScheme]; } } } } } } }]; __block NSMutableSet* allowedTccServices = [NSMutableSet new]; [self enumerateAllEntitlements:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"com.apple.private.tcc.allow"]) { NSArray* valueArr = (NSArray*)value; if([valueArr isKindOfClass:NSArray.class]) { for(NSString* serviceID in valueArr) { if([serviceID isKindOfClass:NSString.class]) { NSString* displayName = commonTCCServices[serviceID]; if(displayName == nil) { [allowedTccServices addObject:[serviceID stringByReplacingOccurrencesOfString:@"kTCCService" withString:@""]]; } else { [allowedTccServices addObject:displayName]; } } } } } else if ([key isEqualToString:@"com.apple.locationd.preauthorized"]) { NSNumber* valueNum = (NSNumber*)value; if([valueNum isKindOfClass:NSNumber.class]) { if([valueNum boolValue]) { [allowedTccServices addObject:@"Location"]; } } } }]; __block NSMutableSet* allowedMGKeys = [NSMutableSet new]; [self enumerateAllEntitlements:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"com.apple.private.MobileGestalt.AllowedProtectedKeys"]) { NSArray* valueArr = (NSArray*)value; if([valueArr isKindOfClass:NSArray.class]) { for(NSString* protectedKey in valueArr) { if([protectedKey isKindOfClass:NSString.class]) { [allowedMGKeys addObject:protectedKey]; } } } } }]; NSMutableParagraphStyle* leftAlignment = [[NSMutableParagraphStyle alloc] init]; leftAlignment.alignment = NSTextAlignmentLeft; UIColor* dangerColor = [UIColor colorWithDynamicProvider:^UIColor*(UITraitCollection *traitCollection) { if(traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { return [UIColor orangeColor]; } else { return [UIColor redColor]; } }]; UIColor* warningColor = [UIColor colorWithDynamicProvider:^UIColor*(UITraitCollection *traitCollection) { if(traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { return [UIColor yellowColor]; } else { return [UIColor orangeColor]; } }]; NSMutableAttributedString* description = [NSMutableAttributedString new]; NSDictionary* headerAttributes = @{ NSFontAttributeName : [UIFont boldSystemFontOfSize:14], NSParagraphStyleAttributeName : leftAlignment }; NSDictionary* bodyAttributes = @{ NSFontAttributeName : [UIFont systemFontOfSize:11], NSParagraphStyleAttributeName : leftAlignment }; NSDictionary* bodyWarningAttributes = @{ NSFontAttributeName : [UIFont systemFontOfSize:11], NSParagraphStyleAttributeName : leftAlignment, NSForegroundColorAttributeName : warningColor }; NSDictionary* bodyDangerAttributes = @{ NSFontAttributeName : [UIFont systemFontOfSize:11], NSParagraphStyleAttributeName : leftAlignment, NSForegroundColorAttributeName : dangerColor }; [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"Metadata" attributes:headerAttributes]]; [description appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\nBundle Identifier: %@", bundleId] attributes:bodyAttributes]]; [description appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\nVersion: %@", version] attributes:bodyAttributes]]; [description appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\nSize: %@", sizeString] attributes:bodyAttributes]]; [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\nSandboxing" attributes:headerAttributes]]; if(isUnsandboxed) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\nThe app runs unsandboxed and can access most of the file system." attributes:bodyWarningAttributes]]; } else { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\nThe app runs sandboxed and can only access the containers listed below." attributes:bodyAttributes]]; } [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\nCapabilities" attributes:headerAttributes]]; if(isPlatformApplication && isUnsandboxed && hasPersonaMngmt) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\nThe app can spawn its own embedded binaries with root privileges." attributes:bodyDangerAttributes]]; } else if(isPlatformApplication && isUnsandboxed) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\nThe app can spawn arbitrary binaries as the mobile user." attributes:bodyWarningAttributes]]; } else { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\nThe app can not spawn other binaries." attributes:bodyAttributes]]; } if(allowedTccServices.count) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\nPrivacy" attributes:headerAttributes]]; [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\nThe app can access the following services without asking for permission:\n" attributes:bodyWarningAttributes]]; [description appendAttributedString:[[NSAttributedString alloc] initWithString:[NSListFormatter localizedStringByJoiningStrings:[allowedTccServices allObjects]] attributes:bodyAttributes]]; } if (allowedMGKeys.count) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\nDevice Info" attributes:headerAttributes]]; [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\nThe app can access protected information about this device:\n" attributes:bodyWarningAttributes]]; [description appendAttributedString:[[NSAttributedString alloc] initWithString:[NSListFormatter localizedStringByJoiningStrings:[allowedMGKeys allObjects]] attributes:bodyAttributes]]; } if(unrestrictedContainerAccess || accessibleContainers.count) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\nAccessible Containers" attributes:headerAttributes]]; if(unrestrictedContainerAccess) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\nUnrestricted, the app can access all data containers on the system." attributes:bodyDangerAttributes]]; } else { for(NSString* containerID in accessibleContainers) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\n%@", containerID] attributes:bodyAttributes]]; } } } if(unrestrictedKeychainAccess || accessibleKeychainGroups.count) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\nAccessible Keychain Groups" attributes:headerAttributes]]; if(unrestrictedKeychainAccess) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\nUnrestricted, the app can access the entire keychain." attributes:bodyDangerAttributes]]; } else { for(NSString* keychainID in accessibleKeychainGroups) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\n%@", keychainID] attributes:bodyAttributes]]; } } } if(URLSchemes.count) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\nURL Schemes" attributes:headerAttributes]]; for(NSString* URLScheme in URLSchemes) { [description appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\n%@", URLScheme] attributes:bodyAttributes]]; } } return description; } - (void)log { NSLog(@"entitlements:"); [self enumerateAllEntitlements:^(NSString *key, NSObject *value, BOOL *stop) { NSLog(@"%@ -> %@", key, value); }]; NSLog(@"info dictionaries:"); [self enumerateAllInfoDictionaries:^(NSString *key, NSObject *value, BOOL *stop) { NSLog(@"%@ -> %@", key, value); }]; } - (BOOL)isDebuggable { [self loadEntitlements]; __block BOOL debuggable = NO; [self enumerateAllEntitlements:^(NSString *key, NSObject *value, BOOL *stop) { if([key isEqualToString:@"get-task-allow"]) { NSNumber* valueNum = (NSNumber*)value; if(valueNum && [valueNum isKindOfClass:NSNumber.class]) { debuggable = valueNum.boolValue; *stop = YES; } } }]; return debuggable; } @end ================================================ FILE: TrollStore/TSAppTableViewController.h ================================================ #import #import "TSAppInfo.h" #import @interface TSAppTableViewController : UITableViewController { UIImage* _placeholderIcon; NSArray* _cachedAppInfos; NSMutableDictionary* _cachedIcons; UISearchController* _searchController; NSString* _searchKey; } @end ================================================ FILE: TrollStore/TSAppTableViewController.m ================================================ #import "TSAppTableViewController.h" #import "TSApplicationsManager.h" #import #import "TSInstallationController.h" #import "TSUtil.h" @import UniformTypeIdentifiers; #define ICON_FORMAT_IPAD 8 #define ICON_FORMAT_IPHONE 10 NSInteger iconFormatToUse(void) { if(UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) { return ICON_FORMAT_IPAD; } else { return ICON_FORMAT_IPHONE; } } UIImage* imageWithSize(UIImage* image, CGSize size) { if(CGSizeEqualToSize(image.size, size)) return image; UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale); CGRect imageRect = CGRectMake(0.0, 0.0, size.width, size.height); [image drawInRect:imageRect]; UIImage* outImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return outImage; } @interface UIImage () + (UIImage *)_applicationIconImageForBundleIdentifier:(NSString *)id format:(NSInteger)format scale:(double)scale; @end @implementation TSAppTableViewController - (void)loadAppInfos { NSArray* appPaths = [[TSApplicationsManager sharedInstance] installedAppPaths]; NSMutableArray* appInfos = [NSMutableArray new]; for(NSString* appPath in appPaths) { TSAppInfo* appInfo = [[TSAppInfo alloc] initWithAppBundlePath:appPath]; [appInfo sync_loadBasicInfo]; [appInfos addObject:appInfo]; } if(_searchKey && ![_searchKey isEqualToString:@""]) { [appInfos enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(TSAppInfo* appInfo, NSUInteger idx, BOOL* stop) { NSString* appName = [appInfo displayName]; BOOL nameMatch = [appName rangeOfString:_searchKey options:NSCaseInsensitiveSearch range:NSMakeRange(0, [appName length]) locale:[NSLocale currentLocale]].location != NSNotFound; if(!nameMatch) { [appInfos removeObjectAtIndex:idx]; } }]; } [appInfos sortUsingComparator:^(TSAppInfo* appInfoA, TSAppInfo* appInfoB) { return [[appInfoA displayName] localizedStandardCompare:[appInfoB displayName]]; }]; _cachedAppInfos = appInfos.copy; } - (instancetype)init { self = [super init]; if(self) { [self loadAppInfos]; _placeholderIcon = [UIImage _applicationIconImageForBundleIdentifier:@"com.apple.WebSheet" format:iconFormatToUse() scale:[UIScreen mainScreen].scale]; _cachedIcons = [NSMutableDictionary new]; [[LSApplicationWorkspace defaultWorkspace] addObserver:self]; } return self; } - (void)dealloc { [[LSApplicationWorkspace defaultWorkspace] removeObserver:self]; } - (void)reloadTable { [self loadAppInfos]; dispatch_async(dispatch_get_main_queue(), ^ { [self.tableView reloadData]; }); } - (void)loadView { [super loadView]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadTable) name:@"ApplicationsChanged" object:nil]; } - (void)viewDidLoad { [super viewDidLoad]; self.tableView.allowsMultipleSelectionDuringEditing = NO; self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; [self _setUpNavigationBar]; [self _setUpSearchBar]; } - (void)_setUpNavigationBar { UIAction* installFromFileAction = [UIAction actionWithTitle:@"Install IPA File" image:[UIImage systemImageNamed:@"doc.badge.plus"] identifier:@"InstallIPAFile" handler:^(__kindof UIAction *action) { dispatch_async(dispatch_get_main_queue(), ^ { UTType* ipaType = [UTType typeWithFilenameExtension:@"ipa" conformingToType:UTTypeData]; UTType* tipaType = [UTType typeWithFilenameExtension:@"tipa" conformingToType:UTTypeData]; UIDocumentPickerViewController* documentPickerVC = [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:@[ipaType, tipaType]]; documentPickerVC.allowsMultipleSelection = NO; documentPickerVC.delegate = self; [TSPresentationDelegate presentViewController:documentPickerVC animated:YES completion:nil]; }); }]; UIAction* installFromURLAction = [UIAction actionWithTitle:@"Install from URL" image:[UIImage systemImageNamed:@"link.badge.plus"] identifier:@"InstallFromURL" handler:^(__kindof UIAction *action) { dispatch_async(dispatch_get_main_queue(), ^ { UIAlertController* installURLController = [UIAlertController alertControllerWithTitle:@"Install from URL" message:@"" preferredStyle:UIAlertControllerStyleAlert]; [installURLController addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.placeholder = @"URL"; }]; UIAlertAction* installAction = [UIAlertAction actionWithTitle:@"Install" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { NSString* URLString = installURLController.textFields.firstObject.text; NSURL* remoteURL = [NSURL URLWithString:URLString]; [TSInstallationController handleAppInstallFromRemoteURL:remoteURL completion:nil]; }]; [installURLController addAction:installAction]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; [installURLController addAction:cancelAction]; [TSPresentationDelegate presentViewController:installURLController animated:YES completion:nil]; }); }]; UIMenu* installMenu = [UIMenu menuWithChildren:@[installFromFileAction, installFromURLAction]]; UIBarButtonItem* installBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"plus"] menu:installMenu]; self.navigationItem.rightBarButtonItems = @[installBarButtonItem]; } - (void)_setUpSearchBar { _searchController = [[UISearchController alloc] initWithSearchResultsController:nil]; _searchController.searchResultsUpdater = self; _searchController.obscuresBackgroundDuringPresentation = NO; self.navigationItem.searchController = _searchController; self.navigationItem.hidesSearchBarWhenScrolling = YES; } - (void)updateSearchResultsForSearchController:(UISearchController *)searchController { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ _searchKey = searchController.searchBar.text; [self reloadTable]; }); } - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { NSString* pathToIPA = urls.firstObject.path; [TSInstallationController presentInstallationAlertIfEnabledForFile:pathToIPA isRemoteInstall:NO completion:nil]; } - (void)openAppPressedForRowAtIndexPath:(NSIndexPath*)indexPath enableJIT:(BOOL)enableJIT { TSApplicationsManager* appsManager = [TSApplicationsManager sharedInstance]; TSAppInfo* appInfo = _cachedAppInfos[indexPath.row]; NSString* appId = [appInfo bundleIdentifier]; BOOL didOpen = [appsManager openApplicationWithBundleID:appId]; // if we failed to open the app, show an alert if(!didOpen) { NSString* failMessage = @""; if([[appInfo registrationState] isEqualToString:@"User"]) { failMessage = @"This app was not able to launch because it has a \"User\" registration state, register it as \"System\" and try again."; } NSString* failTitle = [NSString stringWithFormat:@"Failed to open %@", appId]; UIAlertController* didFailController = [UIAlertController alertControllerWithTitle:failTitle message:failMessage preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; [didFailController addAction:cancelAction]; [TSPresentationDelegate presentViewController:didFailController animated:YES completion:nil]; } else if (enableJIT) { int ret = [appsManager enableJITForBundleID:appId]; if (ret != 0) { UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"Error enabling JIT: trollstorehelper returned %d", ret] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [errorAlert addAction:closeAction]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; } } } - (void)showDetailsPressedForRowAtIndexPath:(NSIndexPath*)indexPath { TSAppInfo* appInfo = _cachedAppInfos[indexPath.row]; [appInfo loadInfoWithCompletion:^(NSError* error) { dispatch_async(dispatch_get_main_queue(), ^ { if(!error) { UIAlertController* detailsAlert = [UIAlertController alertControllerWithTitle:@"" message:@"" preferredStyle:UIAlertControllerStyleAlert]; detailsAlert.attributedTitle = [appInfo detailedInfoTitle]; detailsAlert.attributedMessage = [appInfo detailedInfoDescription]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [detailsAlert addAction:closeAction]; [TSPresentationDelegate presentViewController:detailsAlert animated:YES completion:nil]; } else { UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Parse Error %ld", error.code] message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [errorAlert addAction:closeAction]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; } }); }]; } - (void)changeAppRegistrationForRowAtIndexPath:(NSIndexPath*)indexPath toState:(NSString*)newState { TSAppInfo* appInfo = _cachedAppInfos[indexPath.row]; if([newState isEqualToString:@"User"]) { NSString* title = [NSString stringWithFormat:@"Switching '%@' to \"User\" Registration", [appInfo displayName]]; UIAlertController* confirmationAlert = [UIAlertController alertControllerWithTitle:title message:@"Switching this app to a \"User\" registration will make it unlaunchable after the next respring because the bugs exploited in TrollStore only affect apps registered as \"System\".\nThe purpose of this option is to make the app temporarily show up in settings, so you can adjust the settings and then switch it back to a \"System\" registration (TrollStore installed apps do not show up in settings otherwise). Additionally, the \"User\" registration state is also useful to temporarily fix iTunes file sharing, which also doesn't work for TrollStore installed apps otherwise.\nWhen you're done making the changes you need and want the app to become launchable again, you will need to switch it back to \"System\" state in TrollStore." preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* switchToUserAction = [UIAlertAction actionWithTitle:@"Switch to \"User\"" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) { [[TSApplicationsManager sharedInstance] changeAppRegistration:[appInfo bundlePath] toState:newState]; [appInfo sync_loadBasicInfo]; }]; [confirmationAlert addAction:switchToUserAction]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; [confirmationAlert addAction:cancelAction]; [TSPresentationDelegate presentViewController:confirmationAlert animated:YES completion:nil]; } else { [[TSApplicationsManager sharedInstance] changeAppRegistration:[appInfo bundlePath] toState:newState]; [appInfo sync_loadBasicInfo]; NSString* title = [NSString stringWithFormat:@"Switched '%@' to \"System\" Registration", [appInfo displayName]]; UIAlertController* infoAlert = [UIAlertController alertControllerWithTitle:title message:@"The app has been switched to the \"System\" registration state and will become launchable again after a respring." preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* respringAction = [UIAlertAction actionWithTitle:@"Respring" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { respring(); }]; [infoAlert addAction:respringAction]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [infoAlert addAction:closeAction]; [TSPresentationDelegate presentViewController:infoAlert animated:YES completion:nil]; } } - (void)uninstallPressedForRowAtIndexPath:(NSIndexPath*)indexPath { TSApplicationsManager* appsManager = [TSApplicationsManager sharedInstance]; TSAppInfo* appInfo = _cachedAppInfos[indexPath.row]; NSString* appPath = [appInfo bundlePath]; NSString* appId = [appInfo bundleIdentifier]; NSString* appName = [appInfo displayName]; UIAlertController* confirmAlert = [UIAlertController alertControllerWithTitle:@"Confirm Uninstallation" message:[NSString stringWithFormat:@"Uninstalling the app '%@' will delete the app and all data associated to it.", appName] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* uninstallAction = [UIAlertAction actionWithTitle:@"Uninstall" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) { if(appId) { [appsManager uninstallApp:appId]; } else { [appsManager uninstallAppByPath:appPath]; } }]; [confirmAlert addAction:uninstallAction]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; [confirmAlert addAction:cancelAction]; [TSPresentationDelegate presentViewController:confirmAlert animated:YES completion:nil]; } - (void)deselectRow { [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES]; } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return _cachedAppInfos.count; } - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { [self reloadTable]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ApplicationCell"]; if(!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"ApplicationCell"]; } if(!indexPath || indexPath.row > (_cachedAppInfos.count - 1)) return cell; TSAppInfo* appInfo = _cachedAppInfos[indexPath.row]; NSString* appId = [appInfo bundleIdentifier]; NSString* appVersion = [appInfo versionString]; // Configure the cell... cell.textLabel.text = [appInfo displayName]; cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ • %@", appVersion, appId]; cell.imageView.layer.borderWidth = 1; cell.imageView.layer.borderColor = [UIColor.labelColor colorWithAlphaComponent:0.1].CGColor; cell.imageView.layer.cornerRadius = 13.5; cell.imageView.layer.masksToBounds = YES; cell.imageView.layer.cornerCurve = kCACornerCurveContinuous; if(appId) { UIImage* cachedIcon = _cachedIcons[appId]; if(cachedIcon) { cell.imageView.image = cachedIcon; } else { cell.imageView.image = _placeholderIcon; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ { UIImage* iconImage = imageWithSize([UIImage _applicationIconImageForBundleIdentifier:appId format:iconFormatToUse() scale:[UIScreen mainScreen].scale], _placeholderIcon.size); _cachedIcons[appId] = iconImage; dispatch_async(dispatch_get_main_queue(), ^{ NSIndexPath *curIndexPath = [NSIndexPath indexPathForRow:[_cachedAppInfos indexOfObject:appInfo] inSection:0]; UITableViewCell *curCell = [tableView cellForRowAtIndexPath:curIndexPath]; if(curCell) { curCell.imageView.image = iconImage; [curCell setNeedsLayout]; } }); }); } } else { cell.imageView.image = _placeholderIcon; } cell.preservesSuperviewLayoutMargins = NO; cell.separatorInset = UIEdgeInsetsZero; cell.layoutMargins = UIEdgeInsetsZero; return cell; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 80.0f; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if(editingStyle == UITableViewCellEditingStyleDelete) { [self uninstallPressedForRowAtIndexPath:indexPath]; } } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { TSAppInfo* appInfo = _cachedAppInfos[indexPath.row]; NSString* appId = [appInfo bundleIdentifier]; NSString* appName = [appInfo displayName]; UIAlertController* appSelectAlert = [UIAlertController alertControllerWithTitle:appName?:@"" message:appId?:@"" preferredStyle:UIAlertControllerStyleActionSheet]; UIAlertAction* openAction = [UIAlertAction actionWithTitle:@"Open" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { [self openAppPressedForRowAtIndexPath:indexPath enableJIT:NO]; [self deselectRow]; }]; [appSelectAlert addAction:openAction]; if ([appInfo isDebuggable]) { UIAlertAction* openWithJITAction = [UIAlertAction actionWithTitle:@"Open with JIT" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { [self openAppPressedForRowAtIndexPath:indexPath enableJIT:YES]; [self deselectRow]; }]; [appSelectAlert addAction:openWithJITAction]; } UIAlertAction* showDetailsAction = [UIAlertAction actionWithTitle:@"Show Details" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { [self showDetailsPressedForRowAtIndexPath:indexPath]; [self deselectRow]; }]; [appSelectAlert addAction:showDetailsAction]; NSString* switchState; NSString* registrationState = [appInfo registrationState]; UIAlertActionStyle switchActionStyle = 0; if([registrationState isEqualToString:@"System"]) { switchState = @"User"; switchActionStyle = UIAlertActionStyleDestructive; } else if([registrationState isEqualToString:@"User"]) { switchState = @"System"; switchActionStyle = UIAlertActionStyleDefault; } UIAlertAction* switchRegistrationAction = [UIAlertAction actionWithTitle:[NSString stringWithFormat:@"Switch to \"%@\" Registration", switchState] style:switchActionStyle handler:^(UIAlertAction* action) { [self changeAppRegistrationForRowAtIndexPath:indexPath toState:switchState]; [self deselectRow]; }]; [appSelectAlert addAction:switchRegistrationAction]; UIAlertAction* uninstallAction = [UIAlertAction actionWithTitle:@"Uninstall App" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) { [self uninstallPressedForRowAtIndexPath:indexPath]; [self deselectRow]; }]; [appSelectAlert addAction:uninstallAction]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action) { [self deselectRow]; }]; [appSelectAlert addAction:cancelAction]; appSelectAlert.popoverPresentationController.sourceView = tableView; appSelectAlert.popoverPresentationController.sourceRect = [tableView rectForRowAtIndexPath:indexPath]; [TSPresentationDelegate presentViewController:appSelectAlert animated:YES completion:nil]; } - (void)purgeCachedIconsForApps:(NSArray *)apps { for (LSApplicationProxy *appProxy in apps) { NSString *appId = appProxy.bundleIdentifier; if (_cachedIcons[appId]) { [_cachedIcons removeObjectForKey:appId]; } } } - (void)applicationsDidInstall:(NSArray *)apps { [self purgeCachedIconsForApps:apps]; [self reloadTable]; } - (void)applicationsDidUninstall:(NSArray *)apps { [self purgeCachedIconsForApps:apps]; [self reloadTable]; } @end ================================================ FILE: TrollStore/TSApplicationsManager.h ================================================ #import #define TROLLSTORE_ROOT_PATH @"/var/containers/Bundle/TrollStore" #define TROLLSTORE_MAIN_PATH [TROLLSTORE_ROOT_PATH stringByAppendingPathComponent:@"Main"] #define TROLLSTORE_APPLICATIONS_PATH [TROLLSTORE_ROOT_PATH stringByAppendingPathComponent:@"Applications"] @interface TSApplicationsManager : NSObject + (instancetype)sharedInstance; - (NSArray*)installedAppPaths; - (NSError*)errorForCode:(int)code; - (int)installIpa:(NSString*)pathToIpa force:(BOOL)force log:(NSString**)logOut; - (int)installIpa:(NSString*)pathToIpa; - (int)uninstallApp:(NSString*)appId; - (int)uninstallAppByPath:(NSString*)path; - (BOOL)openApplicationWithBundleID:(NSString *)appID; - (int)enableJITForBundleID:(NSString *)appID; - (int)changeAppRegistration:(NSString*)appPath toState:(NSString*)newState; @end ================================================ FILE: TrollStore/TSApplicationsManager.m ================================================ #import "TSApplicationsManager.h" #import extern NSUserDefaults* trollStoreUserDefaults(); @implementation TSApplicationsManager + (instancetype)sharedInstance { static TSApplicationsManager *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[TSApplicationsManager alloc] init]; }); return sharedInstance; } - (NSArray*)installedAppPaths { return trollStoreInstalledAppBundlePaths(); } - (NSError*)errorForCode:(int)code { NSString* errorDescription = @"Unknown Error"; switch(code) { // IPA install errors case 166: errorDescription = @"The IPA file does not exist or is not accessible."; break; case 167: errorDescription = @"The IPA file does not appear to contain an app."; break; case 168: errorDescription = @"Failed to extract IPA file."; break; case 169: errorDescription = @"Failed to extract update tar file."; break; // App install errors case 170: errorDescription = @"Failed to create container for app bundle."; break; case 171: errorDescription = @"A non "APP_NAME@" or a "OTHER_APP_NAME@" app with the same identifier is already installed. If you are absolutely sure it is not, you can force install it."; break; case 172: errorDescription = @"The app does not contain an Info.plist file."; break; case 173: errorDescription = @"The app is not signed with a fake CoreTrust certificate and ldid is not installed. Install ldid in the settings tab and try again."; break; case 174: errorDescription = @"The app's main executable does not exist."; break; case 175: { //if (@available(iOS 16, *)) { // errorDescription = @"Failed to sign the app."; //} //else { errorDescription = @"Failed to sign the app. ldid returned a non zero status code."; //} } break; case 176: errorDescription = @"The app's Info.plist is missing required values."; break; case 177: errorDescription = @"Failed to mark app as TrollStore app."; break; case 178: errorDescription = @"Failed to copy app bundle."; break; case 179: errorDescription = @"The app you tried to install has the same identifier as a system app already installed on the device. The installation has been prevented to protect you from possible bootloops or other issues."; break; case 180: errorDescription = @"The app you tried to install has an encrypted main binary, which cannot have the CoreTrust bypass applied to it. Please ensure you install decrypted apps."; break; case 181: errorDescription = @"Failed to add app to icon cache."; break; case 182: errorDescription = @"The app was installed successfully, but requires developer mode to be enabled to run. After rebooting, select \"Turn On\" to enable developer mode."; break; case 183: errorDescription = @"Failed to enable developer mode."; break; case 184: errorDescription = @"The app was installed successfully, but has additional binaries that are encrypted (e.g. extensions, plugins). The app itself should work, but you may experience broken functionality as a result."; break; case 185: errorDescription = @"Failed to sign the app. The CoreTrust bypass returned a non zero status code."; } NSError* error = [NSError errorWithDomain:TrollStoreErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; return error; } - (int)installIpa:(NSString*)pathToIpa force:(BOOL)force log:(NSString**)logOut { NSMutableArray* args = [NSMutableArray new]; [args addObject:@"install"]; if(force) { [args addObject:@"force"]; } NSNumber* installationMethodToUseNum = [trollStoreUserDefaults() objectForKey:@"installationMethod"]; int installationMethodToUse = installationMethodToUseNum ? installationMethodToUseNum.intValue : 1; if(installationMethodToUse == 1) { [args addObject:@"custom"]; } else { [args addObject:@"installd"]; } [args addObject:pathToIpa]; int ret = spawnRoot(rootHelperPath(), args, nil, logOut); [[NSNotificationCenter defaultCenter] postNotificationName:@"ApplicationsChanged" object:nil]; return ret; } - (int)installIpa:(NSString*)pathToIpa { return [self installIpa:pathToIpa force:NO log:nil]; } - (int)uninstallApp:(NSString*)appId { if(!appId) return -200; NSMutableArray* args = [NSMutableArray new]; [args addObject:@"uninstall"]; NSNumber* uninstallationMethodToUseNum = [trollStoreUserDefaults() objectForKey:@"uninstallationMethod"]; int uninstallationMethodToUse = uninstallationMethodToUseNum ? uninstallationMethodToUseNum.intValue : 0; if(uninstallationMethodToUse == 1) { [args addObject:@"custom"]; } else { [args addObject:@"installd"]; } [args addObject:appId]; int ret = spawnRoot(rootHelperPath(), args, nil, nil); [[NSNotificationCenter defaultCenter] postNotificationName:@"ApplicationsChanged" object:nil]; return ret; } - (int)uninstallAppByPath:(NSString*)path { if(!path) return -200; NSMutableArray* args = [NSMutableArray new]; [args addObject:@"uninstall-path"]; NSNumber* uninstallationMethodToUseNum = [trollStoreUserDefaults() objectForKey:@"uninstallationMethod"]; int uninstallationMethodToUse = uninstallationMethodToUseNum ? uninstallationMethodToUseNum.intValue : 0; if(uninstallationMethodToUse == 1) { [args addObject:@"custom"]; } else { [args addObject:@"installd"]; } [args addObject:path]; int ret = spawnRoot(rootHelperPath(), args, nil, nil); [[NSNotificationCenter defaultCenter] postNotificationName:@"ApplicationsChanged" object:nil]; return ret; } - (BOOL)openApplicationWithBundleID:(NSString *)appId { return [[LSApplicationWorkspace defaultWorkspace] openApplicationWithBundleID:appId]; } - (int)enableJITForBundleID:(NSString *)appId { return spawnRoot(rootHelperPath(), @[@"enable-jit", appId], nil, nil); } - (int)changeAppRegistration:(NSString*)appPath toState:(NSString*)newState { if(!appPath || !newState) return -200; return spawnRoot(rootHelperPath(), @[@"modify-registration", appPath, newState], nil, nil); } @end ================================================ FILE: TrollStore/TSCommonTCCServiceNames.h ================================================ // // TSCommonTCCServiceNames.h // IPAInfo // // Created by Luke Noble on 30.10.22. // #import static NSDictionary* const commonTCCServices = @{ @"kTCCServicePhotos": @"Photo Library", @"kTCCServicePhotosAdd": @"Photo Library (Add)", @"kTCCServiceCamera": @"Camera", @"kTCCServiceMicrophone": @"Microphone", @"kTCCServiceAddressBook": @"Contacts", @"kTCCServiceCalendar": @"Calendars", @"kTCCServiceReminders": @"Reminders", @"kTCCServiceWillow": @"HomeKit", @"kTCCServiceGameCenterFriends": @"Game Center Friends", @"kTCCServiceExposureNotification": @"Exposure Notifications", @"kTCCServiceFocusStatus": @"Focus Status", @"kTCCServiceUserTracking": @"User Tracking", @"kTCCServiceFaceID": @"Face ID", @"kTCCServiceMediaLibrary": @"Apple Media Library", @"kTCCServiceMotion": @"Motion Sensors", @"kTCCServiceNearbyInteraction": @"Nearby Device Interaction", @"kTCCServiceBluetoothAlways": @"Bluetooth (Always)", @"kTCCServiceBluetoothWhileInUse": @"Bluetooth (While In Use)", @"kTCCServiceBluetoothPeripheral": @"Bluetooth (Peripherals)", @"kTCCServiceLocation": @"Location" }; ================================================ FILE: TrollStore/TSDonateListController.h ================================================ #import @interface TSDonateListController : PSListController @end ================================================ FILE: TrollStore/TSDonateListController.m ================================================ #import "TSDonateListController.h" #import @implementation TSDonateListController - (void)donateToAlfiePressed { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://ko-fi.com/alfiecg_dev"] options:@{} completionHandler:^(BOOL success){}]; } - (void)donateToOpaPressed { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=opa334@protonmail.com&item_name=TrollStore"] options:@{} completionHandler:^(BOOL success){}]; } - (NSMutableArray*)specifiers { if(!_specifiers) { _specifiers = [NSMutableArray new]; PSSpecifier* alfieGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; alfieGroupSpecifier.name = @"Alfie"; [alfieGroupSpecifier setProperty:@"Alfie found the new CoreTrust bug (CVE-2023-41991) via patchdiffing, produced a POC binary and worked on automatically applying it with the help of the ChOma library, while also contributing to said library." forKey:@"footerText"]; [_specifiers addObject:alfieGroupSpecifier]; PSSpecifier* alfieDonateSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Donate to alfiecg_dev" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; alfieDonateSpecifier.identifier = @"donateToAlfie"; [alfieDonateSpecifier setProperty:@YES forKey:@"enabled"]; alfieDonateSpecifier.buttonAction = @selector(donateToAlfiePressed); [_specifiers addObject:alfieDonateSpecifier]; PSSpecifier* opaGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; opaGroupSpecifier.name = @"Opa"; [opaGroupSpecifier setProperty:@"Opa developed the ChOma library, helped with automating the bug using it and integrated it into TrollStore." forKey:@"footerText"]; [_specifiers addObject:opaGroupSpecifier]; PSSpecifier* opaDonateSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Donate to opa334" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; opaDonateSpecifier.identifier = @"donateToOpa"; [opaDonateSpecifier setProperty:@YES forKey:@"enabled"]; opaDonateSpecifier.buttonAction = @selector(donateToOpaPressed); [_specifiers addObject:opaDonateSpecifier]; } [(UINavigationItem *)self.navigationItem setTitle:@"Donate"]; return _specifiers; } @end ================================================ FILE: TrollStore/TSInstallationController.h ================================================ @import Foundation; @interface TSInstallationController : NSObject + (void)presentInstallationAlertIfEnabledForFile:(NSString*)pathToIPA isRemoteInstall:(BOOL)remoteInstall completion:(void (^)(BOOL, NSError*))completionBlock; + (void)handleAppInstallFromFile:(NSString*)pathToIPA forceInstall:(BOOL)force completion:(void (^)(BOOL, NSError*))completion; + (void)handleAppInstallFromFile:(NSString*)pathToIPA completion:(void (^)(BOOL, NSError*))completion; + (void)handleAppInstallFromRemoteURL:(NSURL*)remoteURL completion:(void (^)(BOOL, NSError*))completion; + (void)installLdid; @end ================================================ FILE: TrollStore/TSInstallationController.m ================================================ #import "TSInstallationController.h" #import "TSApplicationsManager.h" #import "TSAppInfo.h" #import #import extern NSUserDefaults* trollStoreUserDefaults(void); @implementation TSInstallationController + (void)handleAppInstallFromFile:(NSString*)pathToIPA forceInstall:(BOOL)force completion:(void (^)(BOOL, NSError*))completionBlock { dispatch_async(dispatch_get_main_queue(), ^ { [TSPresentationDelegate startActivity:@"Installing"]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ { // Install IPA NSString* log; int ret = [[TSApplicationsManager sharedInstance] installIpa:pathToIPA force:force log:&log]; NSError* error; if(ret != 0) { error = [[TSApplicationsManager sharedInstance] errorForCode:ret]; } NSLog(@"installed app! ret:%d, error: %@", ret, error); dispatch_async(dispatch_get_main_queue(), ^ { [TSPresentationDelegate stopActivityWithCompletion:^ { if (ret == 0) { // success if(completionBlock) completionBlock(YES, nil); } else if (ret == 171) { // recoverable error UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Install Error %d", ret] message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { if(completionBlock) completionBlock(NO, error); }]; [errorAlert addAction:closeAction]; UIAlertAction* forceInstallAction = [UIAlertAction actionWithTitle:@"Force Installation" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { [self handleAppInstallFromFile:pathToIPA forceInstall:YES completion:completionBlock]; }]; [errorAlert addAction:forceInstallAction]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; } else if (ret == 182) { // non-fatal informative message UIAlertController* rebootNotification = [UIAlertController alertControllerWithTitle:@"Reboot Required" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action) { if(completionBlock) completionBlock(YES, nil); }]; [rebootNotification addAction:closeAction]; UIAlertAction* rebootAction = [UIAlertAction actionWithTitle:@"Reboot Now" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { if(completionBlock) completionBlock(YES, nil); spawnRoot(rootHelperPath(), @[@"reboot"], nil, nil); }]; [rebootNotification addAction:rebootAction]; [TSPresentationDelegate presentViewController:rebootNotification animated:YES completion:nil]; } else if (ret == 184) { // warning UIAlertController* warningAlert = [UIAlertController alertControllerWithTitle:@"Warning" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action) { if(completionBlock) completionBlock(YES, nil); }]; [warningAlert addAction:closeAction]; [TSPresentationDelegate presentViewController:warningAlert animated:YES completion:nil]; } else { // unrecoverable error UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Install Error %d", ret] message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [errorAlert addAction:closeAction]; UIAlertAction* copyLogAction = [UIAlertAction actionWithTitle:@"Copy Debug Log" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { UIPasteboard* pasteboard = [UIPasteboard generalPasteboard]; pasteboard.string = log; }]; [errorAlert addAction:copyLogAction]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; if(completionBlock) completionBlock(NO, error); } }]; }); }); }); } + (void)presentInstallationAlertIfEnabledForFile:(NSString*)pathToIPA isRemoteInstall:(BOOL)remoteInstall completion:(void (^)(BOOL, NSError*))completionBlock { NSNumber* installAlertConfigurationNum = [trollStoreUserDefaults() objectForKey:@"installAlertConfiguration"]; NSUInteger installAlertConfiguration = 0; if(installAlertConfigurationNum) { installAlertConfiguration = installAlertConfigurationNum.unsignedIntegerValue; if(installAlertConfiguration > 2) { // broken pref? revert to 0 installAlertConfiguration = 0; } } // Check if user disabled alert for this kind of install if(installAlertConfiguration > 0) { if(installAlertConfiguration == 2 || (installAlertConfiguration == 1 && !remoteInstall)) { [self handleAppInstallFromFile:pathToIPA completion:completionBlock]; return; } } TSAppInfo* appInfo = [[TSAppInfo alloc] initWithIPAPath:pathToIPA]; [appInfo loadInfoWithCompletion:^(NSError* error) { dispatch_async(dispatch_get_main_queue(), ^ { if(!error) { UIAlertController* installAlert = [UIAlertController alertControllerWithTitle:@"" message:@"" preferredStyle:UIAlertControllerStyleAlert]; installAlert.attributedTitle = [appInfo detailedInfoTitle]; installAlert.attributedMessage = [appInfo detailedInfoDescription]; UIAlertAction* installAction = [UIAlertAction actionWithTitle:@"Install" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [self handleAppInstallFromFile:pathToIPA completion:completionBlock]; }]; [installAlert addAction:installAction]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action) { if(completionBlock) completionBlock(NO, nil); }]; [installAlert addAction:cancelAction]; [TSPresentationDelegate presentViewController:installAlert animated:YES completion:nil]; } else { UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Parse Error %ld", error.code] message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [errorAlert addAction:closeAction]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; } }); }]; } + (void)handleAppInstallFromFile:(NSString*)pathToIPA completion:(void (^)(BOOL, NSError*))completionBlock { [self handleAppInstallFromFile:pathToIPA forceInstall:NO completion:completionBlock]; } + (void)handleAppInstallFromRemoteURL:(NSURL*)remoteURL completion:(void (^)(BOOL, NSError*))completionBlock { NSURLRequest* downloadRequest = [NSURLRequest requestWithURL:remoteURL]; dispatch_async(dispatch_get_main_queue(), ^ { NSURLSessionDownloadTask* downloadTask = [NSURLSession.sharedSession downloadTaskWithRequest:downloadRequest completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^ { [TSPresentationDelegate stopActivityWithCompletion:^ { if(error) { UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"Error downloading app: %@", error] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [errorAlert addAction:closeAction]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:^ { if(completionBlock) completionBlock(NO, error); }]; } else { NSString* tmpIpaPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.ipa"]; [[NSFileManager defaultManager] removeItemAtPath:tmpIpaPath error:nil]; [[NSFileManager defaultManager] moveItemAtPath:location.path toPath:tmpIpaPath error:nil]; [self presentInstallationAlertIfEnabledForFile:tmpIpaPath isRemoteInstall:YES completion:^(BOOL success, NSError* error) { [[NSFileManager defaultManager] removeItemAtPath:tmpIpaPath error:nil]; if(completionBlock) completionBlock(success, error); }]; } }]; }); }]; [TSPresentationDelegate startActivity:@"Downloading" withCancelHandler:^ { [downloadTask cancel]; }]; [downloadTask resume]; }); } + (void)installLdid { fetchLatestLdidVersion(^(NSString* latestVersion) { if(!latestVersion) return; dispatch_async(dispatch_get_main_queue(), ^ { NSURL* ldidURL = [NSURL URLWithString:@"https://github.com/opa334/ldid/releases/latest/download/ldid"]; NSURLRequest* ldidRequest = [NSURLRequest requestWithURL:ldidURL]; [TSPresentationDelegate startActivity:@"Installing ldid"]; NSURLSessionDownloadTask* downloadTask = [NSURLSession.sharedSession downloadTaskWithRequest:ldidRequest completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { if(error) { UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"Error downloading ldid: %@", error] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [errorAlert addAction:closeAction]; dispatch_async(dispatch_get_main_queue(), ^ { [TSPresentationDelegate stopActivityWithCompletion:^ { [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; }]; }); } else if(location) { spawnRoot(rootHelperPath(), @[@"install-ldid", location.path, latestVersion], nil, nil); dispatch_async(dispatch_get_main_queue(), ^ { [TSPresentationDelegate stopActivityWithCompletion:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"TrollStoreReloadSettingsNotification" object:nil userInfo:nil]; }); } }]; [downloadTask resume]; }); }); } @end ================================================ FILE: TrollStore/TSRootViewController.h ================================================ #import @interface TSRootViewController : UITabBarController @end ================================================ FILE: TrollStore/TSRootViewController.m ================================================ #import "TSRootViewController.h" #import "TSAppTableViewController.h" #import "TSSettingsListController.h" #import @implementation TSRootViewController - (void)loadView { [super loadView]; TSAppTableViewController* appTableVC = [[TSAppTableViewController alloc] init]; appTableVC.title = @"Apps"; TSSettingsListController* settingsListVC = [[TSSettingsListController alloc] init]; settingsListVC.title = @"Settings"; UINavigationController* appNavigationController = [[UINavigationController alloc] initWithRootViewController:appTableVC]; UINavigationController* settingsNavigationController = [[UINavigationController alloc] initWithRootViewController:settingsListVC]; appNavigationController.tabBarItem.image = [UIImage systemImageNamed:@"square.stack.3d.up.fill"]; settingsNavigationController.tabBarItem.image = [UIImage systemImageNamed:@"gear"]; self.title = @"Root View Controller"; self.viewControllers = @[appNavigationController, settingsNavigationController]; } - (void)viewDidLoad { [super viewDidLoad]; TSPresentationDelegate.presentationViewController = self; } @end ================================================ FILE: TrollStore/TSSceneDelegate.h ================================================ #import @interface TSSceneDelegate : UIResponder @property (strong, nonatomic) UIWindow * window; @property (nonatomic, strong) UITabBarController *rootViewController; @end ================================================ FILE: TrollStore/TSSceneDelegate.m ================================================ #import "TSSceneDelegate.h" #import "TSRootViewController.h" #import "TSUtil.h" #import "TSApplicationsManager.h" #import "TSInstallationController.h" #import @implementation TSSceneDelegate - (void)handleURLContexts:(NSSet*)URLContexts scene:(UIWindowScene*)scene { for(UIOpenURLContext* context in URLContexts) { NSURL* url = context.URL; if(url) { if([url isFileURL]) { [url startAccessingSecurityScopedResource]; void (^doneBlock)(BOOL) = ^(BOOL shouldExit) { [url stopAccessingSecurityScopedResource]; [[NSFileManager defaultManager] removeItemAtURL:url error:nil]; if(shouldExit) { NSLog(@"Respring + Exit"); respring(); exit(0); } }; if ([url.pathExtension.lowercaseString isEqualToString:@"ipa"] || [url.pathExtension.lowercaseString isEqualToString:@"tipa"]) { [TSInstallationController presentInstallationAlertIfEnabledForFile:url.path isRemoteInstall:NO completion:^(BOOL success, NSError* error){ doneBlock(NO); }]; } else if([url.pathExtension.lowercaseString isEqualToString:@"tar"]) { // Update TrollStore itself NSLog(@"Updating TrollStore..."); int ret = spawnRoot(rootHelperPath(), @[@"install-trollstore", url.path], nil, nil); doneBlock(ret == 0); NSLog(@"Updated TrollStore!"); } } else if([url.scheme isEqualToString:@"apple-magnifier"]) { NSURLComponents* components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; if([components.host isEqualToString:@"install"]) { NSString* URLStringToInstall; for(NSURLQueryItem* queryItem in components.queryItems) { if([queryItem.name isEqualToString:@"url"]) { URLStringToInstall = queryItem.value; break; } } if(URLStringToInstall && [URLStringToInstall isKindOfClass:NSString.class]) { NSURL* URLToInstall = [NSURL URLWithString:URLStringToInstall]; [TSInstallationController handleAppInstallFromRemoteURL:URLToInstall completion:nil]; } } else if([components.host isEqualToString:@"enable-jit"]) { NSString* BundleIDToEnableJIT; for(NSURLQueryItem* queryItem in components.queryItems) { if([queryItem.name isEqualToString:@"bundle-id"]) { BundleIDToEnableJIT = queryItem.value; break; } } if(BundleIDToEnableJIT && [BundleIDToEnableJIT isKindOfClass:NSString.class]) { dispatch_async(dispatch_get_main_queue(), ^ { [self handleEnableJITForBundleID:BundleIDToEnableJIT]; }); } } } } } } - (void)handleEnableJITForBundleID:(NSString *)appId { TSApplicationsManager* appsManager = [TSApplicationsManager sharedInstance]; BOOL didOpen = [appsManager openApplicationWithBundleID:appId]; // if we failed to open the app, show an alert if(!didOpen) { NSString* failMessage = @""; // we don't have TSAppInfo here so we cannot check the registration state NSString* failTitle = [NSString stringWithFormat:@"Failed to open %@", appId]; UIAlertController* didFailController = [UIAlertController alertControllerWithTitle:failTitle message:failMessage preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; [didFailController addAction:cancelAction]; [TSPresentationDelegate presentViewController:didFailController animated:YES completion:nil]; } else { int ret = [appsManager enableJITForBundleID:appId]; if (ret != 0) { UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"Error enabling JIT: trollstorehelper returned %d", ret] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [errorAlert addAction:closeAction]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; } } } // We want to auto install ldid if either it doesn't exist // or if it's the one from an old TrollStore version that's no longer supported - (void)handleLdidCheck { #ifndef TROLLSTORE_LITE //if (@available(iOS 16, *)) {} else { NSString* tsAppPath = [NSBundle mainBundle].bundlePath; NSString* ldidPath = [tsAppPath stringByAppendingPathComponent:@"ldid"]; NSString* ldidVersionPath = [tsAppPath stringByAppendingPathComponent:@"ldid.version"]; if(![[NSFileManager defaultManager] fileExistsAtPath:ldidPath] || ![[NSFileManager defaultManager] fileExistsAtPath:ldidVersionPath]) { [TSInstallationController installLdid]; } //} #endif } - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). UIWindowScene* windowScene = (UIWindowScene*)scene; _window = [[UIWindow alloc] initWithWindowScene:windowScene]; _rootViewController = [[TSRootViewController alloc] init]; _window.rootViewController = _rootViewController; [_window makeKeyAndVisible]; if(connectionOptions.URLContexts.count) { [self handleURLContexts:connectionOptions.URLContexts scene:(UIWindowScene*)scene]; } else { [self handleLdidCheck]; } } - (void)sceneDidDisconnect:(UIScene *)scene { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). } - (void)sceneDidBecomeActive:(UIScene *)scene { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } - (void)sceneWillResignActive:(UIScene *)scene { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } - (void)sceneWillEnterForeground:(UIScene *)scene { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } - (void)sceneDidEnterBackground:(UIScene *)scene { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } - (void)scene:(UIScene *)scene openURLContexts:(NSSet *)URLContexts { [self handleURLContexts:URLContexts scene:(UIWindowScene*)scene]; } @end ================================================ FILE: TrollStore/TSSettingsAdvancedListController.h ================================================ #import @interface TSSettingsAdvancedListController : PSListController @end ================================================ FILE: TrollStore/TSSettingsAdvancedListController.m ================================================ #import "TSSettingsAdvancedListController.h" #import "TSUtil.h" #import extern NSUserDefaults* trollStoreUserDefaults(); @interface PSSpecifier () @property (nonatomic,retain) NSArray* values; @end @implementation TSSettingsAdvancedListController - (NSMutableArray*)specifiers { if(!_specifiers) { _specifiers = [NSMutableArray new]; PSSpecifier* installationMethodGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; //installationMethodGroupSpecifier.name = @"Installation"; [installationMethodGroupSpecifier setProperty:@"installd:\nInstalls applications by doing a placeholder installation through installd, fixing the permissions and then adding it to icon cache.\nAdvantage: Might be slightly more persistent than the custom method in terms of icon cache reloads.\nDisadvantage: Causes some small issues with certain applications for seemingly no reason (E.g. Watusi cannot save preferences when being installed using this method).\n\nCustom (Recommended):\nInstalls applications by manually creating a bundle using MobileContainerManager, copying the app into it and adding it to icon cache.\nAdvantage: No known issues (As opposed to the Watusi issue outlined in the installd method).\nDisadvantage: Might be slightly less persistent then the installd method in terms of icon cache reloads.\n\nNOTE: In cases where installd is selected but the placeholder installation fails, TrollStore automatically falls back to using the Custom method." forKey:@"footerText"]; [_specifiers addObject:installationMethodGroupSpecifier]; PSSpecifier* installationMethodSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Installation Method" target:self set:nil get:nil detail:nil cell:PSStaticTextCell edit:nil]; [installationMethodSpecifier setProperty:@YES forKey:@"enabled"]; installationMethodSpecifier.identifier = @"installationMethodLabel"; [_specifiers addObject:installationMethodSpecifier]; PSSpecifier* installationMethodSegmentSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Installation Method Segment" target:self set:@selector(setPreferenceValue:specifier:) get:@selector(readPreferenceValue:) detail:nil cell:PSSegmentCell edit:nil]; [installationMethodSegmentSpecifier setProperty:@YES forKey:@"enabled"]; installationMethodSegmentSpecifier.identifier = @"installationMethodSegment"; [installationMethodSegmentSpecifier setProperty:APP_ID forKey:@"defaults"]; [installationMethodSegmentSpecifier setProperty:@"installationMethod" forKey:@"key"]; installationMethodSegmentSpecifier.values = @[@0, @1]; installationMethodSegmentSpecifier.titleDictionary = @{@0 : @"installd", @1 : @"Custom"}; [installationMethodSegmentSpecifier setProperty:@1 forKey:@"default"]; [_specifiers addObject:installationMethodSegmentSpecifier]; PSSpecifier* uninstallationMethodGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; //uninstallationMethodGroupSpecifier.name = @"Uninstallation"; [uninstallationMethodGroupSpecifier setProperty:@"installd (Recommended):\nUninstalls applications using the same API that SpringBoard uses when uninstalling them from the home screen.\n\nCustom:\nUninstalls applications by removing them from icon cache and then deleting their application and data bundles directly.\n\nNOTE: In cases where installd is selected but the stock uninstallation fails, TrollStore automatically falls back to using the Custom method." forKey:@"footerText"]; [_specifiers addObject:uninstallationMethodGroupSpecifier]; PSSpecifier* uninstallationMethodSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Uninstallation Method" target:self set:nil get:nil detail:nil cell:PSStaticTextCell edit:nil]; [uninstallationMethodSpecifier setProperty:@YES forKey:@"enabled"]; uninstallationMethodSpecifier.identifier = @"uninstallationMethodLabel"; [_specifiers addObject:uninstallationMethodSpecifier]; PSSpecifier* uninstallationMethodSegmentSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Installation Method Segment" target:self set:@selector(setPreferenceValue:specifier:) get:@selector(readPreferenceValue:) detail:nil cell:PSSegmentCell edit:nil]; [uninstallationMethodSegmentSpecifier setProperty:@YES forKey:@"enabled"]; uninstallationMethodSegmentSpecifier.identifier = @"uninstallationMethodSegment"; [uninstallationMethodSegmentSpecifier setProperty:APP_ID forKey:@"defaults"]; [uninstallationMethodSegmentSpecifier setProperty:@"uninstallationMethod" forKey:@"key"]; uninstallationMethodSegmentSpecifier.values = @[@0, @1]; uninstallationMethodSegmentSpecifier.titleDictionary = @{@0 : @"installd", @1 : @"Custom"}; [uninstallationMethodSegmentSpecifier setProperty:@0 forKey:@"default"]; [_specifiers addObject:uninstallationMethodSegmentSpecifier]; } [(UINavigationItem *)self.navigationItem setTitle:@"Advanced"]; return _specifiers; } - (void)setPreferenceValue:(NSObject*)value specifier:(PSSpecifier*)specifier { NSUserDefaults* tsDefaults = trollStoreUserDefaults(); [tsDefaults setObject:value forKey:[specifier propertyForKey:@"key"]]; } - (NSObject*)readPreferenceValue:(PSSpecifier*)specifier { NSUserDefaults* tsDefaults = trollStoreUserDefaults(); NSObject* toReturn = [tsDefaults objectForKey:[specifier propertyForKey:@"key"]]; if(!toReturn) { toReturn = [specifier propertyForKey:@"default"]; } return toReturn; } @end ================================================ FILE: TrollStore/TSSettingsListController.h ================================================ #import "TSListControllerShared.h" @interface TSSettingsListController : TSListControllerShared { PSSpecifier* _installPersistenceHelperSpecifier; NSString* _newerVersion; NSString* _newerLdidVersion; BOOL _devModeEnabled; } @end ================================================ FILE: TrollStore/TSSettingsListController.m ================================================ #import "TSSettingsListController.h" #import #import #import #import #import "TSInstallationController.h" #import "TSSettingsAdvancedListController.h" #import "TSDonateListController.h" @interface NSUserDefaults (Private) - (instancetype)_initWithSuiteName:(NSString *)suiteName container:(NSURL *)container; @end extern NSUserDefaults* trollStoreUserDefaults(void); @implementation TSSettingsListController - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadSpecifiers) name:UIApplicationWillEnterForegroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadSpecifiers) name:@"TrollStoreReloadSettingsNotification" object:nil]; #ifndef TROLLSTORE_LITE fetchLatestTrollStoreVersion(^(NSString* latestVersion) { NSString* currentVersion = [self getTrollStoreVersion]; NSComparisonResult result = [currentVersion compare:latestVersion options:NSNumericSearch]; if(result == NSOrderedAscending) { _newerVersion = latestVersion; dispatch_async(dispatch_get_main_queue(), ^ { [self reloadSpecifiers]; }); } }); //if (@available(iOS 16, *)) {} else { fetchLatestLdidVersion(^(NSString* latestVersion) { NSString* ldidVersionPath = [NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@"ldid.version"]; NSString* ldidVersion = nil; NSData* ldidVersionData = [NSData dataWithContentsOfFile:ldidVersionPath]; if(ldidVersionData) { ldidVersion = [[NSString alloc] initWithData:ldidVersionData encoding:NSUTF8StringEncoding]; } if(![latestVersion isEqualToString:ldidVersion]) { _newerLdidVersion = latestVersion; dispatch_async(dispatch_get_main_queue(), ^ { [self reloadSpecifiers]; }); } }); //} if (@available(iOS 16, *)) { _devModeEnabled = spawnRoot(rootHelperPath(), @[@"check-dev-mode"], nil, nil) == 0; } else { _devModeEnabled = YES; } #endif [self reloadSpecifiers]; } - (NSMutableArray*)specifiers { if(!_specifiers) { _specifiers = [NSMutableArray new]; #ifndef TROLLSTORE_LITE if(_newerVersion) { PSSpecifier* updateTrollStoreGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; updateTrollStoreGroupSpecifier.name = @"Update Available"; [_specifiers addObject:updateTrollStoreGroupSpecifier]; PSSpecifier* updateTrollStoreSpecifier = [PSSpecifier preferenceSpecifierNamed:[NSString stringWithFormat:@"Update TrollStore to %@", _newerVersion] target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; updateTrollStoreSpecifier.identifier = @"updateTrollStore"; [updateTrollStoreSpecifier setProperty:@YES forKey:@"enabled"]; updateTrollStoreSpecifier.buttonAction = @selector(updateTrollStorePressed); [_specifiers addObject:updateTrollStoreSpecifier]; } if(!_devModeEnabled) { PSSpecifier* enableDevModeGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; enableDevModeGroupSpecifier.name = @"Developer Mode"; [enableDevModeGroupSpecifier setProperty:@"Some apps require developer mode enabled to launch. This requires a reboot to take effect." forKey:@"footerText"]; [_specifiers addObject:enableDevModeGroupSpecifier]; PSSpecifier* enableDevModeSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Enable Developer Mode" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; enableDevModeSpecifier.identifier = @"enableDevMode"; [enableDevModeSpecifier setProperty:@YES forKey:@"enabled"]; enableDevModeSpecifier.buttonAction = @selector(enableDevModePressed); [_specifiers addObject:enableDevModeSpecifier]; } #endif PSSpecifier* utilitiesGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; utilitiesGroupSpecifier.name = @"Utilities"; NSString *utilitiesDescription = @""; #ifdef TROLLSTORE_LITE if (shouldRegisterAsUserByDefault()) { utilitiesDescription = @"Apps will be registered as User by default since AppSync Unified is installed.\n\n"; } else { utilitiesDescription = @"Apps will be registered as System by default since AppSync Unified is not installed. When apps loose their System registration and stop working, press \"Refresh App Registrations\" here to fix them.\n\n"; } #endif utilitiesDescription = [utilitiesDescription stringByAppendingString:@"If an app does not immediately appear after installation, respring here and it should appear afterwards."]; [utilitiesGroupSpecifier setProperty:utilitiesDescription forKey:@"footerText"]; [_specifiers addObject:utilitiesGroupSpecifier]; PSSpecifier* respringButtonSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Respring" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; respringButtonSpecifier.identifier = @"respring"; [respringButtonSpecifier setProperty:@YES forKey:@"enabled"]; respringButtonSpecifier.buttonAction = @selector(respringButtonPressed); [_specifiers addObject:respringButtonSpecifier]; PSSpecifier* refreshAppRegistrationsSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Refresh App Registrations" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; refreshAppRegistrationsSpecifier.identifier = @"refreshAppRegistrations"; [refreshAppRegistrationsSpecifier setProperty:@YES forKey:@"enabled"]; refreshAppRegistrationsSpecifier.buttonAction = @selector(refreshAppRegistrationsPressed); [_specifiers addObject:refreshAppRegistrationsSpecifier]; PSSpecifier* rebuildIconCacheSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Rebuild Icon Cache" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; rebuildIconCacheSpecifier.identifier = @"uicache"; [rebuildIconCacheSpecifier setProperty:@YES forKey:@"enabled"]; rebuildIconCacheSpecifier.buttonAction = @selector(rebuildIconCachePressed); [_specifiers addObject:rebuildIconCacheSpecifier]; NSArray *inactiveBundlePaths = trollStoreInactiveInstalledAppBundlePaths(); if (inactiveBundlePaths.count > 0) { PSSpecifier* transferAppsSpecifier = [PSSpecifier preferenceSpecifierNamed:[NSString stringWithFormat:@"Transfer %zu "OTHER_APP_NAME@" %@", inactiveBundlePaths.count, inactiveBundlePaths.count > 1 ? @"Apps" : @"App"] target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; transferAppsSpecifier.identifier = @"transferApps"; [transferAppsSpecifier setProperty:@YES forKey:@"enabled"]; transferAppsSpecifier.buttonAction = @selector(transferAppsPressed); [_specifiers addObject:transferAppsSpecifier]; } #ifndef TROLLSTORE_LITE //if (@available(iOS 16, *)) { } else { NSString* ldidPath = [NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@"ldid"]; NSString* ldidVersionPath = [NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@"ldid.version"]; BOOL ldidInstalled = [[NSFileManager defaultManager] fileExistsAtPath:ldidPath]; NSString* ldidVersion = nil; NSData* ldidVersionData = [NSData dataWithContentsOfFile:ldidVersionPath]; if(ldidVersionData) { ldidVersion = [[NSString alloc] initWithData:ldidVersionData encoding:NSUTF8StringEncoding]; } PSSpecifier* signingGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; signingGroupSpecifier.name = @"Signing"; if(ldidInstalled) { [signingGroupSpecifier setProperty:@"ldid is installed and allows TrollStore to install unsigned IPA files." forKey:@"footerText"]; } else { [signingGroupSpecifier setProperty:@"In order for TrollStore to be able to install unsigned IPAs, ldid has to be installed using this button. It can't be directly included in TrollStore because of licensing issues." forKey:@"footerText"]; } [_specifiers addObject:signingGroupSpecifier]; if(ldidInstalled) { NSString* installedTitle = @"ldid: Installed"; if(ldidVersion) { installedTitle = [NSString stringWithFormat:@"%@ (%@)", installedTitle, ldidVersion]; } PSSpecifier* ldidInstalledSpecifier = [PSSpecifier preferenceSpecifierNamed:installedTitle target:self set:nil get:nil detail:nil cell:PSStaticTextCell edit:nil]; [ldidInstalledSpecifier setProperty:@NO forKey:@"enabled"]; ldidInstalledSpecifier.identifier = @"ldidInstalled"; [_specifiers addObject:ldidInstalledSpecifier]; if(_newerLdidVersion && ![_newerLdidVersion isEqualToString:ldidVersion]) { NSString* updateTitle = [NSString stringWithFormat:@"Update to %@", _newerLdidVersion]; PSSpecifier* ldidUpdateSpecifier = [PSSpecifier preferenceSpecifierNamed:updateTitle target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; ldidUpdateSpecifier.identifier = @"updateLdid"; [ldidUpdateSpecifier setProperty:@YES forKey:@"enabled"]; ldidUpdateSpecifier.buttonAction = @selector(installOrUpdateLdidPressed); [_specifiers addObject:ldidUpdateSpecifier]; } } else { PSSpecifier* installLdidSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Install ldid" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; installLdidSpecifier.identifier = @"installLdid"; [installLdidSpecifier setProperty:@YES forKey:@"enabled"]; installLdidSpecifier.buttonAction = @selector(installOrUpdateLdidPressed); [_specifiers addObject:installLdidSpecifier]; } //} PSSpecifier* persistenceGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; persistenceGroupSpecifier.name = @"Persistence"; [_specifiers addObject:persistenceGroupSpecifier]; if([[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/TrollStorePersistenceHelper.app"]) { [persistenceGroupSpecifier setProperty:@"When iOS rebuilds the icon cache, all TrollStore apps including TrollStore itself will be reverted to \"User\" state and either disappear or no longer launch. If that happens, you can use the TrollHelper app on the home screen to refresh the app registrations, which will make them work again." forKey:@"footerText"]; PSSpecifier* installedPersistenceHelperSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Helper Installed as Standalone App" target:self set:nil get:nil detail:nil cell:PSStaticTextCell edit:nil]; [installedPersistenceHelperSpecifier setProperty:@NO forKey:@"enabled"]; installedPersistenceHelperSpecifier.identifier = @"persistenceHelperInstalled"; [_specifiers addObject:installedPersistenceHelperSpecifier]; } else { LSApplicationProxy* persistenceApp = findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE_ALL); if(persistenceApp) { NSString* appName = [persistenceApp localizedName]; [persistenceGroupSpecifier setProperty:[NSString stringWithFormat:@"When iOS rebuilds the icon cache, all TrollStore apps including TrollStore itself will be reverted to \"User\" state and either disappear or no longer launch. If that happens, you can use the persistence helper installed into %@ to refresh the app registrations, which will make them work again.", appName] forKey:@"footerText"]; PSSpecifier* installedPersistenceHelperSpecifier = [PSSpecifier preferenceSpecifierNamed:[NSString stringWithFormat:@"Helper Installed into %@", appName] target:self set:nil get:nil detail:nil cell:PSStaticTextCell edit:nil]; [installedPersistenceHelperSpecifier setProperty:@NO forKey:@"enabled"]; installedPersistenceHelperSpecifier.identifier = @"persistenceHelperInstalled"; [_specifiers addObject:installedPersistenceHelperSpecifier]; PSSpecifier* uninstallPersistenceHelperSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Uninstall Persistence Helper" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; uninstallPersistenceHelperSpecifier.identifier = @"uninstallPersistenceHelper"; [uninstallPersistenceHelperSpecifier setProperty:@YES forKey:@"enabled"]; [uninstallPersistenceHelperSpecifier setProperty:NSClassFromString(@"PSDeleteButtonCell") forKey:@"cellClass"]; uninstallPersistenceHelperSpecifier.buttonAction = @selector(uninstallPersistenceHelperPressed); [_specifiers addObject:uninstallPersistenceHelperSpecifier]; } else { [persistenceGroupSpecifier setProperty:@"When iOS rebuilds the icon cache, all TrollStore apps including TrollStore itself will be reverted to \"User\" state and either disappear or no longer launch. The only way to have persistence in a rootless environment is to replace a system application, here you can select a system app to replace with a persistence helper that can be used to refresh the registrations of all TrollStore related apps in case they disappear or no longer launch." forKey:@"footerText"]; _installPersistenceHelperSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Install Persistence Helper" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; _installPersistenceHelperSpecifier.identifier = @"installPersistenceHelper"; [_installPersistenceHelperSpecifier setProperty:@YES forKey:@"enabled"]; _installPersistenceHelperSpecifier.buttonAction = @selector(installPersistenceHelperPressed); [_specifiers addObject:_installPersistenceHelperSpecifier]; } } #endif PSSpecifier* installationSettingsGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; installationSettingsGroupSpecifier.name = @"Security"; [installationSettingsGroupSpecifier setProperty:@"The URL Scheme, when enabled, will allow apps and websites to trigger TrollStore installations through the apple-magnifier://install?url= URL scheme and enable JIT through the apple-magnifier://enable-jit?bundle-id= URL scheme." forKey:@"footerText"]; [_specifiers addObject:installationSettingsGroupSpecifier]; PSSpecifier* URLSchemeToggle = [PSSpecifier preferenceSpecifierNamed:@"URL Scheme Enabled" target:self set:@selector(setURLSchemeEnabled:forSpecifier:) get:@selector(getURLSchemeEnabledForSpecifier:) detail:nil cell:PSSwitchCell edit:nil]; [_specifiers addObject:URLSchemeToggle]; PSSpecifier* installAlertConfigurationSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Show Install Confirmation Alert" target:self set:@selector(setPreferenceValue:specifier:) get:@selector(readPreferenceValue:) detail:nil cell:PSLinkListCell edit:nil]; installAlertConfigurationSpecifier.detailControllerClass = [PSListItemsController class]; [installAlertConfigurationSpecifier setProperty:@"installationConfirmationValues" forKey:@"valuesDataSource"]; [installAlertConfigurationSpecifier setProperty:@"installationConfirmationNames" forKey:@"titlesDataSource"]; [installAlertConfigurationSpecifier setProperty:APP_ID forKey:@"defaults"]; [installAlertConfigurationSpecifier setProperty:@"installAlertConfiguration" forKey:@"key"]; [installAlertConfigurationSpecifier setProperty:@0 forKey:@"default"]; [_specifiers addObject:installAlertConfigurationSpecifier]; PSSpecifier* otherGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; [otherGroupSpecifier setProperty:[NSString stringWithFormat:@"%@ %@\n\n© 2022-2024 Lars Fröder (opa334)\n\nTrollStore is NOT for piracy!\n\nCredits:\nGoogle TAG, @alfiecg_dev: CoreTrust bug\n@lunotech11, @SerenaKit, @tylinux, @TheRealClarity, @dhinakg, @khanhduytran0: Various contributions\n@ProcursusTeam: uicache, ldid\n@cstar_ow: uicache\n@saurik: ldid", APP_NAME, [self getTrollStoreVersion]] forKey:@"footerText"]; [_specifiers addObject:otherGroupSpecifier]; PSSpecifier* advancedLinkSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Advanced" target:self set:nil get:nil detail:nil cell:PSLinkListCell edit:nil]; advancedLinkSpecifier.detailControllerClass = [TSSettingsAdvancedListController class]; [advancedLinkSpecifier setProperty:@YES forKey:@"enabled"]; [_specifiers addObject:advancedLinkSpecifier]; PSSpecifier* donateSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Donate" target:self set:nil get:nil detail:nil cell:PSLinkListCell edit:nil]; donateSpecifier.detailControllerClass = [TSDonateListController class]; [donateSpecifier setProperty:@YES forKey:@"enabled"]; [_specifiers addObject:donateSpecifier]; #ifndef TROLLSTORE_LITE // Uninstall TrollStore PSSpecifier* uninstallTrollStoreSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Uninstall TrollStore" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; uninstallTrollStoreSpecifier.identifier = @"uninstallTrollStore"; [uninstallTrollStoreSpecifier setProperty:@YES forKey:@"enabled"]; [uninstallTrollStoreSpecifier setProperty:NSClassFromString(@"PSDeleteButtonCell") forKey:@"cellClass"]; uninstallTrollStoreSpecifier.buttonAction = @selector(uninstallTrollStorePressed); [_specifiers addObject:uninstallTrollStoreSpecifier]; #endif /*PSSpecifier* doTheDashSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Do the Dash" target:self set:nil get:nil detail:nil cell:PSButtonCell edit:nil]; doTheDashSpecifier.identifier = @"doTheDash"; [doTheDashSpecifier setProperty:@YES forKey:@"enabled"]; uninstallTrollStoreSpecifier.buttonAction = @selector(doTheDashPressed); [_specifiers addObject:doTheDashSpecifier];*/ } [(UINavigationItem *)self.navigationItem setTitle:@"Settings"]; return _specifiers; } - (NSArray*)installationConfirmationValues { return @[@0, @1, @2]; } - (NSArray*)installationConfirmationNames { return @[@"Always (Recommended)", @"Only on Remote URL Installs", @"Never (Not Recommeded)"]; } - (void)respringButtonPressed { respring(); } - (void)installOrUpdateLdidPressed { [TSInstallationController installLdid]; } - (void)enableDevModePressed { int ret = spawnRoot(rootHelperPath(), @[@"arm-dev-mode"], nil, nil); if (ret == 0) { UIAlertController* rebootNotification = [UIAlertController alertControllerWithTitle:@"Reboot Required" message:@"After rebooting, select \"Turn On\" to enable developer mode." preferredStyle:UIAlertControllerStyleAlert ]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action) { [self reloadSpecifiers]; }]; [rebootNotification addAction:closeAction]; UIAlertAction* rebootAction = [UIAlertAction actionWithTitle:@"Reboot Now" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { spawnRoot(rootHelperPath(), @[@"reboot"], nil, nil); }]; [rebootNotification addAction:rebootAction]; [TSPresentationDelegate presentViewController:rebootNotification animated:YES completion:nil]; } else { UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Error %d", ret] message:@"Failed to enable developer mode." preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [errorAlert addAction:closeAction]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; } } - (void)installPersistenceHelperPressed { NSMutableArray* appCandidates = [NSMutableArray new]; [[LSApplicationWorkspace defaultWorkspace] enumerateApplicationsOfType:1 block:^(LSApplicationProxy* appProxy) { if(appProxy.installed && !appProxy.restricted) { if([[NSFileManager defaultManager] fileExistsAtPath:[@"/System/Library/AppSignatures" stringByAppendingPathComponent:appProxy.bundleIdentifier]]) { NSURL* trollStoreMarkURL = [appProxy.bundleURL.URLByDeletingLastPathComponent URLByAppendingPathComponent:TS_ACTIVE_MARKER]; if(![trollStoreMarkURL checkResourceIsReachableAndReturnError:nil]) { [appCandidates addObject:appProxy]; } } } }]; UIAlertController* selectAppAlert = [UIAlertController alertControllerWithTitle:@"Select App" message:@"Select a system app to install the TrollStore Persistence Helper into. The normal function of the app will not be available, so it is recommended to pick something useless like the Tips app." preferredStyle:UIAlertControllerStyleActionSheet]; for(LSApplicationProxy* appProxy in appCandidates) { UIAlertAction* installAction = [UIAlertAction actionWithTitle:[appProxy localizedName] style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { spawnRoot(rootHelperPath(), @[@"install-persistence-helper", appProxy.bundleIdentifier], nil, nil); [self reloadSpecifiers]; }]; [selectAppAlert addAction:installAction]; } NSIndexPath* indexPath = [self indexPathForSpecifier:_installPersistenceHelperSpecifier]; UITableView* tableView = [self valueForKey:@"_table"]; selectAppAlert.popoverPresentationController.sourceView = tableView; selectAppAlert.popoverPresentationController.sourceRect = [tableView rectForRowAtIndexPath:indexPath]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; [selectAppAlert addAction:cancelAction]; [TSPresentationDelegate presentViewController:selectAppAlert animated:YES completion:nil]; } - (void)transferAppsPressed { UIAlertController *confirmationAlert = [UIAlertController alertControllerWithTitle:@"Transfer Apps" message:[NSString stringWithFormat:@"This option will transfer %zu apps from "OTHER_APP_NAME@" to "APP_NAME@". Continue?", trollStoreInactiveInstalledAppBundlePaths().count] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* transferAction = [UIAlertAction actionWithTitle:@"Transfer" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { [TSPresentationDelegate startActivity:@"Transfering"]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ { NSString *log; int transferRet = spawnRoot(rootHelperPath(), @[@"transfer-apps"], nil, &log); dispatch_async(dispatch_get_main_queue(), ^ { [TSPresentationDelegate stopActivityWithCompletion:^ { [self reloadSpecifiers]; if (transferRet != 0) { NSArray *remainingApps = trollStoreInactiveInstalledAppBundlePaths(); UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:@"Transfer Failed" message:[NSString stringWithFormat:@"Failed to transfer %zu %@", remainingApps.count, remainingApps.count > 1 ? @"apps" : @"app"] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* copyLogAction = [UIAlertAction actionWithTitle:@"Copy Debug Log" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { UIPasteboard* pasteboard = [UIPasteboard generalPasteboard]; pasteboard.string = log; }]; [errorAlert addAction:copyLogAction]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; [errorAlert addAction:closeAction]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; } }]; }); }); }]; [confirmationAlert addAction:transferAction]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; [confirmationAlert addAction:cancelAction]; [TSPresentationDelegate presentViewController:confirmationAlert animated:YES completion:nil]; } - (id)getURLSchemeEnabledForSpecifier:(PSSpecifier*)specifier { BOOL URLSchemeActive = (BOOL)[NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]; return @(URLSchemeActive); } - (void)setURLSchemeEnabled:(id)value forSpecifier:(PSSpecifier*)specifier { NSNumber* newValue = value; NSString* newStateString = [newValue boolValue] ? @"enable" : @"disable"; spawnRoot(rootHelperPath(), @[@"url-scheme", newStateString], nil, nil); UIAlertController* rebuildNoticeAlert = [UIAlertController alertControllerWithTitle:@"URL Scheme Changed" message:@"In order to properly apply the change of the URL scheme setting, rebuilding the icon cache is needed." preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* rebuildNowAction = [UIAlertAction actionWithTitle:@"Rebuild Now" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { [self rebuildIconCachePressed]; }]; [rebuildNoticeAlert addAction:rebuildNowAction]; UIAlertAction* rebuildLaterAction = [UIAlertAction actionWithTitle:@"Rebuild Later" style:UIAlertActionStyleCancel handler:nil]; [rebuildNoticeAlert addAction:rebuildLaterAction]; [TSPresentationDelegate presentViewController:rebuildNoticeAlert animated:YES completion:nil]; } - (void)doTheDashPressed { spawnRoot(rootHelperPath(), @[@"dash"], nil, nil); } - (void)setPreferenceValue:(NSObject*)value specifier:(PSSpecifier*)specifier { NSUserDefaults* tsDefaults = trollStoreUserDefaults(); [tsDefaults setObject:value forKey:[specifier propertyForKey:@"key"]]; } - (NSObject*)readPreferenceValue:(PSSpecifier*)specifier { NSUserDefaults* tsDefaults = trollStoreUserDefaults(); NSObject* toReturn = [tsDefaults objectForKey:[specifier propertyForKey:@"key"]]; if(!toReturn) { toReturn = [specifier propertyForKey:@"default"]; } return toReturn; } - (NSMutableArray*)argsForUninstallingTrollStore { NSMutableArray* args = @[@"uninstall-trollstore"].mutableCopy; NSNumber* uninstallationMethodToUseNum = [trollStoreUserDefaults() objectForKey:@"uninstallationMethod"]; int uninstallationMethodToUse = uninstallationMethodToUseNum ? uninstallationMethodToUseNum.intValue : 0; if(uninstallationMethodToUse == 1) { [args addObject:@"custom"]; } return args; } @end ================================================ FILE: TrollStore/control ================================================ Package: com.opa334.trollstore Name: TrollStore Version: 2.1 Architecture: iphoneos-arm Description: An awesome application! Maintainer: opa334 Author: opa334 Section: Utilities ================================================ FILE: TrollStore/entitlements.plist ================================================ application-identifier com.opa334.TrollStore platform-application com.apple.security.exception.files.absolute-path.read-write / com.apple.security.exception.iokit-user-client-class AGXDeviceUserClient IOSurfaceRootUserClient com.apple.private.security.no-sandbox com.apple.private.persona-mgmt com.apple.private.security.container-manager com.apple.private.coreservices.canmaplsdatabase com.apple.lsapplicationworkspace.rebuildappdatabases com.apple.private.MobileContainerManager.allowed com.apple.private.MobileInstallationHelperService.InstallDaemonOpsEnabled com.apple.private.MobileInstallationHelperService.allowed com.apple.private.uninstall.deletion com.apple.private.security.storage.MobileDocuments com.apple.CommCenter.fine-grained data-allowed-write com.apple.springboard.opensensitiveurl ================================================ FILE: TrollStore/main.m ================================================ #import #import "TSAppDelegate.h" #import "TSUtil.h" NSUserDefaults* trollStoreUserDefaults(void) { return [[NSUserDefaults alloc] initWithSuiteName:[NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"Library/Preferences/%@.plist", APP_ID]]]; } int main(int argc, char *argv[]) { @autoreleasepool { chineseWifiFixup(); return UIApplicationMain(argc, argv, nil, NSStringFromClass(TSAppDelegate.class)); } } ================================================ FILE: TrollStoreLite/.gitignore ================================================ Resources/trollstorehelper ================================================ FILE: TrollStoreLite/Makefile ================================================ TARGET := iphone:clang:16.5:14.0 INSTALL_TARGET_PROCESSES = TrollStoreLite ARCHS = arm64 include $(THEOS)/makefiles/common.mk APPLICATION_NAME = TrollStoreLite TrollStoreLite_FILES = $(wildcard ../TrollStore/*.m) $(wildcard ../Shared/*.m) TrollStoreLite_FRAMEWORKS = UIKit CoreGraphics CoreServices CoreTelephony TrollStoreLite_PRIVATE_FRAMEWORKS = Preferences MobileIcons MobileContainerManager TrollStoreLite_LIBRARIES = archive TrollStoreLite_CFLAGS = -fobjc-arc -I../Shared -I$(shell brew --prefix)/opt/libarchive/include -DTROLLSTORE_LITE TrollStoreLite_CODESIGN_FLAGS = -Sentitlements.plist include $(THEOS_MAKE_PATH)/application.mk ================================================ FILE: TrollStoreLite/Resources/Info.plist ================================================ CFBundleExecutable TrollStoreLite CFBundleDisplayName TrollStore Lite CFBundleIcons CFBundlePrimaryIcon CFBundleIconFiles AppIcon29x29 AppIcon40x40 AppIcon57x57 AppIcon60x60 UIPrerenderedIcon CFBundleIcons~ipad CFBundlePrimaryIcon CFBundleIconFiles AppIcon29x29 AppIcon40x40 AppIcon57x57 AppIcon60x60 AppIcon50x50 AppIcon72x72 AppIcon76x76 UIPrerenderedIcon CFBundleIdentifier com.opa334.TrollStoreLite CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType APPL CFBundleSignature ???? CFBundleSupportedPlatforms iPhoneOS CFBundleVersion 2.1 LSRequiresIPhoneOS UIDeviceFamily 1 2 UIRequiredDeviceCapabilities armv7 UILaunchStoryboardName LaunchScreen UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneConfigurationName Default Configuration UISceneDelegateClassName TSSceneDelegate UTImportedTypeDeclarations UTTypeConformsTo public.data UTTypeDescription iOS App UTTypeIconFiles UTTypeIdentifier com.apple.itunes.ipa UTTypeTagSpecification public.filename-extension ipa public.mime-type CFBundleDocumentTypes CFBundleTypeName iOS App LSHandlerRank Default LSItemContentTypes com.apple.itunes.ipa CFBundleTypeName AirDrop friendly iOS app CFBundleTypeRole Viewer LSHandlerRank Owner LSItemContentTypes com.opa334.trollstore.tipa UTExportedTypeDeclarations UTTypeIdentifier com.opa334.trollstore.tipa UTTypeDescription AirDrop friendly iOS app UTTypeConformsTo public.data UTTypeTagSpecification public.filename-extension tipa public.mime-type application/trollstore-ipa CFBundleURLTypes CFBundleURLName com.apple.Magnifier CFBundleURLSchemes apple-magnifier LSSupportsOpeningDocumentsInPlace TSRootBinaries trollstorehelper ================================================ FILE: TrollStoreLite/control ================================================ Package: com.opa334.trollstorelite Name: TrollStore Lite Version: 2.1 Architecture: iphoneos-arm Description: TrollStore for jailbroken iOS Depends: ldid Maintainer: opa334 Author: opa334 Section: Applications ================================================ FILE: TrollStoreLite/entitlements.plist ================================================ application-identifier com.opa334.TrollStore platform-application com.apple.security.exception.files.absolute-path.read-write / com.apple.security.exception.iokit-user-client-class AGXDeviceUserClient IOSurfaceRootUserClient com.apple.private.security.no-sandbox com.apple.private.persona-mgmt com.apple.private.security.container-manager com.apple.private.coreservices.canmaplsdatabase com.apple.lsapplicationworkspace.rebuildappdatabases com.apple.private.MobileContainerManager.allowed com.apple.private.MobileInstallationHelperService.InstallDaemonOpsEnabled com.apple.private.MobileInstallationHelperService.allowed com.apple.private.uninstall.deletion com.apple.private.security.storage.MobileDocuments com.apple.CommCenter.fine-grained data-allowed-write com.apple.springboard.opensensitiveurl ================================================ FILE: Victim/README.md ================================================ # Victim IPA and Cert In order to compile a pwned TrollHelperOTA arm64 IPA, you need to provide a dev cert with the same team ID as your victim app in this directory. ```bash ./make_cert.sh ``` ================================================ FILE: Victim/make_cert.sh ================================================ set -e export PATH="/opt/homebrew/Cellar/openssl@3/3.0.5/bin:$PATH" true && openssl req -newkey rsa:2048 -nodes -keyout root_key.pem -x509 -days 3650 -out root_certificate.pem \ -subj "/C=CA/O=TrollStore/OU=$1/CN=TrollStore iPhone Root CA" \ -addext "1.2.840.113635.100.6.2.18=DER:0500" \ -addext "basicConstraints=critical, CA:true" -addext "keyUsage=critical, digitalSignature, keyCertSign, cRLSign" true && openssl req -newkey rsa:2048 -nodes -keyout codeca_key.pem -out codeca_certificate.csr \ -subj "/C=CA/O=TrollStore/OU=$1/CN=TrollStore iPhone Certification Authority" \ -addext "1.2.840.113635.100.6.2.18=DER:0500" \ -addext "basicConstraints=critical, CA:true" -addext "keyUsage=critical, keyCertSign, cRLSign" true && openssl x509 -req -CAkey root_key.pem -CA root_certificate.pem -days 3650 \ -in codeca_certificate.csr -out codeca_certificate.pem -CAcreateserial -copy_extensions copyall true && openssl req -newkey rsa:2048 -nodes -keyout dev_key.pem -out dev_certificate.csr \ -subj "/C=CA/O=TrollStore/OU=$1/CN=TrollStore iPhone OS Application Signing" \ -addext "basicConstraints=critical, CA:false" \ -addext "keyUsage = critical, digitalSignature" -addext "extendedKeyUsage = codeSigning" \ -addext "1.2.840.113635.100.6.1.3=DER:0500" true && openssl x509 -req -CAkey codeca_key.pem -CA codeca_certificate.pem -days 3650 \ -in dev_certificate.csr -out dev_certificate.pem -CAcreateserial -copy_extensions copyall true && cat codeca_certificate.pem root_certificate.pem >certificate_chain.pem true && /usr/bin/openssl pkcs12 -export -in dev_certificate.pem -inkey dev_key.pem -certfile certificate_chain.pem \ -keypbe NONE -certpbe NONE -passout pass: \ -out victim.p12 -name "TrollStore iPhone OS Application Signing" rm certificate_chain.pem rm codeca_certificate.csr rm codeca_certificate.pem rm codeca_key.pem rm dev_certificate.csr rm dev_certificate.pem rm dev_key.pem rm root_certificate.pem rm root_key.pem