From d909eee3fe16cec3a4050925cbe8f22a63effd9b Mon Sep 17 00:00:00 2001 From: Luke Noble Date: Sun, 4 Sep 2022 21:18:59 +0100 Subject: [PATCH] Handle apps already signed with a custom root --- Helper/main.m | 368 +++++++++++++++++++++++++++-------------------- Helper/uicache.m | 8 +- 2 files changed, 220 insertions(+), 156 deletions(-) diff --git a/Helper/main.m b/Helper/main.m index c1e2105..e12712d 100644 --- a/Helper/main.m +++ b/Helper/main.m @@ -8,53 +8,10 @@ #import #import "CoreServices.h" #import "Shared.h" -#import -#import -#import -#import -#import -#import -#import -#import #import #import - -#ifdef __LP64__ -#define segment_command_universal segment_command_64 -#define mach_header_universal mach_header_64 -#define MH_MAGIC_UNIVERSAL MH_MAGIC_64 -#define MH_CIGAM_UNIVERSAL MH_CIGAM_64 -#else -#define segment_command_universal segment_command -#define mach_header_universal mach_header -#define MH_MAGIC_UNIVERSAL MH_MAGIC -#define MH_CIGAM_UNIVERSAL MH_CIGAM -#endif - -#define SWAP32(x) ((((x) & 0xff000000) >> 24) | (((x) & 0xff0000) >> 8) | (((x) & 0xff00) << 8) | (((x) & 0xff) << 24)) -uint32_t s32(uint32_t toSwap, BOOL shouldSwap) -{ - return shouldSwap ? SWAP32(toSwap) : toSwap; -} - -#define CPU_SUBTYPE_ARM64E_NEW_ABI 0x80000002 - -struct CSSuperBlob { - uint32_t magic; - uint32_t length; - uint32_t count; -}; - -struct CSBlob { - uint32_t type; - uint32_t offset; -}; - -#define CS_MAGIC_EMBEDDED_SIGNATURE 0xfade0cc0 -#define CS_MAGIC_EMBEDDED_SIGNATURE_REVERSED 0xc00cdefa -#define CS_MAGIC_EMBEDDED_ENTITLEMENTS 0xfade7171 - +#import extern mach_msg_return_t SBReloadIconForIdentifier(mach_port_t machport, const char* identifier); @interface SBSHomeScreenService : NSObject @@ -65,6 +22,26 @@ extern NSString* BKSOpenApplicationOptionKeyActivateForEvent; extern void BKSTerminateApplicationForReasonAndReportWithDescription(NSString *bundleID, int reasonID, bool report, NSString *description); + +typedef CF_OPTIONS(uint32_t, SecCSFlags) { + kSecCSDefaultFlags = 0 +}; + + +#define kSecCSRequirementInformation 1 << 2 +#define kSecCSSigningInformation 1 << 1 + +typedef struct __SecCode const *SecStaticCodeRef; + +extern CFStringRef kSecCodeInfoEntitlementsDict; +extern CFStringRef kSecCodeInfoCertificates; +extern CFStringRef kSecPolicyAppleiPhoneApplicationSigning; +extern CFStringRef kSecPolicyLeafMarkerOid; + +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); + #define kCFPreferencesNoContainer CFSTR("kCFPreferencesNoContainer") typedef CFPropertyListRef (*_CFPreferencesCopyValueWithContainerType)(CFStringRef key, CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath); @@ -221,118 +198,189 @@ int runLdid(NSArray* args, NSString** output, NSString** errorOutput) return WEXITSTATUS(status); } -NSDictionary* dumpEntitlements(NSString* binaryPath) +SecStaticCodeRef getStaticCodeRef(NSString *binaryPath) { - char* entitlementsData = NULL; - uint32_t entitlementsLength = 0; + + 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; +} - FILE* machoFile = fopen(binaryPath.UTF8String, "rb"); - struct mach_header_universal header; - fread(&header,sizeof(header),1,machoFile); +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; +} - uint32_t archOffset = 0; +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; +} - // Get arch offset if FAT binary - if(header.magic == FAT_MAGIC || header.magic == FAT_CIGAM) - { - fseek(machoFile,0,SEEK_SET); +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; +} - struct fat_header fatHeader; - fread(&fatHeader,sizeof(fatHeader),1,machoFile); +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); - BOOL swpFat = fatHeader.magic == FAT_CIGAM; - - for(int i = 0; i < s32(fatHeader.nfat_arch, swpFat); i++) - { - struct fat_arch fatArch; - fseek(machoFile,sizeof(fatHeader) + sizeof(fatArch) * i,SEEK_SET); - fread(&fatArch,sizeof(fatArch),1,machoFile); - - if(s32(fatArch.cputype, swpFat) != CPU_TYPE_ARM64) - { - continue; - } - - archOffset = s32(fatArch.offset, swpFat); - break; - } - } - - fseek(machoFile,archOffset,SEEK_SET); - fread(&header,sizeof(header),1,machoFile); - - if(header.magic == MH_MAGIC_UNIVERSAL || header.magic == MH_CIGAM_UNIVERSAL) - { - BOOL swp = header.magic == MH_CIGAM_UNIVERSAL; - // This code is cursed, don't stare at it too long or it will stare back at you - uint32_t offset = archOffset + sizeof(header); - for(int c = 0; c < s32(header.ncmds, swp); c++) - { - fseek(machoFile,offset,SEEK_SET); - struct load_command cmd; - fread(&cmd,sizeof(cmd),1,machoFile); - uint32_t normalizedCmd = s32(cmd.cmd,swp); - if(normalizedCmd == LC_CODE_SIGNATURE) - { - struct linkedit_data_command codeSignCommand; - fseek(machoFile,offset,SEEK_SET); - fread(&codeSignCommand,sizeof(codeSignCommand),1,machoFile); - uint32_t codeSignCmdOffset = archOffset + s32(codeSignCommand.dataoff, swp); - fseek(machoFile, codeSignCmdOffset, SEEK_SET); - struct CSSuperBlob superBlob; - fread(&superBlob, sizeof(superBlob), 1, machoFile); - if(SWAP32(superBlob.magic) == CS_MAGIC_EMBEDDED_SIGNATURE) // YES starting here everything is swapped no matter if CIGAM or MAGIC... - { - uint32_t itemCount = SWAP32(superBlob.count); - for(int i = 0; i < itemCount; i++) - { - fseek(machoFile, codeSignCmdOffset + sizeof(superBlob) + i * sizeof(struct CSBlob),SEEK_SET); - struct CSBlob blob; - fread(&blob, sizeof(struct CSBlob), 1, machoFile); - fseek(machoFile, codeSignCmdOffset + SWAP32(blob.offset),SEEK_SET); - uint32_t blobMagic; - fread(&blobMagic, sizeof(uint32_t), 1, machoFile); - if(SWAP32(blobMagic) == CS_MAGIC_EMBEDDED_ENTITLEMENTS) - { - uint32_t entitlementsLengthTmp; - fread(&entitlementsLengthTmp, sizeof(uint32_t), 1, machoFile); - entitlementsLength = SWAP32(entitlementsLengthTmp); - entitlementsData = malloc(entitlementsLength - 8); - fread(&entitlementsData[0], entitlementsLength - 8, 1, machoFile); - break; - } - } - } - - break; - } - - offset += cmd.cmdsize; - } - } - - fclose(machoFile); - - NSData* entitlementsNSData = nil; - - if(entitlementsData) - { - entitlementsNSData = [NSData dataWithBytes:entitlementsData length:entitlementsLength]; - free(entitlementsData); - } - - if(entitlementsNSData) - { - NSDictionary* plist = [NSPropertyListSerialization propertyListWithData:entitlementsNSData options:NSPropertyListImmutable format:nil error:nil]; - NSLog(@"%@ dumped entitlements %@", binaryPath, plist); - return plist; - } - else - { - NSLog(@"Failed to dump entitlements of %@... This is bad", binaryPath); - } - - return nil; + // If we match the standard Apple policy, we are signed properly, but we haven't been deliberately signed with a custom root + + SecPolicyRef genuineApplePolicy = SecPolicyCreateWithProperties(kSecPolicyAppleiPhoneApplicationSigning, NULL); + SecTrustRef genuineAppleTrust = NULL; + SecTrustCreateWithCertificates(certificates, genuineApplePolicy, &genuineAppleTrust); + + if(SecTrustEvaluateWithError(genuineAppleTrust, nil)) + { + CFRelease(genuineAppleTrust); + CFRelease(genuineApplePolicy); + CFRelease(signingInfo); + + NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] found certificate extension, but was issued by Apple"); + return NO; + } + + // We haven't matched Apple, so keep going. Are we using a custom root that would take the App Store fastpath? + CFRelease(genuineAppleTrust); + CFRelease(genuineApplePolicy); + + // Cert chain should be of length 3 + if(CFArrayGetCount(certificates) != 3) + { + CFRelease(signingInfo); + + NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] certificate chain length != 3"); + return NO; + } + + const void *keys[1] = { kSecPolicyLeafMarkerOid }; + const void *values[1] = { CFSTR("1.2.840.113635.100.6.1.3") }; + CFDictionaryRef customPolicyOptions = CFDictionaryCreate(NULL, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + // AppleCodeSigning only checks for the codeSigning EKU by default + SecPolicyRef customRootPolicy = SecPolicyCreateWithProperties(kSecPolicyAppleCodeSigning, customPolicyOptions); + SecTrustRef customRootTrust = NULL; + + // Need to add our certificate chain to the anchor as it is expected to be a self-signed root + SecTrustCreateWithCertificates(certificates, customRootPolicy, &customRootTrust); + SecTrustSetAnchorCertificates(customRootTrust, certificates); + + BOOL evaluatesToCustomAnchor = SecTrustEvaluateWithError(customRootTrust, nil); + + CFRelease(customRootTrust); + CFRelease(customRootPolicy); + CFRelease(customPolicyOptions); + + CFRelease(signingInfo); + return evaluatesToCustomAnchor; } BOOL signApp(NSString* appPath, NSError** error) @@ -355,17 +403,33 @@ BOOL signApp(NSString* appPath, NSError** error) NSNumber *tsBundleIsPreSignedNum = (NSNumber *)tsBundleIsPreSigned; if([tsBundleIsPreSignedNum boolValue] == YES) { - NSLog(@"[signApp] taking fast path for app which declares it has already been CT-signed (%@)", executablePath); + NSLog(@"[signApp] taking fast path for app which declares it has already been signed (%@)", executablePath); return YES; } } + + SecStaticCodeRef codeRef = getStaticCodeRef(executablePath); + if(codeRef == NULL) + { + NSLog(@"[signApp] failed to get static code, can't derive entitlements from %@", executablePath); + return NO; + } + + if(codeCertChainContainsFakeAppStoreExtensions(codeRef)) + { + NSLog(@"[signApp] taking fast path for app signed using a custom root certificate (%@)", executablePath); + CFRelease(codeRef); + return YES; + } NSString* certPath = [trollStoreAppPath() stringByAppendingPathComponent:@"cert.p12"]; NSString* certArg = [@"-K" stringByAppendingPathComponent:certPath]; NSString* errorOutput; int ldidRet; - NSDictionary* entitlements = dumpEntitlements(executablePath); + NSDictionary* entitlements = dumpEntitlements(codeRef); + CFRelease(codeRef); + if(!entitlements) { NSLog(@"app main binary has no entitlements, signing app with fallback entitlements..."); diff --git a/Helper/uicache.m b/Helper/uicache.m index 1a2f8d9..97bfeb1 100644 --- a/Helper/uicache.m +++ b/Helper/uicache.m @@ -6,7 +6,7 @@ // uicache on steroids -extern NSDictionary* dumpEntitlements(NSString* binaryPath); +extern NSDictionary* dumpEntitlementsFromBinaryAtPath(NSString* binaryPath); NSDictionary* constructGroupsContainersForEntitlements(NSDictionary* entitlements, BOOL systemGroups) { @@ -118,7 +118,7 @@ void registerPath(char* cPath, int unregister) // Add entitlements NSString* appExecutablePath = [path stringByAppendingPathComponent:appInfoPlist[@"CFBundleExecutable"]]; - NSDictionary* entitlements = dumpEntitlements(appExecutablePath); + NSDictionary* entitlements = dumpEntitlementsFromBinaryAtPath(appExecutablePath); if(entitlements) { dictToRegister[@"Entitlements"] = entitlements; @@ -185,7 +185,7 @@ void registerPath(char* cPath, int unregister) // Add entitlements NSString* pluginExecutablePath = [pluginPath stringByAppendingPathComponent:pluginInfoPlist[@"CFBundleExecutable"]]; - NSDictionary* pluginEntitlements = dumpEntitlements(pluginExecutablePath); + NSDictionary* pluginEntitlements = dumpEntitlementsFromBinaryAtPath(pluginExecutablePath); if(pluginEntitlements) { pluginDict[@"Entitlements"] = pluginEntitlements; @@ -247,4 +247,4 @@ void registerPath(char* cPath, int unregister) NSLog(@"Error: Unable to unregister %@", path); } } -} \ No newline at end of file +}