|
| 1 | +#import "AppRegistrarInstallDaemonSwizzles.h" |
| 2 | + |
| 3 | +#import "CodesigningHelper.h" |
| 4 | + |
| 5 | +#import "installd.h" |
| 6 | +#import "InstalledContentLibrary.h" |
| 7 | + |
| 8 | +#import "SwizzlingHelper.h" |
| 9 | + |
| 10 | +@import os.log; |
| 11 | + |
| 12 | +os_log_t logger(void) |
| 13 | +{ |
| 14 | + static dispatch_once_t onceToken; |
| 15 | + static os_log_t _log; |
| 16 | + dispatch_once(&onceToken, ^{ |
| 17 | + _log = os_log_create("codes.rambo.research.appregistrard", "AppRegistrarInstallDaemonSwizzles"); |
| 18 | + }); |
| 19 | + return _log; |
| 20 | +} |
| 21 | + |
| 22 | +/// Declarations for things we'll need from the runtime. |
| 23 | +@interface NSObject () |
| 24 | + |
| 25 | +@property (readonly) BOOL isPlaceholderInstall; |
| 26 | +@property (strong) MIExecutableBundle *bundle; |
| 27 | +@property (strong) MICodeSigningInfo *bundleSigningInfo; |
| 28 | + |
| 29 | +- (BOOL)__original_performVerificationWithError:(NSError *__autoreleasing *)outError; |
| 30 | + |
| 31 | +@end |
| 32 | + |
| 33 | +@implementation AppRegistrarInstallDaemonSwizzles |
| 34 | + |
| 35 | ++ (void)load |
| 36 | +{ |
| 37 | + static dispatch_once_t onceToken; |
| 38 | + dispatch_once(&onceToken, ^{ |
| 39 | + if (strcmp(getprogname(), "installd")) return; |
| 40 | + |
| 41 | + os_log_debug(logger(), "🚀 Loaded into installd, hooking!"); |
| 42 | + |
| 43 | + SwizzleInstanceMethod(MIInstallableBundle, performVerificationWithError:, AppRegistrarInstallDaemonSwizzles); |
| 44 | + SwizzleInstanceMethod(MIDaemonConfiguration, codeSigningEnforcementIsDisabled, AppRegistrarInstallDaemonSwizzles); |
| 45 | + }); |
| 46 | +} |
| 47 | + |
| 48 | +/** |
| 49 | + This is the swizzle for the method that verifies the code signature and certain special entitlements on bundles being installed. |
| 50 | +
|
| 51 | + It used to be done in `MICodeSigningVerifier` before iOS 18.4, but since then this method in `MIInstallableBundle` |
| 52 | + is what does the heavy lifting and `MICodeSigningVerifier` has lost a lot of its importance. |
| 53 | +
|
| 54 | + Since this cryptex technically only supports iOS 26+, only this override is left, and it has been reimplemented |
| 55 | + to completely fake the verification by extracting the code signature information using the most lenient validation possible, |
| 56 | + which skips a bunch of the special entitlement checks performed by the original implementation. |
| 57 | +
|
| 58 | + The most important step is actually setting `bundleSigningInfo` on the `MIInstallableBundle` to the signing info |
| 59 | + extracted from `MIExecutableBundle` by calling `codeSigningInfoByValidatingResources:...`. |
| 60 | +
|
| 61 | + The presence of that signing info and the return value of `YES` is what concludes the verification successfully. |
| 62 | +
|
| 63 | + Of course none of that would work before first performing the TSS signing and trust cache load for all executables present within the bundle, |
| 64 | + but even then the original implementation has some annoying validations that are skipped by doing the above. |
| 65 | +
|
| 66 | + To get ad-hoc binaries to pass validation, it's also crucial to override `-[MIDaemonConfiguration codeSigningEnforcementIsDisabled]` so that it returns `YES`, which this class also does. |
| 67 | + */ |
| 68 | +- (BOOL)__override_performVerificationWithError:(NSError *__autoreleasing *)outError |
| 69 | +{ |
| 70 | + os_log_t log = logger(); |
| 71 | + |
| 72 | + BOOL isPlaceholder = self.isPlaceholderInstall; |
| 73 | + NSString *logFunctionName = [NSString stringWithFormat:@"%@%@", NSStringFromSelector(_cmd), (isPlaceholder ? @"[placeholder bundle]" : @"")]; |
| 74 | + |
| 75 | + MIExecutableBundle *bundle = self.bundle; |
| 76 | + |
| 77 | + os_log(log, "%{public}@ called with bundle %@", logFunctionName, self.bundle); |
| 78 | + |
| 79 | + NSError *myError; |
| 80 | + BOOL result; |
| 81 | + |
| 82 | + if (isPlaceholder) { |
| 83 | + os_log_debug(log, "Bundle is placeholder install, delegating to original performVerificationWithError implementation"); |
| 84 | + result = [self __original_performVerificationWithError:&myError]; |
| 85 | + } else { |
| 86 | + os_log_debug(log, "Bundle is real install, will perform lenient verification."); |
| 87 | + |
| 88 | + NSString *bundleName = bundle.bundleURL.lastPathComponent; |
| 89 | + |
| 90 | + os_log_debug(log, "Checking if %@ is adhoc-signed...", bundleName); |
| 91 | + |
| 92 | + BOOL isAdHoc = [CodeSigningHelper isAdHocSignedBundleAtURL:bundle.bundleURL]; |
| 93 | + |
| 94 | + if (!isAdHoc) { |
| 95 | + os_log(log, "%@ is not adhoc-signed, proceeding with regular installd flow...", bundleName); |
| 96 | + return [self __original_performVerificationWithError:outError]; |
| 97 | + } |
| 98 | + |
| 99 | + if (!bundle) { |
| 100 | + os_log_fault(log, "MIInstallableBundle has no bundle!"); |
| 101 | + myError = [NSError errorWithDomain:@"codes.rambo.appregistrard" code:0 userInfo:@{NSLocalizedFailureReasonErrorKey: @"MIInstallableBundle has no bundle."}]; |
| 102 | + result = NO; |
| 103 | + } else { |
| 104 | + /// Create signing info with most lenient validation possible. |
| 105 | + /// Regular verification by original implementation would work for most cases, but doing this allows us to skip some annoying checks |
| 106 | + /// such as checks for forbidden entitlement combinations that are only checked during installation. |
| 107 | + MICodeSigningInfo *signingInfo = [bundle codeSigningInfoByValidatingResources:NO |
| 108 | + performingOnlineAuthorization:NO |
| 109 | + ignoringCachedSigningInfo:NO |
| 110 | + checkingTrustCacheIfApplicable:NO |
| 111 | + skippingProfileIDValidation:YES |
| 112 | + error:&myError]; |
| 113 | + |
| 114 | + /// This is the crucial step: we must set the signing info on the installable bundle, |
| 115 | + /// otherwise installation will fail even if we return a success response from this method. |
| 116 | + self.bundleSigningInfo = signingInfo; |
| 117 | + |
| 118 | + if (signingInfo) { |
| 119 | + os_log(log, "Successfully obtained signing info for bundle - %{public}@", signingInfo.dictionaryRepresentation); |
| 120 | + result = YES; |
| 121 | + } else { |
| 122 | + os_log_error(log, "Error obtaining signing info for bundle - %{public}@", myError); |
| 123 | + result = NO; |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + if (result) { |
| 129 | + os_log(log, "%{public}@ OK", logFunctionName); |
| 130 | + return YES; |
| 131 | + } else { |
| 132 | + os_log_error(log, "%{public}@ FAILED: %{public}@", logFunctionName, myError); |
| 133 | + if (outError) *outError = myError; |
| 134 | + return NO; |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +/// This override is on `MIDaemonConfiguration`, it's required so that ad-hoc signed binaries are accepted. |
| 139 | +- (BOOL)__override_codeSigningEnforcementIsDisabled |
| 140 | +{ |
| 141 | + return YES; |
| 142 | +} |
| 143 | + |
| 144 | +@end |
0 commit comments