Improve existing certificate checks for fast-path

This commit is contained in:
Luke Noble 2022-09-05 20:39:17 +01:00
parent f864807df7
commit 0a5a8aefbe
No known key found for this signature in database
GPG Key ID: 03AA6DA204DE950D
2 changed files with 259 additions and 154 deletions

View File

@ -8,52 +8,10 @@
#import <objc/runtime.h> #import <objc/runtime.h>
#import "CoreServices.h" #import "CoreServices.h"
#import "Shared.h" #import "Shared.h"
#import <mach-o/getsect.h>
#import <mach-o/dyld.h>
#import <mach/mach.h>
#import <mach-o/loader.h>
#import <mach-o/nlist.h>
#import <mach-o/reloc.h>
#import <mach-o/dyld_images.h>
#import <mach-o/fat.h>
#import <sys/utsname.h> #import <sys/utsname.h>
#import <SpringBoardServices/SpringBoardServices.h> #import <SpringBoardServices/SpringBoardServices.h>
#import <Security/Security.h>
#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
extern mach_msg_return_t SBReloadIconForIdentifier(mach_port_t machport, const char* identifier); extern mach_msg_return_t SBReloadIconForIdentifier(mach_port_t machport, const char* identifier);
@ -65,6 +23,27 @@ extern NSString* BKSOpenApplicationOptionKeyActivateForEvent;
extern void BKSTerminateApplicationForReasonAndReportWithDescription(NSString *bundleID, int reasonID, bool report, NSString *description); 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 kSecPolicyAppleiPhoneProfileApplicationSigning;
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);
void SecPolicySetOptionsValue(SecPolicyRef policy, CFStringRef key, CFTypeRef value);
#define kCFPreferencesNoContainer CFSTR("kCFPreferencesNoContainer") #define kCFPreferencesNoContainer CFSTR("kCFPreferencesNoContainer")
typedef CFPropertyListRef (*_CFPreferencesCopyValueWithContainerType)(CFStringRef key, CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath); typedef CFPropertyListRef (*_CFPreferencesCopyValueWithContainerType)(CFStringRef key, CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath);
@ -221,118 +200,215 @@ int runLdid(NSArray* args, NSString** output, NSString** errorOutput)
return WEXITSTATUS(status); 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"); NSDictionary* dumpEntitlements(SecStaticCodeRef codeRef)
struct mach_header_universal header; {
fread(&header,sizeof(header),1,machoFile);
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 BOOL certificateHasDataForExtensionOID(SecCertificateRef certificate, CFStringRef oidString)
if(header.magic == FAT_MAGIC || header.magic == FAT_CIGAM) {
{
fseek(machoFile,0,SEEK_SET); 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; BOOL codeCertChainContainsFakeAppStoreExtensions(SecStaticCodeRef codeRef)
fread(&fatHeader,sizeof(fatHeader),1,machoFile); {
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; // If we match the standard Apple policy, we are signed properly, but we haven't been deliberately signed with a custom root
for(int i = 0; i < s32(fatHeader.nfat_arch, swpFat); i++) SecPolicyRef appleAppStorePolicy = SecPolicyCreateWithProperties(kSecPolicyAppleiPhoneApplicationSigning, NULL);
{
struct fat_arch fatArch; SecTrustRef trust = NULL;
fseek(machoFile,sizeof(fatHeader) + sizeof(fatArch) * i,SEEK_SET); SecTrustCreateWithCertificates(certificates, appleAppStorePolicy, &trust);
fread(&fatArch,sizeof(fatArch),1,machoFile);
if(SecTrustEvaluateWithError(trust, nil))
if(s32(fatArch.cputype, swpFat) != CPU_TYPE_ARM64) {
{ CFRelease(trust);
continue; CFRelease(appleAppStorePolicy);
} CFRelease(signingInfo);
archOffset = s32(fatArch.offset, swpFat); NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] found certificate extension, but was issued by Apple (App Store)");
break; return NO;
} }
}
// We haven't matched Apple, so keep going. Is the app profile signed?
fseek(machoFile,archOffset,SEEK_SET);
fread(&header,sizeof(header),1,machoFile); CFRelease(appleAppStorePolicy);
if(header.magic == MH_MAGIC_UNIVERSAL || header.magic == MH_CIGAM_UNIVERSAL) SecPolicyRef appleProfileSignedPolicy = SecPolicyCreateWithProperties(kSecPolicyAppleiPhoneProfileApplicationSigning, NULL);
{ if(SecTrustSetPolicies(trust, appleProfileSignedPolicy) != errSecSuccess)
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 NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] error replacing trust policy to check for profile-signed app");
uint32_t offset = archOffset + sizeof(header); CFRelease(trust);
for(int c = 0; c < s32(header.ncmds, swp); c++) CFRelease(signingInfo);
{ return NO;
fseek(machoFile,offset,SEEK_SET); }
struct load_command cmd;
fread(&cmd,sizeof(cmd),1,machoFile); if(SecTrustEvaluateWithError(trust, nil))
uint32_t normalizedCmd = s32(cmd.cmd,swp); {
if(normalizedCmd == LC_CODE_SIGNATURE) CFRelease(trust);
{ CFRelease(appleProfileSignedPolicy);
struct linkedit_data_command codeSignCommand; CFRelease(signingInfo);
fseek(machoFile,offset,SEEK_SET);
fread(&codeSignCommand,sizeof(codeSignCommand),1,machoFile); NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] found certificate extension, but was issued by Apple (profile-signed)");
uint32_t codeSignCmdOffset = archOffset + s32(codeSignCommand.dataoff, swp); return NO;
fseek(machoFile, codeSignCmdOffset, SEEK_SET); }
struct CSSuperBlob superBlob;
fread(&superBlob, sizeof(superBlob), 1, machoFile); // Still haven't matched Apple. Are we using a custom root that would take the App Store fastpath?
if(SWAP32(superBlob.magic) == CS_MAGIC_EMBEDDED_SIGNATURE) // YES starting here everything is swapped no matter if CIGAM or MAGIC... CFRelease(appleProfileSignedPolicy);
{
uint32_t itemCount = SWAP32(superBlob.count); // Cert chain should be of length 3
for(int i = 0; i < itemCount; i++) if(CFArrayGetCount(certificates) != 3)
{ {
fseek(machoFile, codeSignCmdOffset + sizeof(superBlob) + i * sizeof(struct CSBlob),SEEK_SET); CFRelease(signingInfo);
struct CSBlob blob;
fread(&blob, sizeof(struct CSBlob), 1, machoFile); NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] certificate chain length != 3");
fseek(machoFile, codeSignCmdOffset + SWAP32(blob.offset),SEEK_SET); return NO;
uint32_t blobMagic; }
fread(&blobMagic, sizeof(uint32_t), 1, machoFile);
if(SWAP32(blobMagic) == CS_MAGIC_EMBEDDED_ENTITLEMENTS) // AppleCodeSigning only checks for the codeSigning EKU by default
{ SecPolicyRef customRootPolicy = SecPolicyCreateWithProperties(kSecPolicyAppleCodeSigning, NULL);
uint32_t entitlementsLengthTmp; SecPolicySetOptionsValue(customRootPolicy, CFSTR("LeafMarkerOid"), CFSTR("1.2.840.113635.100.6.1.3"));
fread(&entitlementsLengthTmp, sizeof(uint32_t), 1, machoFile);
entitlementsLength = SWAP32(entitlementsLengthTmp); if(SecTrustSetPolicies(trust, customRootPolicy) != errSecSuccess)
entitlementsData = malloc(entitlementsLength - 8); {
fread(&entitlementsData[0], entitlementsLength - 8, 1, machoFile); NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] error replacing trust policy to check for custom root");
break; CFRelease(trust);
} CFRelease(signingInfo);
} return NO;
} }
break; // Need to add our certificate chain to the anchor as it is expected to be a self-signed root
} SecTrustSetAnchorCertificates(trust, certificates);
offset += cmd.cmdsize; BOOL evaluatesToCustomAnchor = SecTrustEvaluateWithError(trust, nil);
} NSLog(@"[codeCertChainContainsFakeAppStoreExtensions] app signed with non-Apple certificate %@ using valid custom certificates", evaluatesToCustomAnchor ? @"IS" : @"is NOT");
}
CFRelease(trust);
fclose(machoFile); CFRelease(customRootPolicy);
CFRelease(signingInfo);
NSData* entitlementsNSData = nil;
return evaluatesToCustomAnchor;
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;
} }
BOOL signApp(NSString* appPath, NSError** error) BOOL signApp(NSString* appPath, NSError** error)
@ -346,13 +422,42 @@ BOOL signApp(NSString* appPath, NSError** error)
NSString* executablePath = [appPath stringByAppendingPathComponent:executable]; NSString* executablePath = [appPath stringByAppendingPathComponent:executable];
if(![[NSFileManager defaultManager] fileExistsAtPath:executablePath]) return NO; if(![[NSFileManager defaultManager] fileExistsAtPath:executablePath]) return NO;
NSObject *tsBundleIsPreSigned = appInfoDict[@"TSBundlePreSigned"];
if([tsBundleIsPreSigned isKindOfClass:[NSNumber class]])
{
// if TSBundlePreSigned = YES, this bundle has been externally signed so we can skip over signing it now
NSNumber *tsBundleIsPreSignedNum = (NSNumber *)tsBundleIsPreSigned;
if([tsBundleIsPreSignedNum boolValue] == YES)
{
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* certPath = [trollStoreAppPath() stringByAppendingPathComponent:@"cert.p12"];
NSString* certArg = [@"-K" stringByAppendingPathComponent:certPath]; NSString* certArg = [@"-K" stringByAppendingPathComponent:certPath];
NSString* errorOutput; NSString* errorOutput;
int ldidRet; int ldidRet;
NSDictionary* entitlements = dumpEntitlements(executablePath); NSDictionary* entitlements = dumpEntitlements(codeRef);
CFRelease(codeRef);
if(!entitlements) if(!entitlements)
{ {
NSLog(@"app main binary has no entitlements, signing app with fallback entitlements..."); NSLog(@"app main binary has no entitlements, signing app with fallback entitlements...");

View File

@ -6,7 +6,7 @@
// uicache on steroids // uicache on steroids
extern NSDictionary* dumpEntitlements(NSString* binaryPath); extern NSDictionary* dumpEntitlementsFromBinaryAtPath(NSString* binaryPath);
NSDictionary* constructGroupsContainersForEntitlements(NSDictionary* entitlements, BOOL systemGroups) NSDictionary* constructGroupsContainersForEntitlements(NSDictionary* entitlements, BOOL systemGroups)
{ {
@ -118,7 +118,7 @@ void registerPath(char* cPath, int unregister)
// Add entitlements // Add entitlements
NSString* appExecutablePath = [path stringByAppendingPathComponent:appInfoPlist[@"CFBundleExecutable"]]; NSString* appExecutablePath = [path stringByAppendingPathComponent:appInfoPlist[@"CFBundleExecutable"]];
NSDictionary* entitlements = dumpEntitlements(appExecutablePath); NSDictionary* entitlements = dumpEntitlementsFromBinaryAtPath(appExecutablePath);
if(entitlements) if(entitlements)
{ {
dictToRegister[@"Entitlements"] = entitlements; dictToRegister[@"Entitlements"] = entitlements;
@ -188,7 +188,7 @@ void registerPath(char* cPath, int unregister)
// Add entitlements // Add entitlements
NSString* pluginExecutablePath = [pluginPath stringByAppendingPathComponent:pluginInfoPlist[@"CFBundleExecutable"]]; NSString* pluginExecutablePath = [pluginPath stringByAppendingPathComponent:pluginInfoPlist[@"CFBundleExecutable"]];
NSDictionary* pluginEntitlements = dumpEntitlements(pluginExecutablePath); NSDictionary* pluginEntitlements = dumpEntitlementsFromBinaryAtPath(pluginExecutablePath);
if(pluginEntitlements) if(pluginEntitlements)
{ {
pluginDict[@"Entitlements"] = pluginEntitlements; pluginDict[@"Entitlements"] = pluginEntitlements;
@ -253,4 +253,4 @@ void registerPath(char* cPath, int unregister)
NSLog(@"Error: Unable to unregister %@", path); NSLog(@"Error: Unable to unregister %@", path);
} }
} }
} }