Showing preview only (322K chars total). Download the full file or copy to clipboard to get everything.
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 <opa334@protonmail.com>
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 <team@procurs.us>
Modified work Copyright (c) 2022-2024 Lars Fröder <opa334@protonmail.com>
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 <Foundation/Foundation.h>
#import <mach-o/loader.h>
#import <mach-o/fat.h>
#import <sys/stat.h>
#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 <path/to/binary>\n\nInject target slice into victim binary:\npwnify pwn(64e) <path/to/victim/binary> <path/to/target/binary>\n\nModify cpusubtype of a non FAT binary:\npwnify set-cpusubtype <path/to/binary> <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=<URL_to_IPA>`
- `apple-magnifier://enable-jit?bundle-id=<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<path/to/entitlements.plist> <path/to/binary>`) 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
<key>com.apple.private.security.container-required</key>
<false/>
```
```xml
<key>com.apple.private.security.no-container</key>
<true/>
```
```xml
<key>com.apple.private.security.no-sandbox</key>
<true/>
```
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
<key>platform-application</key>
<true/>
```
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
<key>com.apple.private.security.storage.AppDataContainers</key>
<true/>
```
### 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
<key>com.apple.private.persona-mgmt</key>
<true/>
```
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 <Foundation/Foundation.h>
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>platform-application</key>
<true/>
<key>com.apple.private.security.container-required</key>
<false/>
<key>com.apple.private.security.no-sandbox</key>
<true/>
<key>com.apple.private.security.container-manager</key>
<true/>
<key>com.apple.private.MobileContainerManager.allowed</key>
<true/>
<key>com.apple.private.coreservices.canmaplsdatabase</key>
<true/>
<key>com.apple.lsapplicationworkspace.rebuildappdatabases</key>
<true/>
<key>com.apple.private.security.storage.AppBundles</key>
<true/>
<key>com.apple.private.security.storage.MobileDocuments</key>
<true/>
<key>com.apple.private.security.storage-exempt.heritable</key>
<true/>
<key>com.apple.private.MobileInstallationHelperService.InstallDaemonOpsEnabled</key>
<true/>
<key>com.apple.private.MobileInstallationHelperService.allowed</key>
<true/>
<key>com.apple.private.uninstall.deletion</key>
<true/>
<key>com.apple.springboard.launchapplications</key>
<true/>
<key>com.apple.backboardd.launchapplications</key>
<true/>
<key>com.apple.frontboard.launchapplications</key>
<true/>
<key>com.apple.multitasking.termination</key>
<true/>
<key>com.apple.private.mobileinstall.allowedSPI</key>
<array>
<string>InstallForLaunchServices</string>
<string>Install</string>
<string>UninstallForLaunchServices</string>
<string>Uninstall</string>
<string>UpdatePlaceholderMetadata</string>
</array>
<key>com.apple.private.amfi.developer-mode-control</key>
<true/>
<key>com.apple.frontboard.shutdown</key>
<true/>
<key>com.apple.runningboard.process-state</key>
<true/>
</dict>
</plist>
================================================
FILE: RootHelper/jit.h
================================================
#import <Foundation/Foundation.h>
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 <stdio.h>
#import "unarchive.h"
@import Foundation;
#import "uicache.h"
#import <sys/stat.h>
#import <dlfcn.h>
#import <spawn.h>
#import <objc/runtime.h>
#import <TSUtil.h>
#import <sys/utsname.h>
#import <mach-o/loader.h>
#import <mach-o/fat.h>
#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 <SpringBoardServices/SpringBoardServices.h>
#import <FrontBoardServices/FBSSystemService.h>
#import <Security/Security.h>
#import <libroot.h>
#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<LSApplicationProxy*>* applicationsWithGroupId(NSString* groupId)
{
LSEnumerator* enumerator = [LSEnumerator enumeratorForApplicationProxiesWithOptions:0];
enumerator.predicate = [NSPredicate predicateWithFormat:@"groupContainerURLs[%@] != nil", groupId];
return enumerator.allObjects;
}
NSSet<NSString*>* 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<NSString*>* 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=<bundle-identifier> 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<LSApplicationProxy*>* 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 <objc/runtime.h>
#import "dlfcn.h"
#import <TSUtil.h>
#import <version.h>
// uicache on steroids
extern NSSet<NSString*>* 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 <archive.h>
#include <archive_entry.h>
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 <NSObject>
@optional
- (void)applicationsDidInstall:(NSArray <LSApplicationProxy *>*)apps;
- (void)applicationsDidUninstall:(NSArray <LSApplicationProxy *>*)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 <UIKit/UIKit.h>
#import <Preferences/PSListController.h>
#import <Preferences/PSSpecifier.h>
@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 <UIKit/UIKit.h>
@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 <UIKit/UIAlertController.h>
@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 <Foundation/Foundation.h>
#import <spawn.h>
#import <sys/sysctl.h>
#import <mach-o/dyld.h>
#import <libroot.h>
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>TrollStorePersistenceHelper</string>
<key>CFBundleDisplayName</key>
<string>TrollHelper</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon29x29</string>
<string>AppIcon40x40</string>
<string>AppIcon57x57</string>
<string>AppIcon60x60</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
</dict>
<key>CFBundleIcons~ipad</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon29x29</string>
<string>AppIcon40x40</string>
<string>AppIcon57x57</string>
<string>AppIcon60x60</string>
<string>AppIcon50x50</string>
<string>AppIcon72x72</string>
<string>AppIcon76x76</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
</dict>
<key>CFBundleIdentifier</key>
<string>com.opa334.trollstorepersistencehelper</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>2.1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
================================================
FILE: TrollHelper/TSHAppDelegateNoScene.h
================================================
#import <UIKit/UIKit.h>
@interface TSHAppDelegateNoScene : UIResponder <UIApplicationDelegate>
@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 <UIKit/UIKit.h>
@interface TSHAppDelegateWithScene : UIResponder <UIApplicationDelegate>
@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<UISceneSession *> *)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 <TSListControllerShared.h>
@interface TSHRootViewController : TSListControllerShared
{
NSString* _newerVersion;
}
@end
================================================
FILE: TrollHelper/TSHRootViewController.m
================================================
#import "TSHRootViewController.h"
#import <TSUtil.h>
#import <TSPresentationDelegate.h>
@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 <UIKit/UIKit.h>
@interface TSHSceneDelegate : UIResponder <UIWindowSceneDelegate>
@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<UIOpenURLContext *> *)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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>com.opa334.trollstorepersistencehelper</string>
<key>com.apple.CommCenter.fine-grained</key>
<array>
<string>data-allowed-write</string>
</array>
<key>com.apple.private.persona-mgmt</key>
<true/>
<!-- All entitlements from RootHelper except for com.apple.private.security.container-required=false -->
<key>platform-application</key>
<true/>
<key>com.apple.private.security.no-sandbox</key>
<true/>
<key>com.apple.private.security.container-manager</key>
<true/>
<key>com.apple.private.MobileContainerManager.allowed</key>
<true/>
<key>com.apple.private.coreservices.canmaplsdatabase</key>
<true/>
<key>com.apple.lsapplicationworkspace.rebuildappdatabases</key>
<true/>
<key>com.apple.private.security.storage.AppBundles</key>
<true/>
<key>com.apple.private.security.storage.MobileDocuments</key>
<true/>
<key>com.apple.private.security.storage-exempt.heritable</key>
<true/>
<key>com.apple.private.MobileInstallationHelperService.InstallDaemonOpsEnabled</key>
<true/>
<key>com.apple.private.MobileInstallationHelperService.allowed</key>
<true/>
<key>com.apple.private.uninstall.deletion</key>
<true/>
<key>com.apple.springboard.launchapplications</key>
<true/>
<key>com.apple.backboardd.launchapplications</key>
<true/>
<key>com.apple.frontboard.launchapplications</key>
<true/>
<key>com.apple.multitasking.termination</key>
<true/>
<key>com.apple.private.mobileinstall.allowedSPI</key>
<array>
<string>InstallForLaunchServices</string>
<string>Install</string>
<string>UninstallForLaunchServices</string>
<string>Uninstall</string>
<string>UpdatePlaceholderMetadata</string>
</array>
</dict>
</plist>
================================================
FILE: TrollHelper/main.m
================================================
#import <Foundation/Foundation.h>
#import "TSHAppDelegateNoScene.h"
#import "TSHAppDelegateWithScene.h"
#import "TSHSceneDelegate.h"
#import <TSUtil.h>
#import <objc/runtime.h>
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>TrollStore</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon29x29</string>
<string>AppIcon40x40</string>
<string>AppIcon57x57</string>
<string>AppIcon60x60</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
</dict>
<key>CFBundleIcons~ipad</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon29x29</string>
<string>AppIcon40x40</string>
<string>AppIcon57x57</string>
<string>AppIcon60x60</string>
<string>AppIcon50x50</string>
<string>AppIcon72x72</string>
<string>AppIcon76x76</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
</dict>
<key>CFBundleIdentifier</key>
<string>com.opa334.TrollStore</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>2.1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>TSSceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>iOS App</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>com.apple.itunes.ipa</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>ipa</string>
</array>
<key>public.mime-type</key>
<array/>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>iOS App</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>com.apple.itunes.ipa</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>TrollStore Update</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>public.tar-archive</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>AirDrop friendly iOS app</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.opa334.trollstore.tipa</string>
</array>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>com.opa334.trollstore.tipa</string>
<key>UTTypeDescription</key>
<string>AirDrop friendly iOS app</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>tipa</string>
</array>
<key>public.mime-type</key>
<string>application/trollstore-ipa</string>
</dict>
</dict>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.apple.Magnifier</string>
<key>CFBundleURLSchemes</key>
<array>
<string>apple-magnifier</string>
</array>
</dict>
</array>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
<key>TSRootBinaries</key>
<array>
<string>trollstorehelper</string>
<string>ldid</string>
</array>
</dict>
</plist>
================================================
FILE: TrollStore/TSAppDelegate.h
================================================
#import <UIKit/UIKit.h>
@interface TSAppDelegate : UIResponder <UIApplicationDelegate>
@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<UISceneSession *> *)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 <Foundation/Foundation.h>
#import <archive.h>
#import <archive_entry.h>
@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 <TSUtil.h>
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;
N
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
SYMBOL INDEX (6 symbols across 4 files) FILE: Shared/TSUtil.h function interface (line 47) | interface UIAlertController (Private) type EXPLOIT_TYPE (line 65) | typedef enum type __SecCode (line 78) | struct __SecCode FILE: TrollHelper/TSHRootViewController.h function interface (line 3) | interface TSHRootViewController : TSListControllerShared FILE: TrollStore/TSAppInfo.h function interface (line 13) | interface TSAppInfo : NSObject FILE: TrollStore/TSSettingsListController.h function interface (line 3) | interface TSSettingsListController : TSListControllerShared
Condensed preview — 83 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (340K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug-report-issue-template.yml",
"chars": 2429,
"preview": "name: Bug Report\ndescription: Bug Report (No Duplicates Issue Here!)\nbody:\n - type: markdown\n attributes:\n valu"
},
{
"path": ".gitignore",
"chars": 92,
"preview": "out/\n.DS_Store\n.theos/\npackages/\nxcuserdata\n.vscode\nPwnify/pwnify\nInstallerVictim.ipa\n_build"
},
{
"path": ".gitmodules",
"chars": 73,
"preview": "[submodule \"ChOma\"]\n\tpath = ChOma\n\turl = https://github.com/opa334/ChOma\n"
},
{
"path": "LICENSE",
"chars": 3223,
"preview": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: TrollStore\nUpstream-Contact: o"
},
{
"path": "Makefile",
"chars": 4366,
"preview": "TOPTARGETS := all clean update\n\n$(TOPTARGETS): pre_build make_fastPathSign make_roothelper make_trollstore make_trollhel"
},
{
"path": "Pwnify/Makefile",
"chars": 232,
"preview": "pwnify:\n\t@clang main.m -fobjc-arc -fmodules -mmacosx-version-min=11.0 -o pwnify\n\ninstall: pwnify\n\t-@sudo rm /usr/local/b"
},
{
"path": "Pwnify/main.m",
"chars": 13189,
"preview": "//\n// main.m\n// pwnify-universal\n//\n// Created by Lars Fröder on 08.10.22.\n//\n\n#import <Foundation/Foundation.h>\n\n#im"
},
{
"path": "README.md",
"chars": 6167,
"preview": "# TrollStore\n\nTrollStore is a permasigned jailed app that can permanently install any IPA you open in it.\n\nIt works beca"
},
{
"path": "RootHelper/Makefile",
"chars": 1256,
"preview": "TARGET := iphone:clang:16.5:14.0\nARCHS = arm64\n\nifdef TROLLSTORE_LITE\nHELPER_NAME = trollstorehelper_lite\nelse\nHELPER_NA"
},
{
"path": "RootHelper/control",
"chars": 220,
"preview": "Package: com.opa334.trollstoreroothelper\nName: trollstoreroothelper\nVersion: 2.1\nArchitecture: iphoneos-arm\nDescription:"
},
{
"path": "RootHelper/devmode.h",
"chars": 110,
"preview": "#import <Foundation/Foundation.h>\n\nBOOL checkDeveloperMode(void);\nBOOL armDeveloperMode(BOOL* alreadyEnabled);"
},
{
"path": "RootHelper/devmode.m",
"chars": 5218,
"preview": "@import Foundation;\n\n#ifndef __XPC_H__\n// Types\ntypedef NSObject* xpc_object_t;\ntypedef xpc_object_t xpc_connection_t;\nt"
},
{
"path": "RootHelper/entitlements.plist",
"chars": 1761,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "RootHelper/jit.h",
"chars": 70,
"preview": "#import <Foundation/Foundation.h>\n\nint enableJIT(NSString *bundleID);\n"
},
{
"path": "RootHelper/jit.m",
"chars": 936,
"preview": "@import Foundation;\n@import Darwin;\n\n@interface RBSProcessPredicate\n+ (instancetype)predicateMatchingBundleIdentifier:(N"
},
{
"path": "RootHelper/main.m",
"chars": 61079,
"preview": "#import <stdio.h>\n#import \"unarchive.h\"\n@import Foundation;\n#import \"uicache.h\"\n#import <sys/stat.h>\n#import <dlfcn.h>\n#"
},
{
"path": "RootHelper/uicache.h",
"chars": 76,
"preview": "extern bool registerPath(NSString *path, BOOL unregister, BOOL forceSystem);"
},
{
"path": "RootHelper/uicache.m",
"chars": 10338,
"preview": "@import Foundation;\n@import CoreServices;\n#import \"CoreServices.h\"\n#import <objc/runtime.h>\n#import \"dlfcn.h\"\n#import <T"
},
{
"path": "RootHelper/unarchive.h",
"chars": 91,
"preview": "@import Foundation;\n\nextern int extract(NSString* fileToExtract, NSString* extractionPath);"
},
{
"path": "RootHelper/unarchive.m",
"chars": 2605,
"preview": "#import \"unarchive.h\"\n\n#include <archive.h>\n#include <archive_entry.h>\n\nstatic int\ncopy_data(struct archive *ar, struct "
},
{
"path": "Shared/CoreServices.h",
"chars": 2840,
"preview": "extern NSString *LSInstallTypeKey;\n\n@interface LSBundleProxy\n@property (nonatomic,readonly) NSString * bundleIdentifier;"
},
{
"path": "Shared/TSListControllerShared.h",
"chars": 603,
"preview": "#import <UIKit/UIKit.h>\n#import <Preferences/PSListController.h>\n#import <Preferences/PSSpecifier.h>\n\n@interface TSListC"
},
{
"path": "Shared/TSListControllerShared.m",
"chars": 6947,
"preview": "#import \"TSListControllerShared.h\"\n#import \"TSUtil.h\"\n#import \"TSPresentationDelegate.h\"\n\n@implementation TSListControll"
},
{
"path": "Shared/TSPresentationDelegate.h",
"chars": 525,
"preview": "#import <UIKit/UIKit.h>\n\n@interface TSPresentationDelegate : NSObject\n@property (class) UIViewController* presentationVi"
},
{
"path": "Shared/TSPresentationDelegate.m",
"chars": 2215,
"preview": "#import \"TSPresentationDelegate.h\"\n\n@implementation TSPresentationDelegate\n\nstatic UIViewController* g_presentationViewC"
},
{
"path": "Shared/TSUtil.h",
"chars": 4190,
"preview": "@import Foundation;\n#import \"CoreServices.h\"\n\n#define TrollStoreErrorDomain @\"TrollStoreErrorDomain\"\n\n#define TS_MARKER "
},
{
"path": "Shared/TSUtil.m",
"chars": 17627,
"preview": "#import \"TSUtil.h\"\n\n#import <Foundation/Foundation.h>\n#import <spawn.h>\n#import <sys/sysctl.h>\n#import <mach-o/dyld.h>\n#"
},
{
"path": "TrollHelper/Makefile",
"chars": 1374,
"preview": "export EMBEDDED_ROOT_HELPER ?= 0\nexport LEGACY_CT_BUG ?= 0\n\nTARGET := iphone:clang:16.5:14.0\nINSTALL_TARGET_PROCESSES = "
},
{
"path": "TrollHelper/Resources/Info.plist",
"chars": 2286,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "TrollHelper/TSHAppDelegateNoScene.h",
"chars": 222,
"preview": "#import <UIKit/UIKit.h>\n\n@interface TSHAppDelegateNoScene : UIResponder <UIApplicationDelegate>\n@property (nonatomic, st"
},
{
"path": "TrollHelper/TSHAppDelegateNoScene.m",
"chars": 516,
"preview": "#import \"TSHAppDelegateNoScene.h\"\n#import \"TSHRootViewController.h\"\n\n@implementation TSHAppDelegateNoScene\n\n- (BOOL)appl"
},
{
"path": "TrollHelper/TSHAppDelegateWithScene.h",
"chars": 104,
"preview": "\n#import <UIKit/UIKit.h>\n\n@interface TSHAppDelegateWithScene : UIResponder <UIApplicationDelegate>\n\n@end"
},
{
"path": "TrollHelper/TSHAppDelegateWithScene.m",
"chars": 1107,
"preview": "#import \"TSHAppDelegateWithScene.h\"\n\n@implementation TSHAppDelegateWithScene\n\n- (BOOL)application:(UIApplication *)appli"
},
{
"path": "TrollHelper/TSHRootViewController.h",
"chars": 132,
"preview": "#import <TSListControllerShared.h>\n\n@interface TSHRootViewController : TSListControllerShared\n{\n NSString* _newerVers"
},
{
"path": "TrollHelper/TSHRootViewController.m",
"chars": 8885,
"preview": "#import \"TSHRootViewController.h\"\n#import <TSUtil.h>\n#import <TSPresentationDelegate.h>\n\n@implementation TSHRootViewCont"
},
{
"path": "TrollHelper/TSHSceneDelegate.h",
"chars": 218,
"preview": "#import <UIKit/UIKit.h>\n\n@interface TSHSceneDelegate : UIResponder <UIWindowSceneDelegate>\n@property (strong, nonatomic)"
},
{
"path": "TrollHelper/TSHSceneDelegate.m",
"chars": 2413,
"preview": "#import \"TSHSceneDelegate.h\"\n#import \"TSHRootViewController.h\"\n\n@implementation TSHSceneDelegate\n\n- (void)scene:(UIScene"
},
{
"path": "TrollHelper/control",
"chars": 219,
"preview": "Package: com.opa334.trollstorehelper\nName: TrollStore Helper\nVersion: 2.1\nArchitecture: iphoneos-arm\nDescription: Helper"
},
{
"path": "TrollHelper/entitlements.plist",
"chars": 1870,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "TrollHelper/main.m",
"chars": 2643,
"preview": "#import <Foundation/Foundation.h>\n#import \"TSHAppDelegateNoScene.h\"\n#import \"TSHAppDelegateWithScene.h\"\n#import \"TSHScen"
},
{
"path": "TrollStore/Makefile",
"chars": 649,
"preview": "TARGET := iphone:clang:16.5:14.0\nINSTALL_TARGET_PROCESSES = TrollStore\nARCHS = arm64\n\nTARGET_CODESIGN = ../Exploits/fast"
},
{
"path": "TrollStore/Resources/Info.plist",
"chars": 4931,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "TrollStore/TSAppDelegate.h",
"chars": 94,
"preview": "#import <UIKit/UIKit.h>\n\n@interface TSAppDelegate : UIResponder <UIApplicationDelegate>\n\n@end\n"
},
{
"path": "TrollStore/TSAppDelegate.m",
"chars": 1121,
"preview": "#import \"TSAppDelegate.h\"\n#import \"TSRootViewController.h\"\n\n@implementation TSAppDelegate\n\n- (BOOL)application:(UIApplic"
},
{
"path": "TrollStore/TSAppInfo.h",
"chars": 1350,
"preview": "//\n// TSIPAInfo.h\n// IPAInfo\n//\n// Created by Lars Fröder on 22.10.22.\n//\n\n#import <Foundation/Foundation.h>\n#import "
},
{
"path": "TrollStore/TSAppInfo.m",
"chars": 35736,
"preview": "#import \"TSAppInfo.h\"\n#import \"TSCommonTCCServiceNames.h\"\n#import <TSUtil.h>\n\nextern CGImageRef LICreateIconForImage(CGI"
},
{
"path": "TrollStore/TSAppTableViewController.h",
"chars": 411,
"preview": "#import <UIKit/UIKit.h>\n#import \"TSAppInfo.h\"\n#import <CoreServices.h>\n\n@interface TSAppTableViewController : UITableVie"
},
{
"path": "TrollStore/TSAppTableViewController.m",
"chars": 19541,
"preview": "#import \"TSAppTableViewController.h\"\n\n#import \"TSApplicationsManager.h\"\n#import <TSPresentationDelegate.h>\n#import \"TSIn"
},
{
"path": "TrollStore/TSApplicationsManager.h",
"chars": 832,
"preview": "#import <Foundation/Foundation.h>\n\n#define TROLLSTORE_ROOT_PATH @\"/var/containers/Bundle/TrollStore\"\n#define TROLLSTORE_"
},
{
"path": "TrollStore/TSApplicationsManager.m",
"chars": 6711,
"preview": "#import \"TSApplicationsManager.h\"\n#import <TSUtil.h>\nextern NSUserDefaults* trollStoreUserDefaults();\n\n@implementation T"
},
{
"path": "TrollStore/TSCommonTCCServiceNames.h",
"chars": 1190,
"preview": "//\n// TSCommonTCCServiceNames.h\n// IPAInfo\n//\n// Created by Luke Noble on 30.10.22.\n//\n\n#import <Foundation/Foundatio"
},
{
"path": "TrollStore/TSDonateListController.h",
"chars": 100,
"preview": "#import <Preferences/PSListController.h>\n\n@interface TSDonateListController : PSListController\n\n@end"
},
{
"path": "TrollStore/TSDonateListController.m",
"chars": 2382,
"preview": "#import \"TSDonateListController.h\"\n#import <Preferences/PSSpecifier.h>\n\n@implementation TSDonateListController\n\n\n- (void"
},
{
"path": "TrollStore/TSInstallationController.h",
"chars": 594,
"preview": "@import Foundation;\n\n@interface TSInstallationController : NSObject\n\n+ (void)presentInstallationAlertIfEnabledForFile:(N"
},
{
"path": "TrollStore/TSInstallationController.m",
"chars": 10615,
"preview": "#import \"TSInstallationController.h\"\n\n#import \"TSApplicationsManager.h\"\n#import \"TSAppInfo.h\"\n#import <TSUtil.h>\n#import"
},
{
"path": "TrollStore/TSRootViewController.h",
"chars": 84,
"preview": "#import <UIKit/UIKit.h>\n\n@interface TSRootViewController : UITabBarController\n\n@end\n"
},
{
"path": "TrollStore/TSRootViewController.m",
"chars": 1132,
"preview": "#import \"TSRootViewController.h\"\n#import \"TSAppTableViewController.h\"\n#import \"TSSettingsListController.h\"\n#import <TSPr"
},
{
"path": "TrollStore/TSSceneDelegate.h",
"chars": 214,
"preview": "#import <UIKit/UIKit.h>\n\n@interface TSSceneDelegate : UIResponder <UIWindowSceneDelegate>\n@property (strong, nonatomic) "
},
{
"path": "TrollStore/TSSceneDelegate.m",
"chars": 7274,
"preview": "#import \"TSSceneDelegate.h\"\n#import \"TSRootViewController.h\"\n#import \"TSUtil.h\"\n#import \"TSApplicationsManager.h\"\n#impor"
},
{
"path": "TrollStore/TSSettingsAdvancedListController.h",
"chars": 110,
"preview": "#import <Preferences/PSListController.h>\n\n@interface TSSettingsAdvancedListController : PSListController\n\n@end"
},
{
"path": "TrollStore/TSSettingsAdvancedListController.m",
"chars": 5643,
"preview": "#import \"TSSettingsAdvancedListController.h\"\n#import \"TSUtil.h\"\n#import <Preferences/PSSpecifier.h>\n\nextern NSUserDefaul"
},
{
"path": "TrollStore/TSSettingsListController.h",
"chars": 246,
"preview": "#import \"TSListControllerShared.h\"\n\n@interface TSSettingsListController : TSListControllerShared\n{\n PSSpecifier* _ins"
},
{
"path": "TrollStore/TSSettingsListController.m",
"chars": 26727,
"preview": "#import \"TSSettingsListController.h\"\n#import <TSUtil.h>\n#import <Preferences/PSSpecifier.h>\n#import <Preferences/PSListI"
},
{
"path": "TrollStore/control",
"chars": 178,
"preview": "Package: com.opa334.trollstore\nName: TrollStore\nVersion: 2.1\nArchitecture: iphoneos-arm\nDescription: An awesome applicat"
},
{
"path": "TrollStore/entitlements.plist",
"chars": 1439,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "TrollStore/main.m",
"chars": 468,
"preview": "#import <Foundation/Foundation.h>\n#import \"TSAppDelegate.h\"\n#import \"TSUtil.h\"\n\nNSUserDefaults* trollStoreUserDefaults(v"
},
{
"path": "TrollStoreLite/.gitignore",
"chars": 26,
"preview": "Resources/trollstorehelper"
},
{
"path": "TrollStoreLite/Makefile",
"chars": 643,
"preview": "TARGET := iphone:clang:16.5:14.0\nINSTALL_TARGET_PROCESSES = TrollStoreLite\nARCHS = arm64\n\ninclude $(THEOS)/makefiles/com"
},
{
"path": "TrollStoreLite/Resources/Info.plist",
"chars": 4744,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "TrollStoreLite/control",
"chars": 210,
"preview": "Package: com.opa334.trollstorelite\nName: TrollStore Lite\nVersion: 2.1\nArchitecture: iphoneos-arm\nDescription: TrollStore"
},
{
"path": "TrollStoreLite/entitlements.plist",
"chars": 1439,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Victim/README.md",
"chars": 206,
"preview": "# Victim IPA and Cert\n\nIn order to compile a pwned TrollHelperOTA arm64 IPA, you need to provide a dev cert with the sam"
},
{
"path": "Victim/make_cert.sh",
"chars": 1948,
"preview": "set -e\nexport PATH=\"/opt/homebrew/Cellar/openssl@3/3.0.5/bin:$PATH\"\n\ntrue && openssl req -newkey rsa:2048 -nodes -keyout"
}
]
// ... and 10 more files (download for full content)
About this extraction
This page contains the full source code of the opa334/TrollStore GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 83 files (302.2 KB), approximately 81.6k tokens, and a symbol index with 6 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.