diff --git a/Makefile b/Makefile index 6292bc5..8d1a053 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,8 @@ make_trollhelper_embedded: assemble_trollstore: @cp cert.p12 ./TrollStore/.theos/obj/TrollStore.app/cert.p12 + @cp ./Victim/Victim.p12 ./TrollStore/.theos/obj/TrollStore.app/user_cert.p12 + @cp ./Victim/victim ./TrollStore/.theos/obj/TrollStore.app/user_victim @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 diff --git a/Pwnify/Makefile b/Pwnify/Makefile index 20c3d10..a8593cc 100644 --- a/Pwnify/Makefile +++ b/Pwnify/Makefile @@ -1,5 +1,5 @@ pwnify: - @clang main.m -fobjc-arc -fmodules -mmacosx-version-min=11.0 -o pwnify + @clang main.m ../Shared/libPwnify.m -I../Shared -fobjc-arc -fmodules -mmacosx-version-min=11.0 -o pwnify install: pwnify -@sudo rm /usr/local/bin/pwnify 2>/dev/null || true diff --git a/Pwnify/main.m b/Pwnify/main.m index 4843bc1..782b679 100644 --- a/Pwnify/main.m +++ b/Pwnify/main.m @@ -6,91 +6,16 @@ // #import +#import #import #import #import -#define ALIGN_DEFAULT 0xE - -uint32_t roundUp(int numToRound, int multiple) -{ - if (multiple == 0) - return numToRound; - - int remainder = numToRound % multiple; - if (remainder == 0) - return numToRound; - - return numToRound + multiple - remainder; -} - -void expandFile(FILE* file, uint32_t size) -{ - fseek(file, 0, SEEK_END); - if(ftell(file) >= size) return; - - while(ftell(file) != size) - { - char c = 0; - fwrite(&c, 1, 1, file); - } -} - -void copyData(FILE* sourceFile, FILE* targetFile, size_t size) -{ - for(size_t i = 0; i < size; i++) - { - char b; - fread(&b, 1, 1, sourceFile); - fwrite(&b, 1, 1, targetFile); - } -} - -void enumerateArchs(NSString* binaryPath, void (^archEnumBlock)(struct fat_arch* arch, uint32_t archFileOffset, struct mach_header* machHeader, uint32_t sliceFileOffset, FILE* file, BOOL* stop)) -{ - FILE* machoFile = fopen(binaryPath.UTF8String, "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) { + pwnify_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)); @@ -101,267 +26,6 @@ void printArchs(NSString* binaryPath) }); } -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.UTF8String, "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.UTF8String, "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.UTF8String, "rb"); - fseek(binaryToInjectFile, offset, SEEK_SET); - copyData(binaryToInjectFile, tmpFile, size); - fclose(binaryToInjectFile); - *stop = YES; - } - } - }); - - fclose(tmpFile); - chmod(tmpFilePath.UTF8String, 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.UTF8String, "rb+"); - if(!binaryFile) - { - printf("ERROR: File not found\n"); - return; - } - - enumerateArchs(binaryPath, ^(struct fat_arch *arch, uint32_t archFileOffset, struct mach_header *machHeader, uint32_t sliceFileOffset, FILE *file, BOOL *stop) { - - if(arch) - { - if(OSSwapBigToHostInt(arch->cputype) == 0x100000C) - { - if(OSSwapBigToHostInt(arch->cpusubtype) == 0x0) - { - arch->cpusubtype = OSSwapHostToBigInt32(subtype); - fseek(binaryFile, archFileOffset, SEEK_SET); - fwrite(arch, sizeof(struct fat_arch), 1, binaryFile); - } - } - } - - if(OSSwapLittleToHostInt32(machHeader->cputype) == 0x100000C) - { - if(OSSwapLittleToHostInt32(machHeader->cpusubtype) == 0x0) - { - machHeader->cpusubtype = OSSwapHostToLittleInt32(subtype); - fseek(binaryFile, sliceFileOffset, SEEK_SET); - fwrite(machHeader, sizeof(struct mach_header), 1, binaryFile); - } - } - }); - - fclose(binaryFile); -} - void printUsageAndExit(void) { printf("Usage:\n\nPrint architectures of a binary:\npwnify print \n\nInject target slice into victim binary:\npwnify pwn(64e) \n\nModify cpusubtype of a non FAT binary:\npwnify set-cpusubtype \n"); @@ -387,14 +51,14 @@ int main(int argc, const char * argv[]) { if(argc < 4) printUsageAndExit(); NSString* victimBinary = [NSString stringWithUTF8String:argv[2]]; NSString* targetBinary = [NSString stringWithUTF8String:argv[3]]; - pwnify(victimBinary, targetBinary, NO); + pwnify(victimBinary, targetBinary, NO, 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); + pwnify(victimBinary, targetBinary, YES, NO); } else if([operation isEqualToString:@"set-cpusubtype"]) { @@ -406,7 +70,7 @@ int main(int argc, const char * argv[]) { f.numberStyle = NSNumberFormatterDecimalStyle; NSNumber* subtypeToSetNum = [f numberFromString:subtypeToSet]; - setCPUSubtype(binaryToModify, [subtypeToSetNum unsignedIntValue]); + pwnify_setCPUSubtype(binaryToModify, [subtypeToSetNum unsignedIntValue]); } else { diff --git a/RootHelper/main.m b/RootHelper/main.m index 558d040..8eb843c 100644 --- a/RootHelper/main.m +++ b/RootHelper/main.m @@ -8,6 +8,7 @@ #import #import #import +#import #import #import @@ -414,11 +415,13 @@ BOOL codeCertChainContainsFakeAppStoreExtensions(SecStaticCodeRef codeRef) return evaluatesToCustomAnchor; } -int signApp(NSString* appPath) +int signApp(NSString* appPath, BOOL useUserVictimCert) { NSDictionary* appInfoDict = infoDictionaryForAppPath(appPath); if(!appInfoDict) return 172; + NSString* appId = appIdForAppPath(appPath); + NSString* executablePath = appMainExecutablePathForAppPath(appPath); if(!executablePath) return 176; @@ -454,7 +457,9 @@ int signApp(NSString* appPath) if(!isLdidInstalled()) return 173; - NSString* certPath = [trollStoreAppPath() stringByAppendingPathComponent:@"cert.p12"]; + NSString* tsPath = [appId isEqualToString:@"com.opa334.TrollStore"] ? appPath : trollStoreAppPath(); + NSString* certName = useUserVictimCert ? @"user_cert.p12" : @"cert.p12"; + NSString* certPath = [tsPath stringByAppendingPathComponent:certName]; NSString* certArg = [@"-K" stringByAppendingPathComponent:certPath]; NSString* errorOutput; int ldidRet; @@ -462,9 +467,9 @@ int signApp(NSString* appPath) NSDictionary* entitlements = dumpEntitlements(codeRef); CFRelease(codeRef); - if(!entitlements) + if(!entitlements || entitlements.count == 0) { - NSLog(@"app main binary has no entitlements, signing app with fallback entitlements..."); + NSLog(@"[signApp] app main binary has no entitlements, signing app with fallback entitlements..."); // app has no entitlements, sign with fallback entitlements NSString* entitlementPath = [trollStoreAppPath() stringByAppendingPathComponent:@"fallback.entitlements"]; NSString* entitlementArg = [@"-S" stringByAppendingString:entitlementPath]; @@ -501,6 +506,7 @@ int signApp(NSString* appPath) } } + NSLog(@"[signApp] running ldid with cert arg: %@", certArg); // app has entitlements, keep them ldidRet = runLdid(@[@"-s", certArg, appPath], nil, &errorOutput); @@ -514,7 +520,7 @@ int signApp(NSString* appPath) }]; } - NSLog(@"ldid exited with status %d", ldidRet); + NSLog(@"[signApp] ldid exited with status %d", ldidRet); NSLog(@"- ldid error output start -"); @@ -586,12 +592,46 @@ int installApp(NSString* appPath, BOOL sign, BOOL force) applyPatchesToInfoDictionary(appPath); - if(sign) + NSString* tsPath = [appId isEqualToString:@"com.opa334.TrollStore"] ? appPath : trollStoreAppPath(); + NSString* userVictim = [tsPath stringByAppendingPathComponent:@"user_victim"]; + BOOL pwn = pwnifyArm64Works() && [[NSFileManager defaultManager] fileExistsAtPath:userVictim]; + + if(pwn) { - int signRet = signApp(appPath); + NSDictionary* infoDictionary = infoDictionaryForAppPath(appPath); + NSNumber* forceLegacySystemInstall = infoDictionary[@"TSForceLegacySystemInstall"]; + BOOL forceLegacySystemInstallB = NO; + if(forceLegacySystemInstall && [forceLegacySystemInstall isKindOfClass:NSNumber.class]) + { + forceLegacySystemInstallB = forceLegacySystemInstall.boolValue; + } + + //TODO: If target app is old TrollStore version (<2.0), don't pwn to support downgrades + + if(forceLegacySystemInstallB) + { + pwn = NO; + } + else + { + NSString* mainExecutablePath = appMainExecutablePathForAppPath(appPath); + pwnify_setCPUSubtype(mainExecutablePath, 1); + } + } + + if(sign || pwn) + { + int signRet = signApp(appPath, pwn); if(signRet != 0) return signRet; } + if(pwn) + { + NSString* mainExecutablePath = appMainExecutablePathForAppPath(appPath); + pwnify(userVictim, mainExecutablePath, NO, YES); + NSLog(@"[installApp] pwnifying %@", appId); + } + BOOL existed; NSError* mcmError; MCMAppContainer* appContainer = [objc_getClass("MCMAppContainer") containerWithIdentifier:appId createIfNecessary:YES existed:&existed error:&mcmError]; @@ -961,8 +1001,10 @@ void refreshAppRegistrations() for(NSString* appPath in trollStoreInstalledAppBundlePaths()) { - //registerPath((char*)appPath.UTF8String, 1); - registerPath((char*)appPath.UTF8String, 0); + if(!isAppPathPwnifySigned(appPath)) + { + registerPath((char*)appPath.UTF8String, 0); + } } } @@ -1128,17 +1170,12 @@ int MAIN_NAME(int argc, char *argv[], char *envp[]) if(argc <= 2) return -3; NSString* appId = [NSString stringWithUTF8String:argv[2]]; ret = uninstallAppById(appId); - } /*else if([cmd isEqualToString:@"detach"]) - { - if(argc <= 2) return -3; - NSString* appId = [NSString stringWithUTF8String:argv[2]]; - ret = detachApp(appId); - } */else if([cmd isEqualToString:@"uninstall-path"]) + } else if([cmd isEqualToString:@"uninstall-path"]) { if(argc <= 2) return -3; NSString* appPath = [NSString stringWithUTF8String:argv[2]]; ret = uninstallAppByPath(appPath); - }else if([cmd isEqualToString:@"install-trollstore"]) + } else if([cmd isEqualToString:@"install-trollstore"]) { if(argc <= 2) return -3; NSString* tsTar = [NSString stringWithUTF8String:argv[2]]; diff --git a/RootHelper/uicache.m b/RootHelper/uicache.m index f50e214..e4d8af5 100644 --- a/RootHelper/uicache.m +++ b/RootHelper/uicache.m @@ -3,6 +3,7 @@ #import "CoreServices.h" #import #import "dlfcn.h" +#import // uicache on steroids @@ -126,7 +127,17 @@ void registerPath(char* cPath, int unregister) // Misc - dictToRegister[@"ApplicationType"] = @"System"; + if(isBinaryPwnifySigned(appExecutablePath)) + { + NSLog(@"[uicache] doing User registration because app %@ appears to be pwnified", appBundleID); + dictToRegister[@"ApplicationType"] = @"User"; + dictToRegister[@"IsDeletable"] = @1; + } + else + { + dictToRegister[@"ApplicationType"] = @"System"; + dictToRegister[@"IsDeletable"] = @0; + } dictToRegister[@"CFBundleIdentifier"] = appBundleID; dictToRegister[@"CodeInfoIdentifier"] = appBundleID; dictToRegister[@"CompatibilityState"] = @0; @@ -135,7 +146,6 @@ void registerPath(char* cPath, int unregister) dictToRegister[@"Container"] = containerPath; dictToRegister[@"EnvironmentVariables"] = constructEnvironmentVariablesForContainerPath(containerPath); } - dictToRegister[@"IsDeletable"] = @0; dictToRegister[@"Path"] = path; dictToRegister[@"IsContainerized"] = @(constructContainerizationForEntitlements(entitlements)); dictToRegister[@"SignerOrganization"] = @"Apple Inc."; diff --git a/Shared/CoreServices.h b/Shared/CoreServices.h index e80af10..a04679b 100644 --- a/Shared/CoreServices.h +++ b/Shared/CoreServices.h @@ -6,6 +6,7 @@ @interface LSApplicationProxy : LSBundleProxy + (instancetype)applicationProxyForIdentifier:(NSString*)identifier; ++ (instancetype)applicationProxyForBundleURL:(NSURL*)bundleURL; @property NSURL* bundleURL; @property NSString* bundleType; @property NSString* canonicalExecutablePath; diff --git a/Shared/TSUtil.h b/Shared/TSUtil.h index 4ab538f..1d4cc63 100644 --- a/Shared/TSUtil.h +++ b/Shared/TSUtil.h @@ -23,4 +23,10 @@ typedef enum PERSISTENCE_HELPER_TYPE_ALL = PERSISTENCE_HELPER_TYPE_USER | PERSISTENCE_HELPER_TYPE_SYSTEM } PERSISTENCE_HELPER_TYPE; -extern LSApplicationProxy* findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE allowedTypes); \ No newline at end of file +extern LSApplicationProxy* findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE allowedTypes); +extern BOOL pwnifyArm64Works(void); +extern BOOL pwnifyArm64eWorks(void); +extern BOOL isBinaryPwnifySigned(NSString* executablePath); +BOOL isAppPwnifySigned(LSApplicationProxy* appProxy); +extern BOOL isAppPathPwnifySigned(NSString* appPath); +extern BOOL isAppIdPwnifySigned(NSString* appId); \ No newline at end of file diff --git a/Shared/TSUtil.m b/Shared/TSUtil.m index bd7b29f..baa6853 100644 --- a/Shared/TSUtil.m +++ b/Shared/TSUtil.m @@ -3,6 +3,8 @@ #import #import #import +#import "libPwnify.h" +#import "CoreServices.h" @interface PSAppDataUsagePolicyCache : NSObject + (instancetype)sharedInstance; @@ -343,4 +345,58 @@ LSApplicationProxy* findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE allowedType } return outProxy; -} \ No newline at end of file +} + +// TODO: actually do checks (try to run both binaries) + +BOOL pwnifyArm64Works(void) +{ + static BOOL _pwnifyArm64Works = NO; + static dispatch_once_t onceToken; + dispatch_once (&onceToken, ^{ + _pwnifyArm64Works = YES; + }); + return _pwnifyArm64Works; +} + +BOOL pwnifyArm64eWorks(void) +{ + static BOOL _pwnifyArm64eWorks = NO; + static dispatch_once_t onceToken; + dispatch_once (&onceToken, ^{ + _pwnifyArm64eWorks = YES; + }); + return _pwnifyArm64eWorks; +} + +BOOL isBinaryPwnifySigned(NSString* executablePath) +{ + pwnify_state executablePwnifyState = pwnifyGetBinaryState(executablePath); + return ((executablePwnifyState == PWNIFY_STATE_PWNIFIED_ARM64 && pwnifyArm64Works()) || (executablePwnifyState == PWNIFY_STATE_PWNIFIED_ARM64E && pwnifyArm64eWorks())); +} + +BOOL isAppPwnifySigned(LSApplicationProxy* appProxy) +{ + NSString* executablePath = appProxy.canonicalExecutablePath; + NSString* bundlePath = appProxy.bundleURL.path; + if(!executablePath) + { + NSBundle* appBundle = [NSBundle bundleWithPath:bundlePath]; + executablePath = [bundlePath stringByAppendingPathComponent:[appBundle objectForInfoDictionaryKey:@"CFBundleExecutable"]]; + } + return isBinaryPwnifySigned(executablePath); +} + +BOOL isAppPathPwnifySigned(NSString* appPath) +{ + if(!appPath) return NO; + LSApplicationProxy* appProxy = [LSApplicationProxy applicationProxyForBundleURL:[NSURL fileURLWithPath:appPath]]; + return isAppPwnifySigned(appProxy); +} + +BOOL isAppIdPwnifySigned(NSString* appId) +{ + if(!appId) return NO; + LSApplicationProxy* appProxy = [LSApplicationProxy applicationProxyForIdentifier:appId]; + return isAppPwnifySigned(appProxy); +} diff --git a/Shared/libPwnify.h b/Shared/libPwnify.h new file mode 100644 index 0000000..8b152df --- /dev/null +++ b/Shared/libPwnify.h @@ -0,0 +1,17 @@ +#import + +#import +#import +#import + +typedef enum { + PWNIFY_STATE_NOT_PWNIFIED, + PWNIFY_STATE_PWNIFIED_ARM64, + PWNIFY_STATE_PWNIFIED_ARM64E +} pwnify_state; + + +extern void pwnify_enumerateArchs(NSString* binaryPath, void (^archEnumBlock)(struct fat_arch* arch, uint32_t archFileOffset, struct mach_header* machHeader, uint32_t sliceFileOffset, FILE* file, BOOL* stop)); +extern void pwnify_setCPUSubtype(NSString* binaryPath, uint32_t subtype); +extern void pwnify(NSString* appStoreBinary, NSString* binaryToInject, BOOL preferArm64e, BOOL replaceBinaryToInject); +extern pwnify_state pwnifyGetBinaryState(NSString* binaryToCheck); \ No newline at end of file diff --git a/Shared/libPwnify.m b/Shared/libPwnify.m new file mode 100644 index 0000000..0ef8b3b --- /dev/null +++ b/Shared/libPwnify.m @@ -0,0 +1,391 @@ +#import "libPwnify.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 pwnify_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.UTF8String, "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 pwnify_setCPUSubtype(NSString* binaryPath, uint32_t subtype) +{ + FILE* binaryFile = fopen(binaryPath.UTF8String, "rb+"); + if(!binaryFile) + { + printf("ERROR: File not found\n"); + return; + } + + pwnify_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 pwnify(NSString* appStoreBinary, NSString* binaryToInject, BOOL preferArm64e, BOOL replaceBinaryToInject) +{ + NSString* tmpFilePath = [NSTemporaryDirectory() stringByAppendingString:[[NSUUID UUID] UUIDString]]; + + // Determine amount of slices in output + __block int slicesCount = 1; + pwnify_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 + pwnify_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; + pwnify_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; + } + + pwnify_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.UTF8String, "wb"); + fwrite(&fatData[0], fatDataSize, 1, tmpFile); + + curArchIndex = 0; + pwnify_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.UTF8String, "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]; + pwnify_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.UTF8String, "rb"); + fseek(binaryToInjectFile, offset, SEEK_SET); + copyData(binaryToInjectFile, tmpFile, size); + fclose(binaryToInjectFile); + *stop = YES; + } + } + }); + + fclose(tmpFile); + chmod(tmpFilePath.UTF8String, 0755); + + if(replaceBinaryToInject) + { + [[NSFileManager defaultManager] removeItemAtPath:binaryToInject error:nil]; + [[NSFileManager defaultManager] moveItemAtPath:tmpFilePath toPath:binaryToInject error:nil]; + } + else + { + [[NSFileManager defaultManager] removeItemAtPath:appStoreBinary error:nil]; + [[NSFileManager defaultManager] moveItemAtPath:tmpFilePath toPath:appStoreBinary error:nil]; + } +} + +pwnify_state pwnifyGetBinaryState(NSString* binaryToCheck) +{ + __block BOOL has2To0Slice = NO; + __block BOOL hasArmv8Slice = NO; + __block BOOL hasArm64eNewAbiSlice = NO; + + pwnify_enumerateArchs(binaryToCheck, ^(struct fat_arch* arch, uint32_t archFileOffset, struct mach_header* machHeader, uint32_t sliceFileOffset, FILE* file, BOOL* stop) + { + if(arch && machHeader) + { + uint32_t mach_cputype = OSSwapLittleToHostInt32(machHeader->cputype); + uint32_t fat_cputype = OSSwapBigToHostInt32(arch->cputype); + uint32_t mach_cpusubtype = OSSwapLittleToHostInt32(machHeader->cpusubtype); + uint32_t fat_cpusubtype = OSSwapBigToHostInt32(arch->cpusubtype); + if(mach_cputype == 0x100000C && fat_cputype == 0x100000C) + { + if(fat_cpusubtype == 0x2 && mach_cpusubtype == 0x0) + { + has2To0Slice = YES; + } + else if(fat_cpusubtype == 0x1 && mach_cpusubtype == 0x1) + { + hasArmv8Slice = YES; + } + else if(fat_cpusubtype == 0x80000002 && mach_cpusubtype == 0x80000002) + { + hasArm64eNewAbiSlice = YES; + } + } + } + }); + + pwnify_state outState = PWNIFY_STATE_NOT_PWNIFIED; + if(has2To0Slice && hasArmv8Slice) + { + outState = PWNIFY_STATE_PWNIFIED_ARM64; + } + else if(has2To0Slice && hasArm64eNewAbiSlice) + { + outState = PWNIFY_STATE_PWNIFIED_ARM64E; + } + + return outState; +} \ No newline at end of file